1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-06 13:38:13 -05:00

support credit purchases and prorated upgrades

This commit is contained in:
Kyle Spearrin 2019-02-20 23:54:27 -05:00
parent e10c99ec96
commit 01d324a8b4
4 changed files with 76 additions and 29 deletions

View File

@ -22,7 +22,8 @@ namespace Bit.Core.Models.Api
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(string.IsNullOrWhiteSpace(PaymentToken) && License == null)
var creditType = PaymentMethodType.HasValue && PaymentMethodType.Value == Enums.PaymentMethodType.Credit;
if(string.IsNullOrWhiteSpace(PaymentToken) && !creditType && License == null)
{
yield return new ValidationResult("Payment token or license is required.");
}

View File

@ -550,7 +550,7 @@ namespace Bit.Core.Services
}
else
{
if(!signup.PaymentMethodType.HasValue)
if(!signup.PaymentMethodType.HasValue && !string.IsNullOrWhiteSpace(signup.PaymentToken))
{
if(signup.PaymentToken.StartsWith("btok_"))
{

View File

@ -161,6 +161,20 @@ namespace Bit.Core.Services
public async Task PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
short additionalStorageGb)
{
if(paymentMethodType != PaymentMethodType.Credit && string.IsNullOrWhiteSpace(paymentToken))
{
throw new BadRequestException("Payment token is required.");
}
if(paymentMethodType == PaymentMethodType.Credit &&
(user.Gateway != GatewayType.Stripe || string.IsNullOrWhiteSpace(user.GatewayCustomerId)))
{
throw new BadRequestException("Your account does not have any credit available.");
}
if(paymentMethodType == PaymentMethodType.BankAccount)
{
throw new GatewayException("Bank account payment method is not supported at this time.");
}
var invoiceService = new InvoiceService();
var customerService = new CustomerService();
@ -169,28 +183,26 @@ namespace Bit.Core.Services
Braintree.Transaction braintreeTransaction = null;
Braintree.Customer braintreeCustomer = null;
var stripePaymentMethod = paymentMethodType == PaymentMethodType.Card ||
paymentMethodType == PaymentMethodType.BankAccount;
if(paymentMethodType == PaymentMethodType.BankAccount)
{
throw new GatewayException("Bank account payment method is not supported at this time.");
}
paymentMethodType == PaymentMethodType.BankAccount || paymentMethodType == PaymentMethodType.Credit;
if(user.Gateway == GatewayType.Stripe && !string.IsNullOrWhiteSpace(user.GatewayCustomerId))
{
try
if(!string.IsNullOrWhiteSpace(paymentToken))
{
await UpdatePaymentMethodAsync(user, paymentMethodType, paymentToken);
stripeCustomerId = user.GatewayCustomerId;
createdStripeCustomer = false;
}
catch(Exception)
{
stripeCustomerId = null;
try
{
await UpdatePaymentMethodAsync(user, paymentMethodType, paymentToken);
}
catch
{
stripeCustomerId = null;
}
}
stripeCustomerId = user.GatewayCustomerId;
createdStripeCustomer = false;
}
if(string.IsNullOrWhiteSpace(stripeCustomerId))
if(string.IsNullOrWhiteSpace(stripeCustomerId) && !string.IsNullOrWhiteSpace(paymentToken))
{
string stipeCustomerSourceToken = null;
var stripeCustomerMetadata = new Dictionary<string, string>();
@ -316,6 +328,18 @@ namespace Bit.Core.Services
throw new GatewayException("No payment was able to be collected.");
}
}
else if(paymentMethodType == PaymentMethodType.Credit)
{
var previewInvoice = await invoiceService.UpcomingAsync(new UpcomingInvoiceOptions
{
CustomerId = stripeCustomerId,
SubscriptionItems = ToInvoiceSubscriptionItemOptions(subCreateOptions.Items)
});
if(previewInvoice.AmountDue > 0)
{
throw new GatewayException("Your account does not have enough credit available.");
}
}
var subscriptionService = new SubscriptionService();
subscription = await subscriptionService.CreateAsync(subCreateOptions);
@ -533,6 +557,20 @@ namespace Bit.Core.Services
// Owes more than prorateThreshold on next invoice.
// Invoice them and pay now instead of waiting until next billing cycle.
var customerService = new CustomerService();
customerService.ExpandDefaultSource = true;
var customer = await customerService.GetAsync(subscriber.GatewayCustomerId);
var invoiceAmountDue = upcomingPreview.StartingBalance + invoiceAmount;
if(invoiceAmountDue > 0 && !customer.Metadata.ContainsKey("btCustomerId"))
{
if(customer.DefaultSource == null ||
(!(customer.DefaultSource is Card) && !(customer.DefaultSource is BankAccount)))
{
// throw new BadRequestException("No payment method is available.");
}
}
Invoice invoice = null;
var createdInvoiceItems = new List<InvoiceItem>();
Braintree.Transaction braintreeTransaction = null;
@ -565,14 +603,12 @@ namespace Bit.Core.Services
});
var invoicePayOptions = new InvoicePayOptions();
var customerService = new CustomerService();
var customer = await customerService.GetAsync(subscriber.GatewayCustomerId);
if(customer != null)
if(invoice.AmountDue > 0)
{
if(customer.Metadata.ContainsKey("btCustomerId"))
if(customer?.Metadata?.ContainsKey("btCustomerId") ?? false)
{
invoicePayOptions.PaidOutOfBand = true;
var btInvoiceAmount = (invoiceAmount / 100M);
var btInvoiceAmount = (invoice.AmountDue / 100M);
var transactionResult = await _btGateway.Transaction.SaleAsync(
new Braintree.TransactionRequest
{
@ -610,7 +646,14 @@ namespace Bit.Core.Services
}
}
await invoiceService.PayAsync(invoice.Id, invoicePayOptions);
try
{
await invoiceService.PayAsync(invoice.Id, invoicePayOptions);
}
catch(StripeException)
{
throw new GatewayException("Unable to pay invoice.");
}
}
catch(Exception e)
{
@ -620,7 +663,14 @@ namespace Bit.Core.Services
}
if(invoice != null)
{
await invoiceService.DeleteAsync(invoice.Id);
await invoiceService.VoidInvoiceAsync(invoice.Id, new InvoiceVoidOptions());
if(invoice.StartingBalance != 0)
{
await customerService.UpdateAsync(customer.Id, new CustomerUpdateOptions
{
AccountBalance = customer.AccountBalance
});
}
// Restore invoice items that were brought in
foreach(var item in pendingInvoiceItems)

View File

@ -708,7 +708,7 @@ namespace Bit.Core.Services
Directory.CreateDirectory(dir);
File.WriteAllText($"{dir}/{user.Id}.json", JsonConvert.SerializeObject(license, Formatting.Indented));
}
else if(!string.IsNullOrWhiteSpace(paymentToken))
else
{
if(!paymentMethodType.HasValue)
{
@ -729,10 +729,6 @@ namespace Bit.Core.Services
await _paymentService.PurchasePremiumAsync(user, paymentMethodType.Value,
paymentToken, additionalStorageGb);
}
else
{
throw new InvalidOperationException("License or payment token is required.");
}
user.Premium = true;
user.RevisionDate = DateTime.UtcNow;