public async Task <bool> UpdatePaymentMethodAsync(ISubscriber subscriber, PaymentMethodType paymentMethodType, string paymentToken) { if (subscriber == null) { throw new ArgumentNullException(nameof(subscriber)); } if (subscriber.Gateway.HasValue && subscriber.Gateway.Value != GatewayType.Stripe) { throw new GatewayException("Switching from one payment type to another is not supported. " + "Contact us for assistance."); } var createdCustomer = false; Braintree.Customer braintreeCustomer = null; string stipeCustomerSourceToken = null; var stripeCustomerMetadata = new Dictionary <string, string>(); var stripePaymentMethod = paymentMethodType == PaymentMethodType.Card || paymentMethodType == PaymentMethodType.BankAccount; var cardService = new CardService(); var bankSerice = new BankAccountService(); var customerService = new CustomerService(); Customer customer = null; if (!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId)) { customer = await customerService.GetAsync(subscriber.GatewayCustomerId); if (customer.Metadata?.Any() ?? false) { stripeCustomerMetadata = customer.Metadata; } } var hadBtCustomer = stripeCustomerMetadata.ContainsKey("btCustomerId"); if (stripePaymentMethod) { stipeCustomerSourceToken = paymentToken; } else if (paymentMethodType == PaymentMethodType.PayPal) { if (hadBtCustomer) { var pmResult = await _btGateway.PaymentMethod.CreateAsync(new Braintree.PaymentMethodRequest { CustomerId = stripeCustomerMetadata["btCustomerId"], PaymentMethodNonce = paymentToken }); if (pmResult.IsSuccess()) { var customerResult = await _btGateway.Customer.UpdateAsync( stripeCustomerMetadata["btCustomerId"], new Braintree.CustomerRequest { DefaultPaymentMethodToken = pmResult.Target.Token }); if (customerResult.IsSuccess() && customerResult.Target.PaymentMethods.Length > 0) { braintreeCustomer = customerResult.Target; } else { await _btGateway.PaymentMethod.DeleteAsync(pmResult.Target.Token); hadBtCustomer = false; } } else { hadBtCustomer = false; } } if (!hadBtCustomer) { var customerResult = await _btGateway.Customer.CreateAsync(new Braintree.CustomerRequest { PaymentMethodNonce = paymentToken, Email = subscriber.BillingEmailAddress(), Id = subscriber.BraintreeCustomerIdPrefix() + subscriber.Id.ToString("N").ToLower() + Utilities.CoreHelpers.RandomString(3, upper: false, numeric: false), CustomFields = new Dictionary <string, string> { [subscriber.BraintreeIdField()] = subscriber.Id.ToString() } }); if (!customerResult.IsSuccess() || customerResult.Target.PaymentMethods.Length == 0) { throw new GatewayException("Failed to create PayPal customer record."); } braintreeCustomer = customerResult.Target; } } else { throw new GatewayException("Payment method is not supported at this time."); } if (stripeCustomerMetadata.ContainsKey("btCustomerId")) { if (braintreeCustomer?.Id != stripeCustomerMetadata["btCustomerId"]) { var nowSec = Utilities.CoreHelpers.ToEpocSeconds(DateTime.UtcNow); stripeCustomerMetadata.Add($"btCustomerId_{nowSec}", stripeCustomerMetadata["btCustomerId"]); } stripeCustomerMetadata["btCustomerId"] = braintreeCustomer?.Id; } else if (!string.IsNullOrWhiteSpace(braintreeCustomer?.Id)) { stripeCustomerMetadata.Add("btCustomerId", braintreeCustomer.Id); } try { if (customer == null) { customer = await customerService.CreateAsync(new CustomerCreateOptions { Description = subscriber.BillingName(), Email = subscriber.BillingEmailAddress(), SourceToken = stipeCustomerSourceToken, Metadata = stripeCustomerMetadata }); subscriber.Gateway = GatewayType.Stripe; subscriber.GatewayCustomerId = customer.Id; createdCustomer = true; } if (!createdCustomer) { string defaultSourceId = null; if (stripePaymentMethod) { if (paymentToken.StartsWith("btok_")) { var bankAccount = await bankSerice.CreateAsync(customer.Id, new BankAccountCreateOptions { SourceToken = paymentToken }); defaultSourceId = bankAccount.Id; } else { var card = await cardService.CreateAsync(customer.Id, new CardCreateOptions { SourceToken = paymentToken, }); defaultSourceId = card.Id; } } foreach (var source in customer.Sources.Where(s => s.Id != defaultSourceId)) { if (source is BankAccount) { await bankSerice.DeleteAsync(customer.Id, source.Id); } else if (source is Card) { await cardService.DeleteAsync(customer.Id, source.Id); } } customer = await customerService.UpdateAsync(customer.Id, new CustomerUpdateOptions { Metadata = stripeCustomerMetadata, DefaultSource = defaultSourceId }); } } catch (Exception e) { if (braintreeCustomer != null && !hadBtCustomer) { await _btGateway.Customer.DeleteAsync(braintreeCustomer.Id); } throw e; } return(createdCustomer); }
public async Task <bool> PreviewUpcomingInvoiceAndPayAsync(ISubscriber subscriber, string planId, List <InvoiceSubscriptionItemOptions> subItemOptions, int prorateThreshold = 500) { var invoiceService = new InvoiceService(); var invoiceItemService = new InvoiceItemService(); var pendingInvoiceItems = invoiceItemService.ListAutoPaging(new InvoiceItemListOptions { CustomerId = subscriber.GatewayCustomerId }).ToList().Where(i => i.InvoiceId == null); var pendingInvoiceItemsDict = pendingInvoiceItems.ToDictionary(pii => pii.Id); var upcomingPreview = await invoiceService.UpcomingAsync(new UpcomingInvoiceOptions { CustomerId = subscriber.GatewayCustomerId, SubscriptionId = subscriber.GatewaySubscriptionId, SubscriptionItems = subItemOptions }); var itemsForInvoice = upcomingPreview.Lines?.Data? .Where(i => pendingInvoiceItemsDict.ContainsKey(i.Id) || (i.Plan.Id == planId && i.Proration)); var invoiceAmount = itemsForInvoice?.Sum(i => i.Amount) ?? 0; var invoiceNow = invoiceAmount >= prorateThreshold; if (invoiceNow) { // Owes more than prorateThreshold on next invoice. // Invoice them and pay now instead of waiting until next billing cycle. Invoice invoice = null; var createdInvoiceItems = new List <InvoiceItem>(); Braintree.Transaction braintreeTransaction = null; try { foreach (var ii in itemsForInvoice) { if (pendingInvoiceItemsDict.ContainsKey(ii.Id)) { continue; } var invoiceItem = await invoiceItemService.CreateAsync(new InvoiceItemCreateOptions { Currency = ii.Currency, Description = ii.Description, CustomerId = subscriber.GatewayCustomerId, SubscriptionId = ii.SubscriptionId, Discountable = ii.Discountable, Amount = ii.Amount }); createdInvoiceItems.Add(invoiceItem); } invoice = await invoiceService.CreateAsync(new InvoiceCreateOptions { Billing = Billing.SendInvoice, DaysUntilDue = 1, CustomerId = subscriber.GatewayCustomerId, SubscriptionId = subscriber.GatewaySubscriptionId }); var invoicePayOptions = new InvoicePayOptions(); var customerService = new CustomerService(); var customer = await customerService.GetAsync(subscriber.GatewayCustomerId); if (customer != null) { if (customer.Metadata.ContainsKey("btCustomerId")) { invoicePayOptions.PaidOutOfBand = true; var btInvoiceAmount = (invoiceAmount / 100M); var transactionResult = await _btGateway.Transaction.SaleAsync( new Braintree.TransactionRequest { Amount = btInvoiceAmount, CustomerId = customer.Metadata["btCustomerId"], Options = new Braintree.TransactionOptionsRequest { SubmitForSettlement = true, PayPal = new Braintree.TransactionOptionsPayPalRequest { CustomField = $"{subscriber.BraintreeIdField()}:{subscriber.Id}" } }, CustomFields = new Dictionary <string, string> { [subscriber.BraintreeIdField()] = subscriber.Id.ToString() } }); if (!transactionResult.IsSuccess()) { throw new GatewayException("Failed to charge PayPal customer."); } braintreeTransaction = transactionResult.Target; await invoiceService.UpdateAsync(invoice.Id, new InvoiceUpdateOptions { Metadata = new Dictionary <string, string> { ["btTransactionId"] = braintreeTransaction.Id, ["btPayPalTransactionId"] = braintreeTransaction.PayPalDetails.AuthorizationId } }); } } await invoiceService.PayAsync(invoice.Id, invoicePayOptions); } catch (Exception e) { if (braintreeTransaction != null) { await _btGateway.Transaction.RefundAsync(braintreeTransaction.Id); } if (invoice != null) { await invoiceService.DeleteAsync(invoice.Id); // Restore invoice items that were brought in foreach (var item in pendingInvoiceItems) { var i = new InvoiceItemCreateOptions { Currency = item.Currency, Description = item.Description, CustomerId = item.CustomerId, SubscriptionId = item.SubscriptionId, Discountable = item.Discountable, Metadata = item.Metadata, Quantity = item.Quantity, UnitAmount = item.UnitAmount }; await invoiceItemService.CreateAsync(i); } } else { foreach (var ii in createdInvoiceItems) { await invoiceItemService.DeleteAsync(ii.Id); } } throw e; } } return(invoiceNow); }