From 78ca3d23c30b7eb9fbd9a436a6c97d6357b5ed19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=ABl=20Mugnier?= Date: Mon, 9 Nov 2020 19:18:41 +0100 Subject: [PATCH] =?UTF-8?q?Update=20-=20Ajout=20de=20v=C3=A9rification=20s?= =?UTF-8?q?uppl=C3=A9mentaire=20pour=20cr=C3=A9er=20des=20payouts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Bank/EnsureBankAccountValidatedCommand.cs | 16 +++++++++++ .../Producer/UpdateProducerCommand.cs | 1 + .../Commands/PaymentCommandsHandler.cs | 20 +++++++++++++- .../Commands/PayoutCommandsHandler.cs | 27 ++++++++++++------- .../Commands/ProducerCommandsHandler.cs | 7 ++++- .../Inputs/BusinessInput.cs | 2 +- .../ViewModels/BusinessViewModel.cs | 1 + .../Inputs/RegisterProducerInputType.cs | 4 ++- Sheaft.Web.Api/Startup.cs | 11 ++++++++ Sheaft.Web.Api/appsettings.json | 14 +++++++++- Sheaft.Web.Jobs/appsettings.json | 14 +++++++++- .../Controllers/ProducersController.cs | 11 +++++--- Sheaft.Web.Manage/appsettings.json | 14 +++++++++- Sheaft.Web.Payment/appsettings.json | 14 +++++++++- 14 files changed, 135 insertions(+), 21 deletions(-) create mode 100644 Sheaft.Application.Commands/Bank/EnsureBankAccountValidatedCommand.cs diff --git a/Sheaft.Application.Commands/Bank/EnsureBankAccountValidatedCommand.cs b/Sheaft.Application.Commands/Bank/EnsureBankAccountValidatedCommand.cs new file mode 100644 index 000000000..c9bf740f7 --- /dev/null +++ b/Sheaft.Application.Commands/Bank/EnsureBankAccountValidatedCommand.cs @@ -0,0 +1,16 @@ +using System; +using Sheaft.Core; +using Newtonsoft.Json; + +namespace Sheaft.Application.Commands +{ + public class EnsureBankAccountValidatedCommand : Command + { + [JsonConstructor] + public EnsureBankAccountValidatedCommand(RequestUser requestUser) : base(requestUser) + { + } + + public Guid ProducerId { get; set; } + } +} diff --git a/Sheaft.Application.Commands/Producer/UpdateProducerCommand.cs b/Sheaft.Application.Commands/Producer/UpdateProducerCommand.cs index 0fd5a9f3f..5712d785a 100644 --- a/Sheaft.Application.Commands/Producer/UpdateProducerCommand.cs +++ b/Sheaft.Application.Commands/Producer/UpdateProducerCommand.cs @@ -26,5 +26,6 @@ public UpdateProducerCommand(RequestUser requestUser) : base(requestUser) public bool OpenForNewBusiness { get; set; } public FullAddressInput Address { get; set; } public IEnumerable Tags { get; set; } + public bool? NotSubjectToVat { get; set; } } } diff --git a/Sheaft.Application.Handlers/Commands/PaymentCommandsHandler.cs b/Sheaft.Application.Handlers/Commands/PaymentCommandsHandler.cs index ec5ff8561..b339ce851 100644 --- a/Sheaft.Application.Handlers/Commands/PaymentCommandsHandler.cs +++ b/Sheaft.Application.Handlers/Commands/PaymentCommandsHandler.cs @@ -11,7 +11,8 @@ namespace Sheaft.Application.Handlers { public class PaymentCommandsHandler : ResultsHandler, - IRequestHandler> + IRequestHandler>, + IRequestHandler> { private readonly IPspService _pspService; @@ -56,5 +57,22 @@ public async Task> Handle(CreateBankAccountCommand request, Cancell } }); } + + public async Task> Handle(EnsureBankAccountValidatedCommand request, CancellationToken token) + { + return await ExecuteAsync(request, async () => + { + var user = await _context.GetByIdAsync(request.ProducerId, token); + var bankAccount = await _context.GetSingleAsync(c => c.User.Id == request.ProducerId && c.IsActive, token); + if (!string.IsNullOrWhiteSpace(bankAccount.Identifier)) + return Ok(true); + + var result = await _pspService.CreateBankIbanAsync(bankAccount, token); + if (!result.Success) + return Failed(result.Exception); + + return Ok(true); + }); + } } } \ No newline at end of file diff --git a/Sheaft.Application.Handlers/Commands/PayoutCommandsHandler.cs b/Sheaft.Application.Handlers/Commands/PayoutCommandsHandler.cs index 86e2fdfac..53ca7466c 100644 --- a/Sheaft.Application.Handlers/Commands/PayoutCommandsHandler.cs +++ b/Sheaft.Application.Handlers/Commands/PayoutCommandsHandler.cs @@ -14,6 +14,7 @@ using Microsoft.EntityFrameworkCore; using Sheaft.Options; using Microsoft.Extensions.Options; +using Sheaft.Exceptions; namespace Sheaft.Application.Handlers { @@ -163,22 +164,30 @@ public async Task> Handle(CreatePayoutCommand request, Cancellation var wallet = await _context.GetSingleAsync(c => c.User.Id == request.ProducerId, token); var bankAccount = await _context.GetSingleAsync(c => c.User.Id == request.ProducerId && c.IsActive, token); + var bankConfigurationResult = await _mediatr.Process(new EnsureBankAccountValidatedCommand(request.RequestUser) { ProducerId = request.ProducerId }, token); + if (!bankConfigurationResult.Success) + return Failed(bankConfigurationResult.Exception); + var transfers = await _context.GetAsync( t => request.TransferIds.Contains(t.Id) && t.PurchaseOrder.Status == PurchaseOrderStatus.Delivered && (t.Payout == null || t.Payout.Status == TransactionStatus.Failed), token); - var hasAlreadyPaidComission = await _context.AnyAsync( - p => p.Fees > 0 - && p.DebitedWallet.User.Id == request.ProducerId - && p.Status != TransactionStatus.Failed, - token); - var amount = transfers.Sum(t => t.Credited); - var fees = hasAlreadyPaidComission || amount < _pspOptions.ProducerFees ? 0m : _pspOptions.ProducerFees; - if (!hasAlreadyPaidComission && fees == 0m) - return Failed(new InvalidCastException()); + var fees = 0m; + if (producerLegals.DeclarationRequired) + { + var hasAlreadyPaidComission = await _context.AnyAsync( + p => p.Fees > 0 + && p.DebitedWallet.User.Id == request.ProducerId + && p.Status != TransactionStatus.Failed, + token); + + fees = hasAlreadyPaidComission || amount < _pspOptions.ProducerFees ? 0m : _pspOptions.ProducerFees; + if (!hasAlreadyPaidComission && fees == 0m) + return Failed(new Exception("Invalid fees for payout without paid commission.")); + } using (var transaction = await _context.BeginTransactionAsync(token)) { diff --git a/Sheaft.Application.Handlers/Commands/ProducerCommandsHandler.cs b/Sheaft.Application.Handlers/Commands/ProducerCommandsHandler.cs index b50be154d..85a1ca661 100644 --- a/Sheaft.Application.Handlers/Commands/ProducerCommandsHandler.cs +++ b/Sheaft.Application.Handlers/Commands/ProducerCommandsHandler.cs @@ -102,7 +102,7 @@ public async Task> Handle(RegisterProducerCommand request, Cancella Email = request.Legals.Email, Siret = request.Legals.Siret, Kind = request.Legals.Kind, - VatIdentifier = request.Legals.VatIdentifier, + VatIdentifier = request.NotSubjectToVat ? null : request.Legals.VatIdentifier, UserId = producer.Id, Owner = request.Legals.Owner }, token); @@ -139,6 +139,11 @@ public async Task> Handle(UpdateProducerCommand request, Cancellati producer.SetDescription(request.Description); producer.SetOpenForNewBusiness(request.OpenForNewBusiness); + if (request.NotSubjectToVat.HasValue) + { + producer.SetNotSubjectToVat(request.NotSubjectToVat.Value); + } + var departmentCode = UserAddress.GetDepartmentCode(request.Address.Zipcode); var department = await _context.Departments.SingleOrDefaultAsync(d => d.Code == departmentCode, token); diff --git a/Sheaft.Application.Models/Inputs/BusinessInput.cs b/Sheaft.Application.Models/Inputs/BusinessInput.cs index 06e71eb06..84e0353d2 100644 --- a/Sheaft.Application.Models/Inputs/BusinessInput.cs +++ b/Sheaft.Application.Models/Inputs/BusinessInput.cs @@ -14,7 +14,7 @@ public class BusinessInput public string Picture { get; set; } public FullAddressInput Address { get; set; } public bool OpenForNewBusiness { get; set; } - public bool NotSubjectToVat { get; set; } + public bool? NotSubjectToVat { get; set; } public IEnumerable Tags { get; set; } } } \ No newline at end of file diff --git a/Sheaft.Application.Models/ViewModels/BusinessViewModel.cs b/Sheaft.Application.Models/ViewModels/BusinessViewModel.cs index c4b85f4b1..2c053a9bb 100644 --- a/Sheaft.Application.Models/ViewModels/BusinessViewModel.cs +++ b/Sheaft.Application.Models/ViewModels/BusinessViewModel.cs @@ -22,5 +22,6 @@ public class ProducerViewModel public bool OpenForNewBusiness { get; set; } public AddressViewModel Address { get; set; } public IEnumerable Tags { get; set; } + public bool NotSubjectToVat { get; set; } } } diff --git a/Sheaft.GraphQL.Types/Inputs/RegisterProducerInputType.cs b/Sheaft.GraphQL.Types/Inputs/RegisterProducerInputType.cs index 5fc1973d5..e975d1818 100644 --- a/Sheaft.GraphQL.Types/Inputs/RegisterProducerInputType.cs +++ b/Sheaft.GraphQL.Types/Inputs/RegisterProducerInputType.cs @@ -8,12 +8,14 @@ public class RegisterProducerInputType : SheaftInputType protected override void Configure(IInputObjectTypeDescriptor descriptor) { descriptor.Field(c => c.OpenForNewBusiness); - descriptor.Field(c => c.NotSubjectToVat); descriptor.Field(c => c.Description); descriptor.Field(c => c.Phone); descriptor.Field(c => c.Picture); descriptor.Field(c => c.SponsoringCode); + descriptor.Field(c => c.NotSubjectToVat) + .Type>(); + descriptor.Field(c => c.Address) .Type>(); diff --git a/Sheaft.Web.Api/Startup.cs b/Sheaft.Web.Api/Startup.cs index 2add3794f..e177dc968 100644 --- a/Sheaft.Web.Api/Startup.cs +++ b/Sheaft.Web.Api/Startup.cs @@ -387,6 +387,17 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IConfigu wallet.SetIdentifier(configuration.GetValue("Psp:WalletId")); context.Add(wallet); + var bankAccount = new BankAccount(Guid.NewGuid(), "Dons", "Sheaft", configuration.GetValue("Psp:Bank:Iban"), configuration.GetValue("Psp:Bank:Bic"), + new BankAddress( + configuration.GetValue("Psp:Bank:Address:Line1"), + configuration.GetValue("Psp:Bank:Address:Line2"), + configuration.GetValue("Psp:Bank:Address:Zipcode"), + configuration.GetValue("Psp:Bank:Address:City"), + configuration.GetValue("Psp:Bank:Address:Country")), admin); + + bankAccount.SetIdentifier(configuration.GetValue("Psp:Bank:Id")); + context.Add(bankAccount); + context.SaveChanges(); } diff --git a/Sheaft.Web.Api/appsettings.json b/Sheaft.Web.Api/appsettings.json index eb5d0a1c2..e26bc9388 100644 --- a/Sheaft.Web.Api/appsettings.json +++ b/Sheaft.Web.Api/appsettings.json @@ -40,7 +40,19 @@ "FixedAmount": 0.18, "Percent": 0.018, "UserId": "##REPLACE##", - "WalletId": "##REPLACE##" + "WalletId": "##REPLACE##", + "Bank": { + "Id": "##REPLACE##", + "Iban": "##REPLACE##", + "Bic": "##REPLACE##", + "Address": { + "Line1": "##REPLACE##", + "Line2": "##REPLACE##", + "Zipcode": "##REPLACE##", + "City": "##REPLACE##", + "Country": "##REPLACE##" + } + } }, "Routines": { "CheckOrderExpiredFromMinutes": 1440, diff --git a/Sheaft.Web.Jobs/appsettings.json b/Sheaft.Web.Jobs/appsettings.json index c587ecf93..57f554f56 100644 --- a/Sheaft.Web.Jobs/appsettings.json +++ b/Sheaft.Web.Jobs/appsettings.json @@ -40,7 +40,19 @@ "FixedAmount": 0.18, "Percent": 0.018, "UserId": "##REPLACE##", - "WalletId": "##REPLACE##" + "WalletId": "##REPLACE##", + "Bank": { + "Id": "##REPLACE##", + "Iban": "##REPLACE##", + "Bic": "##REPLACE##", + "Address": { + "Line1": "##REPLACE##", + "Line2": "##REPLACE##", + "Zipcode": "##REPLACE##", + "City": "##REPLACE##", + "Country": "##REPLACE##" + } + } }, "Routines": { "CheckOrderExpiredFromMinutes": 1440, diff --git a/Sheaft.Web.Manage/Controllers/ProducersController.cs b/Sheaft.Web.Manage/Controllers/ProducersController.cs index 026583411..56be84bd6 100644 --- a/Sheaft.Web.Manage/Controllers/ProducersController.cs +++ b/Sheaft.Web.Manage/Controllers/ProducersController.cs @@ -116,7 +116,8 @@ public async Task Edit(ProducerViewModel model, IFormFile picture Kind = model.Kind, Phone = model.Phone, Tags = model.Tags, - Picture = model.Picture + Picture = model.Picture, + NotSubjectToVat = model.NotSubjectToVat }, token); if (!result.Success) @@ -137,7 +138,6 @@ public async Task CreateLegal(Guid userId, CancellationToken toke var entity = await _context.Users.OfType() .AsNoTracking() .Where(c => c.Id == userId) - .ProjectTo(_configurationProvider) .SingleOrDefaultAsync(token); if (entity == null) @@ -159,6 +159,8 @@ public async Task CreateLegal(Guid userId, CancellationToken toke public async Task CreateLegal(BusinessLegalViewModel model, CancellationToken token) { var requestUser = await GetRequestUser(token); + var user = await _context.FindByIdAsync(model.Owner.Id, token); + var result = await _mediatr.Process(new CreateBusinessLegalCommand(requestUser) { UserId = model.Owner.Id, @@ -167,7 +169,7 @@ public async Task CreateLegal(BusinessLegalViewModel model, Cance Kind = model.Kind, Owner = _mapper.Map(model.Owner), Siret = model.Siret, - VatIdentifier = model.VatIdentifier + VatIdentifier = user != null && user.NotSubjectToVat ? null : model.VatIdentifier }, token); if (!result.Success) @@ -203,6 +205,7 @@ public async Task UpdateLegal(Guid userId, CancellationToken toke public async Task UpdateLegal(BusinessLegalViewModel model, CancellationToken token) { var requestUser = await GetRequestUser(token); + var user = await _context.FindByIdAsync(model.Owner.Id, token); var result = await _mediatr.Process(new UpdateBusinessLegalCommand(requestUser) { Id = model.Id, @@ -211,7 +214,7 @@ public async Task UpdateLegal(BusinessLegalViewModel model, Cance Kind = model.Kind, Owner = _mapper.Map(model.Owner), Siret = model.Siret, - VatIdentifier = model.VatIdentifier + VatIdentifier = user != null && user.NotSubjectToVat ? null : model.VatIdentifier }, token); if (!result.Success) diff --git a/Sheaft.Web.Manage/appsettings.json b/Sheaft.Web.Manage/appsettings.json index 40fa920b8..806e43812 100644 --- a/Sheaft.Web.Manage/appsettings.json +++ b/Sheaft.Web.Manage/appsettings.json @@ -40,7 +40,19 @@ "FixedAmount": 0.18, "Percent": 0.018, "UserId": "##REPLACE##", - "WalletId": "##REPLACE##" + "WalletId": "##REPLACE##", + "Bank": { + "Id": "##REPLACE##", + "Iban": "##REPLACE##", + "Bic": "##REPLACE##", + "Address": { + "Line1": "##REPLACE##", + "Line2": "##REPLACE##", + "Zipcode": "##REPLACE##", + "City": "##REPLACE##", + "Country": "##REPLACE##" + } + } }, "Routines": { "CheckOrderExpiredFromMinutes": 1440, diff --git a/Sheaft.Web.Payment/appsettings.json b/Sheaft.Web.Payment/appsettings.json index f4ba31f7c..12d93cd34 100644 --- a/Sheaft.Web.Payment/appsettings.json +++ b/Sheaft.Web.Payment/appsettings.json @@ -40,7 +40,19 @@ "FixedAmount": 0.18, "Percent": 0.018, "UserId": "##REPLACE##", - "WalletId": "##REPLACE##" + "WalletId": "##REPLACE##", + "Bank": { + "Id": "##REPLACE##", + "Iban": "##REPLACE##", + "Bic": "##REPLACE##", + "Address": { + "Line1": "##REPLACE##", + "Line2": "##REPLACE##", + "Zipcode": "##REPLACE##", + "City": "##REPLACE##", + "Country": "##REPLACE##" + } + } }, "Routines": { "CheckOrderExpiredFromMinutes": 1440,