From 8b65f89b47739fc79e860c2c5b0829382c467f12 Mon Sep 17 00:00:00 2001 From: shij Date: Thu, 10 Oct 2024 10:42:20 +0800 Subject: [PATCH] feat: add payment authorization workflow --- .../src/cart/workflows/complete-cart.ts | 5 +- .../mark-payment-collection-as-paid.ts | 13 ++- .../steps/authorize-payment-session.ts | 50 ++++++++++ .../src/payment-collection/steps/index.ts | 1 + .../workflows/authorize-payment-session.ts | 19 ++++ .../src/payment-collection/workflows/index.ts | 1 + .../steps/authorize-payment-session.ts | 93 ------------------- .../core-flows/src/payment/steps/index.ts | 1 - packages/core/types/src/payment/service.ts | 4 +- .../payment/src/services/payment-module.ts | 20 ++-- 10 files changed, 93 insertions(+), 114 deletions(-) create mode 100644 packages/core/core-flows/src/payment-collection/steps/authorize-payment-session.ts create mode 100644 packages/core/core-flows/src/payment-collection/workflows/authorize-payment-session.ts delete mode 100644 packages/core/core-flows/src/payment/steps/authorize-payment-session.ts diff --git a/packages/core/core-flows/src/cart/workflows/complete-cart.ts b/packages/core/core-flows/src/cart/workflows/complete-cart.ts index c1393128cd432..219d9345c7b58 100644 --- a/packages/core/core-flows/src/cart/workflows/complete-cart.ts +++ b/packages/core/core-flows/src/cart/workflows/complete-cart.ts @@ -21,7 +21,7 @@ import { useRemoteQueryStep, } from "../../common" import { createOrdersStep } from "../../order/steps/create-orders" -import { authorizePaymentSessionStep } from "../../payment/steps/authorize-payment-session" +import { authorizePaymentSessionStep } from "../../payment-collection" import { registerUsageStep } from "../../promotion/steps/register-usage" import { updateCartsStep, validateCartPaymentsStep } from "../steps" import { reserveInventoryStep } from "../steps/reserve-inventory" @@ -36,6 +36,7 @@ import { export type CompleteCartWorkflowInput = { id: string + provider_token?: string } export const completeCartWorkflowId = "complete-cart" @@ -62,7 +63,7 @@ export const completeCartWorkflow = createWorkflow( // We choose the first payment session, as there will only be one active payment session // This might change in the future. id: paymentSessions[0].id, - context: { cart_id: cart.id }, + provider_token: input.provider_token, }) const { variants, sales_channel_id } = transform({ cart }, (data) => { diff --git a/packages/core/core-flows/src/order/workflows/mark-payment-collection-as-paid.ts b/packages/core/core-flows/src/order/workflows/mark-payment-collection-as-paid.ts index 9f47828710b0f..7d9147874f847 100644 --- a/packages/core/core-flows/src/order/workflows/mark-payment-collection-as-paid.ts +++ b/packages/core/core-flows/src/order/workflows/mark-payment-collection-as-paid.ts @@ -1,4 +1,4 @@ -import { PaymentCollectionDTO } from "@medusajs/framework/types" +import { PaymentCollectionDTO, PaymentDTO } from "@medusajs/framework/types" import { MedusaError } from "@medusajs/framework/utils" import { WorkflowData, @@ -7,11 +7,11 @@ import { createWorkflow, } from "@medusajs/framework/workflows-sdk" import { useRemoteQueryStep } from "../../common" +import { capturePaymentWorkflow } from "../../payment" import { authorizePaymentSessionStep, - capturePaymentWorkflow, -} from "../../payment" -import { createPaymentSessionsWorkflow } from "../../payment-collection" + createPaymentSessionsWorkflow, +} from "../../payment-collection" /** * This step validates that the payment collection is not_paid @@ -62,17 +62,16 @@ export const markPaymentCollectionAsPaid = createWorkflow( const payment = authorizePaymentSessionStep({ id: paymentSession.id, - context: { order_id: input.order_id }, }) capturePaymentWorkflow.runAsStep({ input: { - payment_id: payment.id, + payment_id: payment!.id, captured_by: input.captured_by, amount: paymentCollection.amount, }, }) - return new WorkflowResponse(payment) + return new WorkflowResponse(payment!) } ) diff --git a/packages/core/core-flows/src/payment-collection/steps/authorize-payment-session.ts b/packages/core/core-flows/src/payment-collection/steps/authorize-payment-session.ts new file mode 100644 index 0000000000000..eb52374bfba9b --- /dev/null +++ b/packages/core/core-flows/src/payment-collection/steps/authorize-payment-session.ts @@ -0,0 +1,50 @@ +import { + IPaymentModuleService, + Logger, + PaymentProviderContext, +} from "@medusajs/framework/types" +import { ContainerRegistrationKeys, Modules } from "@medusajs/framework/utils" +import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk" + +export type AuthorizePaymentSessionStepInput = { + id: string + provider_token?: string + context?: PaymentProviderContext +} + +export const authorizePaymentSessionStepId = "authorize-payment-session-step" +/** + * This step authorizes a payment session. + */ +export const authorizePaymentSessionStep = createStep( + authorizePaymentSessionStepId, + async (input: AuthorizePaymentSessionStepInput, { container }) => { + const paymentModule = container.resolve( + Modules.PAYMENT + ) + const payment = await paymentModule.authorizePaymentSession(input.id, { + provider_token: input.provider_token, + context: input.context, + }) + return new StepResponse(payment) + }, + // If payment or any other part of complete cart fails post payment step, we cancel any payments made + async (payment, { container }) => { + if (!payment) { + return + } + + const logger = container.resolve(ContainerRegistrationKeys.LOGGER) + const paymentModule = container.resolve( + Modules.PAYMENT + ) + + try { + await paymentModule.cancelPayment(payment.id) + } catch (e) { + logger.error( + `Error was thrown trying to cancel payment - ${payment.id} - ${e}` + ) + } + } +) diff --git a/packages/core/core-flows/src/payment-collection/steps/index.ts b/packages/core/core-flows/src/payment-collection/steps/index.ts index 28c08dcb37f45..2e2bd37ce0dbe 100644 --- a/packages/core/core-flows/src/payment-collection/steps/index.ts +++ b/packages/core/core-flows/src/payment-collection/steps/index.ts @@ -6,3 +6,4 @@ export * from "./update-payment-collection" export * from "./update-refund-reasons" export * from "./validate-deleted-payment-sessions" export * from "./cancel-payment-session" +export * from "./authorize-payment-session" diff --git a/packages/core/core-flows/src/payment-collection/workflows/authorize-payment-session.ts b/packages/core/core-flows/src/payment-collection/workflows/authorize-payment-session.ts new file mode 100644 index 0000000000000..3378c59a7e9d3 --- /dev/null +++ b/packages/core/core-flows/src/payment-collection/workflows/authorize-payment-session.ts @@ -0,0 +1,19 @@ +import { + WorkflowData, + WorkflowResponse, + createWorkflow, +} from "@medusajs/framework/workflows-sdk" +import { authorizePaymentSessionStep } from "../steps" + +export const authorizePaymentSessionWorkflowId = + "authorize-payment-session-workflow" + +/** + * This workflow authorizes a payment session. + */ +export const authorizePaymentSessionWorkflow = createWorkflow( + authorizePaymentSessionWorkflowId, + (input: WorkflowData<{ id: string; provider_token?: string }>) => { + return new WorkflowResponse(authorizePaymentSessionStep(input)) + } +) diff --git a/packages/core/core-flows/src/payment-collection/workflows/index.ts b/packages/core/core-flows/src/payment-collection/workflows/index.ts index a7dc7e1983f26..3fa45e1cf5b00 100644 --- a/packages/core/core-flows/src/payment-collection/workflows/index.ts +++ b/packages/core/core-flows/src/payment-collection/workflows/index.ts @@ -3,3 +3,4 @@ export * from "./create-refund-reasons" export * from "./delete-payment-sessions" export * from "./update-refund-reasons" export * from "./cancel-payment-session" +export * from "./authorize-payment-session" diff --git a/packages/core/core-flows/src/payment/steps/authorize-payment-session.ts b/packages/core/core-flows/src/payment/steps/authorize-payment-session.ts deleted file mode 100644 index 6f6f9ba27cd23..0000000000000 --- a/packages/core/core-flows/src/payment/steps/authorize-payment-session.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { - IPaymentModuleService, - Logger, - PaymentDTO, -} from "@medusajs/framework/types" -import { - ContainerRegistrationKeys, - MedusaError, - Modules, - PaymentSessionStatus, -} from "@medusajs/framework/utils" -import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk" - -export type AuthorizePaymentSessionStepInput = { - id: string - context: Record -} - -export const authorizePaymentSessionStepId = "authorize-payment-session-step" -/** - * This step authorizes a payment session. - */ -export const authorizePaymentSessionStep = createStep( - authorizePaymentSessionStepId, - async (input: AuthorizePaymentSessionStepInput, { container }) => { - let payment: PaymentDTO | undefined - const logger = container.resolve(ContainerRegistrationKeys.LOGGER) - const paymentModule = container.resolve( - Modules.PAYMENT - ) - - try { - payment = await paymentModule.authorizePaymentSession( - input.id, - input.context || {} - ) - } catch (e) { - logger.error( - `Error was thrown trying to authorize payment session - ${input.id} - ${e}` - ) - } - - const paymentSession = await paymentModule.retrievePaymentSession(input.id) - - // Throw a special error type when the status is requires_more as it requires a specific further action - // from the consumer - if (paymentSession.status === PaymentSessionStatus.REQUIRES_MORE) { - throw new MedusaError( - MedusaError.Types.PAYMENT_REQUIRES_MORE_ERROR, - `More information is required for payment` - ) - } - - // If any other error other than requires_more shows up, this usually requires the consumer to create a new payment session - // This could also be a system error thats caused by invalid setup or a failure in connecting to external providers - if (paymentSession.status !== PaymentSessionStatus.AUTHORIZED || !payment) { - throw new MedusaError( - MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR, - `Payment authorization failed` - ) - } - - return new StepResponse(payment) - }, - // If payment or any other part of complete cart fails post payment step, we cancel any payments made - async (payment, { container }) => { - if (!payment) { - return - } - - const logger = container.resolve(ContainerRegistrationKeys.LOGGER) - const paymentModule = container.resolve( - Modules.PAYMENT - ) - - // If the payment session status is requires_more, we don't have to revert the payment. - // Return the same status for the cart completion to be re-run. - if ( - payment.payment_session && - payment.payment_session.status === PaymentSessionStatus.REQUIRES_MORE - ) { - return - } - - try { - await paymentModule.cancelPayment(payment.id) - } catch (e) { - logger.error( - `Error was thrown trying to cancel payment - ${payment.id} - ${e}` - ) - } - } -) diff --git a/packages/core/core-flows/src/payment/steps/index.ts b/packages/core/core-flows/src/payment/steps/index.ts index af0294b7892c0..6a6e956966464 100644 --- a/packages/core/core-flows/src/payment/steps/index.ts +++ b/packages/core/core-flows/src/payment/steps/index.ts @@ -1,4 +1,3 @@ -export * from "./authorize-payment-session" export * from "./cancel-payment" export * from "./capture-payment" export * from "./refund-payment" diff --git a/packages/core/types/src/payment/service.ts b/packages/core/types/src/payment/service.ts index 7971e50473458..430b2eaf6a5bf 100644 --- a/packages/core/types/src/payment/service.ts +++ b/packages/core/types/src/payment/service.ts @@ -512,7 +512,7 @@ export interface IPaymentModuleService extends IModuleService { * @param {string} id - The payment session's ID. * @param {AuthorizePaymentSessionDTO} data - The attributes to authorize in a payment session. * @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module. - * @returns {Promise} The created payment. + * @returns {Promise} The created payment or void when authorization is blocked. * * @example * await paymentModuleService.authorizePaymentSession("payses_123", { provider_token: "" }) @@ -521,7 +521,7 @@ export interface IPaymentModuleService extends IModuleService { id: string, data?: AuthorizePaymentSessionDTO, sharedContext?: Context - ): Promise + ): Promise /** * This method cancels a payment session. diff --git a/packages/modules/payment/src/services/payment-module.ts b/packages/modules/payment/src/services/payment-module.ts index c61c7ba886302..9f0337f38e1f1 100644 --- a/packages/modules/payment/src/services/payment-module.ts +++ b/packages/modules/payment/src/services/payment-module.ts @@ -426,8 +426,8 @@ export default class PaymentModuleService id: string, data?: AuthorizePaymentSessionDTO, @MedusaContext() sharedContext?: Context - ): Promise { - const paymentSession = await this.paymentSessionService_.retrieve( + ): Promise { + let paymentSession = await this.paymentSessionService_.retrieve( id, { select: ["data", "context", "provider_id"] }, sharedContext @@ -449,13 +449,15 @@ export default class PaymentModuleService sharedContext ) - const payment = await this.paymentService_.retrieve( - { payment_session_id: paymentSession.id }, - {}, - sharedContext - ) - - return this.baseRepository_.serialize(payment, { populate: true }) + paymentSession = await this.paymentSessionService_.retrieve(id, { + relations: ["payment"], + }) + if (paymentSession.payment) { + return this.baseRepository_.serialize( + paymentSession.payment, + { populate: true } + ) + } } @InjectManager()