1
0
mirror of https://github.com/bitwarden/server.git synced 2024-12-22 16:57:36 +01:00

[AC-2239] fix automatic tax errors (#3834)

* Ensuring customer has address before enabling automatic tax

* StripeController fixes

* Refactored automatic tax logic to use customer's automatic tax values

* Downgraded refund error in paypal controller to be a warning

* Resolved broken test after downgrading error to warning

* Resolved broken paypal unit tests on windows machines

---------

Co-authored-by: Lotus Scott <148992878+lscottbw@users.noreply.github.com>
This commit is contained in:
Conner Turnbull 2024-03-05 13:04:26 -05:00 committed by GitHub
parent 9d59e4dc9e
commit 2dc068a983
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 260 additions and 205 deletions

View File

@ -204,8 +204,8 @@ public class PayPalController : Controller
if (parentTransaction == null)
{
_logger.LogError("PayPal IPN ({Id}): Could not find parent transaction", transactionModel.TransactionId);
return BadRequest();
_logger.LogWarning("PayPal IPN ({Id}): Could not find parent transaction", transactionModel.TransactionId);
return Ok();
}
var refundAmount = Math.Abs(transactionModel.MerchantGross);

View File

@ -3,6 +3,7 @@ using Bit.Billing.Models;
using Bit.Billing.Services;
using Bit.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Constants;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
@ -188,7 +189,7 @@ public class StripeController : Controller
}
var user = await _userService.GetUserByIdAsync(userId);
if (user.Premium)
if (user?.Premium == true)
{
await _userService.DisablePremiumAsync(userId, subscription.CurrentPeriodEnd);
}
@ -250,21 +251,21 @@ public class StripeController : Controller
var pm5766AutomaticTaxIsEnabled = _featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax);
if (pm5766AutomaticTaxIsEnabled)
{
var customer = await _stripeFacade.GetCustomer(subscription.CustomerId);
var customerGetOptions = new CustomerGetOptions();
customerGetOptions.AddExpand("tax");
var customer = await _stripeFacade.GetCustomer(subscription.CustomerId, customerGetOptions);
if (!subscription.AutomaticTax.Enabled &&
!string.IsNullOrEmpty(customer.Address?.PostalCode) &&
!string.IsNullOrEmpty(customer.Address?.Country))
customer.Tax?.AutomaticTax == StripeCustomerAutomaticTaxStatus.Supported)
{
subscription = await _stripeFacade.UpdateSubscription(subscription.Id,
new SubscriptionUpdateOptions
{
DefaultTaxRates = new List<string>(),
DefaultTaxRates = [],
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
});
}
}
var updatedSubscription = pm5766AutomaticTaxIsEnabled
? subscription
: await VerifyCorrectTaxRateForCharge(invoice, subscription);
@ -319,7 +320,7 @@ public class StripeController : Controller
{
var user = await _userService.GetUserByIdAsync(userId.Value);
if (user.Premium)
if (user?.Premium == true)
{
await SendEmails(new List<string> { user.Email });
}
@ -571,7 +572,7 @@ public class StripeController : Controller
else if (parsedEvent.Type.Equals(HandledStripeWebhook.CustomerUpdated))
{
var customer =
await _stripeEventService.GetCustomer(parsedEvent, true, new List<string> { "subscriptions" });
await _stripeEventService.GetCustomer(parsedEvent, true, ["subscriptions"]);
if (customer.Subscriptions == null || !customer.Subscriptions.Any())
{
@ -614,7 +615,7 @@ public class StripeController : Controller
{
Customer = paymentMethod.CustomerId,
Status = StripeSubscriptionStatus.Unpaid,
Expand = new List<string> { "data.latest_invoice" }
Expand = ["data.latest_invoice"]
};
StripeList<Subscription> unpaidSubscriptions;
@ -672,9 +673,9 @@ public class StripeController : Controller
}
}
private Tuple<Guid?, Guid?> GetIdsFromMetaData(IDictionary<string, string> metaData)
private static Tuple<Guid?, Guid?> GetIdsFromMetaData(Dictionary<string, string> metaData)
{
if (metaData == null || !metaData.Any())
if (metaData == null || metaData.Count == 0)
{
return new Tuple<Guid?, Guid?>(null, null);
}
@ -682,29 +683,35 @@ public class StripeController : Controller
Guid? orgId = null;
Guid? userId = null;
if (metaData.ContainsKey("organizationId"))
if (metaData.TryGetValue("organizationId", out var orgIdString))
{
orgId = new Guid(metaData["organizationId"]);
orgId = new Guid(orgIdString);
}
else if (metaData.ContainsKey("userId"))
else if (metaData.TryGetValue("userId", out var userIdString))
{
userId = new Guid(metaData["userId"]);
userId = new Guid(userIdString);
}
if (userId == null && orgId == null)
if (userId != null && userId != Guid.Empty || orgId != null && orgId != Guid.Empty)
{
var orgIdKey = metaData.Keys.FirstOrDefault(k => k.ToLowerInvariant() == "organizationid");
if (!string.IsNullOrWhiteSpace(orgIdKey))
return new Tuple<Guid?, Guid?>(orgId, userId);
}
var orgIdKey = metaData.Keys
.FirstOrDefault(k => k.Equals("organizationid", StringComparison.InvariantCultureIgnoreCase));
if (!string.IsNullOrWhiteSpace(orgIdKey))
{
orgId = new Guid(metaData[orgIdKey]);
}
else
{
var userIdKey = metaData.Keys
.FirstOrDefault(k => k.Equals("userid", StringComparison.InvariantCultureIgnoreCase));
if (!string.IsNullOrWhiteSpace(userIdKey))
{
orgId = new Guid(metaData[orgIdKey]);
}
else
{
var userIdKey = metaData.Keys.FirstOrDefault(k => k.ToLowerInvariant() == "userid");
if (!string.IsNullOrWhiteSpace(userIdKey))
{
userId = new Guid(metaData[userIdKey]);
}
userId = new Guid(metaData[userIdKey]);
}
}
@ -891,9 +898,9 @@ public class StripeController : Controller
return subscription;
}
subscription.DefaultTaxRates = new List<Stripe.TaxRate> { stripeTaxRate };
subscription.DefaultTaxRates = [stripeTaxRate];
var subscriptionOptions = new SubscriptionUpdateOptions { DefaultTaxRates = new List<string> { stripeTaxRate.Id } };
var subscriptionOptions = new SubscriptionUpdateOptions { DefaultTaxRates = [stripeTaxRate.Id] };
subscription = await _stripeFacade.UpdateSubscription(subscription.Id, subscriptionOptions);
return subscription;

View File

@ -25,7 +25,10 @@ public class PayPalIPNTransactionModel
var data = queryString
.AllKeys
.ToDictionary(key => key, key => queryString[key]);
.Where(key => !string.IsNullOrWhiteSpace(key))
.ToDictionary(key =>
key.Trim('\r'),
key => queryString[key]?.Trim('\r'));
TransactionId = Extract(data, "txn_id");
TransactionType = Extract(data, "txn_type");

View File

@ -0,0 +1,9 @@
namespace Bit.Core.Billing.Constants;
public static class StripeCustomerAutomaticTaxStatus
{
public const string Failed = "failed";
public const string NotCollecting = "not_collecting";
public const string Supported = "supported";
public const string UnrecognizedLocation = "unrecognized_location";
}

View File

@ -1,4 +1,5 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Constants;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
@ -125,59 +126,61 @@ public class StripePaymentService : IPaymentService
var subCreateOptions = new OrganizationPurchaseSubscriptionOptions(org, plan, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon
, additionalSmSeats, additionalServiceAccount);
Stripe.Customer customer = null;
Stripe.Subscription subscription;
Customer customer = null;
Subscription subscription;
try
{
customer = await _stripeAdapter.CustomerCreateAsync(new Stripe.CustomerCreateOptions
var customerCreateOptions = new CustomerCreateOptions
{
Description = org.DisplayBusinessName(),
Email = org.BillingEmail,
Source = stipeCustomerSourceToken,
PaymentMethod = stipeCustomerPaymentMethodId,
Metadata = stripeCustomerMetadata,
InvoiceSettings = new Stripe.CustomerInvoiceSettingsOptions
InvoiceSettings = new CustomerInvoiceSettingsOptions
{
DefaultPaymentMethod = stipeCustomerPaymentMethodId,
CustomFields = new List<Stripe.CustomerInvoiceSettingsCustomFieldOptions>
{
new Stripe.CustomerInvoiceSettingsCustomFieldOptions()
CustomFields =
[
new CustomerInvoiceSettingsCustomFieldOptions
{
Name = org.SubscriberType(),
Value = GetFirstThirtyCharacters(org.SubscriberName()),
},
},
}
],
},
Coupon = signupIsFromSecretsManagerTrial
? SecretsManagerStandaloneDiscountId
: provider
? ProviderDiscountId
: null,
Address = new Stripe.AddressOptions
Address = new AddressOptions
{
Country = taxInfo.BillingAddressCountry,
PostalCode = taxInfo.BillingAddressPostalCode,
Country = taxInfo?.BillingAddressCountry,
PostalCode = taxInfo?.BillingAddressPostalCode,
// Line1 is required in Stripe's API, suggestion in Docs is to use Business Name instead.
Line1 = taxInfo.BillingAddressLine1 ?? string.Empty,
Line2 = taxInfo.BillingAddressLine2,
City = taxInfo.BillingAddressCity,
State = taxInfo.BillingAddressState,
Line1 = taxInfo?.BillingAddressLine1 ?? string.Empty,
Line2 = taxInfo?.BillingAddressLine2,
City = taxInfo?.BillingAddressCity,
State = taxInfo?.BillingAddressState,
},
TaxIdData = !taxInfo.HasTaxId ? null : new List<Stripe.CustomerTaxIdDataOptions>
{
new Stripe.CustomerTaxIdDataOptions
{
Type = taxInfo.TaxIdType,
Value = taxInfo.TaxIdNumber,
},
},
});
TaxIdData = taxInfo?.HasTaxId != true
? null
:
[
new CustomerTaxIdDataOptions { Type = taxInfo.TaxIdType, Value = taxInfo.TaxIdNumber, }
],
};
customerCreateOptions.AddExpand("tax");
customer = await _stripeAdapter.CustomerCreateAsync(customerCreateOptions);
subCreateOptions.AddExpand("latest_invoice.payment_intent");
subCreateOptions.Customer = customer.Id;
if (pm5766AutomaticTaxIsEnabled)
if (pm5766AutomaticTaxIsEnabled && CustomerHasTaxLocationVerified(customer))
{
subCreateOptions.AutomaticTax = new Stripe.SubscriptionAutomaticTaxOptions { Enabled = true };
subCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true };
}
subscription = await _stripeAdapter.SubscriptionCreateAsync(subCreateOptions);
@ -185,7 +188,7 @@ public class StripePaymentService : IPaymentService
{
if (subscription.LatestInvoice.PaymentIntent.Status == "requires_payment_method")
{
await _stripeAdapter.SubscriptionCancelAsync(subscription.Id, new Stripe.SubscriptionCancelOptions());
await _stripeAdapter.SubscriptionCancelAsync(subscription.Id, new SubscriptionCancelOptions());
throw new GatewayException("Payment method was declined.");
}
}
@ -252,9 +255,10 @@ public class StripePaymentService : IPaymentService
throw new BadRequestException("Organization already has a subscription.");
}
var customerOptions = new Stripe.CustomerGetOptions();
var customerOptions = new CustomerGetOptions();
customerOptions.AddExpand("default_source");
customerOptions.AddExpand("invoice_settings.default_payment_method");
customerOptions.AddExpand("tax");
var customer = await _stripeAdapter.CustomerGetAsync(org.GatewayCustomerId, customerOptions);
if (customer == null)
{
@ -301,14 +305,15 @@ public class StripePaymentService : IPaymentService
var customerUpdateOptions = new CustomerUpdateOptions { Address = addressOptions };
customerUpdateOptions.AddExpand("default_source");
customerUpdateOptions.AddExpand("invoice_settings.default_payment_method");
customerUpdateOptions.AddExpand("tax");
customer = await _stripeAdapter.CustomerUpdateAsync(org.GatewayCustomerId, customerUpdateOptions);
}
var subCreateOptions = new OrganizationUpgradeSubscriptionOptions(customer.Id, org, plan, upgrade);
if (pm5766AutomaticTaxIsEnabled)
if (pm5766AutomaticTaxIsEnabled && CustomerHasTaxLocationVerified(customer))
{
subCreateOptions.DefaultTaxRates = new List<string>();
subCreateOptions.DefaultTaxRates = [];
subCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true };
}
@ -333,7 +338,7 @@ public class StripePaymentService : IPaymentService
}
private (bool stripePaymentMethod, PaymentMethodType PaymentMethodType) IdentifyPaymentMethod(
Stripe.Customer customer, Stripe.SubscriptionCreateOptions subCreateOptions)
Customer customer, SubscriptionCreateOptions subCreateOptions)
{
var stripePaymentMethod = false;
var paymentMethodType = PaymentMethodType.Credit;
@ -351,12 +356,12 @@ public class StripePaymentService : IPaymentService
}
else if (customer.DefaultSource != null)
{
if (customer.DefaultSource is Stripe.Card || customer.DefaultSource is Stripe.SourceCard)
if (customer.DefaultSource is Card || customer.DefaultSource is SourceCard)
{
paymentMethodType = PaymentMethodType.Card;
stripePaymentMethod = true;
}
else if (customer.DefaultSource is Stripe.BankAccount || customer.DefaultSource is Stripe.SourceAchDebit)
else if (customer.DefaultSource is BankAccount || customer.DefaultSource is SourceAchDebit)
{
paymentMethodType = PaymentMethodType.BankAccount;
stripePaymentMethod = true;
@ -394,7 +399,7 @@ public class StripePaymentService : IPaymentService
}
var createdStripeCustomer = false;
Stripe.Customer customer = null;
Customer customer = null;
Braintree.Customer braintreeCustomer = null;
var stripePaymentMethod = paymentMethodType is PaymentMethodType.Card or PaymentMethodType.BankAccount
or PaymentMethodType.Credit;
@ -422,14 +427,23 @@ public class StripePaymentService : IPaymentService
try
{
customer = await _stripeAdapter.CustomerGetAsync(user.GatewayCustomerId);
var customerGetOptions = new CustomerGetOptions();
customerGetOptions.AddExpand("tax");
customer = await _stripeAdapter.CustomerGetAsync(user.GatewayCustomerId, customerGetOptions);
}
catch
{
_logger.LogWarning(
"Attempted to get existing customer from Stripe, but customer ID was not found. Attempting to recreate customer...");
}
catch { }
}
if (customer == null && !string.IsNullOrWhiteSpace(paymentToken))
{
var stripeCustomerMetadata = new Dictionary<string, string> { { "region", _globalSettings.BaseServiceUri.CloudRegion } };
var stripeCustomerMetadata = new Dictionary<string, string>
{
{ "region", _globalSettings.BaseServiceUri.CloudRegion }
};
if (paymentMethodType == PaymentMethodType.PayPal)
{
var randomSuffix = Utilities.CoreHelpers.RandomString(3, upper: false, numeric: false);
@ -458,32 +472,35 @@ public class StripePaymentService : IPaymentService
throw new GatewayException("Payment method is not supported at this time.");
}
customer = await _stripeAdapter.CustomerCreateAsync(new Stripe.CustomerCreateOptions
var customerCreateOptions = new CustomerCreateOptions
{
Description = user.Name,
Email = user.Email,
Metadata = stripeCustomerMetadata,
PaymentMethod = stipeCustomerPaymentMethodId,
Source = stipeCustomerSourceToken,
InvoiceSettings = new Stripe.CustomerInvoiceSettingsOptions
InvoiceSettings = new CustomerInvoiceSettingsOptions
{
DefaultPaymentMethod = stipeCustomerPaymentMethodId,
CustomFields = new List<Stripe.CustomerInvoiceSettingsCustomFieldOptions>
{
new Stripe.CustomerInvoiceSettingsCustomFieldOptions()
CustomFields =
[
new CustomerInvoiceSettingsCustomFieldOptions()
{
Name = user.SubscriberType(),
Value = GetFirstThirtyCharacters(user.SubscriberName()),
},
}
}
]
},
Address = new Stripe.AddressOptions
Address = new AddressOptions
{
Line1 = string.Empty,
Country = taxInfo.BillingAddressCountry,
PostalCode = taxInfo.BillingAddressPostalCode,
},
});
};
customerCreateOptions.AddExpand("tax");
customer = await _stripeAdapter.CustomerCreateAsync(customerCreateOptions);
createdStripeCustomer = true;
}
@ -492,17 +509,17 @@ public class StripePaymentService : IPaymentService
throw new GatewayException("Could not set up customer payment profile.");
}
var subCreateOptions = new Stripe.SubscriptionCreateOptions
var subCreateOptions = new SubscriptionCreateOptions
{
Customer = customer.Id,
Items = new List<Stripe.SubscriptionItemOptions>(),
Items = [],
Metadata = new Dictionary<string, string>
{
[user.GatewayIdField()] = user.Id.ToString()
}
};
subCreateOptions.Items.Add(new Stripe.SubscriptionItemOptions
subCreateOptions.Items.Add(new SubscriptionItemOptions
{
Plan = PremiumPlanId,
Quantity = 1
@ -524,25 +541,22 @@ public class StripePaymentService : IPaymentService
var taxRate = taxRates.FirstOrDefault();
if (taxRate != null)
{
subCreateOptions.DefaultTaxRates = new List<string>(1)
{
taxRate.Id
};
subCreateOptions.DefaultTaxRates = [taxRate.Id];
}
}
if (additionalStorageGb > 0)
{
subCreateOptions.Items.Add(new Stripe.SubscriptionItemOptions
subCreateOptions.Items.Add(new SubscriptionItemOptions
{
Plan = StoragePlanId,
Quantity = additionalStorageGb
});
}
if (pm5766AutomaticTaxIsEnabled)
if (pm5766AutomaticTaxIsEnabled && CustomerHasTaxLocationVerified(customer))
{
subCreateOptions.DefaultTaxRates = new List<string>();
subCreateOptions.DefaultTaxRates = [];
subCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true };
}
@ -558,34 +572,33 @@ public class StripePaymentService : IPaymentService
{
return subscription.LatestInvoice.PaymentIntent.ClientSecret;
}
else
{
user.Premium = true;
user.PremiumExpirationDate = subscription.CurrentPeriodEnd;
return null;
}
user.Premium = true;
user.PremiumExpirationDate = subscription.CurrentPeriodEnd;
return null;
}
private async Task<Stripe.Subscription> ChargeForNewSubscriptionAsync(ISubscriber subscriber, Stripe.Customer customer,
private async Task<Subscription> ChargeForNewSubscriptionAsync(ISubscriber subscriber, Customer customer,
bool createdStripeCustomer, bool stripePaymentMethod, PaymentMethodType paymentMethodType,
Stripe.SubscriptionCreateOptions subCreateOptions, Braintree.Customer braintreeCustomer)
SubscriptionCreateOptions subCreateOptions, Braintree.Customer braintreeCustomer)
{
var addedCreditToStripeCustomer = false;
Braintree.Transaction braintreeTransaction = null;
var subInvoiceMetadata = new Dictionary<string, string>();
Stripe.Subscription subscription = null;
Subscription subscription = null;
try
{
if (!stripePaymentMethod)
{
var previewInvoice = await _stripeAdapter.InvoiceUpcomingAsync(new Stripe.UpcomingInvoiceOptions
var previewInvoice = await _stripeAdapter.InvoiceUpcomingAsync(new UpcomingInvoiceOptions
{
Customer = customer.Id,
SubscriptionItems = ToInvoiceSubscriptionItemOptions(subCreateOptions.Items)
});
if (_featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax))
if (_featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax) &&
CustomerHasTaxLocationVerified(customer))
{
previewInvoice.AutomaticTax = new InvoiceAutomaticTax { Enabled = true };
}
@ -632,7 +645,7 @@ public class StripePaymentService : IPaymentService
throw new GatewayException("No payment was able to be collected.");
}
await _stripeAdapter.CustomerUpdateAsync(customer.Id, new Stripe.CustomerUpdateOptions
await _stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions
{
Balance = customer.Balance - previewInvoice.AmountDue
});
@ -649,10 +662,10 @@ public class StripePaymentService : IPaymentService
};
var pm5766AutomaticTaxIsEnabled = _featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax);
if (pm5766AutomaticTaxIsEnabled)
if (pm5766AutomaticTaxIsEnabled && CustomerHasTaxLocationVerified(customer))
{
upcomingInvoiceOptions.AutomaticTax = new InvoiceAutomaticTaxOptions { Enabled = true };
upcomingInvoiceOptions.SubscriptionDefaultTaxRates = new List<string>();
upcomingInvoiceOptions.SubscriptionDefaultTaxRates = [];
}
var previewInvoice = await _stripeAdapter.InvoiceUpcomingAsync(upcomingInvoiceOptions);
@ -666,17 +679,12 @@ public class StripePaymentService : IPaymentService
subCreateOptions.OffSession = true;
subCreateOptions.AddExpand("latest_invoice.payment_intent");
if (_featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax))
{
subCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true };
}
subscription = await _stripeAdapter.SubscriptionCreateAsync(subCreateOptions);
if (subscription.Status == "incomplete" && subscription.LatestInvoice?.PaymentIntent != null)
{
if (subscription.LatestInvoice.PaymentIntent.Status == "requires_payment_method")
{
await _stripeAdapter.SubscriptionCancelAsync(subscription.Id, new Stripe.SubscriptionCancelOptions());
await _stripeAdapter.SubscriptionCancelAsync(subscription.Id, new SubscriptionCancelOptions());
throw new GatewayException("Payment method was declined.");
}
}
@ -694,7 +702,7 @@ public class StripePaymentService : IPaymentService
throw new GatewayException("Invoice not found.");
}
await _stripeAdapter.InvoiceUpdateAsync(invoice.Id, new Stripe.InvoiceUpdateOptions
await _stripeAdapter.InvoiceUpdateAsync(invoice.Id, new InvoiceUpdateOptions
{
Metadata = subInvoiceMetadata
});
@ -712,7 +720,7 @@ public class StripePaymentService : IPaymentService
}
else if (addedCreditToStripeCustomer || customer.Balance < 0)
{
await _stripeAdapter.CustomerUpdateAsync(customer.Id, new Stripe.CustomerUpdateOptions
await _stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions
{
Balance = customer.Balance
});
@ -727,7 +735,7 @@ public class StripePaymentService : IPaymentService
await _btGateway.Customer.DeleteAsync(braintreeCustomer.Id);
}
if (e is Stripe.StripeException strEx &&
if (e is StripeException strEx &&
(strEx.StripeError?.Message?.Contains("cannot be used because it is not verified") ?? false))
{
throw new GatewayException("Bank account is not yet verified.");
@ -737,10 +745,10 @@ public class StripePaymentService : IPaymentService
}
}
private List<Stripe.InvoiceSubscriptionItemOptions> ToInvoiceSubscriptionItemOptions(
List<Stripe.SubscriptionItemOptions> subItemOptions)
private List<InvoiceSubscriptionItemOptions> ToInvoiceSubscriptionItemOptions(
List<SubscriptionItemOptions> subItemOptions)
{
return subItemOptions.Select(si => new Stripe.InvoiceSubscriptionItemOptions
return subItemOptions.Select(si => new InvoiceSubscriptionItemOptions
{
Plan = si.Plan,
Price = si.Price,
@ -753,7 +761,10 @@ public class StripePaymentService : IPaymentService
SubscriptionUpdate subscriptionUpdate, DateTime? prorationDate, bool invoiceNow = false)
{
// remember, when in doubt, throw
var sub = await _stripeAdapter.SubscriptionGetAsync(storableSubscriber.GatewaySubscriptionId);
var subGetOptions = new SubscriptionGetOptions();
// subGetOptions.AddExpand("customer");
subGetOptions.AddExpand("customer.tax");
var sub = await _stripeAdapter.SubscriptionGetAsync(storableSubscriber.GatewaySubscriptionId, subGetOptions);
if (sub == null)
{
throw new GatewayException("Subscription not found.");
@ -766,7 +777,7 @@ public class StripePaymentService : IPaymentService
var updatedItemOptions = subscriptionUpdate.UpgradeItemsOptions(sub);
var isPm5864DollarThresholdEnabled = _featureService.IsEnabled(FeatureFlagKeys.PM5864DollarThreshold);
var subUpdateOptions = new Stripe.SubscriptionUpdateOptions
var subUpdateOptions = new SubscriptionUpdateOptions
{
Items = updatedItemOptions,
ProrationBehavior = !isPm5864DollarThresholdEnabled || invoiceNow
@ -797,9 +808,11 @@ public class StripePaymentService : IPaymentService
}
var pm5766AutomaticTaxIsEnabled = _featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax);
if (pm5766AutomaticTaxIsEnabled)
if (pm5766AutomaticTaxIsEnabled &&
sub.AutomaticTax.Enabled != true &&
CustomerHasTaxLocationVerified(sub.Customer))
{
subUpdateOptions.DefaultTaxRates = new List<string>();
subUpdateOptions.DefaultTaxRates = [];
subUpdateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true };
}
@ -824,7 +837,7 @@ public class StripePaymentService : IPaymentService
var taxRate = taxRates.FirstOrDefault();
if (taxRate != null && !sub.DefaultTaxRates.Any(x => x.Equals(taxRate.Id)))
{
subUpdateOptions.DefaultTaxRates = new List<string>(1) { taxRate.Id };
subUpdateOptions.DefaultTaxRates = [taxRate.Id];
}
}
}
@ -834,7 +847,7 @@ public class StripePaymentService : IPaymentService
{
var subResponse = await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, subUpdateOptions);
var invoice = await _stripeAdapter.InvoiceGetAsync(subResponse?.LatestInvoiceId, new Stripe.InvoiceGetOptions());
var invoice = await _stripeAdapter.InvoiceGetAsync(subResponse?.LatestInvoiceId, new InvoiceGetOptions());
if (invoice == null)
{
throw new BadRequestException("Unable to locate draft invoice for subscription update.");
@ -852,11 +865,11 @@ public class StripePaymentService : IPaymentService
}
else
{
invoice = await _stripeAdapter.InvoiceFinalizeInvoiceAsync(subResponse.LatestInvoiceId, new Stripe.InvoiceFinalizeOptions
invoice = await _stripeAdapter.InvoiceFinalizeInvoiceAsync(subResponse.LatestInvoiceId, new InvoiceFinalizeOptions
{
AutoAdvance = false,
});
await _stripeAdapter.InvoiceSendInvoiceAsync(invoice.Id, new Stripe.InvoiceSendOptions());
await _stripeAdapter.InvoiceSendInvoiceAsync(invoice.Id, new InvoiceSendOptions());
paymentIntentClientSecret = null;
}
}
@ -864,7 +877,7 @@ public class StripePaymentService : IPaymentService
catch
{
// Need to revert the subscription
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, new Stripe.SubscriptionUpdateOptions
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, new SubscriptionUpdateOptions
{
Items = subscriptionUpdate.RevertItemsOptions(sub),
// This proration behavior prevents a false "credit" from
@ -889,7 +902,7 @@ public class StripePaymentService : IPaymentService
// Change back the subscription collection method and/or days until due
if (collectionMethod != "send_invoice" || daysUntilDue == null)
{
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, new Stripe.SubscriptionUpdateOptions
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, new SubscriptionUpdateOptions
{
CollectionMethod = collectionMethod,
DaysUntilDue = daysUntilDue,
@ -950,7 +963,7 @@ public class StripePaymentService : IPaymentService
if (!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
{
await _stripeAdapter.SubscriptionCancelAsync(subscriber.GatewaySubscriptionId,
new Stripe.SubscriptionCancelOptions());
new SubscriptionCancelOptions());
}
if (string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
@ -983,7 +996,7 @@ public class StripePaymentService : IPaymentService
}
else
{
var charges = await _stripeAdapter.ChargeListAsync(new Stripe.ChargeListOptions
var charges = await _stripeAdapter.ChargeListAsync(new ChargeListOptions
{
Customer = subscriber.GatewayCustomerId
});
@ -992,7 +1005,7 @@ public class StripePaymentService : IPaymentService
{
foreach (var charge in charges.Data.Where(c => c.Captured && !c.Refunded))
{
await _stripeAdapter.RefundCreateAsync(new Stripe.RefundCreateOptions { Charge = charge.Id });
await _stripeAdapter.RefundCreateAsync(new RefundCreateOptions { Charge = charge.Id });
}
}
}
@ -1000,9 +1013,9 @@ public class StripePaymentService : IPaymentService
await _stripeAdapter.CustomerDeleteAsync(subscriber.GatewayCustomerId);
}
public async Task<string> PayInvoiceAfterSubscriptionChangeAsync(ISubscriber subscriber, Stripe.Invoice invoice)
public async Task<string> PayInvoiceAfterSubscriptionChangeAsync(ISubscriber subscriber, Invoice invoice)
{
var customerOptions = new Stripe.CustomerGetOptions();
var customerOptions = new CustomerGetOptions();
customerOptions.AddExpand("default_source");
customerOptions.AddExpand("invoice_settings.default_payment_method");
var customer = await _stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId, customerOptions);
@ -1016,7 +1029,7 @@ public class StripePaymentService : IPaymentService
{
var hasDefaultCardPaymentMethod = customer.InvoiceSettings?.DefaultPaymentMethod?.Type == "card";
var hasDefaultValidSource = customer.DefaultSource != null &&
(customer.DefaultSource is Stripe.Card || customer.DefaultSource is Stripe.BankAccount);
(customer.DefaultSource is Card || customer.DefaultSource is BankAccount);
if (!hasDefaultCardPaymentMethod && !hasDefaultValidSource)
{
cardPaymentMethodId = GetLatestCardPaymentMethod(customer.Id)?.Id;
@ -1029,7 +1042,7 @@ public class StripePaymentService : IPaymentService
}
catch
{
await _stripeAdapter.InvoiceFinalizeInvoiceAsync(invoice.Id, new Stripe.InvoiceFinalizeOptions
await _stripeAdapter.InvoiceFinalizeInvoiceAsync(invoice.Id, new InvoiceFinalizeOptions
{
AutoAdvance = false
});
@ -1045,11 +1058,11 @@ public class StripePaymentService : IPaymentService
{
// Finalize the invoice (from Draft) w/o auto-advance so we
// can attempt payment manually.
invoice = await _stripeAdapter.InvoiceFinalizeInvoiceAsync(invoice.Id, new Stripe.InvoiceFinalizeOptions
invoice = await _stripeAdapter.InvoiceFinalizeInvoiceAsync(invoice.Id, new InvoiceFinalizeOptions
{
AutoAdvance = false,
});
var invoicePayOptions = new Stripe.InvoicePayOptions
var invoicePayOptions = new InvoicePayOptions
{
PaymentMethod = cardPaymentMethodId,
};
@ -1083,7 +1096,7 @@ public class StripePaymentService : IPaymentService
}
braintreeTransaction = transactionResult.Target;
invoice = await _stripeAdapter.InvoiceUpdateAsync(invoice.Id, new Stripe.InvoiceUpdateOptions
invoice = await _stripeAdapter.InvoiceUpdateAsync(invoice.Id, new InvoiceUpdateOptions
{
Metadata = new Dictionary<string, string>
{
@ -1099,13 +1112,13 @@ public class StripePaymentService : IPaymentService
{
invoice = await _stripeAdapter.InvoicePayAsync(invoice.Id, invoicePayOptions);
}
catch (Stripe.StripeException e)
catch (StripeException e)
{
if (e.HttpStatusCode == System.Net.HttpStatusCode.PaymentRequired &&
e.StripeError?.Code == "invoice_payment_intent_requires_action")
{
// SCA required, get intent client secret
var invoiceGetOptions = new Stripe.InvoiceGetOptions();
var invoiceGetOptions = new InvoiceGetOptions();
invoiceGetOptions.AddExpand("payment_intent");
invoice = await _stripeAdapter.InvoiceGetAsync(invoice.Id, invoiceGetOptions);
paymentIntentClientSecret = invoice?.PaymentIntent?.ClientSecret;
@ -1130,7 +1143,7 @@ public class StripePaymentService : IPaymentService
return paymentIntentClientSecret;
}
invoice = await _stripeAdapter.InvoiceVoidInvoiceAsync(invoice.Id, new Stripe.InvoiceVoidOptions());
invoice = await _stripeAdapter.InvoiceVoidInvoiceAsync(invoice.Id, new InvoiceVoidOptions());
// HACK: Workaround for customer balance credit
if (invoice.StartingBalance < 0)
@ -1143,7 +1156,7 @@ public class StripePaymentService : IPaymentService
// Assumption: Customer balance should now be $0, otherwise payment would not have failed.
if (customer.Balance == 0)
{
await _stripeAdapter.CustomerUpdateAsync(customer.Id, new Stripe.CustomerUpdateOptions
await _stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions
{
Balance = invoice.StartingBalance
});
@ -1151,7 +1164,7 @@ public class StripePaymentService : IPaymentService
}
}
if (e is Stripe.StripeException strEx &&
if (e is StripeException strEx &&
(strEx.StripeError?.Message?.Contains("cannot be used because it is not verified") ?? false))
{
throw new GatewayException("Bank account is not yet verified.");
@ -1192,14 +1205,14 @@ public class StripePaymentService : IPaymentService
{
var canceledSub = endOfPeriod ?
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id,
new Stripe.SubscriptionUpdateOptions { CancelAtPeriodEnd = true }) :
await _stripeAdapter.SubscriptionCancelAsync(sub.Id, new Stripe.SubscriptionCancelOptions());
new SubscriptionUpdateOptions { CancelAtPeriodEnd = true }) :
await _stripeAdapter.SubscriptionCancelAsync(sub.Id, new SubscriptionCancelOptions());
if (!canceledSub.CanceledAt.HasValue)
{
throw new GatewayException("Unable to cancel subscription.");
}
}
catch (Stripe.StripeException e)
catch (StripeException e)
{
if (e.Message != $"No such subscription: {subscriber.GatewaySubscriptionId}")
{
@ -1233,7 +1246,7 @@ public class StripePaymentService : IPaymentService
}
var updatedSub = await _stripeAdapter.SubscriptionUpdateAsync(sub.Id,
new Stripe.SubscriptionUpdateOptions { CancelAtPeriodEnd = false });
new SubscriptionUpdateOptions { CancelAtPeriodEnd = false });
if (updatedSub.CanceledAt.HasValue)
{
throw new GatewayException("Unable to reinstate subscription.");
@ -1264,12 +1277,11 @@ public class StripePaymentService : IPaymentService
};
var stripePaymentMethod = paymentMethodType is PaymentMethodType.Card or PaymentMethodType.BankAccount;
Stripe.Customer customer = null;
Customer customer = null;
if (!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
{
var options = new Stripe.CustomerGetOptions();
options.AddExpand("sources");
var options = new CustomerGetOptions { Expand = ["sources", "tax", "subscriptions"] };
customer = await _stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId, options);
if (customer.Metadata?.Any() ?? false)
{
@ -1369,26 +1381,27 @@ public class StripePaymentService : IPaymentService
{
if (customer == null)
{
customer = await _stripeAdapter.CustomerCreateAsync(new Stripe.CustomerCreateOptions
customer = await _stripeAdapter.CustomerCreateAsync(new CustomerCreateOptions
{
Description = subscriber.BillingName(),
Email = subscriber.BillingEmailAddress(),
Metadata = stripeCustomerMetadata,
Source = stipeCustomerSourceToken,
PaymentMethod = stipeCustomerPaymentMethodId,
InvoiceSettings = new Stripe.CustomerInvoiceSettingsOptions
InvoiceSettings = new CustomerInvoiceSettingsOptions
{
DefaultPaymentMethod = stipeCustomerPaymentMethodId,
CustomFields = new List<Stripe.CustomerInvoiceSettingsCustomFieldOptions>
{
new Stripe.CustomerInvoiceSettingsCustomFieldOptions()
CustomFields =
[
new CustomerInvoiceSettingsCustomFieldOptions()
{
Name = subscriber.SubscriberType(),
Value = GetFirstThirtyCharacters(subscriber.SubscriberName()),
},
}
}
]
},
Address = taxInfo == null ? null : new Stripe.AddressOptions
Address = taxInfo == null ? null : new AddressOptions
{
Country = taxInfo.BillingAddressCountry,
PostalCode = taxInfo.BillingAddressPostalCode,
@ -1397,7 +1410,7 @@ public class StripePaymentService : IPaymentService
City = taxInfo.BillingAddressCity,
State = taxInfo.BillingAddressState,
},
Expand = new List<string> { "sources" },
Expand = ["sources", "tax", "subscriptions"],
});
subscriber.Gateway = GatewayType.Stripe;
@ -1413,7 +1426,7 @@ public class StripePaymentService : IPaymentService
{
if (!string.IsNullOrWhiteSpace(stipeCustomerSourceToken) && paymentToken.StartsWith("btok_"))
{
var bankAccount = await _stripeAdapter.BankAccountCreateAsync(customer.Id, new Stripe.BankAccountCreateOptions
var bankAccount = await _stripeAdapter.BankAccountCreateAsync(customer.Id, new BankAccountCreateOptions
{
Source = paymentToken
});
@ -1422,7 +1435,7 @@ public class StripePaymentService : IPaymentService
else if (!string.IsNullOrWhiteSpace(stipeCustomerPaymentMethodId))
{
await _stripeAdapter.PaymentMethodAttachAsync(stipeCustomerPaymentMethodId,
new Stripe.PaymentMethodAttachOptions { Customer = customer.Id });
new PaymentMethodAttachOptions { Customer = customer.Id });
defaultPaymentMethodId = stipeCustomerPaymentMethodId;
}
}
@ -1431,44 +1444,44 @@ public class StripePaymentService : IPaymentService
{
foreach (var source in customer.Sources.Where(s => s.Id != defaultSourceId))
{
if (source is Stripe.BankAccount)
if (source is BankAccount)
{
await _stripeAdapter.BankAccountDeleteAsync(customer.Id, source.Id);
}
else if (source is Stripe.Card)
else if (source is Card)
{
await _stripeAdapter.CardDeleteAsync(customer.Id, source.Id);
}
}
}
var cardPaymentMethods = _stripeAdapter.PaymentMethodListAutoPaging(new Stripe.PaymentMethodListOptions
var cardPaymentMethods = _stripeAdapter.PaymentMethodListAutoPaging(new PaymentMethodListOptions
{
Customer = customer.Id,
Type = "card"
});
foreach (var cardMethod in cardPaymentMethods.Where(m => m.Id != defaultPaymentMethodId))
{
await _stripeAdapter.PaymentMethodDetachAsync(cardMethod.Id, new Stripe.PaymentMethodDetachOptions());
await _stripeAdapter.PaymentMethodDetachAsync(cardMethod.Id, new PaymentMethodDetachOptions());
}
customer = await _stripeAdapter.CustomerUpdateAsync(customer.Id, new Stripe.CustomerUpdateOptions
customer = await _stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions
{
Metadata = stripeCustomerMetadata,
DefaultSource = defaultSourceId,
InvoiceSettings = new Stripe.CustomerInvoiceSettingsOptions
InvoiceSettings = new CustomerInvoiceSettingsOptions
{
DefaultPaymentMethod = defaultPaymentMethodId,
CustomFields = new List<Stripe.CustomerInvoiceSettingsCustomFieldOptions>
{
new Stripe.CustomerInvoiceSettingsCustomFieldOptions()
CustomFields =
[
new CustomerInvoiceSettingsCustomFieldOptions()
{
Name = subscriber.SubscriberType(),
Value = GetFirstThirtyCharacters(subscriber.SubscriberName())
},
}
}
]
},
Address = taxInfo == null ? null : new Stripe.AddressOptions
Address = taxInfo == null ? null : new AddressOptions
{
Country = taxInfo.BillingAddressCountry,
PostalCode = taxInfo.BillingAddressPostalCode,
@ -1477,8 +1490,27 @@ public class StripePaymentService : IPaymentService
City = taxInfo.BillingAddressCity,
State = taxInfo.BillingAddressState,
},
Expand = ["tax", "subscriptions"]
});
}
if (_featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax) &&
!string.IsNullOrEmpty(subscriber.GatewaySubscriptionId) &&
customer.Subscriptions.Any(sub =>
sub.Id == subscriber.GatewaySubscriptionId &&
!sub.AutomaticTax.Enabled) &&
CustomerHasTaxLocationVerified(customer))
{
var subscriptionUpdateOptions = new SubscriptionUpdateOptions
{
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true },
DefaultTaxRates = []
};
_ = await _stripeAdapter.SubscriptionUpdateAsync(
subscriber.GatewaySubscriptionId,
subscriptionUpdateOptions);
}
}
catch
{
@ -1494,7 +1526,7 @@ public class StripePaymentService : IPaymentService
public async Task<bool> CreditAccountAsync(ISubscriber subscriber, decimal creditAmount)
{
Stripe.Customer customer = null;
Customer customer = null;
var customerExists = subscriber.Gateway == GatewayType.Stripe &&
!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId);
if (customerExists)
@ -1503,7 +1535,7 @@ public class StripePaymentService : IPaymentService
}
else
{
customer = await _stripeAdapter.CustomerCreateAsync(new Stripe.CustomerCreateOptions
customer = await _stripeAdapter.CustomerCreateAsync(new CustomerCreateOptions
{
Email = subscriber.BillingEmailAddress(),
Description = subscriber.BillingName(),
@ -1511,7 +1543,7 @@ public class StripePaymentService : IPaymentService
subscriber.Gateway = GatewayType.Stripe;
subscriber.GatewayCustomerId = customer.Id;
}
await _stripeAdapter.CustomerUpdateAsync(customer.Id, new Stripe.CustomerUpdateOptions
await _stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions
{
Balance = customer.Balance - (long)(creditAmount * 100)
});
@ -1614,7 +1646,7 @@ public class StripePaymentService : IPaymentService
}
var customer = await _stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId,
new Stripe.CustomerGetOptions { Expand = new List<string> { "tax_ids" } });
new CustomerGetOptions { Expand = ["tax_ids"] });
if (customer == null)
{
@ -1647,9 +1679,9 @@ public class StripePaymentService : IPaymentService
{
if (subscriber != null && !string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
{
var customer = await _stripeAdapter.CustomerUpdateAsync(subscriber.GatewayCustomerId, new Stripe.CustomerUpdateOptions
var customer = await _stripeAdapter.CustomerUpdateAsync(subscriber.GatewayCustomerId, new CustomerUpdateOptions
{
Address = new Stripe.AddressOptions
Address = new AddressOptions
{
Line1 = taxInfo.BillingAddressLine1 ?? string.Empty,
Line2 = taxInfo.BillingAddressLine2,
@ -1658,7 +1690,7 @@ public class StripePaymentService : IPaymentService
PostalCode = taxInfo.BillingAddressPostalCode,
Country = taxInfo.BillingAddressCountry,
},
Expand = new List<string> { "tax_ids" }
Expand = ["tax_ids"]
});
if (!subscriber.IsUser() && customer != null)
@ -1672,7 +1704,7 @@ public class StripePaymentService : IPaymentService
if (!string.IsNullOrWhiteSpace(taxInfo.TaxIdNumber) &&
!string.IsNullOrWhiteSpace(taxInfo.TaxIdType))
{
await _stripeAdapter.TaxIdCreateAsync(customer.Id, new Stripe.TaxIdCreateOptions
await _stripeAdapter.TaxIdCreateAsync(customer.Id, new TaxIdCreateOptions
{
Type = taxInfo.TaxIdType,
Value = taxInfo.TaxIdNumber,
@ -1684,7 +1716,7 @@ public class StripePaymentService : IPaymentService
public async Task<TaxRate> CreateTaxRateAsync(TaxRate taxRate)
{
var stripeTaxRateOptions = new Stripe.TaxRateCreateOptions()
var stripeTaxRateOptions = new TaxRateCreateOptions()
{
DisplayName = $"{taxRate.Country} - {taxRate.PostalCode}",
Inclusive = false,
@ -1717,7 +1749,7 @@ public class StripePaymentService : IPaymentService
var updatedStripeTaxRate = await _stripeAdapter.TaxRateUpdateAsync(
taxRate.Id,
new Stripe.TaxRateUpdateOptions() { Active = false }
new TaxRateUpdateOptions() { Active = false }
);
if (!updatedStripeTaxRate.Active)
{
@ -1755,19 +1787,19 @@ public class StripePaymentService : IPaymentService
return paymentSource == null;
}
private Stripe.PaymentMethod GetLatestCardPaymentMethod(string customerId)
private PaymentMethod GetLatestCardPaymentMethod(string customerId)
{
var cardPaymentMethods = _stripeAdapter.PaymentMethodListAutoPaging(
new Stripe.PaymentMethodListOptions { Customer = customerId, Type = "card" });
new PaymentMethodListOptions { Customer = customerId, Type = "card" });
return cardPaymentMethods.OrderByDescending(m => m.Created).FirstOrDefault();
}
private decimal GetBillingBalance(Stripe.Customer customer)
private decimal GetBillingBalance(Customer customer)
{
return customer != null ? customer.Balance / 100M : default;
}
private async Task<BillingInfo.BillingSource> GetBillingPaymentSourceAsync(Stripe.Customer customer)
private async Task<BillingInfo.BillingSource> GetBillingPaymentSourceAsync(Customer customer)
{
if (customer == null)
{
@ -1796,7 +1828,7 @@ public class StripePaymentService : IPaymentService
}
if (customer.DefaultSource != null &&
(customer.DefaultSource is Stripe.Card || customer.DefaultSource is Stripe.BankAccount))
(customer.DefaultSource is Card || customer.DefaultSource is BankAccount))
{
return new BillingInfo.BillingSource(customer.DefaultSource);
}
@ -1805,27 +1837,27 @@ public class StripePaymentService : IPaymentService
return paymentMethod != null ? new BillingInfo.BillingSource(paymentMethod) : null;
}
private Stripe.CustomerGetOptions GetCustomerPaymentOptions()
private CustomerGetOptions GetCustomerPaymentOptions()
{
var customerOptions = new Stripe.CustomerGetOptions();
var customerOptions = new CustomerGetOptions();
customerOptions.AddExpand("default_source");
customerOptions.AddExpand("invoice_settings.default_payment_method");
return customerOptions;
}
private async Task<Stripe.Customer> GetCustomerAsync(string gatewayCustomerId, Stripe.CustomerGetOptions options = null)
private async Task<Customer> GetCustomerAsync(string gatewayCustomerId, CustomerGetOptions options = null)
{
if (string.IsNullOrWhiteSpace(gatewayCustomerId))
{
return null;
}
Stripe.Customer customer = null;
Customer customer = null;
try
{
customer = await _stripeAdapter.CustomerGetAsync(gatewayCustomerId, options);
}
catch (Stripe.StripeException) { }
catch (StripeException) { }
return customer;
}
@ -1847,7 +1879,7 @@ public class StripePaymentService : IPaymentService
}
private async Task<IEnumerable<BillingInfo.BillingInvoice>> GetBillingInvoicesAsync(Stripe.Customer customer)
private async Task<IEnumerable<BillingInfo.BillingInvoice>> GetBillingInvoicesAsync(Customer customer)
{
if (customer == null)
{
@ -1869,28 +1901,32 @@ public class StripePaymentService : IPaymentService
.OrderByDescending(invoice => invoice.Created)
.Select(invoice => new BillingInfo.BillingInvoice(invoice));
}
catch (Stripe.StripeException exception)
catch (StripeException exception)
{
_logger.LogError(exception, "An error occurred while listing Stripe invoices");
throw new GatewayException("Failed to retrieve current invoices", exception);
}
}
/// <summary>
/// Determines if a Stripe customer supports automatic tax
/// </summary>
/// <param name="customer"></param>
/// <returns></returns>
private static bool CustomerHasTaxLocationVerified(Customer customer) =>
customer?.Tax?.AutomaticTax == StripeCustomerAutomaticTaxStatus.Supported;
// We are taking only first 30 characters of the SubscriberName because stripe provide
// for 30 characters for custom_fields,see the link: https://stripe.com/docs/api/invoices/create
public static string GetFirstThirtyCharacters(string subscriberName)
private static string GetFirstThirtyCharacters(string subscriberName)
{
if (string.IsNullOrWhiteSpace(subscriberName))
{
return "";
}
else if (subscriberName.Length <= 30)
{
return subscriberName;
}
else
{
return subscriberName.Substring(0, 30);
return string.Empty;
}
return subscriberName.Length <= 30
? subscriberName
: subscriberName[..30];
}
}

View File

@ -487,7 +487,7 @@ public class PayPalControllerTests
}
[Fact]
public async Task PostIpn_Refunded_MissingParentTransaction_BadRequest()
public async Task PostIpn_Refunded_MissingParentTransaction_Ok()
{
var logger = _testOutputHelper.BuildLoggerFor<PayPalController>();
@ -518,9 +518,9 @@ public class PayPalControllerTests
var result = await controller.PostIpn();
HasStatusCode(result, 400);
HasStatusCode(result, 200);
LoggedError(logger, "PayPal IPN (2PK15573S8089712Y): Could not find parent transaction");
LoggedWarning(logger, "PayPal IPN (2PK15573S8089712Y): Could not find parent transaction");
await _transactionRepository.DidNotReceiveWithAnyArgs().ReplaceAsync(Arg.Any<Transaction>());