mirror of
https://github.com/bitwarden/server
synced 2025-12-18 17:23:28 +00:00
[PM-27123] Account Credit not Showing for Premium Upgrade Payment (#6484)
* feat(billing): add PaymentMethod union * feat(billing): add nontokenized payment method * feat(billing): add validation for tokinized and nontokenized payments * feat(billing): update and add payment method requests * feat(billing): update command with new union object * test(billing): add tests for account credit for user. * feat(billing): update premium cloud hosted subscription request * fix(billing): dotnet format * tests(billing): include payment method tests * fix(billing): clean up tests and converter method
This commit is contained in:
@@ -16,6 +16,7 @@ using Microsoft.Extensions.Logging;
|
||||
using OneOf.Types;
|
||||
using Stripe;
|
||||
using Customer = Stripe.Customer;
|
||||
using PaymentMethod = Bit.Core.Billing.Payment.Models.PaymentMethod;
|
||||
using Subscription = Stripe.Subscription;
|
||||
|
||||
namespace Bit.Core.Billing.Premium.Commands;
|
||||
@@ -38,7 +39,7 @@ public interface ICreatePremiumCloudHostedSubscriptionCommand
|
||||
/// <returns>A billing command result indicating success or failure with appropriate error details.</returns>
|
||||
Task<BillingCommandResult<None>> Run(
|
||||
User user,
|
||||
TokenizedPaymentMethod paymentMethod,
|
||||
PaymentMethod paymentMethod,
|
||||
BillingAddress billingAddress,
|
||||
short additionalStorageGb);
|
||||
}
|
||||
@@ -60,7 +61,7 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
||||
|
||||
public Task<BillingCommandResult<None>> Run(
|
||||
User user,
|
||||
TokenizedPaymentMethod paymentMethod,
|
||||
PaymentMethod paymentMethod,
|
||||
BillingAddress billingAddress,
|
||||
short additionalStorageGb) => HandleAsync<None>(async () =>
|
||||
{
|
||||
@@ -74,6 +75,7 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
||||
return new BadRequest("Additional storage must be greater than 0.");
|
||||
}
|
||||
|
||||
// Note: A customer will already exist if the customer has purchased account credits.
|
||||
var customer = string.IsNullOrEmpty(user.GatewayCustomerId)
|
||||
? await CreateCustomerAsync(user, paymentMethod, billingAddress)
|
||||
: await subscriberService.GetCustomerOrThrow(user, new CustomerGetOptions { Expand = _expand });
|
||||
@@ -82,18 +84,31 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
||||
|
||||
var subscription = await CreateSubscriptionAsync(user.Id, customer, additionalStorageGb > 0 ? additionalStorageGb : null);
|
||||
|
||||
switch (paymentMethod)
|
||||
{
|
||||
case { Type: TokenizablePaymentMethodType.PayPal }
|
||||
when subscription.Status == StripeConstants.SubscriptionStatus.Incomplete:
|
||||
case { Type: not TokenizablePaymentMethodType.PayPal }
|
||||
when subscription.Status == StripeConstants.SubscriptionStatus.Active:
|
||||
paymentMethod.Switch(
|
||||
tokenized =>
|
||||
{
|
||||
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
|
||||
switch (tokenized)
|
||||
{
|
||||
case { Type: TokenizablePaymentMethodType.PayPal }
|
||||
when subscription.Status == StripeConstants.SubscriptionStatus.Incomplete:
|
||||
case { Type: not TokenizablePaymentMethodType.PayPal }
|
||||
when subscription.Status == StripeConstants.SubscriptionStatus.Active:
|
||||
{
|
||||
user.Premium = true;
|
||||
user.PremiumExpirationDate = subscription.GetCurrentPeriodEnd();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
nonTokenized =>
|
||||
{
|
||||
if (subscription.Status == StripeConstants.SubscriptionStatus.Active)
|
||||
{
|
||||
user.Premium = true;
|
||||
user.PremiumExpirationDate = subscription.GetCurrentPeriodEnd();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
user.Gateway = GatewayType.Stripe;
|
||||
user.GatewayCustomerId = customer.Id;
|
||||
@@ -109,9 +124,15 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
||||
});
|
||||
|
||||
private async Task<Customer> CreateCustomerAsync(User user,
|
||||
TokenizedPaymentMethod paymentMethod,
|
||||
PaymentMethod paymentMethod,
|
||||
BillingAddress billingAddress)
|
||||
{
|
||||
if (paymentMethod.IsNonTokenized)
|
||||
{
|
||||
_logger.LogError("Cannot create customer for user ({UserID}) using non-tokenized payment method. The customer should already exist", user.Id);
|
||||
throw new BillingException();
|
||||
}
|
||||
|
||||
var subscriberName = user.SubscriberName();
|
||||
var customerCreateOptions = new CustomerCreateOptions
|
||||
{
|
||||
@@ -153,13 +174,14 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
||||
|
||||
var braintreeCustomerId = "";
|
||||
|
||||
// We have checked that the payment method is tokenized, so we can safely cast it.
|
||||
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
|
||||
switch (paymentMethod.Type)
|
||||
switch (paymentMethod.AsT0.Type)
|
||||
{
|
||||
case TokenizablePaymentMethodType.BankAccount:
|
||||
{
|
||||
var setupIntent =
|
||||
(await stripeAdapter.SetupIntentList(new SetupIntentListOptions { PaymentMethod = paymentMethod.Token }))
|
||||
(await stripeAdapter.SetupIntentList(new SetupIntentListOptions { PaymentMethod = paymentMethod.AsT0.Token }))
|
||||
.FirstOrDefault();
|
||||
|
||||
if (setupIntent == null)
|
||||
@@ -173,19 +195,19 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
||||
}
|
||||
case TokenizablePaymentMethodType.Card:
|
||||
{
|
||||
customerCreateOptions.PaymentMethod = paymentMethod.Token;
|
||||
customerCreateOptions.InvoiceSettings.DefaultPaymentMethod = paymentMethod.Token;
|
||||
customerCreateOptions.PaymentMethod = paymentMethod.AsT0.Token;
|
||||
customerCreateOptions.InvoiceSettings.DefaultPaymentMethod = paymentMethod.AsT0.Token;
|
||||
break;
|
||||
}
|
||||
case TokenizablePaymentMethodType.PayPal:
|
||||
{
|
||||
braintreeCustomerId = await subscriberService.CreateBraintreeCustomer(user, paymentMethod.Token);
|
||||
braintreeCustomerId = await subscriberService.CreateBraintreeCustomer(user, paymentMethod.AsT0.Token);
|
||||
customerCreateOptions.Metadata[BraintreeCustomerIdKey] = braintreeCustomerId;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
_logger.LogError("Cannot create customer for user ({UserID}) using payment method type ({PaymentMethodType}) as it is not supported", user.Id, paymentMethod.Type.ToString());
|
||||
_logger.LogError("Cannot create customer for user ({UserID}) using payment method type ({PaymentMethodType}) as it is not supported", user.Id, paymentMethod.AsT0.Type.ToString());
|
||||
throw new BillingException();
|
||||
}
|
||||
}
|
||||
@@ -203,18 +225,21 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
||||
async Task Revert()
|
||||
{
|
||||
// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
|
||||
switch (paymentMethod.Type)
|
||||
if (paymentMethod.IsTokenized)
|
||||
{
|
||||
case TokenizablePaymentMethodType.BankAccount:
|
||||
{
|
||||
await setupIntentCache.RemoveSetupIntentForSubscriber(user.Id);
|
||||
break;
|
||||
}
|
||||
case TokenizablePaymentMethodType.PayPal when !string.IsNullOrEmpty(braintreeCustomerId):
|
||||
{
|
||||
await braintreeGateway.Customer.DeleteAsync(braintreeCustomerId);
|
||||
break;
|
||||
}
|
||||
switch (paymentMethod.AsT0.Type)
|
||||
{
|
||||
case TokenizablePaymentMethodType.BankAccount:
|
||||
{
|
||||
await setupIntentCache.RemoveSetupIntentForSubscriber(user.Id);
|
||||
break;
|
||||
}
|
||||
case TokenizablePaymentMethodType.PayPal when !string.IsNullOrEmpty(braintreeCustomerId):
|
||||
{
|
||||
await braintreeGateway.Customer.DeleteAsync(braintreeCustomerId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user