@ -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 ;
}
}
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 / 100 M : 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 ] ;
}
}