1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-26 12:55:17 +01:00

adjust storage with payment intent/method handling

This commit is contained in:
Kyle Spearrin 2019-08-10 12:59:32 -04:00
parent e60f1a4f50
commit 74bbeae776
9 changed files with 66 additions and 22 deletions

View File

@ -529,7 +529,7 @@ namespace Bit.Api.Controllers
[HttpPost("storage")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PostStorage([FromBody]StorageRequestModel model)
public async Task<PaymentResponseModel> PostStorage([FromBody]StorageRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if(user == null)
@ -537,7 +537,12 @@ namespace Bit.Api.Controllers
throw new UnauthorizedAccessException();
}
await _userService.AdjustStorageAsync(user, model.StorageGbAdjustment.Value);
var result = await _userService.AdjustStorageAsync(user, model.StorageGbAdjustment.Value);
return new PaymentResponseModel
{
Success = true,
PaymentIntentClientSecret = result
};
}
[HttpPost("license")]

View File

@ -246,7 +246,7 @@ namespace Bit.Api.Controllers
[HttpPost("{id}/storage")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task PostStorage(string id, [FromBody]StorageRequestModel model)
public async Task<PaymentResponseModel> PostStorage(string id, [FromBody]StorageRequestModel model)
{
var orgIdGuid = new Guid(id);
if(!_currentContext.OrganizationOwner(orgIdGuid))
@ -254,7 +254,12 @@ namespace Bit.Api.Controllers
throw new NotFoundException();
}
await _organizationService.AdjustStorageAsync(orgIdGuid, model.StorageGbAdjustment.Value);
var result = await _organizationService.AdjustStorageAsync(orgIdGuid, model.StorageGbAdjustment.Value);
return new PaymentResponseModel
{
Success = true,
PaymentIntentClientSecret = result
};
}
[HttpPost("{id}/verify-bank")]

View File

@ -14,7 +14,7 @@ namespace Bit.Core.Services
Task CancelSubscriptionAsync(Guid organizationId, bool? endOfPeriod = null);
Task ReinstateSubscriptionAsync(Guid organizationId);
Task<Tuple<bool, string>> UpgradePlanAsync(Guid organizationId, OrganizationUpgrade upgrade);
Task AdjustStorageAsync(Guid organizationId, short storageAdjustmentGb);
Task<string> AdjustStorageAsync(Guid organizationId, short storageAdjustmentGb);
Task AdjustSeatsAsync(Guid organizationId, int seatAdjustment);
Task VerifyBankAsync(Guid organizationId, int amount1, int amount2);
Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationSignup organizationSignup);

View File

@ -15,7 +15,7 @@ namespace Bit.Core.Services
short additionalStorageGb, short additionalSeats, bool premiumAccessAddon);
Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
short additionalStorageGb);
Task AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId);
Task<string> AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId);
Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false);
Task ReinstateSubscriptionAsync(ISubscriber subscriber);
Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, PaymentMethodType paymentMethodType,

View File

@ -46,7 +46,7 @@ namespace Bit.Core.Services
Task<Tuple<bool, string>> SignUpPremiumAsync(User user, string paymentToken,
PaymentMethodType paymentMethodType, short additionalStorageGb, UserLicense license);
Task UpdateLicenseAsync(User user, UserLicense license);
Task AdjustStorageAsync(User user, short storageAdjustmentGb);
Task<string> AdjustStorageAsync(User user, short storageAdjustmentGb);
Task ReplacePaymentMethodAsync(User user, string paymentToken, PaymentMethodType paymentMethodType);
Task CancelPremiumAsync(User user, bool? endOfPeriod = null);
Task ReinstatePremiumAsync(User user);

View File

