feat: Add support for managing account holder in payment module (#11015)
This commit is contained in:
@@ -57,13 +57,13 @@ export const THREE_DAYS = 60 * 60 * 24 * 3
|
||||
|
||||
export const completeCartWorkflowId = "complete-cart"
|
||||
/**
|
||||
* This workflow completes a cart and places an order for the customer. It's executed by the
|
||||
* This workflow completes a cart and places an order for the customer. It's executed by the
|
||||
* [Complete Cart Store API Route](https://docs.medusajs.com/api/store#carts_postcartsidcomplete).
|
||||
*
|
||||
*
|
||||
* You can use this workflow within your own customizations or custom workflows, allowing you to wrap custom logic around completing a cart.
|
||||
* For example, in the [Subscriptions recipe](https://docs.medusajs.com/resources/recipes/subscriptions/examples/standard#create-workflow),
|
||||
* For example, in the [Subscriptions recipe](https://docs.medusajs.com/resources/recipes/subscriptions/examples/standard#create-workflow),
|
||||
* this workflow is used within another workflow that creates a subscription order.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const { result } = await completeCartWorkflow(container)
|
||||
* .run({
|
||||
@@ -71,11 +71,11 @@ export const completeCartWorkflowId = "complete-cart"
|
||||
* id: "cart_123"
|
||||
* }
|
||||
* })
|
||||
*
|
||||
*
|
||||
* @summary
|
||||
*
|
||||
*
|
||||
* Complete a cart and place an order.
|
||||
*
|
||||
*
|
||||
* @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution.
|
||||
*/
|
||||
export const completeCartWorkflow = createWorkflow(
|
||||
@@ -118,7 +118,6 @@ 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 },
|
||||
})
|
||||
|
||||
const { variants, sales_channel_id } = transform({ cart }, (data) => {
|
||||
|
||||
@@ -26,14 +26,14 @@ export type ThrowUnlessPaymentCollectionNotePaidInput = {
|
||||
/**
|
||||
* This step validates that the payment collection is not paid. If not valid,
|
||||
* the step will throw an error.
|
||||
*
|
||||
*
|
||||
* :::note
|
||||
*
|
||||
*
|
||||
* You can retrieve a payment collection's details using [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query),
|
||||
* or [useQueryGraphStep](https://docs.medusajs.com/resources/references/medusa-workflows/steps/useQueryGraphStep).
|
||||
*
|
||||
*
|
||||
* :::
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const data = throwUnlessPaymentCollectionNotPaid({
|
||||
* paymentCollection: {
|
||||
@@ -77,10 +77,10 @@ export const markPaymentCollectionAsPaidId = "mark-payment-collection-as-paid"
|
||||
/**
|
||||
* This workflow marks a payment collection for an order as paid. It's used by the
|
||||
* [Mark Payment Collection as Paid Admin API Route](https://docs.medusajs.com/api/admin#payment-collections_postpaymentcollectionsidmarkaspaid).
|
||||
*
|
||||
*
|
||||
* You can use this workflow within your customizations or your own custom workflows, allowing you to wrap custom logic around
|
||||
* marking a payment collection for an order as paid.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const { result } = await markPaymentCollectionAsPaid(container)
|
||||
* .run({
|
||||
@@ -89,16 +89,14 @@ export const markPaymentCollectionAsPaidId = "mark-payment-collection-as-paid"
|
||||
* payment_collection_id: "paycol_123",
|
||||
* }
|
||||
* })
|
||||
*
|
||||
*
|
||||
* @summary
|
||||
*
|
||||
*
|
||||
* Mark a payment collection for an order as paid.
|
||||
*/
|
||||
export const markPaymentCollectionAsPaid = createWorkflow(
|
||||
markPaymentCollectionAsPaidId,
|
||||
(
|
||||
input: WorkflowData<MarkPaymentCollectionAsPaidInput>
|
||||
) => {
|
||||
(input: WorkflowData<MarkPaymentCollectionAsPaidInput>) => {
|
||||
const paymentCollection = useRemoteQueryStep({
|
||||
entry_point: "payment_collection",
|
||||
fields: ["id", "status", "amount"],
|
||||
@@ -120,7 +118,6 @@ export const markPaymentCollectionAsPaid = createWorkflow(
|
||||
|
||||
const payment = authorizePaymentSessionStep({
|
||||
id: paymentSession.id,
|
||||
context: { order_id: input.order_id },
|
||||
})
|
||||
|
||||
capturePaymentWorkflow.runAsStep({
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import {
|
||||
IPaymentModuleService,
|
||||
CreateAccountHolderDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||
|
||||
export const createPaymentAccountHolderStepId = "create-payment-account-holder"
|
||||
/**
|
||||
* This step creates the account holder in the payment provider.
|
||||
*/
|
||||
export const createPaymentAccountHolderStep = createStep(
|
||||
createPaymentAccountHolderStepId,
|
||||
async (data: CreateAccountHolderDTO, { container }) => {
|
||||
const service = container.resolve<IPaymentModuleService>(Modules.PAYMENT)
|
||||
|
||||
const accountHolder = await service.createAccountHolder(data)
|
||||
|
||||
return new StepResponse(accountHolder, accountHolder)
|
||||
},
|
||||
async (createdAccountHolder, { container }) => {
|
||||
if (!createdAccountHolder) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IPaymentModuleService>(Modules.PAYMENT)
|
||||
await service.deleteAccountHolder(createdAccountHolder.id)
|
||||
}
|
||||
)
|
||||
@@ -24,7 +24,7 @@ export interface CreatePaymentSessionStepInput {
|
||||
amount: BigNumberInput
|
||||
/**
|
||||
* The currency code of the payment session.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* usd
|
||||
*/
|
||||
@@ -42,7 +42,7 @@ export interface CreatePaymentSessionStepInput {
|
||||
|
||||
export const createPaymentSessionStepId = "create-payment-session"
|
||||
/**
|
||||
* This step creates a payment session.
|
||||
* This step creates a payment session.
|
||||
*/
|
||||
export const createPaymentSessionStep = createStep(
|
||||
createPaymentSessionStepId,
|
||||
|
||||
@@ -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 "./create-payment-account-holder"
|
||||
|
||||
+95
-10
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
PaymentProviderContext,
|
||||
AccountHolderDTO,
|
||||
CustomerDTO,
|
||||
PaymentSessionDTO,
|
||||
} from "@medusajs/framework/types"
|
||||
import {
|
||||
@@ -8,10 +9,15 @@ import {
|
||||
createWorkflow,
|
||||
parallelize,
|
||||
transform,
|
||||
when,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { useRemoteQueryStep } from "../../common"
|
||||
import { createPaymentSessionStep } from "../steps"
|
||||
import { createRemoteLinkStep, useRemoteQueryStep } from "../../common"
|
||||
import {
|
||||
createPaymentSessionStep,
|
||||
createPaymentAccountHolderStep,
|
||||
} from "../steps"
|
||||
import { deletePaymentSessionsWorkflow } from "./delete-payment-sessions"
|
||||
import { isPresent, Modules } from "@medusajs/framework/utils"
|
||||
|
||||
/**
|
||||
* The data to create payment sessions.
|
||||
@@ -26,25 +32,31 @@ export interface CreatePaymentSessionsWorkflowInput {
|
||||
* This provider is used to later process the payment sessions and their payments.
|
||||
*/
|
||||
provider_id: string
|
||||
/**
|
||||
* The ID of the customer that the payment session should be associated with.
|
||||
*/
|
||||
customer_id?: string
|
||||
/**
|
||||
* Custom data relevant for the payment provider to process the payment session.
|
||||
* Learn more in [this documentation](https://docs.medusajs.com/resources/commerce-modules/payment/payment-session#data-property).
|
||||
*/
|
||||
data?: Record<string, unknown>
|
||||
|
||||
/**
|
||||
* Additional context that's useful for the payment provider to process the payment session.
|
||||
* Currently all of the context is calculated within the workflow.
|
||||
*/
|
||||
context?: PaymentProviderContext
|
||||
context?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const createPaymentSessionsWorkflowId = "create-payment-sessions"
|
||||
/**
|
||||
* This workflow creates payment sessions. It's used by the
|
||||
* [Initialize Payment Session Store API Route](https://docs.medusajs.com/api/store#payment-collections_postpaymentcollectionsidpaymentsessions).
|
||||
*
|
||||
*
|
||||
* You can use this workflow within your own customizations or custom workflows, allowing you
|
||||
* to create payment sessions in your custom flows.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const { result } = await createPaymentSessionsWorkflow(container)
|
||||
* .run({
|
||||
@@ -53,9 +65,9 @@ export const createPaymentSessionsWorkflowId = "create-payment-sessions"
|
||||
* provider_id: "pp_system"
|
||||
* }
|
||||
* })
|
||||
*
|
||||
*
|
||||
* @summary
|
||||
*
|
||||
*
|
||||
* Create payment sessions.
|
||||
*/
|
||||
export const createPaymentSessionsWorkflow = createWorkflow(
|
||||
@@ -68,16 +80,89 @@ export const createPaymentSessionsWorkflow = createWorkflow(
|
||||
fields: ["id", "amount", "currency_code", "payment_sessions.*"],
|
||||
variables: { id: input.payment_collection_id },
|
||||
list: false,
|
||||
}).config({ name: "get-payment-collection" })
|
||||
|
||||
const { paymentCustomer, accountHolder } = when(
|
||||
"customer-id-exists",
|
||||
{ input },
|
||||
(data) => {
|
||||
return !!data.input.customer_id
|
||||
}
|
||||
).then(() => {
|
||||
const customer: CustomerDTO & { account_holder: AccountHolderDTO } =
|
||||
useRemoteQueryStep({
|
||||
entry_point: "customer",
|
||||
fields: [
|
||||
"id",
|
||||
"email",
|
||||
"company_name",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"phone",
|
||||
"addresses.*",
|
||||
"account_holder.*",
|
||||
"metadata",
|
||||
],
|
||||
variables: { id: input.customer_id },
|
||||
list: false,
|
||||
}).config({ name: "get-customer" })
|
||||
|
||||
const paymentCustomer = transform({ customer }, (data) => {
|
||||
return {
|
||||
...data.customer,
|
||||
billing_address:
|
||||
data.customer.addresses?.find((a) => a.is_default_billing) ??
|
||||
data.customer.addresses?.[0],
|
||||
}
|
||||
})
|
||||
|
||||
const accountHolderInput = {
|
||||
provider_id: input.provider_id,
|
||||
context: {
|
||||
// The module is idempotent, so if there already is a linked account holder, the module will simply return it back.
|
||||
account_holder: customer.account_holder,
|
||||
customer: paymentCustomer,
|
||||
},
|
||||
}
|
||||
|
||||
const accountHolder = createPaymentAccountHolderStep(accountHolderInput)
|
||||
return { paymentCustomer, accountHolder }
|
||||
})
|
||||
|
||||
when(
|
||||
"account-holder-created",
|
||||
{ paymentCustomer, accountHolder },
|
||||
(data) => {
|
||||
return (
|
||||
!isPresent(data.paymentCustomer?.account_holder) &&
|
||||
isPresent(data.accountHolder)
|
||||
)
|
||||
}
|
||||
).then(() => {
|
||||
createRemoteLinkStep([
|
||||
{
|
||||
[Modules.CUSTOMER]: {
|
||||
customer_id: paymentCustomer.id,
|
||||
},
|
||||
[Modules.PAYMENT]: {
|
||||
account_holder_id: accountHolder.id,
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
const paymentSessionInput = transform(
|
||||
{ paymentCollection, input },
|
||||
{ paymentCollection, paymentCustomer, accountHolder, input },
|
||||
(data) => {
|
||||
return {
|
||||
payment_collection_id: data.input.payment_collection_id,
|
||||
provider_id: data.input.provider_id,
|
||||
data: data.input.data,
|
||||
context: data.input.context,
|
||||
context: {
|
||||
...data.input.context,
|
||||
customer: data.paymentCustomer,
|
||||
account_holder: data.accountHolder,
|
||||
},
|
||||
amount: data.paymentCollection.amount,
|
||||
currency_code: data.paymentCollection.currency_code,
|
||||
}
|
||||
|
||||
@@ -23,13 +23,13 @@ export type AuthorizePaymentSessionStepInput = {
|
||||
* The context to authorize the payment session with.
|
||||
* This context is passed to the payment provider associated with the payment session.
|
||||
*/
|
||||
context: Record<string, unknown>
|
||||
context?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const authorizePaymentSessionStepId = "authorize-payment-session-step"
|
||||
/**
|
||||
* This step authorizes a payment session.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const data = authorizePaymentSessionStep({
|
||||
* id: "payses_123",
|
||||
|
||||
@@ -31,12 +31,12 @@ export type CapturePaymentWorkflowInput = {
|
||||
|
||||
export const capturePaymentWorkflowId = "capture-payment-workflow"
|
||||
/**
|
||||
* This workflow captures a payment. It's used by the
|
||||
* This workflow captures a payment. It's used by the
|
||||
* [Capture Payment Admin API Route](https://docs.medusajs.com/api/admin#payments_postpaymentsidcapture).
|
||||
*
|
||||
*
|
||||
* You can use this workflow within your own customizations or custom workflows, allowing you
|
||||
* to capture a payment in your custom flows.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* const { result } = await capturePaymentWorkflow(container)
|
||||
* .run({
|
||||
@@ -44,9 +44,9 @@ export const capturePaymentWorkflowId = "capture-payment-workflow"
|
||||
* payment_id: "pay_123"
|
||||
* }
|
||||
* })
|
||||
*
|
||||
*
|
||||
* @summary
|
||||
*
|
||||
*
|
||||
* Capture a payment.
|
||||
*/
|
||||
export const capturePaymentWorkflow = createWorkflow(
|
||||
|
||||
Reference in New Issue
Block a user