1
0
mirror of https://github.com/bitwarden/server synced 2025-12-16 00:03:54 +00:00

[PM-21638] Stripe .NET v48 (#6202)

* Upgrade Stripe.net to v48.4.0

* Update PreviewTaxAmountCommand

* Remove unused UpcomingInvoiceOptionExtensions

* Added SubscriptionExtensions with GetCurrentPeriodEnd

* Update PremiumUserBillingService

* Update OrganizationBillingService

* Update GetOrganizationWarningsQuery

* Update BillingHistoryInfo

* Update SubscriptionInfo

* Remove unused Sql Billing folder

* Update StripeAdapter

* Update StripePaymentService

* Update InvoiceCreatedHandler

* Update PaymentFailedHandler

* Update PaymentSucceededHandler

* Update ProviderEventService

* Update StripeEventUtilityService

* Update SubscriptionDeletedHandler

* Update SubscriptionUpdatedHandler

* Update UpcomingInvoiceHandler

* Update ProviderSubscriptionResponse

* Remove unused Stripe Subscriptions Admin Tool

* Update RemoveOrganizationFromProviderCommand

* Update ProviderBillingService

* Update RemoveOrganizatinoFromProviderCommandTests

* Update PreviewTaxAmountCommandTests

* Update GetCloudOrganizationLicenseQueryTests

* Update GetOrganizationWarningsQueryTests

* Update StripePaymentServiceTests

* Update ProviderBillingControllerTests

* Update ProviderEventServiceTests

* Update SubscriptionDeletedHandlerTests

* Update SubscriptionUpdatedHandlerTests

* Resolve Billing test failures

I completely removed tests for the StripeEventService as they were using a system I setup a while back that read JSON files of the Stripe event structure. I did not anticipate how frequently these structures would change with each API version and the cost of trying to update these specific JSON files to test a very static data retrieval service far outweigh the benefit.

* Resolve Core test failures

* Run dotnet format

* Remove unused provider migration

* Fixed failing tests

* Run dotnet format

* Replace the old webhook secret key with new one (#6223)

* Fix compilation failures in additions

* Run dotnet format

* Bump Stripe API version

* Fix recent addition: CreatePremiumCloudHostedSubscriptionCommand

* Fix new code in main according to Stripe update

* Fix InvoiceExtensions

* Bump SDK version to match API Version

* Fix provider invoice generation validation

* More QA fixes

* Fix tests

* QA defect resolutions

* QA defect resolutions

* Run dotnet format

* Fix tests

---------

Co-authored-by: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com>
This commit is contained in:
Alex Morask
2025-10-21 14:07:55 -05:00
committed by GitHub
parent 6324f692b8
commit 9c51c9971b
81 changed files with 1273 additions and 3959 deletions

View File

@@ -65,19 +65,20 @@ public class StripePaymentService : IPaymentService
bool applySponsorship)
{
var existingPlan = await _pricingClient.GetPlanOrThrow(org.PlanType);
var sponsoredPlan = sponsorship?.PlanSponsorshipType != null ?
Utilities.StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value) :
null;
var subscriptionUpdate = new SponsorOrganizationSubscriptionUpdate(existingPlan, sponsoredPlan, applySponsorship);
var sponsoredPlan = sponsorship?.PlanSponsorshipType != null
? Utilities.StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value)
: null;
var subscriptionUpdate =
new SponsorOrganizationSubscriptionUpdate(existingPlan, sponsoredPlan, applySponsorship);
await FinalizeSubscriptionChangeAsync(org, subscriptionUpdate, true);
var sub = await _stripeAdapter.SubscriptionGetAsync(org.GatewaySubscriptionId);
org.ExpirationDate = sub.CurrentPeriodEnd;
org.ExpirationDate = sub.GetCurrentPeriodEnd();
if (sponsorship is not null)
{
sponsorship.ValidUntil = sub.CurrentPeriodEnd;
sponsorship.ValidUntil = sub.GetCurrentPeriodEnd();
}
}
@@ -100,7 +101,8 @@ public class StripePaymentService : IPaymentService
if (sub.Status == SubscriptionStatuses.Canceled)
{
throw new BadRequestException("You do not have an active subscription. Reinstate your subscription to make changes.");
throw new BadRequestException(
"You do not have an active subscription. Reinstate your subscription to make changes.");
}
var existingCoupon = sub.Customer.Discount?.Coupon?.Id;
@@ -191,24 +193,24 @@ public class StripePaymentService : IPaymentService
throw;
}
}
else if (!invoice.Paid)
else if (invoice.Status != StripeConstants.InvoiceStatus.Paid)
{
// Pay invoice with no charge to the customer this completes the invoice immediately without waiting the scheduled 1h
invoice = await _stripeAdapter.InvoicePayAsync(subResponse.LatestInvoiceId);
paymentIntentClientSecret = null;
}
}
finally
{
// Change back the subscription collection method and/or days until due
if (collectionMethod != "send_invoice" || daysUntilDue == null)
{
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, new SubscriptionUpdateOptions
{
CollectionMethod = collectionMethod,
DaysUntilDue = daysUntilDue,
});
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id,
new SubscriptionUpdateOptions
{
CollectionMethod = collectionMethod,
DaysUntilDue = daysUntilDue,
});
}
var customer = await _stripeAdapter.CustomerGetAsync(sub.CustomerId);
@@ -218,9 +220,15 @@ public class StripePaymentService : IPaymentService
if (!string.IsNullOrEmpty(existingCoupon) && string.IsNullOrEmpty(newCoupon))
{
// Re-add the lost coupon due to the update.
await _stripeAdapter.CustomerUpdateAsync(sub.CustomerId, new CustomerUpdateOptions
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, new SubscriptionUpdateOptions
{
Coupon = existingCoupon
Discounts =
[
new SubscriptionDiscountOptions
{
Coupon = existingCoupon
}
]
});
}
}
@@ -352,7 +360,7 @@ public class StripePaymentService : IPaymentService
{
var hasDefaultCardPaymentMethod = customer.InvoiceSettings?.DefaultPaymentMethod?.Type == "card";
var hasDefaultValidSource = customer.DefaultSource != null &&
(customer.DefaultSource is Card || customer.DefaultSource is BankAccount);
(customer.DefaultSource is Card || customer.DefaultSource is BankAccount);
if (!hasDefaultCardPaymentMethod && !hasDefaultValidSource)
{
cardPaymentMethodId = GetLatestCardPaymentMethod(customer.Id)?.Id;
@@ -365,12 +373,11 @@ public class StripePaymentService : IPaymentService
}
catch
{
await _stripeAdapter.InvoiceFinalizeInvoiceAsync(invoice.Id, new InvoiceFinalizeOptions
{
AutoAdvance = false
});
await _stripeAdapter.InvoiceFinalizeInvoiceAsync(invoice.Id,
new InvoiceFinalizeOptions { AutoAdvance = false });
await _stripeAdapter.InvoiceVoidInvoiceAsync(invoice.Id);
}
throw new BadRequestException("No payment method is available.");
}
}
@@ -381,14 +388,9 @@ 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 InvoiceFinalizeOptions
{
AutoAdvance = false,
});
var invoicePayOptions = new InvoicePayOptions
{
PaymentMethod = cardPaymentMethodId,
};
invoice = await _stripeAdapter.InvoiceFinalizeInvoiceAsync(invoice.Id,
new InvoiceFinalizeOptions { AutoAdvance = false, });
var invoicePayOptions = new InvoicePayOptions { PaymentMethod = cardPaymentMethodId, };
if (customer?.Metadata?.ContainsKey("btCustomerId") ?? false)
{
invoicePayOptions.PaidOutOfBand = true;
@@ -403,13 +405,15 @@ public class StripePaymentService : IPaymentService
SubmitForSettlement = true,
PayPal = new Braintree.TransactionOptionsPayPalRequest
{
CustomField = $"{subscriber.BraintreeIdField()}:{subscriber.Id},{subscriber.BraintreeCloudRegionField()}:{_globalSettings.BaseServiceUri.CloudRegion}"
CustomField =
$"{subscriber.BraintreeIdField()}:{subscriber.Id},{subscriber.BraintreeCloudRegionField()}:{_globalSettings.BaseServiceUri.CloudRegion}"
}
},
CustomFields = new Dictionary<string, string>
{
[subscriber.BraintreeIdField()] = subscriber.Id.ToString(),
[subscriber.BraintreeCloudRegionField()] = _globalSettings.BaseServiceUri.CloudRegion
[subscriber.BraintreeCloudRegionField()] =
_globalSettings.BaseServiceUri.CloudRegion
}
});
@@ -442,9 +446,9 @@ public class StripePaymentService : IPaymentService
{
// SCA required, get intent client secret
var invoiceGetOptions = new InvoiceGetOptions();
invoiceGetOptions.AddExpand("payment_intent");
invoiceGetOptions.AddExpand("confirmation_secret");
invoice = await _stripeAdapter.InvoiceGetAsync(invoice.Id, invoiceGetOptions);
paymentIntentClientSecret = invoice?.PaymentIntent?.ClientSecret;
paymentIntentClientSecret = invoice?.ConfirmationSecret?.ClientSecret;
}
else
{
@@ -458,6 +462,7 @@ public class StripePaymentService : IPaymentService
{
await _btGateway.Transaction.RefundAsync(braintreeTransaction.Id);
}
if (invoice != null)
{
if (invoice.Status == "paid")
@@ -479,10 +484,8 @@ 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 CustomerUpdateOptions
{
Balance = invoice.StartingBalance
});
await _stripeAdapter.CustomerUpdateAsync(customer.Id,
new CustomerUpdateOptions { Balance = invoice.StartingBalance });
}
}
}
@@ -496,6 +499,7 @@ public class StripePaymentService : IPaymentService
// Let the caller perform any subscription change cleanup
throw;
}
return paymentIntentClientSecret;
}
@@ -526,10 +530,10 @@ public class StripePaymentService : IPaymentService
try
{
var canceledSub = endOfPeriod ?
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id,
new SubscriptionUpdateOptions { CancelAtPeriodEnd = true }) :
await _stripeAdapter.SubscriptionCancelAsync(sub.Id, new SubscriptionCancelOptions());
var canceledSub = endOfPeriod
? await _stripeAdapter.SubscriptionUpdateAsync(sub.Id,
new SubscriptionUpdateOptions { CancelAtPeriodEnd = true })
: await _stripeAdapter.SubscriptionCancelAsync(sub.Id, new SubscriptionCancelOptions());
if (!canceledSub.CanceledAt.HasValue)
{
throw new GatewayException("Unable to cancel subscription.");
@@ -580,7 +584,7 @@ public class StripePaymentService : IPaymentService
{
Customer customer = null;
var customerExists = subscriber.Gateway == GatewayType.Stripe &&
!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId);
!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId);
if (customerExists)
{
customer = await _stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId);
@@ -595,10 +599,10 @@ public class StripePaymentService : IPaymentService
subscriber.Gateway = GatewayType.Stripe;
subscriber.GatewayCustomerId = customer.Id;
}
await _stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions
{
Balance = customer.Balance - (long)(creditAmount * 100)
});
await _stripeAdapter.CustomerUpdateAsync(customer.Id,
new CustomerUpdateOptions { Balance = customer.Balance - (long)(creditAmount * 100) });
return !customerExists;
}
@@ -630,50 +634,45 @@ public class StripePaymentService : IPaymentService
{
var subscriptionInfo = new SubscriptionInfo();
if (!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
{
var customerGetOptions = new CustomerGetOptions();
customerGetOptions.AddExpand("discount.coupon.applies_to");
var customer = await _stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId, customerGetOptions);
if (customer.Discount != null)
{
subscriptionInfo.CustomerDiscount = new SubscriptionInfo.BillingCustomerDiscount(customer.Discount);
}
}
if (string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
if (string.IsNullOrEmpty(subscriber.GatewaySubscriptionId))
{
return subscriptionInfo;
}
var sub = await _stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId, new SubscriptionGetOptions
var subscription = await _stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId,
new SubscriptionGetOptions { Expand = ["customer", "discounts", "test_clock"] });
subscriptionInfo.Subscription = new SubscriptionInfo.BillingSubscription(subscription);
var discount = subscription.Customer.Discount ?? subscription.Discounts.FirstOrDefault();
if (discount != null)
{
Expand = ["test_clock"]
});
if (sub != null)
{
subscriptionInfo.Subscription = new SubscriptionInfo.BillingSubscription(sub);
var (suspensionDate, unpaidPeriodEndDate) = await GetSuspensionDateAsync(sub);
if (suspensionDate.HasValue && unpaidPeriodEndDate.HasValue)
{
subscriptionInfo.Subscription.SuspensionDate = suspensionDate;
subscriptionInfo.Subscription.UnpaidPeriodEndDate = unpaidPeriodEndDate;
}
subscriptionInfo.CustomerDiscount = new SubscriptionInfo.BillingCustomerDiscount(discount);
}
if (sub is { CanceledAt: not null } || string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
var (suspensionDate, unpaidPeriodEndDate) = await GetSuspensionDateAsync(subscription);
if (suspensionDate.HasValue && unpaidPeriodEndDate.HasValue)
{
subscriptionInfo.Subscription.SuspensionDate = suspensionDate;
subscriptionInfo.Subscription.UnpaidPeriodEndDate = unpaidPeriodEndDate;
}
if (subscription is { CanceledAt: not null } || string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
{
return subscriptionInfo;
}
try
{
var upcomingInvoiceOptions = new UpcomingInvoiceOptions { Customer = subscriber.GatewayCustomerId };
var upcomingInvoice = await _stripeAdapter.InvoiceUpcomingAsync(upcomingInvoiceOptions);
var invoiceCreatePreviewOptions = new InvoiceCreatePreviewOptions
{
Customer = subscriber.GatewayCustomerId,
Subscription = subscriber.GatewaySubscriptionId
};
var upcomingInvoice = await _stripeAdapter.InvoiceCreatePreviewAsync(invoiceCreatePreviewOptions);
if (upcomingInvoice != null)
{
@@ -682,7 +681,12 @@ public class StripePaymentService : IPaymentService
}
catch (StripeException ex)
{
_logger.LogWarning(ex, "Encountered an unexpected Stripe error");
_logger.LogWarning(
ex,
"Failed to retrieve upcoming invoice for customer {CustomerId}, subscription {SubscriptionId}. Error Code: {ErrorCode}",
subscriber.GatewayCustomerId,
subscriber.GatewaySubscriptionId,
ex.StripeError?.Code);
}
return subscriptionInfo;
@@ -788,7 +792,11 @@ public class StripePaymentService : IPaymentService
if (taxInfo.TaxIdType == StripeConstants.TaxIdType.SpanishNIF)
{
await _stripeAdapter.TaxIdCreateAsync(customer.Id,
new TaxIdCreateOptions { Type = StripeConstants.TaxIdType.EUVAT, Value = $"ES{taxInfo.TaxIdNumber}" });
new TaxIdCreateOptions
{
Type = StripeConstants.TaxIdType.EUVAT,
Value = $"ES{taxInfo.TaxIdNumber}"
});
}
}
catch (StripeException e)
@@ -829,7 +837,8 @@ public class StripePaymentService : IPaymentService
await HasSecretsManagerStandaloneAsync(gatewayCustomerId: organization.GatewayCustomerId,
organizationHasSecretsManager: organization.UseSecretsManager);
private async Task<bool> HasSecretsManagerStandaloneAsync(string gatewayCustomerId, bool organizationHasSecretsManager)
private async Task<bool> HasSecretsManagerStandaloneAsync(string gatewayCustomerId,
bool organizationHasSecretsManager)
{
if (string.IsNullOrEmpty(gatewayCustomerId))
{
@@ -894,26 +903,14 @@ public class StripePaymentService : IPaymentService
{
var options = new InvoiceCreatePreviewOptions
{
AutomaticTax = new InvoiceAutomaticTaxOptions
{
Enabled = true,
},
AutomaticTax = new InvoiceAutomaticTaxOptions { Enabled = true, },
Currency = "usd",
SubscriptionDetails = new InvoiceSubscriptionDetailsOptions
{
Items =
[
new()
{
Quantity = 1,
Plan = StripeConstants.Prices.PremiumAnnually
},
new()
{
Quantity = parameters.PasswordManager.AdditionalStorage,
Plan = "storage-gb-annually"
}
new InvoiceSubscriptionDetailsItemOptions { Quantity = 1, Plan = StripeConstants.Prices.PremiumAnnually },
new InvoiceSubscriptionDetailsItemOptions { Quantity = parameters.PasswordManager.AdditionalStorage, Plan = StripeConstants.Prices.StoragePlanPersonal }
]
},
CustomerDetails = new InvoiceCustomerDetailsOptions
@@ -940,12 +937,9 @@ public class StripePaymentService : IPaymentService
throw new BadRequestException("billingPreviewInvalidTaxIdError");
}
options.CustomerDetails.TaxIds = [
new InvoiceCustomerDetailsTaxIdOptions
{
Type = taxIdType,
Value = parameters.TaxInformation.TaxId
}
options.CustomerDetails.TaxIds =
[
new InvoiceCustomerDetailsTaxIdOptions { Type = taxIdType, Value = parameters.TaxInformation.TaxId }
];
if (taxIdType == StripeConstants.TaxIdType.SpanishNIF)
@@ -964,7 +958,7 @@ public class StripePaymentService : IPaymentService
if (gatewayCustomer.Discount != null)
{
options.Coupon = gatewayCustomer.Discount.Coupon.Id;
options.Discounts = [new InvoiceDiscountOptions { Coupon = gatewayCustomer.Discount.Coupon.Id }];
}
}
@@ -972,24 +966,31 @@ public class StripePaymentService : IPaymentService
{
var gatewaySubscription = await _stripeAdapter.SubscriptionGetAsync(gatewaySubscriptionId);
if (gatewaySubscription?.Discount != null)
if (gatewaySubscription?.Discounts is { Count: > 0 })
{
options.Coupon ??= gatewaySubscription.Discount.Coupon.Id;
options.Discounts = gatewaySubscription.Discounts.Select(x => new InvoiceDiscountOptions { Coupon = x.Coupon.Id }).ToList();
}
}
if (options.Discounts is { Count: > 0 })
{
options.Discounts = options.Discounts.DistinctBy(invoiceDiscountOptions => invoiceDiscountOptions.Coupon).ToList();
}
try
{
var invoice = await _stripeAdapter.InvoiceCreatePreviewAsync(options);
var effectiveTaxRate = invoice.Tax != null && invoice.TotalExcludingTax != null && invoice.TotalExcludingTax.Value != 0
? invoice.Tax.Value.ToMajor() / invoice.TotalExcludingTax.Value.ToMajor()
var tax = invoice.TotalTaxes.Sum(invoiceTotalTax => invoiceTotalTax.Amount);
var effectiveTaxRate = invoice.TotalExcludingTax != null && invoice.TotalExcludingTax.Value != 0
? tax.ToMajor() / invoice.TotalExcludingTax.Value.ToMajor()
: 0M;
var result = new PreviewInvoiceResponseModel(
effectiveTaxRate,
invoice.TotalExcludingTax.ToMajor() ?? 0,
invoice.Tax.ToMajor() ?? 0,
tax.ToMajor(),
invoice.Total.ToMajor());
return result;
}
@@ -1003,7 +1004,8 @@ public class StripePaymentService : IPaymentService
parameters.TaxInformation.Country);
throw new BadRequestException("billingPreviewInvalidTaxIdError");
default:
_logger.LogError(e, "Unexpected error previewing invoice with tax ID '{TaxId}' in country '{Country}'.",
_logger.LogError(e,
"Unexpected error previewing invoice with tax ID '{TaxId}' in country '{Country}'.",
parameters.TaxInformation.TaxId,
parameters.TaxInformation.Country);
throw new BadRequestException("billingPreviewInvoiceError");
@@ -1101,12 +1103,9 @@ public class StripePaymentService : IPaymentService
throw new BadRequestException("billingTaxIdTypeInferenceError");
}
options.CustomerDetails.TaxIds = [
new InvoiceCustomerDetailsTaxIdOptions
{
Type = taxIdType,
Value = parameters.TaxInformation.TaxId
}
options.CustomerDetails.TaxIds =
[
new InvoiceCustomerDetailsTaxIdOptions { Type = taxIdType, Value = parameters.TaxInformation.TaxId }
];
if (taxIdType == StripeConstants.TaxIdType.SpanishNIF)
@@ -1127,7 +1126,10 @@ public class StripePaymentService : IPaymentService
if (gatewayCustomer.Discount != null)
{
options.Coupon = gatewayCustomer.Discount.Coupon.Id;
options.Discounts =
[
new InvoiceDiscountOptions { Coupon = gatewayCustomer.Discount.Coupon.Id }
];
}
}
@@ -1135,9 +1137,10 @@ public class StripePaymentService : IPaymentService
{
var gatewaySubscription = await _stripeAdapter.SubscriptionGetAsync(gatewaySubscriptionId);
if (gatewaySubscription?.Discount != null)
if (gatewaySubscription?.Discounts != null)
{
options.Coupon ??= gatewaySubscription.Discount.Coupon.Id;
options.Discounts = gatewaySubscription.Discounts
.Select(discount => new InvoiceDiscountOptions { Coupon = discount.Coupon.Id }).ToList();
}
}
@@ -1152,14 +1155,16 @@ public class StripePaymentService : IPaymentService
{
var invoice = await _stripeAdapter.InvoiceCreatePreviewAsync(options);
var effectiveTaxRate = invoice.Tax != null && invoice.TotalExcludingTax != null && invoice.TotalExcludingTax.Value != 0
? invoice.Tax.Value.ToMajor() / invoice.TotalExcludingTax.Value.ToMajor()
var tax = invoice.TotalTaxes.Sum(invoiceTotalTax => invoiceTotalTax.Amount);
var effectiveTaxRate = invoice.TotalExcludingTax != null && invoice.TotalExcludingTax.Value != 0
? tax.ToMajor() / invoice.TotalExcludingTax.Value.ToMajor()
: 0M;
var result = new PreviewInvoiceResponseModel(
effectiveTaxRate,
invoice.TotalExcludingTax.ToMajor() ?? 0,
invoice.Tax.ToMajor() ?? 0,
tax.ToMajor(),
invoice.Total.ToMajor());
return result;
}
@@ -1173,7 +1178,8 @@ public class StripePaymentService : IPaymentService
parameters.TaxInformation.Country);
throw new BadRequestException("billingPreviewInvalidTaxIdError");
default:
_logger.LogError(e, "Unexpected error previewing invoice with tax ID '{TaxId}' in country '{Country}'.",
_logger.LogError(e,
"Unexpected error previewing invoice with tax ID '{TaxId}' in country '{Country}'.",
parameters.TaxInformation.TaxId,
parameters.TaxInformation.Country);
throw new BadRequestException("billingPreviewInvoiceError");
@@ -1207,7 +1213,9 @@ public class StripePaymentService : IPaymentService
braintreeCustomer.DefaultPaymentMethod);
}
}
catch (Braintree.Exceptions.NotFoundException) { }
catch (Braintree.Exceptions.NotFoundException)
{
}
}
if (customer.InvoiceSettings?.DefaultPaymentMethod?.Type == "card")
@@ -1246,12 +1254,15 @@ public class StripePaymentService : IPaymentService
{
customer = await _stripeAdapter.CustomerGetAsync(gatewayCustomerId, options);
}
catch (StripeException) { }
catch (StripeException)
{
}
return customer;
}
private async Task<IEnumerable<BillingHistoryInfo.BillingTransaction>> GetBillingTransactionsAsync(ISubscriber subscriber, int? limit = null)
private async Task<IEnumerable<BillingHistoryInfo.BillingTransaction>> GetBillingTransactionsAsync(
ISubscriber subscriber, int? limit = null)
{
var transactions = subscriber switch
{