Skip to content

Commit

Permalink
feat: support payment session cancellation
Browse files Browse the repository at this point in the history
  • Loading branch information
silenaker committed Sep 26, 2024
1 parent cb27a57 commit 91e9e3e
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
IPaymentModuleService,
Logger,
PaymentSessionDTO,
} from "@medusajs/types"
import {
ContainerRegistrationKeys,
ModuleRegistrationName,
promiseAll,
} from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"

export type CancelPaymentSessionStepInput = {
id: string | string[]
}

export const cancelPaymentSessionStepId = "cancel-payment-session-step"

/**
* This step cancels one or more payment sessions.
*/
export const cancelPaymentSessionStep = createStep(
cancelPaymentSessionStepId,
async (input: CancelPaymentSessionStepInput, { container }) => {
const logger = container.resolve<Logger>(ContainerRegistrationKeys.LOGGER)
const paymentModule = container.resolve<IPaymentModuleService>(
ModuleRegistrationName.PAYMENT
)
const ids = Array.isArray(input.id) ? input.id : [input.id]
const sessions = await promiseAll(
ids.map((id) =>
paymentModule.cancelPaymentSession(id).catch((e) => {
logger.error(
`Error was thrown trying to cancel payment session - ${id} - ${e}`
)
})
)
)

return new StepResponse(
Array.isArray(input.id)
? (sessions.filter(Boolean) as PaymentSessionDTO[])
: sessions[0]
)
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from "./delete-refund-reasons"
export * from "./update-payment-collection"
export * from "./update-refund-reasons"
export * from "./validate-deleted-payment-sessions"
export * from "./cancel-payment-session"
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {
WorkflowData,
WorkflowResponse,
createWorkflow,
} from "@medusajs/workflows-sdk"
import { cancelPaymentSessionStep } from "../steps/cancel-payment-session"

export const cancelPaymentSessionWorkflowId = "cancel-payment-session-workflow"

/**
* This workflow cancels one or more payment sessions.
*/
export const cancelPaymentSessionWorkflow = createWorkflow(
cancelPaymentSessionWorkflowId,
(input: WorkflowData<{ id: string | string[] }>) => {
return new WorkflowResponse(cancelPaymentSessionStep(input))
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from "./create-payment-session"
export * from "./create-refund-reasons"
export * from "./delete-payment-sessions"
export * from "./update-refund-reasons"
export * from "./cancel-payment-session"
15 changes: 15 additions & 0 deletions packages/core/types/src/payment/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,21 @@ export interface IPaymentModuleService extends IModuleService {
sharedContext?: Context
): Promise<PaymentDTO>

/**
* This method cancels a payment session.
*
* @param {string} id - The ID of the payment session.
* @param {Context} sharedContext - A context used to share resources, such as transaction manager, between the application and the module.
* @returns {Promise<PaymentSessionDTO>} The payment session's details.
*
* @example
* const paymentSession = await paymentModuleService.cancelPaymentSession("payses_123")
*/
cancelPaymentSession(
id: string,
sharedContext?: Context
): Promise<PaymentSessionDTO>

/**
* This method retrieves a paginated list of payment sessions based on optional filters and configuration.
*
Expand Down
107 changes: 73 additions & 34 deletions packages/modules/payment/src/services/payment-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,76 @@ export default class PaymentModuleService
)
}

@InjectManager("baseRepository_")
async cancelPaymentSession(
id: string,
@MedusaContext() sharedContext?: Context
): Promise<PaymentSessionDTO> {
const paymentSession = await this.paymentSessionService_.retrieve(
id,
{ select: ["data", "provider_id"] },
sharedContext
)

await this.handleProviderSessionResponse_(
await this.paymentProviderService_.cancelPayment(
paymentSession.provider_id,
paymentSession.data
),
sharedContext
)

return this.retrievePaymentSession(id, {}, sharedContext)
}

@InjectManager("baseRepository_")
private async cancelPaymentSession_(
id: string,
data?: PaymentProviderSessionResponse["data"],
@MedusaContext() sharedContext?: Context
): Promise<PaymentSession> {
const session = await this.paymentSessionService_.retrieve(
id,
{ select: ["status"], relations: ["payment"] },
sharedContext
)

if (session.status === PaymentSessionStatus.CANCELED) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`The payment session: ${session.id} has been canceled.`
)
}

if (
session.status !== PaymentSessionStatus.PENDING &&
session.status !== PaymentSessionStatus.REQUIRES_MORE &&
session.status !== PaymentSessionStatus.AUTHORIZED
) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`The payment session: ${session.id} cannot be canceled.`
)
}

if (session.payment) {
await this.paymentService_.update(
{
id: session.payment.id,
canceled_at: new Date(),
data,
},
sharedContext
)
}

const result = await this.paymentSessionService_.update(
{ id, data, status: PaymentSessionStatus.CANCELED },
sharedContext
)
return result[0]
}

@InjectManager("baseRepository_")
// @ts-expect-error
async retrievePaymentSession(
Expand Down Expand Up @@ -1075,43 +1145,12 @@ export default class PaymentModuleService
break
}
case "canceled": {
await this.paymentSessionService_.update(
{ id: session_id, data, status },
sharedContext
)
const session = await this.paymentSessionService_.retrieve(
const session = await this.cancelPaymentSession_(
session_id,
{
select: ["payment_collection_id"],
relations: ["payment", "payment.captures"],
},
data,
sharedContext
)
if (session.payment) {
if (session.payment.captures.length > 0) {
await this.paymentService_.update(
{
id: session.payment.id,
captured_at: new Date(),
data,
},
sharedContext
)
} else {
await this.paymentService_.update(
{
id: session.payment.id,
canceled_at: new Date(),
data,
},
sharedContext
)
}

await this.maybeUpdatePaymentCollection_(
session.payment_collection_id
)
}
await this.maybeUpdatePaymentCollection_(session.payment_collection_id)
break
}
default: {
Expand Down

0 comments on commit 91e9e3e

Please sign in to comment.