From b5782f7b7280ad19feb467003c1cf5d8cabf27fe Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Sat, 23 Feb 2019 17:40:46 -0500 Subject: [PATCH] fix paypal edge case --- .../Implementations/StripePaymentService.cs | 127 ++++++++++-------- 1 file changed, 74 insertions(+), 53 deletions(-) diff --git a/src/Core/Services/Implementations/StripePaymentService.cs b/src/Core/Services/Implementations/StripePaymentService.cs index fd64d07f0..2f3a94e8e 100644 --- a/src/Core/Services/Implementations/StripePaymentService.cs +++ b/src/Core/Services/Implementations/StripePaymentService.cs @@ -183,7 +183,8 @@ namespace Bit.Core.Services var customerService = new CustomerService(); var createdStripeCustomer = false; - string stripeCustomerId = null; + var addedCreditToStripeCustomer = false; + Customer customer = null; Braintree.Transaction braintreeTransaction = null; Braintree.Customer braintreeCustomer = null; var stripePaymentMethod = paymentMethodType == PaymentMethodType.Card || @@ -197,16 +198,16 @@ namespace Bit.Core.Services { await UpdatePaymentMethodAsync(user, paymentMethodType, paymentToken); } - catch - { - stripeCustomerId = null; - } + catch { } } - stripeCustomerId = user.GatewayCustomerId; - createdStripeCustomer = false; + try + { + customer = await customerService.GetAsync(user.GatewayCustomerId); + } + catch { } } - if(string.IsNullOrWhiteSpace(stripeCustomerId) && !string.IsNullOrWhiteSpace(paymentToken)) + if(customer == null && !string.IsNullOrWhiteSpace(paymentToken)) { string stipeCustomerSourceToken = null; var stripeCustomerMetadata = new Dictionary(); @@ -242,20 +243,24 @@ namespace Bit.Core.Services throw new GatewayException("Payment method is not supported at this time."); } - var customer = await customerService.CreateAsync(new CustomerCreateOptions + customer = await customerService.CreateAsync(new CustomerCreateOptions { Description = user.Name, Email = user.Email, SourceToken = stipeCustomerSourceToken, Metadata = stripeCustomerMetadata }); - stripeCustomerId = customer.Id; createdStripeCustomer = true; } + if(customer == null) + { + throw new GatewayException("Could not set up customer payment profile."); + } + var subCreateOptions = new SubscriptionCreateOptions { - CustomerId = stripeCustomerId, + CustomerId = customer.Id, Items = new List(), Metadata = new Dictionary { @@ -286,57 +291,63 @@ namespace Bit.Core.Services { var previewInvoice = await invoiceService.UpcomingAsync(new UpcomingInvoiceOptions { - CustomerId = stripeCustomerId, + CustomerId = customer.Id, SubscriptionItems = ToInvoiceSubscriptionItemOptions(subCreateOptions.Items) }); - await customerService.UpdateAsync(stripeCustomerId, new CustomerUpdateOptions + if(previewInvoice.AmountDue > 0) { - AccountBalance = -1 * previewInvoice.AmountDue - }); - - if(braintreeCustomer != null) - { - var btInvoiceAmount = (previewInvoice.AmountDue / 100M); - var transactionResult = await _btGateway.Transaction.SaleAsync( - new Braintree.TransactionRequest - { - Amount = btInvoiceAmount, - CustomerId = braintreeCustomer.Id, - Options = new Braintree.TransactionOptionsRequest - { - SubmitForSettlement = true, - PayPal = new Braintree.TransactionOptionsPayPalRequest - { - CustomField = $"{user.BraintreeIdField()}:{user.Id}" - } - }, - CustomFields = new Dictionary - { - [user.BraintreeIdField()] = user.Id.ToString() - } - }); - - if(!transactionResult.IsSuccess()) + var braintreeCustomerId = customer.Metadata != null && + customer.Metadata.ContainsKey("btCustomerId") ? customer.Metadata["btCustomerId"] : null; + if(!string.IsNullOrWhiteSpace(braintreeCustomerId)) { - throw new GatewayException("Failed to charge PayPal customer."); - } + var btInvoiceAmount = (previewInvoice.AmountDue / 100M); + var transactionResult = await _btGateway.Transaction.SaleAsync( + new Braintree.TransactionRequest + { + Amount = btInvoiceAmount, + CustomerId = braintreeCustomerId, + Options = new Braintree.TransactionOptionsRequest + { + SubmitForSettlement = true, + PayPal = new Braintree.TransactionOptionsPayPalRequest + { + CustomField = $"{user.BraintreeIdField()}:{user.Id}" + } + }, + CustomFields = new Dictionary + { + [user.BraintreeIdField()] = user.Id.ToString() + } + }); - braintreeTransaction = transactionResult.Target; - subInvoiceMetadata.Add("btTransactionId", braintreeTransaction.Id); - subInvoiceMetadata.Add("btPayPalTransactionId", - braintreeTransaction.PayPalDetails.AuthorizationId); - } - else - { - throw new GatewayException("No payment was able to be collected."); + if(!transactionResult.IsSuccess()) + { + throw new GatewayException("Failed to charge PayPal customer."); + } + + braintreeTransaction = transactionResult.Target; + subInvoiceMetadata.Add("btTransactionId", braintreeTransaction.Id); + subInvoiceMetadata.Add("btPayPalTransactionId", + braintreeTransaction.PayPalDetails.AuthorizationId); + + await customerService.UpdateAsync(customer.Id, new CustomerUpdateOptions + { + AccountBalance = -1 * previewInvoice.AmountDue + }); + addedCreditToStripeCustomer = true; + } + else + { + throw new GatewayException("No payment was able to be collected."); + } } } else if(paymentMethodType == PaymentMethodType.Credit) { var previewInvoice = await invoiceService.UpcomingAsync(new UpcomingInvoiceOptions { - CustomerId = stripeCustomerId, + CustomerId = customer.Id, SubscriptionItems = ToInvoiceSubscriptionItemOptions(subCreateOptions.Items) }); if(previewInvoice.AmountDue > 0) @@ -369,9 +380,19 @@ namespace Bit.Core.Services } catch(Exception e) { - if(createdStripeCustomer && !string.IsNullOrWhiteSpace(stripeCustomerId)) + if(customer != null) { - await customerService.DeleteAsync(stripeCustomerId); + if(createdStripeCustomer) + { + await customerService.DeleteAsync(customer.Id); + } + else if(addedCreditToStripeCustomer) + { + await customerService.UpdateAsync(customer.Id, new CustomerUpdateOptions + { + AccountBalance = customer.AccountBalance + }); + } } if(braintreeTransaction != null) { @@ -385,7 +406,7 @@ namespace Bit.Core.Services } user.Gateway = GatewayType.Stripe; - user.GatewayCustomerId = stripeCustomerId; + user.GatewayCustomerId = customer.Id; user.GatewaySubscriptionId = subscription.Id; user.Premium = true; user.PremiumExpirationDate = subscription.CurrentPeriodEnd;