mirror of
https://github.com/bitwarden/server.git
synced 2025-04-08 14:38:15 -05:00
setup: process paypal with stripe subscription
This commit is contained in:
parent
54c6ffeece
commit
e54a381dba
@ -78,7 +78,7 @@ namespace Bit.Api.Controllers
|
|||||||
|
|
||||||
if(!_globalSettings.SelfHosted && organization.Gateway != null)
|
if(!_globalSettings.SelfHosted && organization.Gateway != null)
|
||||||
{
|
{
|
||||||
var paymentService = new StripePaymentService();
|
var paymentService = new StripePaymentService(_globalSettings);
|
||||||
var billingInfo = await paymentService.GetBillingAsync(organization);
|
var billingInfo = await paymentService.GetBillingAsync(organization);
|
||||||
if(billingInfo == null)
|
if(billingInfo == null)
|
||||||
{
|
{
|
||||||
|
@ -95,7 +95,7 @@ namespace Bit.Core.Models.Table
|
|||||||
switch(Gateway)
|
switch(Gateway)
|
||||||
{
|
{
|
||||||
case GatewayType.Stripe:
|
case GatewayType.Stripe:
|
||||||
paymentService = new StripePaymentService();
|
paymentService = new StripePaymentService(globalSettings);
|
||||||
break;
|
break;
|
||||||
case GatewayType.Braintree:
|
case GatewayType.Braintree:
|
||||||
paymentService = new BraintreePaymentService(globalSettings);
|
paymentService = new BraintreePaymentService(globalSettings);
|
||||||
|
@ -144,7 +144,7 @@ namespace Bit.Core.Models.Table
|
|||||||
switch(Gateway)
|
switch(Gateway)
|
||||||
{
|
{
|
||||||
case GatewayType.Stripe:
|
case GatewayType.Stripe:
|
||||||
paymentService = new StripePaymentService();
|
paymentService = new StripePaymentService(globalSettings);
|
||||||
break;
|
break;
|
||||||
case GatewayType.Braintree:
|
case GatewayType.Braintree:
|
||||||
paymentService = new BraintreePaymentService(globalSettings);
|
paymentService = new BraintreePaymentService(globalSettings);
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
public interface IPaymentService
|
public interface IPaymentService
|
||||||
{
|
{
|
||||||
Task CancelAndRecoverChargesAsync(ISubscriber subscriber);
|
Task CancelAndRecoverChargesAsync(ISubscriber subscriber);
|
||||||
Task PurchasePremiumAsync(User user, string paymentToken, short additionalStorageGb);
|
Task PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
|
||||||
|
short additionalStorageGb);
|
||||||
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);
|
||||||
|
@ -5,6 +5,7 @@ using Bit.Core.Models.Table;
|
|||||||
using Braintree;
|
using Braintree;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@ -214,7 +215,8 @@ namespace Bit.Core.Services
|
|||||||
return billingInfo;
|
return billingInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task PurchasePremiumAsync(User user, string paymentToken, short additionalStorageGb)
|
public async Task PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
|
||||||
|
short additionalStorageGb)
|
||||||
{
|
{
|
||||||
var customerResult = await _gateway.Customer.CreateAsync(new CustomerRequest
|
var customerResult = await _gateway.Customer.CreateAsync(new CustomerRequest
|
||||||
{
|
{
|
||||||
|
@ -66,7 +66,7 @@ namespace Bit.Core.Services
|
|||||||
_eventService = eventService;
|
_eventService = eventService;
|
||||||
_installationRepository = installationRepository;
|
_installationRepository = installationRepository;
|
||||||
_applicationCacheService = applicationCacheService;
|
_applicationCacheService = applicationCacheService;
|
||||||
_stripePaymentService = new StripePaymentService();
|
_stripePaymentService = new StripePaymentService(globalSettings);
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1208,7 +1208,7 @@ namespace Bit.Core.Services
|
|||||||
throw new BadRequestException("Invalid installation id");
|
throw new BadRequestException("Invalid installation id");
|
||||||
}
|
}
|
||||||
|
|
||||||
var paymentService = new StripePaymentService();
|
var paymentService = new StripePaymentService(_globalSettings);
|
||||||
var billingInfo = await paymentService.GetBillingAsync(organization);
|
var billingInfo = await paymentService.GetBillingAsync(organization);
|
||||||
return new OrganizationLicense(organization, billingInfo, installationId, _licensingService);
|
return new OrganizationLicense(organization, billingInfo, installationId, _licensingService);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ using System.Collections.Generic;
|
|||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
|
using Braintree;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@ -13,21 +15,68 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
private const string PremiumPlanId = "premium-annually";
|
private const string PremiumPlanId = "premium-annually";
|
||||||
private const string StoragePlanId = "storage-gb-annually";
|
private const string StoragePlanId = "storage-gb-annually";
|
||||||
|
private readonly BraintreeGateway _btGateway;
|
||||||
|
|
||||||
public async Task PurchasePremiumAsync(User user, string paymentToken, short additionalStorageGb)
|
public StripePaymentService(
|
||||||
|
GlobalSettings globalSettings)
|
||||||
{
|
{
|
||||||
|
_btGateway = new BraintreeGateway
|
||||||
|
{
|
||||||
|
Environment = globalSettings.Braintree.Production ?
|
||||||
|
Braintree.Environment.PRODUCTION : Braintree.Environment.SANDBOX,
|
||||||
|
MerchantId = globalSettings.Braintree.MerchantId,
|
||||||
|
PublicKey = globalSettings.Braintree.PublicKey,
|
||||||
|
PrivateKey = globalSettings.Braintree.PrivateKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
|
||||||
|
short additionalStorageGb)
|
||||||
|
{
|
||||||
|
Customer braintreeCustomer = null;
|
||||||
|
StripeBilling? stripeSubscriptionBilling = null;
|
||||||
|
string stipeCustomerSourceToken = null;
|
||||||
|
var stripeCustomerMetadata = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
if(paymentMethodType == PaymentMethodType.PayPal)
|
||||||
|
{
|
||||||
|
stripeSubscriptionBilling = StripeBilling.SendInvoice;
|
||||||
|
var randomSuffix = Utilities.CoreHelpers.RandomString(3, upper: false, numeric: false);
|
||||||
|
var customerResult = await _btGateway.Customer.CreateAsync(new CustomerRequest
|
||||||
|
{
|
||||||
|
PaymentMethodNonce = paymentToken,
|
||||||
|
Email = user.Email,
|
||||||
|
Id = "u" + user.Id.ToString("N").ToLower() + randomSuffix
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!customerResult.IsSuccess() || customerResult.Target.PaymentMethods.Length == 0)
|
||||||
|
{
|
||||||
|
throw new GatewayException("Failed to create PayPal customer record.");
|
||||||
|
}
|
||||||
|
|
||||||
|
braintreeCustomer = customerResult.Target;
|
||||||
|
stripeCustomerMetadata.Add("btCustomerId", braintreeCustomer.Id);
|
||||||
|
}
|
||||||
|
else if(paymentMethodType == PaymentMethodType.Card || paymentMethodType == PaymentMethodType.BankAccount)
|
||||||
|
{
|
||||||
|
stipeCustomerSourceToken = paymentToken;
|
||||||
|
}
|
||||||
|
|
||||||
var customerService = new StripeCustomerService();
|
var customerService = new StripeCustomerService();
|
||||||
var customer = await customerService.CreateAsync(new StripeCustomerCreateOptions
|
var customer = await customerService.CreateAsync(new StripeCustomerCreateOptions
|
||||||
{
|
{
|
||||||
Description = user.Name,
|
Description = user.Name,
|
||||||
Email = user.Email,
|
Email = user.Email,
|
||||||
SourceToken = paymentToken
|
SourceToken = stipeCustomerSourceToken,
|
||||||
|
Metadata = stripeCustomerMetadata
|
||||||
});
|
});
|
||||||
|
|
||||||
var subCreateOptions = new StripeSubscriptionCreateOptions
|
var subCreateOptions = new StripeSubscriptionCreateOptions
|
||||||
{
|
{
|
||||||
CustomerId = customer.Id,
|
CustomerId = customer.Id,
|
||||||
Items = new List<StripeSubscriptionItemOption>(),
|
Items = new List<StripeSubscriptionItemOption>(),
|
||||||
|
Billing = stripeSubscriptionBilling,
|
||||||
|
DaysUntilDue = stripeSubscriptionBilling != null ? 1 : 0,
|
||||||
Metadata = new Dictionary<string, string>
|
Metadata = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
["userId"] = user.Id.ToString()
|
["userId"] = user.Id.ToString()
|
||||||
@ -54,14 +103,70 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
var subscriptionService = new StripeSubscriptionService();
|
var subscriptionService = new StripeSubscriptionService();
|
||||||
subscription = await subscriptionService.CreateAsync(subCreateOptions);
|
subscription = await subscriptionService.CreateAsync(subCreateOptions);
|
||||||
|
|
||||||
|
if(stripeSubscriptionBilling == StripeBilling.SendInvoice)
|
||||||
|
{
|
||||||
|
var invoiceService = new StripeInvoiceService();
|
||||||
|
var invoices = await invoiceService.ListAsync(new StripeInvoiceListOptions
|
||||||
|
{
|
||||||
|
SubscriptionId = subscription.Id
|
||||||
|
});
|
||||||
|
|
||||||
|
var invoice = invoices?.FirstOrDefault(i => i.AmountDue > 0);
|
||||||
|
if(invoice == null)
|
||||||
|
{
|
||||||
|
throw new GatewayException("Invoice not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(braintreeCustomer != null)
|
||||||
|
{
|
||||||
|
var btInvoiceAmount = (invoice.AmountDue / 100M);
|
||||||
|
var transactionResult = await _btGateway.Transaction.SaleAsync(new TransactionRequest
|
||||||
|
{
|
||||||
|
Amount = btInvoiceAmount,
|
||||||
|
CustomerId = braintreeCustomer.Id
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!transactionResult.IsSuccess() || transactionResult.Target.Amount != btInvoiceAmount)
|
||||||
|
{
|
||||||
|
throw new GatewayException("Failed to charge PayPal customer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var invoiceItemService = new StripeInvoiceItemService();
|
||||||
|
await invoiceItemService.CreateAsync(new StripeInvoiceItemCreateOptions
|
||||||
|
{
|
||||||
|
Currency = "USD",
|
||||||
|
CustomerId = customer.Id,
|
||||||
|
InvoiceId = invoice.Id,
|
||||||
|
Amount = -1 * invoice.AmountDue,
|
||||||
|
Description = $"PayPal Credit, Transaction ID " +
|
||||||
|
transactionResult.Target.PayPalDetails.AuthorizationId,
|
||||||
|
Metadata = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["btTransactionId"] = transactionResult.Target.Id,
|
||||||
|
["btPayPalTransactionId"] = transactionResult.Target.PayPalDetails.AuthorizationId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new GatewayException("No payment was able to be collected.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await invoiceService.PayAsync(invoice.Id, new StripeInvoicePayOptions { });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(StripeException)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
await customerService.DeleteAsync(customer.Id);
|
await customerService.DeleteAsync(customer.Id);
|
||||||
throw;
|
if(braintreeCustomer != null)
|
||||||
|
{
|
||||||
|
await _btGateway.Customer.DeleteAsync(braintreeCustomer.Id);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Gateway = Enums.GatewayType.Stripe;
|
user.Gateway = GatewayType.Stripe;
|
||||||
user.GatewayCustomerId = customer.Id;
|
user.GatewayCustomerId = customer.Id;
|
||||||
user.GatewaySubscriptionId = subscription.Id;
|
user.GatewaySubscriptionId = subscription.Id;
|
||||||
user.Premium = true;
|
user.Premium = true;
|
||||||
@ -169,6 +274,36 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
if(invoice.AmountDue > 0)
|
if(invoice.AmountDue > 0)
|
||||||
{
|
{
|
||||||
|
var customerService = new StripeCustomerService();
|
||||||
|
var customer = await customerService.GetAsync(subscriber.GatewayCustomerId);
|
||||||
|
if(customer != null)
|
||||||
|
{
|
||||||
|
if(customer.Metadata.ContainsKey("btCustomerId"))
|
||||||
|
{
|
||||||
|
var invoiceAmount = (invoice.AmountDue / 100M);
|
||||||
|
var transactionResult = await _btGateway.Transaction.SaleAsync(new TransactionRequest
|
||||||
|
{
|
||||||
|
Amount = invoiceAmount,
|
||||||
|
CustomerId = customer.Metadata["btCustomerId"]
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!transactionResult.IsSuccess() || transactionResult.Target.Amount != invoiceAmount)
|
||||||
|
{
|
||||||
|
await invoiceService.UpdateAsync(invoice.Id, new StripeInvoiceUpdateOptions
|
||||||
|
{
|
||||||
|
Closed = true
|
||||||
|
});
|
||||||
|
throw new GatewayException("Failed to charge PayPal customer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await customerService.UpdateAsync(customer.Id, new StripeCustomerUpdateOptions
|
||||||
|
{
|
||||||
|
AccountBalance = customer.AccountBalance - invoice.AmountDue,
|
||||||
|
Metadata = customer.Metadata
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await invoiceService.PayAsync(invoice.Id, new StripeInvoicePayOptions());
|
await invoiceService.PayAsync(invoice.Id, new StripeInvoicePayOptions());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -706,16 +706,14 @@ namespace Bit.Core.Services
|
|||||||
throw new BadRequestException("Invalid token.");
|
throw new BadRequestException("Invalid token.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(paymentToken.StartsWith("tok_"))
|
var paymentMethodType = PaymentMethodType.Card;
|
||||||
|
if(!paymentToken.StartsWith("tok_"))
|
||||||
{
|
{
|
||||||
paymentService = new StripePaymentService();
|
paymentMethodType = PaymentMethodType.PayPal;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
paymentService = new BraintreePaymentService(_globalSettings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await paymentService.PurchasePremiumAsync(user, paymentToken, additionalStorageGb);
|
await new StripePaymentService(_globalSettings).PurchasePremiumAsync(user, paymentMethodType,
|
||||||
|
paymentToken, additionalStorageGb);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -805,7 +803,7 @@ namespace Bit.Core.Services
|
|||||||
IPaymentService paymentService = null;
|
IPaymentService paymentService = null;
|
||||||
if(paymentToken.StartsWith("tok_"))
|
if(paymentToken.StartsWith("tok_"))
|
||||||
{
|
{
|
||||||
paymentService = new StripePaymentService();
|
paymentService = new StripePaymentService(_globalSettings);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user