mirror of
https://github.com/bitwarden/server.git
synced 2025-05-23 04:21:05 -05:00
change payment methods between stripe and paypal
This commit is contained in:
parent
fca1ee4253
commit
952d624d72
@ -1,15 +1,18 @@
|
|||||||
using Bit.Core.Enums;
|
using System;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
|
|
||||||
namespace Bit.Core.Models.Table
|
namespace Bit.Core.Models.Table
|
||||||
{
|
{
|
||||||
public interface ISubscriber
|
public interface ISubscriber
|
||||||
{
|
{
|
||||||
|
Guid Id { get; }
|
||||||
GatewayType? Gateway { get; set; }
|
GatewayType? Gateway { get; set; }
|
||||||
string GatewayCustomerId { get; set; }
|
string GatewayCustomerId { get; set; }
|
||||||
string GatewaySubscriptionId { get; set; }
|
string GatewaySubscriptionId { get; set; }
|
||||||
string BillingEmailAddress();
|
string BillingEmailAddress();
|
||||||
string BillingName();
|
string BillingName();
|
||||||
|
string BraintreeCustomerIdPrefix();
|
||||||
IPaymentService GetPaymentService(GlobalSettings globalSettings);
|
IPaymentService GetPaymentService(GlobalSettings globalSettings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,11 @@ namespace Bit.Core.Models.Table
|
|||||||
return BusinessName;
|
return BusinessName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string BraintreeCustomerIdPrefix()
|
||||||
|
{
|
||||||
|
return "o";
|
||||||
|
}
|
||||||
|
|
||||||
public long StorageBytesRemaining()
|
public long StorageBytesRemaining()
|
||||||
{
|
{
|
||||||
if(!MaxStorageGb.HasValue)
|
if(!MaxStorageGb.HasValue)
|
||||||
|
@ -58,6 +58,11 @@ namespace Bit.Core.Models.Table
|
|||||||
return Name;
|
return Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string BraintreeCustomerIdPrefix()
|
||||||
|
{
|
||||||
|
return "u";
|
||||||
|
}
|
||||||
|
|
||||||
public Dictionary<TwoFactorProviderType, TwoFactorProvider> GetTwoFactorProviders()
|
public Dictionary<TwoFactorProviderType, TwoFactorProvider> GetTwoFactorProviders()
|
||||||
{
|
{
|
||||||
if(string.IsNullOrWhiteSpace(TwoFactorProviders))
|
if(string.IsNullOrWhiteSpace(TwoFactorProviders))
|
||||||
|
@ -13,7 +13,8 @@ namespace Bit.Core.Services
|
|||||||
Task AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId);
|
Task AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId);
|
||||||
Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false);
|
Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false);
|
||||||
Task ReinstateSubscriptionAsync(ISubscriber subscriber);
|
Task ReinstateSubscriptionAsync(ISubscriber subscriber);
|
||||||
Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, string paymentToken);
|
Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, PaymentMethodType paymentMethodType,
|
||||||
|
string paymentToken);
|
||||||
Task<BillingInfo.BillingInvoice> GetUpcomingInvoiceAsync(ISubscriber subscriber);
|
Task<BillingInfo.BillingInvoice> GetUpcomingInvoiceAsync(ISubscriber subscriber);
|
||||||
Task<BillingInfo> GetBillingAsync(ISubscriber subscriber);
|
Task<BillingInfo> GetBillingAsync(ISubscriber subscriber);
|
||||||
}
|
}
|
||||||
|
@ -303,14 +303,20 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, string paymentToken)
|
public async Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, PaymentMethodType paymentMethodType,
|
||||||
|
string paymentToken)
|
||||||
{
|
{
|
||||||
if(subscriber == null)
|
if(subscriber == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(subscriber));
|
throw new ArgumentNullException(nameof(subscriber));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(subscriber.Gateway.HasValue && subscriber.Gateway.Value != Enums.GatewayType.Braintree)
|
if(paymentMethodType != PaymentMethodType.PayPal)
|
||||||
|
{
|
||||||
|
throw new GatewayException("Payment method not allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(subscriber.Gateway.HasValue && subscriber.Gateway.Value != GatewayType.Braintree)
|
||||||
{
|
{
|
||||||
throw new GatewayException("Switching from one payment type to another is not supported. " +
|
throw new GatewayException("Switching from one payment type to another is not supported. " +
|
||||||
"Contact us for assistance.");
|
"Contact us for assistance.");
|
||||||
|
@ -78,7 +78,22 @@ namespace Bit.Core.Services
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var updated = await _stripePaymentService.UpdatePaymentMethodAsync(organization, paymentToken);
|
PaymentMethodType paymentMethodType;
|
||||||
|
if(paymentToken.StartsWith("btok_"))
|
||||||
|
{
|
||||||
|
paymentMethodType = PaymentMethodType.BankAccount;
|
||||||
|
}
|
||||||
|
else if(paymentToken.StartsWith("tok_"))
|
||||||
|
{
|
||||||
|
paymentMethodType = PaymentMethodType.Card;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
paymentMethodType = PaymentMethodType.PayPal;
|
||||||
|
}
|
||||||
|
|
||||||
|
var updated = await _stripePaymentService.UpdatePaymentMethodAsync(organization,
|
||||||
|
paymentMethodType, paymentToken);
|
||||||
if(updated)
|
if(updated)
|
||||||
{
|
{
|
||||||
await ReplaceAndUpdateCache(organization);
|
await ReplaceAndUpdateCache(organization);
|
||||||
|
@ -65,6 +65,10 @@ namespace Bit.Core.Services
|
|||||||
braintreeCustomer = customerResult.Target;
|
braintreeCustomer = customerResult.Target;
|
||||||
stripeCustomerMetadata.Add("btCustomerId", braintreeCustomer.Id);
|
stripeCustomerMetadata.Add("btCustomerId", braintreeCustomer.Id);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new GatewayException("Payment method is not supported at this time.");
|
||||||
|
}
|
||||||
|
|
||||||
var customer = await customerService.CreateAsync(new CustomerCreateOptions
|
var customer = await customerService.CreateAsync(new CustomerCreateOptions
|
||||||
{
|
{
|
||||||
@ -542,20 +546,26 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, string paymentToken)
|
public async Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, PaymentMethodType paymentMethodType,
|
||||||
|
string paymentToken)
|
||||||
{
|
{
|
||||||
if(subscriber == null)
|
if(subscriber == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(subscriber));
|
throw new ArgumentNullException(nameof(subscriber));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(subscriber.Gateway.HasValue && subscriber.Gateway.Value != Enums.GatewayType.Stripe)
|
if(subscriber.Gateway.HasValue && subscriber.Gateway.Value != GatewayType.Stripe)
|
||||||
{
|
{
|
||||||
throw new GatewayException("Switching from one payment type to another is not supported. " +
|
throw new GatewayException("Switching from one payment type to another is not supported. " +
|
||||||
"Contact us for assistance.");
|
"Contact us for assistance.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var updatedSubscriber = false;
|
var createdCustomer = false;
|
||||||
|
Braintree.Customer braintreeCustomer = null;
|
||||||
|
string stipeCustomerSourceToken = null;
|
||||||
|
var stripeCustomerMetadata = new Dictionary<string, string>();
|
||||||
|
var stripePaymentMethod = paymentMethodType == PaymentMethodType.Card ||
|
||||||
|
paymentMethodType == PaymentMethodType.BankAccount;
|
||||||
|
|
||||||
var cardService = new CardService();
|
var cardService = new CardService();
|
||||||
var bankSerice = new BankAccountService();
|
var bankSerice = new BankAccountService();
|
||||||
@ -565,53 +575,122 @@ namespace Bit.Core.Services
|
|||||||
if(!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
|
if(!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
|
||||||
{
|
{
|
||||||
customer = await customerService.GetAsync(subscriber.GatewayCustomerId);
|
customer = await customerService.GetAsync(subscriber.GatewayCustomerId);
|
||||||
|
if(customer.Metadata?.Any() ?? false)
|
||||||
|
{
|
||||||
|
stripeCustomerMetadata = customer.Metadata;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(customer == null)
|
if(stripeCustomerMetadata.ContainsKey("btCustomerId"))
|
||||||
{
|
{
|
||||||
customer = await customerService.CreateAsync(new CustomerCreateOptions
|
var nowSec = Utilities.CoreHelpers.ToEpocSeconds(DateTime.UtcNow);
|
||||||
|
stripeCustomerMetadata.Add($"btCustomerId_{nowSec}", stripeCustomerMetadata["btCustomerId"]);
|
||||||
|
stripeCustomerMetadata["btCustomerId"] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(stripePaymentMethod)
|
||||||
|
{
|
||||||
|
stipeCustomerSourceToken = paymentToken;
|
||||||
|
}
|
||||||
|
else if(paymentMethodType == PaymentMethodType.PayPal)
|
||||||
|
{
|
||||||
|
var randomSuffix = Utilities.CoreHelpers.RandomString(3, upper: false, numeric: false);
|
||||||
|
var customerResult = await _btGateway.Customer.CreateAsync(new Braintree.CustomerRequest
|
||||||
{
|
{
|
||||||
Description = subscriber.BillingName(),
|
PaymentMethodNonce = paymentToken,
|
||||||
Email = subscriber.BillingEmailAddress(),
|
Email = subscriber.BillingEmailAddress(),
|
||||||
SourceToken = paymentToken
|
Id = subscriber.BraintreeCustomerIdPrefix() + subscriber.Id.ToString("N").ToLower() + randomSuffix
|
||||||
});
|
});
|
||||||
|
|
||||||
subscriber.Gateway = Enums.GatewayType.Stripe;
|
if(!customerResult.IsSuccess() || customerResult.Target.PaymentMethods.Length == 0)
|
||||||
subscriber.GatewayCustomerId = customer.Id;
|
|
||||||
updatedSubscriber = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(paymentToken.StartsWith("btok_"))
|
|
||||||
{
|
{
|
||||||
await bankSerice.CreateAsync(customer.Id, new BankAccountCreateOptions
|
throw new GatewayException("Failed to create PayPal customer record.");
|
||||||
{
|
}
|
||||||
SourceToken = paymentToken
|
|
||||||
});
|
braintreeCustomer = customerResult.Target;
|
||||||
|
if(stripeCustomerMetadata.ContainsKey("btCustomerId"))
|
||||||
|
{
|
||||||
|
stripeCustomerMetadata["btCustomerId"] = braintreeCustomer.Id;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await cardService.CreateAsync(customer.Id, new CardCreateOptions
|
stripeCustomerMetadata.Add("btCustomerId", braintreeCustomer.Id);
|
||||||
{
|
|
||||||
SourceToken = paymentToken
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!string.IsNullOrWhiteSpace(customer.DefaultSourceId))
|
|
||||||
{
|
|
||||||
var source = customer.Sources.FirstOrDefault(s => s.Id == customer.DefaultSourceId);
|
|
||||||
if(source is BankAccount)
|
|
||||||
{
|
|
||||||
await bankSerice.DeleteAsync(customer.Id, customer.DefaultSourceId);
|
|
||||||
}
|
|
||||||
else if(source is Card)
|
|
||||||
{
|
|
||||||
await cardService.DeleteAsync(customer.Id, customer.DefaultSourceId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new GatewayException("Payment method is not supported at this time.");
|
||||||
|
}
|
||||||
|
|
||||||
return updatedSubscriber;
|
try
|
||||||
|
{
|
||||||
|
if(customer == null)
|
||||||
|
{
|
||||||
|
customer = await customerService.CreateAsync(new CustomerCreateOptions
|
||||||
|
{
|
||||||
|
Description = subscriber.BillingName(),
|
||||||
|
Email = subscriber.BillingEmailAddress(),
|
||||||
|
SourceToken = stipeCustomerSourceToken,
|
||||||
|
Metadata = stripeCustomerMetadata
|
||||||
|
});
|
||||||
|
|
||||||
|
subscriber.Gateway = GatewayType.Stripe;
|
||||||
|
subscriber.GatewayCustomerId = customer.Id;
|
||||||
|
createdCustomer = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!createdCustomer)
|
||||||
|
{
|
||||||
|
string defaultSourceId = null;
|
||||||
|
if(stripePaymentMethod)
|
||||||
|
{
|
||||||
|
if(paymentToken.StartsWith("btok_"))
|
||||||
|
{
|
||||||
|
var bankAccount = await bankSerice.CreateAsync(customer.Id, new BankAccountCreateOptions
|
||||||
|
{
|
||||||
|
SourceToken = paymentToken
|
||||||
|
});
|
||||||
|
defaultSourceId = bankAccount.Id;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var card = await cardService.CreateAsync(customer.Id, new CardCreateOptions
|
||||||
|
{
|
||||||
|
SourceToken = paymentToken,
|
||||||
|
});
|
||||||
|
defaultSourceId = card.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(var source in customer.Sources.Where(s => s.Id != defaultSourceId))
|
||||||
|
{
|
||||||
|
if(source is BankAccount)
|
||||||
|
{
|
||||||
|
await bankSerice.DeleteAsync(customer.Id, source.Id);
|
||||||
|
}
|
||||||
|
else if(source is Card)
|
||||||
|
{
|
||||||
|
await cardService.DeleteAsync(customer.Id, source.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customer = await customerService.UpdateAsync(customer.Id, new CustomerUpdateOptions
|
||||||
|
{
|
||||||
|
Metadata = stripeCustomerMetadata,
|
||||||
|
DefaultSource = defaultSourceId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
if(braintreeCustomer != null)
|
||||||
|
{
|
||||||
|
await _btGateway.Customer.DeleteAsync(braintreeCustomer.Id);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return createdCustomer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<BillingInfo.BillingInvoice> GetUpcomingInvoiceAsync(ISubscriber subscriber)
|
public async Task<BillingInfo.BillingInvoice> GetUpcomingInvoiceAsync(ISubscriber subscriber)
|
||||||
@ -660,12 +739,17 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
if(customer.Metadata?.ContainsKey("btCustomerId") ?? false)
|
if(customer.Metadata?.ContainsKey("btCustomerId") ?? false)
|
||||||
{
|
{
|
||||||
var braintreeCustomer = await _btGateway.Customer.FindAsync(customer.Metadata["btCustomerId"]);
|
try
|
||||||
if(braintreeCustomer?.DefaultPaymentMethod != null)
|
|
||||||
{
|
{
|
||||||
billingInfo.PaymentSource = new BillingInfo.BillingSource(
|
var braintreeCustomer = await _btGateway.Customer.FindAsync(
|
||||||
braintreeCustomer.DefaultPaymentMethod);
|
customer.Metadata["btCustomerId"]);
|
||||||
|
if(braintreeCustomer?.DefaultPaymentMethod != null)
|
||||||
|
{
|
||||||
|
billingInfo.PaymentSource = new BillingInfo.BillingSource(
|
||||||
|
braintreeCustomer.DefaultPaymentMethod);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
catch(Braintree.Exceptions.NotFoundException) { }
|
||||||
}
|
}
|
||||||
else if(!string.IsNullOrWhiteSpace(customer.DefaultSourceId) && customer.Sources?.Data != null)
|
else if(!string.IsNullOrWhiteSpace(customer.DefaultSourceId) && customer.Sources?.Data != null)
|
||||||
{
|
{
|
||||||
|
@ -800,17 +800,18 @@ namespace Bit.Core.Services
|
|||||||
throw new BadRequestException("Invalid token.");
|
throw new BadRequestException("Invalid token.");
|
||||||
}
|
}
|
||||||
|
|
||||||
IPaymentService paymentService = null;
|
PaymentMethodType paymentMethodType;
|
||||||
|
var paymentService = new StripePaymentService(_globalSettings);
|
||||||
if(paymentToken.StartsWith("tok_"))
|
if(paymentToken.StartsWith("tok_"))
|
||||||
{
|
{
|
||||||
paymentService = new StripePaymentService(_globalSettings);
|
paymentMethodType = PaymentMethodType.Card;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
paymentService = new BraintreePaymentService(_globalSettings);
|
paymentMethodType = PaymentMethodType.PayPal;
|
||||||
}
|
}
|
||||||
|
|
||||||
var updated = await paymentService.UpdatePaymentMethodAsync(user, paymentToken);
|
var updated = await paymentService.UpdatePaymentMethodAsync(user, paymentMethodType, paymentToken);
|
||||||
if(updated)
|
if(updated)
|
||||||
{
|
{
|
||||||
await SaveUserAsync(user);
|
await SaveUserAsync(user);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user