feat(core-flows): support ad hoc returns (#13598)

* feat(core-flows): support ad hoc returns

* fix: missing transform

* handle edge case

* refactor

* replace gte for gt

* cleanup

* weird bug fix

* add test

* Create quick-nails-kick.md

* stop sending empty strings

* add code to refund reason

* fix build

* fix tests

* handle code in dashboard

* fix tests

* more tests failing

* add reference and reference id to credit lieng

* rework create refund form
This commit is contained in:
William Bouchard
2025-09-30 07:38:50 -04:00
committed by GitHub
parent bd9ecd5e66
commit 087887fefb
24 changed files with 278 additions and 255 deletions
@@ -1,15 +1,6 @@
import type { BigNumberInput, OrderDTO } from "@medusajs/framework/types"
import {
ChangeActionType,
OrderChangeStatus,
OrderChangeType,
} from "@medusajs/framework/utils"
import {
WorkflowData,
createStep,
createWorkflow,
transform,
} from "@medusajs/framework/workflows-sdk"
import { ChangeActionType, OrderChangeStatus, OrderChangeType, } from "@medusajs/framework/utils"
import { createStep, createWorkflow, transform, WorkflowData, } from "@medusajs/framework/workflows-sdk"
import { useQueryGraphStep } from "../../../common"
import { confirmOrderChanges } from "../../steps/confirm-order-changes"
import { createOrderChangeStep } from "../../steps/create-order-change"
@@ -36,8 +27,10 @@ export const createOrderRefundCreditLinesWorkflow = createWorkflow(
function (
input: WorkflowData<{
order_id: string
amount: BigNumberInput,
reference?: string,
referenceId?: string
created_by?: string
amount: BigNumberInput
}>
) {
const orderQuery = useQueryGraphStep({
@@ -69,8 +62,8 @@ export const createOrderRefundCreditLinesWorkflow = createWorkflow(
order_id: order.id,
version: orderChange.version,
action: ChangeActionType.CREDIT_LINE_ADD,
reference: "payment_collection",
reference_id: order.payment_collections[0]?.id,
reference: input.reference ?? "payment_collection",
reference_id: input.referenceId ?? order.payment_collections[0]?.id,
amount: input.amount,
})
)
@@ -1,12 +1,5 @@
import {
CreateRefundReasonDTO,
RefundReasonDTO,
} from "@medusajs/framework/types"
import {
WorkflowData,
WorkflowResponse,
createWorkflow,
} from "@medusajs/framework/workflows-sdk"
import { CreateRefundReasonDTO, RefundReasonDTO, } from "@medusajs/framework/types"
import { createWorkflow, WorkflowData, WorkflowResponse, } from "@medusajs/framework/workflows-sdk"
import { createRefundReasonStep } from "../steps/create-refund-reasons"
/**
@@ -33,7 +26,8 @@ export const createRefundReasonsWorkflowId = "create-refund-reasons-workflow"
* input: {
* data: [
* {
* label: "damaged",
* label: "Damaged",
* code: "damaged"
* }
* ]
* }
@@ -1,9 +1,6 @@
import {
BigNumberInput,
IPaymentModuleService,
} from "@medusajs/framework/types"
import { BigNumberInput, IPaymentModuleService } from "@medusajs/framework/types"
import { Modules } from "@medusajs/framework/utils"
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
/**
* The data to refund a payment.
@@ -1,89 +1,10 @@
import type {
BigNumberInput,
OrderDTO,
PaymentDTO,
} from "@medusajs/framework/types"
import { MathBN, MedusaError, PaymentEvents } from "@medusajs/framework/utils"
import {
WorkflowData,
WorkflowResponse,
createStep,
createWorkflow,
transform,
when,
} from "@medusajs/framework/workflows-sdk"
import { BigNumberInput } from "@medusajs/framework/types"
import { MathBN, PaymentEvents } from "@medusajs/framework/utils"
import { createWorkflow, transform, when, WorkflowData, WorkflowResponse, } from "@medusajs/framework/workflows-sdk"
import { emitEventStep, useRemoteQueryStep } from "../../common"
import { addOrderTransactionStep } from "../../order/steps/add-order-transaction"
import { refundPaymentStep } from "../steps/refund-payment"
/**
* The data to validate whether the refund is valid for the order.
*/
export type ValidateRefundStepInput = {
/**
* The order's details.
*/
order: OrderDTO
/**
* The order's payment details.
*/
payment: PaymentDTO
/**
* The amound to refund.
*/
amount?: BigNumberInput
}
/**
* This step validates that the refund is valid for the order.
* If the order does not have an outstanding balance to refund, the step throws an error.
*
* :::note
*
* You can retrieve an order or payment'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 = validateRefundStep({
* order: {
* id: "order_123",
* // other order details...
* },
* payment: {
* id: "payment_123",
* // other payment details...
* },
* amount: 10
* })
*/
export const validateRefundStep = createStep(
"validate-refund-step",
async function ({ order, payment, amount }: ValidateRefundStepInput) {
const pendingDifference =
order.summary?.raw_pending_difference! ??
order.summary?.pending_difference! ??
0
if (MathBN.gte(pendingDifference, 0)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Order does not have an outstanding balance to refund`
)
}
const amountPending = MathBN.mult(pendingDifference, -1)
const amountToRefund = amount ?? payment.raw_amount ?? payment.amount
if (MathBN.gt(amountToRefund, amountPending)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cannot refund more than pending difference - ${amountPending}`
)
}
}
)
import { createOrderRefundCreditLinesWorkflow } from "../../order/workflows/payments/create-order-refund-credit-lines"
/**
* The data to refund a payment.
@@ -101,6 +22,14 @@ export type RefundPaymentWorkflowInput = {
* The amount to refund. If not provided, the full payment amount will be refunded.
*/
amount?: BigNumberInput
/**
* The note to attach to the refund.
*/
note?: string
/**
* The ID of the refund reason to attach to the refund.
*/
refund_reason_id?: string
}
export const refundPaymentWorkflowId = "refund-payment-workflow"
@@ -156,7 +85,46 @@ export const refundPaymentWorkflow = createWorkflow(
list: false,
}).config({ name: "order" })
validateRefundStep({ order, payment, amount: input.amount })
const refundReason = when(
"fetch-refund-reason",
{ input }, ({ input }) =>
!!input.refund_reason_id
).then(() => {
return useRemoteQueryStep({
entry_point: "refund_reason",
fields: ["id", "label", "code"],
variables: { id: input.refund_reason_id },
list: false,
throw_if_key_not_found: true,
}).config({ name: "refund-reason" })
})
const creditLineAmount = transform({ order, payment, input }, ({ order, payment, input }) => {
const pendingDifference = order.summary?.raw_pending_difference! ?? order.summary?.pending_difference! ?? 0
const amountToRefund = input.amount ?? payment.raw_amount ?? payment.amount
if (MathBN.lt(pendingDifference, 0)) {
const amountOwed = MathBN.mult(pendingDifference, -1)
return MathBN.gt(amountToRefund, amountOwed) ? MathBN.sub(amountToRefund, amountOwed) : 0
}
return amountToRefund
})
when(
{ creditLineAmount, refundReason }, ({ creditLineAmount, refundReason }) => MathBN.gt(creditLineAmount, 0)
).then(() => {
createOrderRefundCreditLinesWorkflow.runAsStep({
input: {
order_id: order.id,
amount: creditLineAmount,
reference: refundReason?.label,
referenceId: refundReason?.code
},
})
})
const refundPayment = refundPaymentStep(input)
when({ orderPaymentCollection }, ({ orderPaymentCollection }) => {
@@ -6,6 +6,13 @@ type AdminBaseRefundReasonPayload = {
* "Refund"
*/
label: string
/**
* The refund reason's code.
*
* @example
* "refund"
*/
code: string
/**
* The refund reason's description.
*/
@@ -13,6 +13,13 @@ export interface BaseRefundReason {
* "Refund"
*/
label: string
/**
* The refund reason's code.
*
* @example
* "refund"
*/
code: string
/**
* The refund reason's description.
*/
+5 -5
View File
@@ -1,10 +1,6 @@
import { BigNumberInput } from "../totals"
import { PaymentCollectionStatus, PaymentSessionStatus } from "./common"
import {
PaymentAccountHolderDTO,
PaymentCustomerDTO,
PaymentProviderContext,
} from "./provider"
import { PaymentAccountHolderDTO, PaymentCustomerDTO, PaymentProviderContext, } from "./provider"
/**
* The payment collection to be created.
@@ -356,6 +352,10 @@ export interface CreateRefundReasonDTO {
* The label of the refund reason
*/
label: string
/**
* The code of the refund reason
*/
code: string
/**
* The description of the refund reason
*/
+7 -4
View File
@@ -22,20 +22,20 @@ import {
RefundReasonDTO,
} from "./common"
import {
CreateAccountHolderDTO,
CreateCaptureDTO,
CreatePaymentCollectionDTO,
CreatePaymentMethodDTO,
CreatePaymentSessionDTO,
CreateRefundDTO,
CreateRefundReasonDTO,
PaymentCollectionUpdatableFields,
ProviderWebhookPayload,
UpdateAccountHolderDTO,
UpdatePaymentDTO,
UpdatePaymentSessionDTO,
UpdateRefundReasonDTO,
CreateAccountHolderDTO,
UpsertPaymentCollectionDTO,
CreatePaymentMethodDTO,
UpdateAccountHolderDTO,
} from "./mutations"
import { WebhookActionResult } from "./provider"
@@ -1242,9 +1242,11 @@ export interface IPaymentModuleService extends IModuleService {
* await paymentModuleService.createRefundReasons([
* {
* label: "Too big",
* code: "too_big
* },
* {
* label: "Too big",
* label: "Too small",
* code: "too_small
* },
* ])
*/
@@ -1264,6 +1266,7 @@ export interface IPaymentModuleService extends IModuleService {
* const refundReason =
* await paymentModuleService.createRefundReasons({
* label: "Too big",
* code: "too_big"
* })
*/
createRefundReasons(