diff --git a/src/Api/Billing/Models/Requests/VerifyBankAccountRequestBody.cs b/src/Api/Billing/Models/Requests/VerifyBankAccountRequestBody.cs index de98755f3..06767a7ff 100644 --- a/src/Api/Billing/Models/Requests/VerifyBankAccountRequestBody.cs +++ b/src/Api/Billing/Models/Requests/VerifyBankAccountRequestBody.cs @@ -4,8 +4,8 @@ namespace Bit.Api.Billing.Models.Requests; public class VerifyBankAccountRequestBody { - [Range(0, 99)] + [Range(-99, 99)] public long Amount1 { get; set; } - [Range(0, 99)] + [Range(-99, 99)] public long Amount2 { get; set; } } diff --git a/src/Core/Billing/BillingException.cs b/src/Core/Billing/BillingException.cs index cdb3ce6b5..c2b1b9f45 100644 --- a/src/Core/Billing/BillingException.cs +++ b/src/Core/Billing/BillingException.cs @@ -5,5 +5,7 @@ public class BillingException( string message = null, Exception innerException = null) : Exception(message, innerException) { - public string Response { get; } = response ?? "Something went wrong with your request. Please contact support."; + public const string DefaultMessage = "Something went wrong with your request. Please contact support."; + + public string Response { get; } = response ?? DefaultMessage; } diff --git a/src/Core/Billing/Constants/StripeConstants.cs b/src/Core/Billing/Constants/StripeConstants.cs index 44cda35b7..30f8cc220 100644 --- a/src/Core/Billing/Constants/StripeConstants.cs +++ b/src/Core/Billing/Constants/StripeConstants.cs @@ -25,6 +25,16 @@ public static class StripeConstants public static class ErrorCodes { public const string CustomerTaxLocationInvalid = "customer_tax_location_invalid"; + public const string PaymentMethodMicroDepositVerificationAmountsInvalid = + "payment_method_microdeposit_verification_amounts_invalid"; + public const string PaymentMethodMicroDepositVerificationAmountsMismatch = + "payment_method_microdeposit_verification_amounts_mismatch"; + public const string PaymentMethodMicroDepositVerificationAttemptsExceeded = + "payment_method_microdeposit_verification_attempts_exceeded"; + public const string PaymentMethodMicroDepositVerificationDescriptorCodeMismatch = + "payment_method_microdeposit_verification_descriptor_code_mismatch"; + public const string PaymentMethodMicroDepositVerificationTimeout = + "payment_method_microdeposit_verification_timeout"; public const string TaxIdInvalid = "tax_id_invalid"; } diff --git a/src/Core/Billing/Services/Implementations/SubscriberService.cs b/src/Core/Billing/Services/Implementations/SubscriberService.cs index 9daf95622..ff9f4eaab 100644 --- a/src/Core/Billing/Services/Implementations/SubscriberService.cs +++ b/src/Core/Billing/Services/Implementations/SubscriberService.cs @@ -3,6 +3,7 @@ using Bit.Core.Billing.Constants; using Bit.Core.Billing.Models; using Bit.Core.Entities; using Bit.Core.Enums; +using Bit.Core.Exceptions; using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Utilities; @@ -651,39 +652,58 @@ public class SubscriberService( ISubscriber subscriber, (long, long) microdeposits) { - ArgumentNullException.ThrowIfNull(subscriber); - var setupIntentId = await setupIntentCache.Get(subscriber.Id); if (string.IsNullOrEmpty(setupIntentId)) { logger.LogError("No setup intent ID exists to verify for subscriber with ID ({SubscriberID})", subscriber.Id); - throw new BillingException(); } var (amount1, amount2) = microdeposits; - await stripeAdapter.SetupIntentVerifyMicroDeposit(setupIntentId, new SetupIntentVerifyMicrodepositsOptions + try { - Amounts = [amount1, amount2] - }); + await stripeAdapter.SetupIntentVerifyMicroDeposit(setupIntentId, + new SetupIntentVerifyMicrodepositsOptions { Amounts = [amount1, amount2] }); - var setupIntent = await stripeAdapter.SetupIntentGet(setupIntentId); + var setupIntent = await stripeAdapter.SetupIntentGet(setupIntentId); - await stripeAdapter.PaymentMethodAttachAsync(setupIntent.PaymentMethodId, new PaymentMethodAttachOptions - { - Customer = subscriber.GatewayCustomerId - }); + await stripeAdapter.PaymentMethodAttachAsync(setupIntent.PaymentMethodId, + new PaymentMethodAttachOptions { Customer = subscriber.GatewayCustomerId }); - await stripeAdapter.CustomerUpdateAsync(subscriber.GatewayCustomerId, - new CustomerUpdateOptions - { - InvoiceSettings = new CustomerInvoiceSettingsOptions + await stripeAdapter.CustomerUpdateAsync(subscriber.GatewayCustomerId, + new CustomerUpdateOptions { - DefaultPaymentMethod = setupIntent.PaymentMethodId - } - }); + InvoiceSettings = new CustomerInvoiceSettingsOptions + { + DefaultPaymentMethod = setupIntent.PaymentMethodId + } + }); + } + catch (StripeException stripeException) + { + if (!string.IsNullOrEmpty(stripeException.StripeError?.Code)) + { + var response = stripeException.StripeError.Code switch + { + StripeConstants.ErrorCodes.PaymentMethodMicroDepositVerificationAmountsInvalid => + "The two micro-deposit amounts you provided were invalid. Please try again or contact support.", + StripeConstants.ErrorCodes.PaymentMethodMicroDepositVerificationAmountsMismatch => + "The two micro-deposit amounts you provided did not match the amounts we sent. Please try again or contact support.", + StripeConstants.ErrorCodes.PaymentMethodMicroDepositVerificationAttemptsExceeded => + "You have reached the maximum number of attempts to verify your bank account. Please contact support.", + StripeConstants.ErrorCodes.PaymentMethodMicroDepositVerificationTimeout => + "Your bank account was not verified within the required time period. Please contact support.", + _ => BillingException.DefaultMessage + }; + + throw new BadRequestException(response); + } + + logger.LogError(stripeException, "Encountered an unhandled Stripe exception when trying to verify bank account for subscriber ({SubscriberID})", subscriber.Id); + throw new BillingException(); + } } #region Shared Utilities