feat: Add support for idempotency key in payments (#11494)
This commit is contained in:
@@ -76,6 +76,11 @@ export type PaymentProviderContext = {
|
||||
* The customer information from Medusa.
|
||||
*/
|
||||
customer?: PaymentCustomerDTO
|
||||
|
||||
/**
|
||||
* Idempotency key for the request, if the payment provider supports it. It will be ignored otherwise.
|
||||
*/
|
||||
idempotency_key?: string
|
||||
}
|
||||
|
||||
export type PaymentProviderInput = {
|
||||
|
||||
@@ -347,7 +347,10 @@ export default class PaymentModuleService
|
||||
providerPaymentSession = await this.paymentProviderService_.createSession(
|
||||
input.provider_id,
|
||||
{
|
||||
context: input.context,
|
||||
context: {
|
||||
idempotency_key: paymentSession!.id,
|
||||
...input.context,
|
||||
},
|
||||
data: { ...input.data, session_id: paymentSession!.id },
|
||||
amount: input.amount,
|
||||
currency_code: input.currency_code,
|
||||
@@ -421,6 +424,7 @@ export default class PaymentModuleService
|
||||
data: data.data,
|
||||
amount: data.amount,
|
||||
currency_code: data.currency_code,
|
||||
context: data.context,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -490,7 +494,7 @@ export default class PaymentModuleService
|
||||
session.provider_id,
|
||||
{
|
||||
data: session.data,
|
||||
context,
|
||||
context: { idempotency_key: session.id, ...context },
|
||||
}
|
||||
)
|
||||
|
||||
@@ -515,6 +519,10 @@ export default class PaymentModuleService
|
||||
} catch (error) {
|
||||
await this.paymentProviderService_.cancelPayment(session.provider_id, {
|
||||
data,
|
||||
context: {
|
||||
idempotency_key: payment?.id,
|
||||
...context,
|
||||
},
|
||||
})
|
||||
|
||||
throw error
|
||||
@@ -623,6 +631,7 @@ export default class PaymentModuleService
|
||||
try {
|
||||
await this.capturePaymentFromProvider_(
|
||||
payment,
|
||||
capture,
|
||||
isFullyCaptured,
|
||||
sharedContext
|
||||
)
|
||||
@@ -703,6 +712,7 @@ export default class PaymentModuleService
|
||||
@InjectManager()
|
||||
private async capturePaymentFromProvider_(
|
||||
payment: InferEntityType<typeof Payment>,
|
||||
capture: InferEntityType<typeof Capture> | undefined,
|
||||
isFullyCaptured: boolean,
|
||||
@MedusaContext() sharedContext: Context = {}
|
||||
) {
|
||||
@@ -710,6 +720,9 @@ export default class PaymentModuleService
|
||||
payment.provider_id,
|
||||
{
|
||||
data: payment.data!,
|
||||
context: {
|
||||
idempotency_key: capture?.id,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -818,6 +831,9 @@ export default class PaymentModuleService
|
||||
{
|
||||
data: payment.data!,
|
||||
amount: refund.raw_amount as BigNumberInput,
|
||||
context: {
|
||||
idempotency_key: refund.id,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -842,6 +858,9 @@ export default class PaymentModuleService
|
||||
|
||||
await this.paymentProviderService_.cancelPayment(payment.provider_id, {
|
||||
data: payment.data!,
|
||||
context: {
|
||||
idempotency_key: payment.id,
|
||||
},
|
||||
})
|
||||
|
||||
await this.paymentService_.update(
|
||||
@@ -984,7 +1003,12 @@ export default class PaymentModuleService
|
||||
providerAccountHolder =
|
||||
await this.paymentProviderService_.createAccountHolder(
|
||||
input.provider_id,
|
||||
{ context: input.context }
|
||||
{
|
||||
context: {
|
||||
idempotency_key: input.context?.customer?.id,
|
||||
...input.context,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// This can be empty when either the method is not supported or an account holder wasn't created
|
||||
|
||||
@@ -173,9 +173,9 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
|
||||
let sessionData
|
||||
try {
|
||||
sessionData = (await this.stripe_.paymentIntents.create(
|
||||
intentRequest
|
||||
)) as unknown as Record<string, unknown>
|
||||
sessionData = (await this.stripe_.paymentIntents.create(intentRequest, {
|
||||
idempotencyKey: context?.idempotency_key,
|
||||
})) as unknown as Record<string, unknown>
|
||||
} catch (e) {
|
||||
throw this.buildError(
|
||||
"An error occurred in InitiatePayment during the creation of the stripe payment intent",
|
||||
@@ -198,6 +198,7 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
|
||||
async cancelPayment({
|
||||
data,
|
||||
context,
|
||||
}: CancelPaymentInput): Promise<CancelPaymentOutput> {
|
||||
try {
|
||||
const id = data?.id as string
|
||||
@@ -206,7 +207,9 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
return { data: data }
|
||||
}
|
||||
|
||||
const res = await this.stripe_.paymentIntents.cancel(id)
|
||||
const res = await this.stripe_.paymentIntents.cancel(id, {
|
||||
idempotencyKey: context?.idempotency_key,
|
||||
})
|
||||
return { data: res as unknown as Record<string, unknown> }
|
||||
} catch (error) {
|
||||
if (error.payment_intent?.status === ErrorIntentStatus.CANCELED) {
|
||||
@@ -219,11 +222,14 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
|
||||
async capturePayment({
|
||||
data,
|
||||
context,
|
||||
}: CapturePaymentInput): Promise<CapturePaymentOutput> {
|
||||
const id = data?.id as string
|
||||
|
||||
try {
|
||||
const intent = await this.stripe_.paymentIntents.capture(id)
|
||||
const intent = await this.stripe_.paymentIntents.capture(id, {
|
||||
idempotencyKey: context?.idempotency_key,
|
||||
})
|
||||
return { data: intent as unknown as Record<string, unknown> }
|
||||
} catch (error) {
|
||||
if (error.code === ErrorCodes.PAYMENT_INTENT_UNEXPECTED_STATE) {
|
||||
@@ -243,6 +249,7 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
async refundPayment({
|
||||
amount,
|
||||
data,
|
||||
context,
|
||||
}: RefundPaymentInput): Promise<RefundPaymentOutput> {
|
||||
const id = data?.id as string
|
||||
if (!id) {
|
||||
@@ -254,10 +261,15 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
|
||||
try {
|
||||
const currencyCode = data?.currency as string
|
||||
await this.stripe_.refunds.create({
|
||||
amount: getSmallestUnit(amount, currencyCode),
|
||||
payment_intent: id as string,
|
||||
})
|
||||
await this.stripe_.refunds.create(
|
||||
{
|
||||
amount: getSmallestUnit(amount, currencyCode),
|
||||
payment_intent: id as string,
|
||||
},
|
||||
{
|
||||
idempotencyKey: context?.idempotency_key,
|
||||
}
|
||||
)
|
||||
} catch (e) {
|
||||
throw this.buildError("An error occurred in refundPayment", e)
|
||||
}
|
||||
@@ -284,6 +296,7 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
data,
|
||||
currency_code,
|
||||
amount,
|
||||
context,
|
||||
}: UpdatePaymentInput): Promise<UpdatePaymentOutput> {
|
||||
const amountNumeric = getSmallestUnit(amount, currency_code)
|
||||
if (isPresent(amount) && data?.amount === amountNumeric) {
|
||||
@@ -292,9 +305,15 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
|
||||
try {
|
||||
const id = data?.id as string
|
||||
const sessionData = (await this.stripe_.paymentIntents.update(id, {
|
||||
amount: amountNumeric,
|
||||
})) as unknown as Record<string, unknown>
|
||||
const sessionData = (await this.stripe_.paymentIntents.update(
|
||||
id,
|
||||
{
|
||||
amount: amountNumeric,
|
||||
},
|
||||
{
|
||||
idempotencyKey: context?.idempotency_key,
|
||||
}
|
||||
)) as unknown as Record<string, unknown>
|
||||
|
||||
return { data: sessionData }
|
||||
} catch (e) {
|
||||
@@ -305,7 +324,7 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
async createAccountHolder({
|
||||
context,
|
||||
}: CreateAccountHolderInput): Promise<CreateAccountHolderOutput> {
|
||||
const { account_holder, customer } = context
|
||||
const { account_holder, customer, idempotency_key } = context
|
||||
|
||||
if (account_holder?.data?.id) {
|
||||
return { id: account_holder.data.id as string }
|
||||
@@ -332,15 +351,20 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
: undefined
|
||||
|
||||
try {
|
||||
const stripeCustomer = await this.stripe_.customers.create({
|
||||
email: customer.email,
|
||||
name:
|
||||
customer.company_name ||
|
||||
`${customer.first_name ?? ""} ${customer.last_name ?? ""}`.trim() ||
|
||||
undefined,
|
||||
phone: customer.phone as string | undefined,
|
||||
...shipping,
|
||||
})
|
||||
const stripeCustomer = await this.stripe_.customers.create(
|
||||
{
|
||||
email: customer.email,
|
||||
name:
|
||||
customer.company_name ||
|
||||
`${customer.first_name ?? ""} ${customer.last_name ?? ""}`.trim() ||
|
||||
undefined,
|
||||
phone: customer.phone as string | undefined,
|
||||
...shipping,
|
||||
},
|
||||
{
|
||||
idempotencyKey: idempotency_key,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
id: stripeCustomer.id,
|
||||
@@ -412,10 +436,15 @@ abstract class StripeBase extends AbstractPaymentProvider<StripeOptions> {
|
||||
)
|
||||
}
|
||||
|
||||
const resp = await this.stripe_.setupIntents.create({
|
||||
customer: accountHolderId,
|
||||
...data,
|
||||
})
|
||||
const resp = await this.stripe_.setupIntents.create(
|
||||
{
|
||||
customer: accountHolderId,
|
||||
...data,
|
||||
},
|
||||
{
|
||||
idempotencyKey: context?.idempotency_key,
|
||||
}
|
||||
)
|
||||
|
||||
return { id: resp.id, data: resp as unknown as Record<string, unknown> }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user