feat: Normalize payment method data and options when passed to Stripe (#12757)
This commit is contained in:
1 parent
9bdd5429af
commit
5856963e0b
1 file changed
+36
-25
@@ -51,12 +51,12 @@ import {
|
|||||||
} from "../utils/get-smallest-unit"
|
} from "../utils/get-smallest-unit"
|
||||||
|
|
||||||
type StripeIndeterminateState = {
|
type StripeIndeterminateState = {
|
||||||
indeterminate_due_to: string,
|
indeterminate_due_to: string
|
||||||
}
|
}
|
||||||
type StripeErrorData = Stripe.PaymentIntent | StripeIndeterminateState
|
type StripeErrorData = Stripe.PaymentIntent | StripeIndeterminateState
|
||||||
type HandledErrorType =
|
type HandledErrorType =
|
||||||
| { retry: true; }
|
| { retry: true }
|
||||||
| { retry: false; data: StripeErrorData };
|
| { retry: false; data: StripeErrorData }
|
||||||
|
|
||||||
abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||||
protected readonly options_: StripeOptions
|
protected readonly options_: StripeOptions
|
||||||
@@ -105,8 +105,15 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
|||||||
(extra?.setup_future_usage as "off_session" | "on_session" | undefined) ??
|
(extra?.setup_future_usage as "off_session" | "on_session" | undefined) ??
|
||||||
this.paymentIntentOptions.setup_future_usage
|
this.paymentIntentOptions.setup_future_usage
|
||||||
|
|
||||||
res.payment_method_types = this.paymentIntentOptions
|
res.payment_method_types =
|
||||||
.payment_method_types as string[]
|
(extra?.payment_method_types as string[]) ??
|
||||||
|
(this.paymentIntentOptions.payment_method_types as string[])
|
||||||
|
|
||||||
|
res.payment_method_data =
|
||||||
|
extra?.payment_method_data as Stripe.PaymentIntentCreateParams.PaymentMethodData
|
||||||
|
|
||||||
|
res.payment_method_options =
|
||||||
|
extra?.payment_method_options as Stripe.PaymentIntentCreateParams.PaymentMethodOptions
|
||||||
|
|
||||||
res.automatic_payment_methods =
|
res.automatic_payment_methods =
|
||||||
(extra?.automatic_payment_methods as { enabled: true } | undefined) ??
|
(extra?.automatic_payment_methods as { enabled: true } | undefined) ??
|
||||||
@@ -125,7 +132,7 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
|||||||
|
|
||||||
handleStripeError(error: any): HandledErrorType {
|
handleStripeError(error: any): HandledErrorType {
|
||||||
switch (error.type) {
|
switch (error.type) {
|
||||||
case 'StripeCardError':
|
case "StripeCardError":
|
||||||
// Stripe has created a payment intent but it failed
|
// Stripe has created a payment intent but it failed
|
||||||
// Extract and return paymentIntent object to be stored in payment_session
|
// Extract and return paymentIntent object to be stored in payment_session
|
||||||
// Allows for reference to the failed intent and potential webhook reconciliation
|
// Allows for reference to the failed intent and potential webhook reconciliation
|
||||||
@@ -133,7 +140,7 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
|||||||
if (stripeError.payment_intent) {
|
if (stripeError.payment_intent) {
|
||||||
return {
|
return {
|
||||||
retry: false,
|
retry: false,
|
||||||
data: stripeError.payment_intent
|
data: stripeError.payment_intent,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw this.buildError(
|
throw this.buildError(
|
||||||
@@ -142,21 +149,21 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'StripeConnectionError':
|
case "StripeConnectionError":
|
||||||
case 'StripeRateLimitError':
|
case "StripeRateLimitError":
|
||||||
// Connection or rate limit errors indicate an uncertain result
|
// Connection or rate limit errors indicate an uncertain result
|
||||||
// Retry the operation
|
// Retry the operation
|
||||||
return {
|
return {
|
||||||
retry: true,
|
retry: true,
|
||||||
}
|
}
|
||||||
case 'StripeAPIError': {
|
case "StripeAPIError": {
|
||||||
// API errors should be treated as indeterminate per Stripe documentation
|
// API errors should be treated as indeterminate per Stripe documentation
|
||||||
// Rely on webhooks rather than assuming failure
|
// Rely on webhooks rather than assuming failure
|
||||||
return {
|
return {
|
||||||
retry: false,
|
retry: false,
|
||||||
data: {
|
data: {
|
||||||
indeterminate_due_to: "stripe_api_error"
|
indeterminate_due_to: "stripe_api_error",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -177,23 +184,28 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
|||||||
currentAttempt: number = 1
|
currentAttempt: number = 1
|
||||||
): Promise<T | StripeErrorData> {
|
): Promise<T | StripeErrorData> {
|
||||||
try {
|
try {
|
||||||
return await apiCall();
|
return await apiCall()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const handledError = this.handleStripeError(error)
|
const handledError = this.handleStripeError(error)
|
||||||
|
|
||||||
if (!handledError.retry) {
|
if (!handledError.retry) {
|
||||||
// If retry is false, we know data exists per the type definition
|
// If retry is false, we know data exists per the type definition
|
||||||
return handledError.data;
|
return handledError.data
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (handledError.retry && currentAttempt <= maxRetries) {
|
||||||
handledError.retry &&
|
|
||||||
currentAttempt <= maxRetries
|
|
||||||
) {
|
|
||||||
// Logic for retrying
|
// Logic for retrying
|
||||||
const delay = baseDelay * Math.pow(2, currentAttempt - 1) * (0.5 + Math.random() * 0.5);
|
const delay =
|
||||||
await setTimeout(delay);
|
baseDelay *
|
||||||
return this.executeWithRetry(apiCall, maxRetries, baseDelay, currentAttempt + 1);
|
Math.pow(2, currentAttempt - 1) *
|
||||||
|
(0.5 + Math.random() * 0.5)
|
||||||
|
await setTimeout(delay)
|
||||||
|
return this.executeWithRetry(
|
||||||
|
apiCall,
|
||||||
|
maxRetries,
|
||||||
|
baseDelay,
|
||||||
|
currentAttempt + 1
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// Retries are exhausted
|
// Retries are exhausted
|
||||||
throw this.buildError(
|
throw this.buildError(
|
||||||
@@ -203,7 +215,6 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async getPaymentStatus({
|
async getPaymentStatus({
|
||||||
data,
|
data,
|
||||||
}: GetPaymentStatusInput): Promise<GetPaymentStatusOutput> {
|
}: GetPaymentStatusInput): Promise<GetPaymentStatusOutput> {
|
||||||
@@ -262,12 +273,12 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
|||||||
| string
|
| string
|
||||||
| undefined
|
| undefined
|
||||||
|
|
||||||
const sessionData = await this.executeWithRetry<Stripe.PaymentIntent>(
|
const sessionData = await this.executeWithRetry<Stripe.PaymentIntent>(() =>
|
||||||
() => this.stripe_.paymentIntents.create(intentRequest, {
|
this.stripe_.paymentIntents.create(intentRequest, {
|
||||||
idempotencyKey: context?.idempotency_key,
|
idempotencyKey: context?.idempotency_key,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
const isPaymentIntent = 'id' in sessionData;
|
const isPaymentIntent = "id" in sessionData
|
||||||
return {
|
return {
|
||||||
id: isPaymentIntent ? sessionData.id : (data?.session_id as string),
|
id: isPaymentIntent ? sessionData.id : (data?.session_id as string),
|
||||||
data: sessionData as unknown as Record<string, unknown>,
|
data: sessionData as unknown as Record<string, unknown>,
|
||||||
|
|||||||
Reference in new issue
Block a user