mirror of
https://github.com/bitwarden/server.git
synced 2025-04-09 07:08:15 -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)
|
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.");
|
yield return new ValidationResult("Payment token or license is required.");
|
||||||
}
|
}
|
||||||
|
@ -550,7 +550,7 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(!signup.PaymentMethodType.HasValue)
|
if(!signup.PaymentMethodType.HasValue && !string.IsNullOrWhiteSpace(signup.PaymentToken))
|
||||||
{
|
{
|
||||||
if(signup.PaymentToken.StartsWith("btok_"))
|
if(signup.PaymentToken.StartsWith("btok_"))
|
||||||
{
|
{
|
||||||
|
@ -161,6 +161,20 @@ namespace Bit.Core.Services
|
|||||||
public async Task PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
|
public async Task PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
|
||||||
short additionalStorageGb)
|
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 invoiceService = new InvoiceService();
|
||||||
var customerService = new CustomerService();
|
var customerService = new CustomerService();
|
||||||
|
|
||||||
@ -169,28 +183,26 @@ namespace Bit.Core.Services
|
|||||||
Braintree.Transaction braintreeTransaction = null;
|
Braintree.Transaction braintreeTransaction = null;
|
||||||
Braintree.Customer braintreeCustomer = null;
|
Braintree.Customer braintreeCustomer = null;
|
||||||
var stripePaymentMethod = paymentMethodType == PaymentMethodType.Card ||
|
var stripePaymentMethod = paymentMethodType == PaymentMethodType.Card ||
|
||||||
paymentMethodType == PaymentMethodType.BankAccount;
|
paymentMethodType == PaymentMethodType.BankAccount || paymentMethodType == PaymentMethodType.Credit;
|
||||||
|
|
||||||
if(paymentMethodType == PaymentMethodType.BankAccount)
|
|
||||||
{
|
|
||||||
throw new GatewayException("Bank account payment method is not supported at this time.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(user.Gateway == GatewayType.Stripe && !string.IsNullOrWhiteSpace(user.GatewayCustomerId))
|
if(user.Gateway == GatewayType.Stripe && !string.IsNullOrWhiteSpace(user.GatewayCustomerId))
|
||||||
{
|
{
|
||||||
try
|
if(!string.IsNullOrWhiteSpace(paymentToken))
|
||||||
{
|
{
|
||||||
await UpdatePaymentMethodAsync(user, paymentMethodType, paymentToken);
|
try
|
||||||
stripeCustomerId = user.GatewayCustomerId;
|
{
|
||||||
createdStripeCustomer = false;
|
await UpdatePaymentMethodAsync(user, paymentMethodType, paymentToken);
|
||||||
}
|
}
|
||||||
catch(Exception)
|
catch
|
||||||
{
|
{
|
||||||
stripeCustomerId = null;
|
stripeCustomerId = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
stripeCustomerId = user.GatewayCustomerId;
|
||||||
|
createdStripeCustomer = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(string.IsNullOrWhiteSpace(stripeCustomerId))
|
if(string.IsNullOrWhiteSpace(stripeCustomerId) && !string.IsNullOrWhiteSpace(paymentToken))
|
||||||
{
|
{
|
||||||
string stipeCustomerSourceToken = null;
|
string stipeCustomerSourceToken = null;
|
||||||
var stripeCustomerMetadata = new Dictionary<string, string>();
|
var stripeCustomerMetadata = new Dictionary<string, string>();
|
||||||
@ -316,6 +328,18 @@ namespace Bit.Core.Services
|
|||||||
throw new GatewayException("No payment was able to be collected.");
|
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();
|
var subscriptionService = new SubscriptionService();
|
||||||
subscription = await subscriptionService.CreateAsync(subCreateOptions);
|
subscription = await subscriptionService.CreateAsync(subCreateOptions);
|
||||||
@ -533,6 +557,20 @@ namespace Bit.Core.Services
|
|||||||
// Owes more than prorateThreshold on next invoice.
|
// Owes more than prorateThreshold on next invoice.
|
||||||
// Invoice them and pay now instead of waiting until next billing cycle.
|
// 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;
|
Invoice invoice = null;
|
||||||
var createdInvoiceItems = new List<InvoiceItem>();
|
var createdInvoiceItems = new List<InvoiceItem>();
|
||||||
Braintree.Transaction braintreeTransaction = null;
|
Braintree.Transaction braintreeTransaction = null;
|
||||||
@ -565,14 +603,12 @@ namespace Bit.Core.Services
|
|||||||
});
|
});
|
||||||
|
|
||||||
var invoicePayOptions = new InvoicePayOptions();
|
var invoicePayOptions = new InvoicePayOptions();
|
||||||
var customerService = new CustomerService();
|
if(invoice.AmountDue > 0)
|
||||||
var customer = await customerService.GetAsync(subscriber.GatewayCustomerId);
|
|
||||||
if(customer != null)
|
|
||||||
{
|
{
|
||||||
if(customer.Metadata.ContainsKey("btCustomerId"))
|
if(customer?.Metadata?.ContainsKey("btCustomerId") ?? false)
|
||||||
{
|
{
|
||||||
invoicePayOptions.PaidOutOfBand = true;
|
invoicePayOptions.PaidOutOfBand = true;
|
||||||
var btInvoiceAmount = (invoiceAmount / 100M);
|
var btInvoiceAmount = (invoice.AmountDue / 100M);
|
||||||
var transactionResult = await _btGateway.Transaction.SaleAsync(
|
var transactionResult = await _btGateway.Transaction.SaleAsync(
|
||||||
new Braintree.TransactionRequest
|
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)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
@ -620,7 +663,14 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
if(invoice != null)
|
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
|
// Restore invoice items that were brought in
|
||||||
foreach(var item in pendingInvoiceItems)
|
foreach(var item in pendingInvoiceItems)
|
||||||
|
@ -708,7 +708,7 @@ namespace Bit.Core.Services
|
|||||||
Directory.CreateDirectory(dir);
|
Directory.CreateDirectory(dir);
|
||||||
File.WriteAllText($"{dir}/{user.Id}.json", JsonConvert.SerializeObject(license, Formatting.Indented));
|
File.WriteAllText($"{dir}/{user.Id}.json", JsonConvert.SerializeObject(license, Formatting.Indented));
|
||||||
}
|
}
|
||||||
else if(!string.IsNullOrWhiteSpace(paymentToken))
|
else
|
||||||
{
|
{
|
||||||
if(!paymentMethodType.HasValue)
|
if(!paymentMethodType.HasValue)
|
||||||
{
|
{
|
||||||
@ -729,10 +729,6 @@ namespace Bit.Core.Services
|
|||||||
await _paymentService.PurchasePremiumAsync(user, paymentMethodType.Value,
|
await _paymentService.PurchasePremiumAsync(user, paymentMethodType.Value,
|
||||||
paymentToken, additionalStorageGb);
|
paymentToken, additionalStorageGb);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("License or payment token is required.");
|
|
||||||
}
|
|
||||||
|
|
||||||
user.Premium = true;
|
user.Premium = true;
|
||||||
user.RevisionDate = DateTime.UtcNow;
|
user.RevisionDate = DateTime.UtcNow;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user