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:
parent
e60f1a4f50
commit
74bbeae776
@ -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")]
|
||||
|
@ -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")]
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user