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:
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user