diff --git a/src/Api/Controllers/OrganizationsController.cs b/src/Api/Controllers/OrganizationsController.cs index 634d0e3ed..3eee6a583 100644 --- a/src/Api/Controllers/OrganizationsController.cs +++ b/src/Api/Controllers/OrganizationsController.cs @@ -181,6 +181,18 @@ namespace Bit.Api.Controllers await _organizationService.AdjustStorageAsync(orgIdGuid, model.StorageGbAdjustment.Value); } + + [HttpPost("{id}/verify-bank")] + public async Task PostVerifyBank(string id, [FromBody]OrganizationVerifyBankRequestModel model) + { + var orgIdGuid = new Guid(id); + if(!_currentContext.OrganizationOwner(orgIdGuid)) + { + throw new NotFoundException(); + } + + await _organizationService.VerifyBankAsync(orgIdGuid, model.Amount1.Value, model.Amount2.Value); + } [HttpPut("{id}/cancel")] [HttpPost("{id}/cancel")] diff --git a/src/Core/Models/Api/Request/Organizations/OrganizationVerifyBankRequestModel.cs b/src/Core/Models/Api/Request/Organizations/OrganizationVerifyBankRequestModel.cs new file mode 100644 index 000000000..cb1c274e7 --- /dev/null +++ b/src/Core/Models/Api/Request/Organizations/OrganizationVerifyBankRequestModel.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace Bit.Core.Models.Api +{ + public class OrganizationVerifyBankRequestModel + { + [Required] + [Range(1, 99)] + public int? Amount1 { get; set; } + [Required] + [Range(1, 99)] + public int? Amount2 { get; set; } + } +} diff --git a/src/Core/Services/IOrganizationService.cs b/src/Core/Services/IOrganizationService.cs index 8bcf5f650..38ce44fbe 100644 --- a/src/Core/Services/IOrganizationService.cs +++ b/src/Core/Services/IOrganizationService.cs @@ -16,6 +16,7 @@ namespace Bit.Core.Services Task UpgradePlanAsync(Guid organizationId, PlanType plan, int additionalSeats); Task AdjustStorageAsync(Guid organizationId, short storageAdjustmentGb); Task AdjustSeatsAsync(Guid organizationId, int seatAdjustment); + Task VerifyBankAsync(Guid organizationId, int amount1, int amount2); Task> SignUpAsync(OrganizationSignup organizationSignup); Task DeleteAsync(Organization organization); Task DisableAsync(Guid organizationId, DateTime? expirationDate); diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index 3af351a48..37ddb5dce 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -350,6 +350,49 @@ namespace Bit.Core.Services await _organizationRepository.ReplaceAsync(organization); } + public async Task VerifyBankAsync(Guid organizationId, int amount1, int amount2) + { + var organization = await _organizationRepository.GetByIdAsync(organizationId); + if(organization == null) + { + throw new NotFoundException(); + } + + if(string.IsNullOrWhiteSpace(organization.GatewayCustomerId)) + { + throw new GatewayException("Not a gateway customer."); + } + + var bankService = new BankAccountService(); + var customerService = new StripeCustomerService(); + var customer = await customerService.GetAsync(organization.GatewayCustomerId); + if(customer == null) + { + throw new GatewayException("Cannot find customer."); + } + + var bankAccount = customer.Sources + .FirstOrDefault(s => s.BankAccount != null && s.BankAccount.Status != "verified")?.BankAccount; + if(bankAccount == null) + { + throw new GatewayException("Cannot find an unverified bank account."); + } + + try + { + var result = await bankService.VerifyAsync(organization.GatewayCustomerId, bankAccount.Id, + new BankAccountVerifyOptions { AmountOne = amount1, AmountTwo = amount2 }); + if(result.Status != "verified") + { + throw new GatewayException("Unable to verify account."); + } + } + catch(StripeException e) + { + throw new GatewayException(e.Message); + } + } + public async Task> SignUpAsync(OrganizationSignup signup) { var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == signup.Plan && !p.Disabled);