mirror of
https://github.com/bitwarden/server.git
synced 2025-02-18 02:11:22 +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")]
|
[HttpPost("storage")]
|
||||||
[SelfHosted(NotSelfHostedOnly = true)]
|
[SelfHosted(NotSelfHostedOnly = true)]
|
||||||
public async Task PostStorage([FromBody]StorageRequestModel model)
|
public async Task<PaymentResponseModel> PostStorage([FromBody]StorageRequestModel model)
|
||||||
{
|
{
|
||||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||||
if(user == null)
|
if(user == null)
|
||||||
@ -537,7 +537,12 @@ namespace Bit.Api.Controllers
|
|||||||
throw new UnauthorizedAccessException();
|
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")]
|
[HttpPost("license")]
|
||||||
|
@ -246,7 +246,7 @@ namespace Bit.Api.Controllers
|
|||||||
|
|
||||||
[HttpPost("{id}/storage")]
|
[HttpPost("{id}/storage")]
|
||||||
[SelfHosted(NotSelfHostedOnly = true)]
|
[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);
|
var orgIdGuid = new Guid(id);
|
||||||
if(!_currentContext.OrganizationOwner(orgIdGuid))
|
if(!_currentContext.OrganizationOwner(orgIdGuid))
|
||||||
@ -254,7 +254,12 @@ namespace Bit.Api.Controllers
|
|||||||
throw new NotFoundException();
|
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")]
|
[HttpPost("{id}/verify-bank")]
|
||||||
|
@ -14,7 +14,7 @@ namespace Bit.Core.Services
|
|||||||
Task CancelSubscriptionAsync(Guid organizationId, bool? endOfPeriod = null);
|
Task CancelSubscriptionAsync(Guid organizationId, bool? endOfPeriod = null);
|
||||||
Task ReinstateSubscriptionAsync(Guid organizationId);
|
Task ReinstateSubscriptionAsync(Guid organizationId);
|
||||||
Task<Tuple<bool, string>> UpgradePlanAsync(Guid organizationId, OrganizationUpgrade upgrade);
|
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 AdjustSeatsAsync(Guid organizationId, int seatAdjustment);
|
||||||
Task VerifyBankAsync(Guid organizationId, int amount1, int amount2);
|
Task VerifyBankAsync(Guid organizationId, int amount1, int amount2);
|
||||||
Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationSignup organizationSignup);
|
Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationSignup organizationSignup);
|
||||||
|
@ -15,7 +15,7 @@ namespace Bit.Core.Services
|
|||||||
short additionalStorageGb, short additionalSeats, bool premiumAccessAddon);
|
short additionalStorageGb, short additionalSeats, bool premiumAccessAddon);
|
||||||
Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
|
Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
|
||||||
short additionalStorageGb);
|
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 CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false);
|
||||||
Task ReinstateSubscriptionAsync(ISubscriber subscriber);
|
Task ReinstateSubscriptionAsync(ISubscriber subscriber);
|
||||||
Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, PaymentMethodType paymentMethodType,
|
Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, PaymentMethodType paymentMethodType,
|
||||||
|
@ -46,7 +46,7 @@ namespace Bit.Core.Services
|
|||||||
Task<Tuple<bool, string>> SignUpPremiumAsync(User user, string paymentToken,
|
Task<Tuple<bool, string>> SignUpPremiumAsync(User user, string paymentToken,
|
||||||
PaymentMethodType paymentMethodType, short additionalStorageGb, UserLicense license);
|
PaymentMethodType paymentMethodType, short additionalStorageGb, UserLicense license);
|
||||||
Task UpdateLicenseAsync(User user, 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 ReplacePaymentMethodAsync(User user, string paymentToken, PaymentMethodType paymentMethodType);
|
||||||
Task CancelPremiumAsync(User user, bool? endOfPeriod = null);
|
Task CancelPremiumAsync(User user, bool? endOfPeriod = null);
|
||||||
Task ReinstatePremiumAsync(User user);
|
Task ReinstatePremiumAsync(User user);
|
||||||
|
@ -230,7 +230,7 @@ namespace Bit.Core.Services
|
|||||||
return new Tuple<bool, string>(success, paymentIntentClientSecret);
|
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);
|
var organization = await GetOrgById(organizationId);
|
||||||
if(organization == null)
|
if(organization == null)
|
||||||
@ -249,9 +249,10 @@ namespace Bit.Core.Services
|
|||||||
throw new BadRequestException("Plan does not allow additional storage.");
|
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);
|
plan.StripeStoragePlanId);
|
||||||
await ReplaceAndUpdateCache(organization);
|
await ReplaceAndUpdateCache(organization);
|
||||||
|
return secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AdjustSeatsAsync(Guid organizationId, int seatAdjustment)
|
public async Task AdjustSeatsAsync(Guid organizationId, int seatAdjustment)
|
||||||
@ -374,8 +375,9 @@ namespace Bit.Core.Services
|
|||||||
var invoicedNow = false;
|
var invoicedNow = false;
|
||||||
if(additionalSeats > 0)
|
if(additionalSeats > 0)
|
||||||
{
|
{
|
||||||
invoicedNow = await (_paymentService as StripePaymentService).PreviewUpcomingInvoiceAndPayAsync(
|
var result = await (_paymentService as StripePaymentService).PreviewUpcomingInvoiceAndPayAsync(
|
||||||
organization, plan.StripeSeatPlanId, subItemOptions, 500);
|
organization, plan.StripeSeatPlanId, subItemOptions, 500);
|
||||||
|
invoicedNow = result.Item1;
|
||||||
}
|
}
|
||||||
|
|
||||||
await subUpdateAction(!invoicedNow);
|
await subUpdateAction(!invoicedNow);
|
||||||
|
@ -616,7 +616,7 @@ namespace Bit.Core.Services
|
|||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage,
|
public async Task<string> AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage,
|
||||||
string storagePlanId)
|
string storagePlanId)
|
||||||
{
|
{
|
||||||
var subscriptionItemService = new SubscriptionItemService();
|
var subscriptionItemService = new SubscriptionItemService();
|
||||||
@ -679,14 +679,18 @@ namespace Bit.Core.Services
|
|||||||
subUpdateAction = (prorate) => subscriptionItemService.DeleteAsync(storageItem.Id);
|
subUpdateAction = (prorate) => subscriptionItemService.DeleteAsync(storageItem.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string paymentIntentClientSecret = null;
|
||||||
var invoicedNow = false;
|
var invoicedNow = false;
|
||||||
if(additionalStorage > 0)
|
if(additionalStorage > 0)
|
||||||
{
|
{
|
||||||
invoicedNow = await PreviewUpcomingInvoiceAndPayAsync(
|
var result = await PreviewUpcomingInvoiceAndPayAsync(
|
||||||
storableSubscriber, storagePlanId, subItemOptions, 400);
|
storableSubscriber, storagePlanId, subItemOptions, 400);
|
||||||
|
invoicedNow = result.Item1;
|
||||||
|
paymentIntentClientSecret = result.Item2;
|
||||||
}
|
}
|
||||||
|
|
||||||
await subUpdateAction(!invoicedNow);
|
await subUpdateAction(!invoicedNow);
|
||||||
|
return paymentIntentClientSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CancelAndRecoverChargesAsync(ISubscriber subscriber)
|
public async Task CancelAndRecoverChargesAsync(ISubscriber subscriber)
|
||||||
@ -748,11 +752,12 @@ namespace Bit.Core.Services
|
|||||||
await customerService.DeleteAsync(subscriber.GatewayCustomerId);
|
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)
|
List<InvoiceSubscriptionItemOptions> subItemOptions, int prorateThreshold = 500)
|
||||||
{
|
{
|
||||||
var invoiceService = new InvoiceService();
|
var invoiceService = new InvoiceService();
|
||||||
var invoiceItemService = new InvoiceItemService();
|
var invoiceItemService = new InvoiceItemService();
|
||||||
|
string paymentIntentClientSecret = null;
|
||||||
|
|
||||||
var pendingInvoiceItems = invoiceItemService.ListAutoPaging(new InvoiceItemListOptions
|
var pendingInvoiceItems = invoiceItemService.ListAutoPaging(new InvoiceItemListOptions
|
||||||
{
|
{
|
||||||
@ -781,13 +786,18 @@ namespace Bit.Core.Services
|
|||||||
customerOptions.AddExpand("default_source");
|
customerOptions.AddExpand("default_source");
|
||||||
var customer = await customerService.GetAsync(subscriber.GatewayCustomerId, customerOptions);
|
var customer = await customerService.GetAsync(subscriber.GatewayCustomerId, customerOptions);
|
||||||
|
|
||||||
|
PaymentMethod cardPaymentMethod = null;
|
||||||
var invoiceAmountDue = upcomingPreview.StartingBalance + invoiceAmount;
|
var invoiceAmountDue = upcomingPreview.StartingBalance + invoiceAmount;
|
||||||
if(invoiceAmountDue > 0 && !customer.Metadata.ContainsKey("btCustomerId"))
|
if(invoiceAmountDue > 0 && !customer.Metadata.ContainsKey("btCustomerId"))
|
||||||
{
|
{
|
||||||
if(customer.DefaultSource == null ||
|
if(customer.DefaultSource == null ||
|
||||||
(!(customer.DefaultSource is Card) && !(customer.DefaultSource is BankAccount)))
|
(!(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",
|
CollectionMethod = "send_invoice",
|
||||||
DaysUntilDue = 1,
|
DaysUntilDue = 1,
|
||||||
CustomerId = subscriber.GatewayCustomerId,
|
CustomerId = subscriber.GatewayCustomerId,
|
||||||
SubscriptionId = subscriber.GatewaySubscriptionId
|
SubscriptionId = subscriber.GatewaySubscriptionId,
|
||||||
|
DefaultPaymentMethodId = cardPaymentMethod?.Id
|
||||||
});
|
});
|
||||||
|
|
||||||
var invoicePayOptions = new InvoicePayOptions();
|
var invoicePayOptions = new InvoicePayOptions();
|
||||||
@ -864,15 +875,32 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
invoicePayOptions.OffSession = true;
|
||||||
|
invoicePayOptions.PaymentMethodId = cardPaymentMethod?.Id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await invoiceService.PayAsync(invoice.Id, invoicePayOptions);
|
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)
|
catch(Exception e)
|
||||||
@ -926,7 +954,7 @@ namespace Bit.Core.Services
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return invoiceNow;
|
return new Tuple<bool, string>(invoiceNow, paymentIntentClientSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false)
|
public async Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false)
|
||||||
|
@ -774,7 +774,7 @@ namespace Bit.Core.Services
|
|||||||
await SaveUserAsync(user);
|
await SaveUserAsync(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AdjustStorageAsync(User user, short storageAdjustmentGb)
|
public async Task<string> AdjustStorageAsync(User user, short storageAdjustmentGb)
|
||||||
{
|
{
|
||||||
if(user == null)
|
if(user == null)
|
||||||
{
|
{
|
||||||
@ -786,8 +786,10 @@ namespace Bit.Core.Services
|
|||||||
throw new BadRequestException("Not a premium user.");
|
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);
|
await SaveUserAsync(user);
|
||||||
|
return secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ReplacePaymentMethodAsync(User user, string paymentToken, PaymentMethodType paymentMethodType)
|
public async Task ReplacePaymentMethodAsync(User user, string paymentToken, PaymentMethodType paymentMethodType)
|
||||||
|
@ -8,7 +8,7 @@ namespace Bit.Core.Utilities
|
|||||||
{
|
{
|
||||||
public static class BillingHelpers
|
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)
|
short storageAdjustmentGb, string storagePlanId)
|
||||||
{
|
{
|
||||||
if(storableSubscriber == null)
|
if(storableSubscriber == null)
|
||||||
@ -51,8 +51,10 @@ namespace Bit.Core.Utilities
|
|||||||
}
|
}
|
||||||
|
|
||||||
var additionalStorage = newStorageGb - 1;
|
var additionalStorage = newStorageGb - 1;
|
||||||
await paymentService.AdjustStorageAsync(storableSubscriber, additionalStorage, storagePlanId);
|
var paymentIntentClientSecret = await paymentService.AdjustStorageAsync(storableSubscriber,
|
||||||
|
additionalStorage, storagePlanId);
|
||||||
storableSubscriber.MaxStorageGb = newStorageGb;
|
storableSubscriber.MaxStorageGb = newStorageGb;
|
||||||
|
return paymentIntentClientSecret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user