chore(payment): provider call outside db transaction (#7536)

This commit is contained in:
Carlos R. L. Rodrigues
2024-05-30 07:24:06 -03:00
committed by GitHub
parent 096372463e
commit 8a5d9d04a3
2 changed files with 135 additions and 67 deletions

View File

@@ -40,7 +40,6 @@ import {
PaymentSessionStatus, PaymentSessionStatus,
promiseAll, promiseAll,
} from "@medusajs/utils" } from "@medusajs/utils"
import { IsolationLevel } from "@mikro-orm/core"
import { import {
Capture, Capture,
Payment, Payment,
@@ -156,7 +155,7 @@ export default class PaymentModuleService<
) )
} }
@InjectTransactionManager("baseRepository_") @InjectManager("baseRepository_")
async createPaymentCollections_( async createPaymentCollections_(
data: CreatePaymentCollectionDTO[], data: CreatePaymentCollectionDTO[],
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
@@ -174,6 +173,7 @@ export default class PaymentModuleService<
data: PaymentCollectionUpdatableFields, data: PaymentCollectionUpdatableFields,
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO[]> ): Promise<PaymentCollectionDTO[]>
@InjectManager("baseRepository_") @InjectManager("baseRepository_")
async updatePaymentCollections( async updatePaymentCollections(
idOrSelector: string | FilterablePaymentCollectionProps, idOrSelector: string | FilterablePaymentCollectionProps,
@@ -215,7 +215,7 @@ export default class PaymentModuleService<
) )
} }
@InjectTransactionManager("baseRepository_") @InjectManager("baseRepository_")
async updatePaymentCollections_( async updatePaymentCollections_(
data: UpdatePaymentCollectionDTO[], data: UpdatePaymentCollectionDTO[],
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
@@ -232,7 +232,7 @@ export default class PaymentModuleService<
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO> ): Promise<PaymentCollectionDTO>
@InjectTransactionManager("baseRepository_") @InjectManager("baseRepository_")
async upsertPaymentCollections( async upsertPaymentCollections(
data: UpsertPaymentCollectionDTO | UpsertPaymentCollectionDTO[], data: UpsertPaymentCollectionDTO | UpsertPaymentCollectionDTO[],
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
@@ -270,7 +270,7 @@ export default class PaymentModuleService<
sharedContext?: Context sharedContext?: Context
): Promise<PaymentCollectionDTO[]> ): Promise<PaymentCollectionDTO[]>
@InjectTransactionManager("baseRepository_") @InjectManager("baseRepository_")
async completePaymentCollections( async completePaymentCollections(
paymentCollectionId: string | string[], paymentCollectionId: string | string[],
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
@@ -358,7 +358,7 @@ export default class PaymentModuleService<
return paymentSession return paymentSession
} }
@InjectTransactionManager("baseRepository_") @InjectManager("baseRepository_")
async updatePaymentSession( async updatePaymentSession(
data: UpdatePaymentSessionDTO, data: UpdatePaymentSessionDTO,
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
@@ -382,7 +382,7 @@ export default class PaymentModuleService<
return await this.baseRepository_.serialize(updated[0], { populate: true }) return await this.baseRepository_.serialize(updated[0], { populate: true })
} }
@InjectTransactionManager("baseRepository_") @InjectManager("baseRepository_")
async deletePaymentSession( async deletePaymentSession(
id: string, id: string,
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
@@ -401,15 +401,12 @@ export default class PaymentModuleService<
await this.paymentSessionService_.delete(id, sharedContext) await this.paymentSessionService_.delete(id, sharedContext)
} }
@InjectTransactionManager("baseRepository_") @InjectManager("baseRepository_")
async authorizePaymentSession( async authorizePaymentSession(
id: string, id: string,
context: Record<string, unknown>, context: Record<string, unknown>,
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
): Promise<PaymentDTO> { ): Promise<PaymentDTO> {
sharedContext ??= {}
sharedContext.isolationLevel = IsolationLevel.SERIALIZABLE
const session = await this.paymentSessionService_.retrieve( const session = await this.paymentSessionService_.retrieve(
id, id,
{ {
@@ -444,6 +441,52 @@ export default class PaymentModuleService<
context context
) )
if (
status !== PaymentSessionStatus.AUTHORIZED &&
status !== PaymentSessionStatus.CAPTURED
) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
`Session: ${session.id} is not authorized with the provider.`
)
}
let payment
try {
payment = await this.authorizePaymentSession_(
session,
data,
status as PaymentSessionStatus,
sharedContext
)
} catch (error) {
await this.paymentProviderService_.cancelPayment({
provider_id: session.provider_id,
data,
})
throw error
}
await this.maybeUpdatePaymentCollection_(
session.payment_collection_id,
sharedContext
)
return await this.retrievePayment(
payment.id,
{ relations: ["payment_collection"] },
sharedContext
)
}
@InjectTransactionManager("baseRepository_")
async authorizePaymentSession_(
session: PaymentSession,
data: Record<string, unknown>,
status: PaymentSessionStatus,
@MedusaContext() sharedContext?: Context
): Promise<Payment> {
let autoCapture = false let autoCapture = false
if (status === PaymentSessionStatus.CAPTURED) { if (status === PaymentSessionStatus.CAPTURED) {
status = PaymentSessionStatus.AUTHORIZED status = PaymentSessionStatus.AUTHORIZED
@@ -461,18 +504,6 @@ export default class PaymentModuleService<
sharedContext sharedContext
) )
if (status !== PaymentSessionStatus.AUTHORIZED) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
`Session: ${session.id} is not authorized with the provider.`
)
}
await this.maybeUpdatePaymentCollection_(
session.payment_collection_id,
sharedContext
)
const payment = await this.paymentService_.create( const payment = await this.paymentService_.create(
{ {
amount: session.amount, amount: session.amount,
@@ -480,27 +511,22 @@ export default class PaymentModuleService<
payment_session: session.id, payment_session: session.id,
payment_collection_id: session.payment_collection_id, payment_collection_id: session.payment_collection_id,
provider_id: session.provider_id, provider_id: session.provider_id,
// customer_id: context.customer.id,
data, data,
}, },
sharedContext sharedContext
) )
if (autoCapture) { if (autoCapture) {
await this.capturePayment_( await this.capturePayment(
{ payment_id: payment.id, amount: session.amount as BigNumberInput }, { payment_id: payment.id, amount: session.amount as BigNumberInput },
sharedContext sharedContext
) )
} }
return await this.retrievePayment( return payment
payment.id,
{ relations: ["payment_collection"] },
sharedContext
)
} }
@InjectTransactionManager("baseRepository_") @InjectManager("baseRepository_")
async updatePayment( async updatePayment(
data: UpdatePaymentDTO, data: UpdatePaymentDTO,
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
@@ -518,7 +544,21 @@ export default class PaymentModuleService<
data: CreateCaptureDTO, data: CreateCaptureDTO,
@MedusaContext() sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<PaymentDTO> { ): Promise<PaymentDTO> {
const payment = (await this.capturePayment_(data, sharedContext)) as Payment const [payment, isFullyCaptured] = await this.capturePayment_(
data,
sharedContext
)
try {
await this.capturePaymentFromProvider_(
payment,
isFullyCaptured,
sharedContext
)
} catch (error) {
await super.deleteCaptures(data.payment_id, sharedContext)
throw error
}
await this.maybeUpdatePaymentCollection_( await this.maybeUpdatePaymentCollection_(
payment.payment_collection_id, payment.payment_collection_id,
@@ -536,7 +576,7 @@ export default class PaymentModuleService<
private async capturePayment_( private async capturePayment_(
data: CreateCaptureDTO, data: CreateCaptureDTO,
@MedusaContext() sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
) { ): Promise<[Payment, boolean]> {
const payment = await this.paymentService_.retrieve( const payment = await this.paymentService_.retrieve(
data.payment_id, data.payment_id,
{ {
@@ -567,11 +607,14 @@ export default class PaymentModuleService<
} }
if (payment.captured_at) { if (payment.captured_at) {
return await this.retrievePayment( return [
data.payment_id, (await this.retrievePayment(
{ relations: ["captures"] }, data.payment_id,
sharedContext { relations: ["captures"] },
) sharedContext
)) as unknown as Payment,
true,
]
} }
const capturedAmount = payment.captures.reduce((captureAmount, next) => { const capturedAmount = payment.captures.reduce((captureAmount, next) => {
@@ -589,10 +632,11 @@ export default class PaymentModuleService<
) )
} }
const paymentData = await this.paymentProviderService_.capturePayment({ // When the entire authorized amount has been captured, we return it as complete
data: payment.data!, const totalCaptured = MathBN.convert(
provider_id: payment.provider_id, MathBN.add(capturedAmount, newCaptureAmount)
}) )
const isFullyCaptured = MathBN.gte(totalCaptured, authorizedAmount)
await this.captureService_.create( await this.captureService_.create(
{ {
@@ -603,17 +647,25 @@ export default class PaymentModuleService<
sharedContext sharedContext
) )
// When the entire authorized amount has been captured, we mark it fully capture by setting the captured_at field return [payment, isFullyCaptured]
let capturedAt: Date | null = null }
const totalCaptured = MathBN.convert( @InjectManager("baseRepository_")
MathBN.add(capturedAmount, newCaptureAmount) private async capturePaymentFromProvider_(
) payment: Payment,
if (MathBN.gte(totalCaptured, authorizedAmount)) { isFullyCaptured: boolean,
capturedAt = new Date() @MedusaContext() sharedContext: Context = {}
} ) {
const paymentData = await this.paymentProviderService_.capturePayment({
data: payment.data!,
provider_id: payment.provider_id,
})
await this.paymentService_.update( await this.paymentService_.update(
{ id: payment.id, data: paymentData, captured_at: capturedAt }, {
id: payment.id,
data: paymentData,
captured_at: isFullyCaptured ? new Date() : undefined,
},
sharedContext sharedContext
) )
@@ -623,10 +675,17 @@ export default class PaymentModuleService<
@InjectManager("baseRepository_") @InjectManager("baseRepository_")
async refundPayment( async refundPayment(
data: CreateRefundDTO, data: CreateRefundDTO,
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext: Context = {}
): Promise<PaymentDTO> { ): Promise<PaymentDTO> {
const payment = await this.refundPayment_(data, sharedContext) const payment = await this.refundPayment_(data, sharedContext)
try {
await this.refundPaymentFromProvider_(payment, sharedContext)
} catch (error) {
await super.deleteRefunds(data.payment_id, sharedContext)
throw error
}
await this.maybeUpdatePaymentCollection_( await this.maybeUpdatePaymentCollection_(
payment.payment_collection_id, payment.payment_collection_id,
sharedContext sharedContext
@@ -642,8 +701,8 @@ export default class PaymentModuleService<
@InjectTransactionManager("baseRepository_") @InjectTransactionManager("baseRepository_")
private async refundPayment_( private async refundPayment_(
data: CreateRefundDTO, data: CreateRefundDTO,
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext: Context = {}
) { ): Promise<Payment> {
const payment = await this.paymentService_.retrieve( const payment = await this.paymentService_.retrieve(
data.payment_id, data.payment_id,
{ {
@@ -661,7 +720,7 @@ export default class PaymentModuleService<
) )
if (!data.amount) { if (!data.amount) {
data.amount = payment.amount as number data.amount = payment.amount as BigNumberInput
} }
const capturedAmount = payment.captures.reduce((captureAmount, next) => { const capturedAmount = payment.captures.reduce((captureAmount, next) => {
@@ -677,14 +736,6 @@ export default class PaymentModuleService<
) )
} }
const paymentData = await this.paymentProviderService_.refundPayment(
{
data: payment.data!,
provider_id: payment.provider_id,
},
data.amount as number
)
await this.refundService_.create( await this.refundService_.create(
{ {
payment: data.payment_id, payment: data.payment_id,
@@ -694,6 +745,22 @@ export default class PaymentModuleService<
sharedContext sharedContext
) )
return payment
}
@InjectManager("baseRepository_")
private async refundPaymentFromProvider_(
payment: Payment,
@MedusaContext() sharedContext: Context = {}
) {
const paymentData = await this.paymentProviderService_.refundPayment(
{
data: payment.data!,
provider_id: payment.provider_id,
},
payment.raw_amount
)
await this.paymentService_.update( await this.paymentService_.update(
{ id: payment.id, data: paymentData }, { id: payment.id, data: paymentData },
sharedContext sharedContext
@@ -702,7 +769,7 @@ export default class PaymentModuleService<
return payment return payment
} }
@InjectTransactionManager("baseRepository_") @InjectManager("baseRepository_")
async cancelPayment( async cancelPayment(
paymentId: string, paymentId: string,
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
@@ -734,7 +801,7 @@ export default class PaymentModuleService<
return await this.retrievePayment(payment.id, {}, sharedContext) return await this.retrievePayment(payment.id, {}, sharedContext)
} }
@InjectTransactionManager("baseRepository_") @InjectManager("baseRepository_")
async processEvent( async processEvent(
eventData: ProviderWebhookPayload, eventData: ProviderWebhookPayload,
@MedusaContext() sharedContext?: Context @MedusaContext() sharedContext?: Context
@@ -815,7 +882,7 @@ export default class PaymentModuleService<
] ]
} }
@InjectTransactionManager("baseRepository_") @InjectManager("baseRepository_")
private async maybeUpdatePaymentCollection_( private async maybeUpdatePaymentCollection_(
paymentCollectionId: string, paymentCollectionId: string,
sharedContext?: Context sharedContext?: Context

View File

@@ -1,4 +1,5 @@
import { import {
BigNumberInput,
Context, Context,
CreatePaymentProviderDTO, CreatePaymentProviderDTO,
CreatePaymentProviderSession, CreatePaymentProviderSession,
@@ -184,7 +185,7 @@ export default class PaymentProviderService {
async refundPayment( async refundPayment(
input: PaymentProviderDataInput, input: PaymentProviderDataInput,
amount: number amount: BigNumberInput
): Promise<Record<string, unknown>> { ): Promise<Record<string, unknown>> {
const provider = this.retrieveProvider(input.provider_id) const provider = this.retrieveProvider(input.provider_id)