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:
parent
e10c99ec96
commit
01d324a8b4
@ -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.");
|
||||
}
|
||||
|
@ -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_"))
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user