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