diff --git a/src/Api/Controllers/MiscController.cs b/src/Api/Controllers/MiscController.cs index b31789850..322eb134f 100644 --- a/src/Api/Controllers/MiscController.cs +++ b/src/Api/Controllers/MiscController.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Bit.Core; +using Stripe; namespace Bit.Api.Controllers { @@ -52,5 +53,19 @@ namespace Bit.Api.Controllers var invoice = await _bitPayClient.CreateInvoiceAsync(model.ToBitpayClientInvoice(_globalSettings)); return invoice.Url; } + + [Authorize("Application")] + [HttpPost("~/setup-payment")] + [SelfHosted(NotSelfHostedOnly = true)] + public async Task PostSetupPayment() + { + var options = new SetupIntentCreateOptions + { + Usage = "off_session" + }; + var service = new SetupIntentService(); + var setupIntent = await service.CreateAsync(options); + return setupIntent.ClientSecret; + } } } diff --git a/src/Core/Services/Implementations/StripePaymentService.cs b/src/Core/Services/Implementations/StripePaymentService.cs index 963bfd91e..1e3d66caa 100644 --- a/src/Core/Services/Implementations/StripePaymentService.cs +++ b/src/Core/Services/Implementations/StripePaymentService.cs @@ -292,6 +292,7 @@ namespace Bit.Core.Services catch { } } + string stipeCustomerPaymentMethodId = null; if(customer == null && !string.IsNullOrWhiteSpace(paymentToken)) { string stipeCustomerSourceToken = null; @@ -299,7 +300,14 @@ namespace Bit.Core.Services if(stripePaymentMethod) { - stipeCustomerSourceToken = paymentToken; + if(paymentToken.StartsWith("pm_")) + { + stipeCustomerPaymentMethodId = paymentToken; + } + else + { + stipeCustomerSourceToken = paymentToken; + } } else if(paymentMethodType == PaymentMethodType.PayPal) { @@ -332,8 +340,9 @@ namespace Bit.Core.Services { Description = user.Name, Email = user.Email, - Source = stipeCustomerSourceToken, - Metadata = stripeCustomerMetadata + Metadata = stripeCustomerMetadata, + PaymentMethodId = stipeCustomerPaymentMethodId, + Source = stipeCustomerSourceToken }); createdStripeCustomer = true; } @@ -346,6 +355,7 @@ namespace Bit.Core.Services var subCreateOptions = new SubscriptionCreateOptions { CustomerId = customer.Id, + DefaultPaymentMethodId = stipeCustomerPaymentMethodId, Items = new List(), Metadata = new Dictionary { @@ -460,8 +470,22 @@ namespace Bit.Core.Services } } + subCreateOptions.OffSession = true; + subCreateOptions.AddExpand("latest_invoice.payment_intent"); var subscriptionService = new SubscriptionService(); subscription = await subscriptionService.CreateAsync(subCreateOptions); + if(subscription.Status == "incomplete" && subscription.LatestInvoice?.PaymentIntent != null) + { + if(subscription.LatestInvoice.PaymentIntent.Status == "requires_payment_method") + { + await subscriptionService.CancelAsync(subscription.Id, new SubscriptionCancelOptions()); + throw new GatewayException("Payment method failed."); + } + else if(subscription.LatestInvoice.PaymentIntent.Status == "requires_action") + { + // Needs SCA. Send email? Should be handled by Stripe. + } + } if(!stripePaymentMethod && subInvoiceMetadata.Any()) {