@ -230,7 +230,7 @@ namespace Bit.Core.Services
return new Tuple<bool, string>(success, paymentIntentClientSecret);
}
public async Task AdjustStorageAsync(Guid organizationId, short storageAdjustmentGb)
public async Task<string> AdjustStorageAsync(Guid organizationId, short storageAdjustmentGb)
{
var organization = await GetOrgById(organizationId);
if(organization == null)
@ -249,9 +249,10 @@ namespace Bit.Core.Services
throw new BadRequestException("Plan does not allow additional storage.");
}
await BillingHelpers.AdjustStorageAsync(_paymentService, organization, storageAdjustmentGb,
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, organization, storageAdjustmentGb,
plan.StripeStoragePlanId);
await ReplaceAndUpdateCache(organization);
return secret;
}
public async Task AdjustSeatsAsync(Guid organizationId, int seatAdjustment)
@ -374,8 +375,9 @@ namespace Bit.Core.Services
var invoicedNow = false;
if(additionalSeats > 0)
{
invoicedNow = await (_paymentService as StripePaymentService).PreviewUpcomingInvoiceAndPayAsync(
var result = await (_paymentService as StripePaymentService).PreviewUpcomingInvoiceAndPayAsync(
organization, plan.StripeSeatPlanId, subItemOptions, 500);
invoicedNow = result.Item1;
}
await subUpdateAction(!invoicedNow);

View File

@ -616,7 +616,7 @@ namespace Bit.Core.Services
}).ToList();
}
public async Task AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage,
public async Task<string> AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage,
string storagePlanId)
{
var subscriptionItemService = new SubscriptionItemService();
@ -679,14 +679,18 @@ namespace Bit.Core.Services
subUpdateAction = (prorate) => subscriptionItemService.DeleteAsync(storageItem.Id);
}
string paymentIntentClientSecret = null;
var invoicedNow = false;
if(additionalStorage > 0)
{
invoicedNow = await PreviewUpcomingInvoiceAndPayAsync(
var result = await PreviewUpcomingInvoiceAndPayAsync(
storableSubscriber, storagePlanId, subItemOptions, 400);
invoicedNow = result.Item1;
paymentIntentClientSecret = result.Item2;
}
await subUpdateAction(!invoicedNow);
return paymentIntentClientSecret;
}
public async Task CancelAndRecoverChargesAsync(ISubscriber subscriber)
@ -748,11 +752,12 @@ namespace Bit.Core.Services
await customerService.DeleteAsync(subscriber.GatewayCustomerId);
}
public async Task<bool> PreviewUpcomingInvoiceAndPayAsync(ISubscriber subscriber, string planId,
public async Task<Tuple<bool, string>> PreviewUpcomingInvoiceAndPayAsync(ISubscriber subscriber, string planId,
List<InvoiceSubscriptionItemOptions> subItemOptions, int prorateThreshold = 500)
{
var invoiceService = new InvoiceService();
var invoiceItemService = new InvoiceItemService();
string paymentIntentClientSecret = null;
var pendingInvoiceItems = invoiceItemService.ListAutoPaging(new InvoiceItemListOptions
{
@ -781,13 +786,18 @@ namespace Bit.Core.Services
customerOptions.AddExpand("default_source");
var customer = await customerService.GetAsync(subscriber.GatewayCustomerId, customerOptions);
PaymentMethod cardPaymentMethod = null;
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.");
cardPaymentMethod = GetDefaultCardPaymentMethod(customer.Id);
if(cardPaymentMethod == null)
{
throw new BadRequestException("No payment method is available.");
}
}
}
@ -819,7 +829,8 @@ namespace Bit.Core.Services
CollectionMethod = "send_invoice",
DaysUntilDue = 1,
CustomerId = subscriber.GatewayCustomerId,
SubscriptionId = subscriber.GatewaySubscriptionId
SubscriptionId = subscriber.GatewaySubscriptionId,
DefaultPaymentMethodId = cardPaymentMethod?.Id
});
var invoicePayOptions = new InvoicePayOptions();
@ -864,15 +875,32 @@ namespace Bit.Core.Services
}
});
}
else
{
invoicePayOptions.OffSession = true;
invoicePayOptions.PaymentMethodId = cardPaymentMethod?.Id;
}
}
try
{
await invoiceService.PayAsync(invoice.Id, invoicePayOptions);
}
catch(StripeException)
catch(StripeException e)
{
throw new GatewayException("Unable to pay invoice.");
if(e.HttpStatusCode == System.Net.HttpStatusCode.PaymentRequired &&
e.StripeError?.Code == "invoice_payment_intent_requires_action")
{
// SCA required, get intent client secret
var invoiceGetOptions = new InvoiceGetOptions();
invoiceGetOptions.AddExpand("payment_intent");
invoice = await invoiceService.GetAsync(invoice.Id, invoiceGetOptions);
paymentIntentClientSecret = invoice?.PaymentIntent?.ClientSecret;
}
else
{
throw new GatewayException("Unable to pay invoice.");
}
}
}
catch(Exception e)
@ -926,7 +954,7 @@ namespace Bit.Core.Services
throw e;
}
}
return invoiceNow;
return new Tuple<bool, string>(invoiceNow, paymentIntentClientSecret);
}
public async Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false)

View File

@ -774,7 +774,7 @@ namespace Bit.Core.Services
await SaveUserAsync(user);
}
public async Task AdjustStorageAsync(User user, short storageAdjustmentGb)
public async Task<string> AdjustStorageAsync(User user, short storageAdjustmentGb)
{
if(user == null)
{
@ -786,8 +786,10 @@ namespace Bit.Core.Services
throw new BadRequestException("Not a premium user.");
}
await BillingHelpers.AdjustStorageAsync(_paymentService, user, storageAdjustmentGb, StoragePlanId);
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, user, storageAdjustmentGb,
StoragePlanId);
await SaveUserAsync(user);
return secret;
}
public async Task ReplacePaymentMethodAsync(User user, string paymentToken, PaymentMethodType paymentMethodType)

View File

@ -8,7 +8,7 @@ namespace Bit.Core.Utilities
{
public static class BillingHelpers
{
internal static async Task AdjustStorageAsync(IPaymentService paymentService, IStorableSubscriber storableSubscriber,
internal static async Task<string> AdjustStorageAsync(IPaymentService paymentService, IStorableSubscriber storableSubscriber,
short storageAdjustmentGb, string storagePlanId)
{
if(storableSubscriber == null)
@ -51,8 +51,10 @@ namespace Bit.Core.Utilities
}
var additionalStorage = newStorageGb - 1;
await paymentService.AdjustStorageAsync(storableSubscriber, additionalStorage, storagePlanId);
var paymentIntentClientSecret = await paymentService.AdjustStorageAsync(storableSubscriber,
additionalStorage, storagePlanId);
storableSubscriber.MaxStorageGb = newStorageGb;
return paymentIntentClientSecret;
}
}
}