feat(medusa,core-flows,types): API to create payment collections for order (#8617)
what: - adds an API to create a payment collection for order
This commit is contained in:
@@ -100,7 +100,7 @@ medusaIntegrationTestRunner({
|
|||||||
prices: [
|
prices: [
|
||||||
{
|
{
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
amount: 123456.1234657890123456789,
|
amount: 50.25,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -333,7 +333,7 @@ medusaIntegrationTestRunner({
|
|||||||
prices: [
|
prices: [
|
||||||
{
|
{
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
amount: 1000,
|
amount: 15,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
rules: [
|
rules: [
|
||||||
@@ -359,7 +359,7 @@ medusaIntegrationTestRunner({
|
|||||||
prices: [
|
prices: [
|
||||||
{
|
{
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
amount: 1000,
|
amount: 20,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
rules: [
|
rules: [
|
||||||
@@ -672,6 +672,40 @@ medusaIntegrationTestRunner({
|
|||||||
0
|
0
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should create a payment collection successfully and throw on multiple", async () => {
|
||||||
|
const paymentDelta = 110.5
|
||||||
|
|
||||||
|
const paymentCollection = (
|
||||||
|
await api.post(
|
||||||
|
`/admin/payment-collections`,
|
||||||
|
{ order_id: order.id },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
).data.payment_collection
|
||||||
|
|
||||||
|
expect(paymentCollection).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
currency_code: "usd",
|
||||||
|
amount: paymentDelta,
|
||||||
|
payment_sessions: [],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const { response } = await api
|
||||||
|
.post(
|
||||||
|
`/admin/payment-collections`,
|
||||||
|
{ order_id: order.id },
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.data).toEqual({
|
||||||
|
type: "not_allowed",
|
||||||
|
message:
|
||||||
|
"Active payment collections were found. Complete existing ones or delete them before proceeding.",
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("with only outbound items", () => {
|
describe("with only outbound items", () => {
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import { PaymentCollectionDTO } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
MathBN,
|
||||||
|
MedusaError,
|
||||||
|
Modules,
|
||||||
|
PaymentCollectionStatus,
|
||||||
|
} from "@medusajs/utils"
|
||||||
|
import {
|
||||||
|
WorkflowData,
|
||||||
|
WorkflowResponse,
|
||||||
|
createStep,
|
||||||
|
createWorkflow,
|
||||||
|
transform,
|
||||||
|
} from "@medusajs/workflows-sdk"
|
||||||
|
import { createRemoteLinkStep, useRemoteQueryStep } from "../../common"
|
||||||
|
import { createPaymentCollectionsStep } from "../../definition"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This step validates that the order doesn't have an active payment collection.
|
||||||
|
*/
|
||||||
|
export const throwIfActivePaymentCollectionExists = createStep(
|
||||||
|
"validate-existing-payment-collection",
|
||||||
|
({ paymentCollection }: { paymentCollection: PaymentCollectionDTO }) => {
|
||||||
|
if (paymentCollection) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`Active payment collections were found. Complete existing ones or delete them before proceeding.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const createOrderPaymentCollectionWorkflowId =
|
||||||
|
"create-order-payment-collection"
|
||||||
|
/**
|
||||||
|
* This workflow creates a payment collection for an order.
|
||||||
|
*/
|
||||||
|
export const createOrderPaymentCollectionWorkflow = createWorkflow(
|
||||||
|
createOrderPaymentCollectionWorkflowId,
|
||||||
|
(
|
||||||
|
input: WorkflowData<{
|
||||||
|
order_id: string
|
||||||
|
amount?: number
|
||||||
|
}>
|
||||||
|
) => {
|
||||||
|
const order = useRemoteQueryStep({
|
||||||
|
entry_point: "order",
|
||||||
|
fields: ["id", "summary", "currency_code", "region_id"],
|
||||||
|
variables: { id: input.order_id },
|
||||||
|
throw_if_key_not_found: true,
|
||||||
|
list: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const orderPaymentCollections = useRemoteQueryStep({
|
||||||
|
entry_point: "order_payment_collection",
|
||||||
|
fields: ["payment_collection_id"],
|
||||||
|
variables: { order_id: order.id },
|
||||||
|
}).config({ name: "order-payment-collection-query" })
|
||||||
|
|
||||||
|
const orderPaymentCollectionIds = transform(
|
||||||
|
{ orderPaymentCollections },
|
||||||
|
({ orderPaymentCollections }) =>
|
||||||
|
orderPaymentCollections.map((opc) => opc.payment_collection_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
const paymentCollection = useRemoteQueryStep({
|
||||||
|
entry_point: "payment_collection",
|
||||||
|
fields: ["id"],
|
||||||
|
variables: {
|
||||||
|
id: orderPaymentCollectionIds,
|
||||||
|
status: [
|
||||||
|
PaymentCollectionStatus.NOT_PAID,
|
||||||
|
PaymentCollectionStatus.AWAITING,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
list: false,
|
||||||
|
}).config({ name: "payment-collection-query" })
|
||||||
|
|
||||||
|
throwIfActivePaymentCollectionExists({ paymentCollection })
|
||||||
|
|
||||||
|
const paymentCollectionData = transform(
|
||||||
|
{ order, input },
|
||||||
|
({ order, input }) => {
|
||||||
|
const pendingPayment = MathBN.sub(
|
||||||
|
order.summary.raw_current_order_total,
|
||||||
|
order.summary.raw_original_order_total
|
||||||
|
)
|
||||||
|
|
||||||
|
if (MathBN.lte(pendingPayment, 0)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`Cannot create a payment collection for amount less than 0`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.amount && MathBN.gt(input.amount, pendingPayment)) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
`Cannot create a payment collection for amount greater than ${pendingPayment}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
currency_code: order.currency_code,
|
||||||
|
amount: input.amount ?? pendingPayment,
|
||||||
|
region_id: order.region_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const createdPaymentCollections = createPaymentCollectionsStep([
|
||||||
|
paymentCollectionData,
|
||||||
|
])
|
||||||
|
|
||||||
|
const orderPaymentCollectionLink = transform(
|
||||||
|
{ order, createdPaymentCollections },
|
||||||
|
({ order, createdPaymentCollections }) => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
[Modules.ORDER]: { order_id: order.id },
|
||||||
|
[Modules.PAYMENT]: {
|
||||||
|
payment_collection_id: createdPaymentCollections[0].id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
createRemoteLinkStep(orderPaymentCollectionLink).config({
|
||||||
|
name: "order-payment-collection-link",
|
||||||
|
})
|
||||||
|
|
||||||
|
return new WorkflowResponse(createdPaymentCollections)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -21,6 +21,7 @@ export * from "./complete-orders"
|
|||||||
export * from "./create-fulfillment"
|
export * from "./create-fulfillment"
|
||||||
export * from "./create-order-change"
|
export * from "./create-order-change"
|
||||||
export * from "./create-order-change-actions"
|
export * from "./create-order-change-actions"
|
||||||
|
export * from "./create-order-payment-collection"
|
||||||
export * from "./create-orders"
|
export * from "./create-orders"
|
||||||
export * from "./create-shipment"
|
export * from "./create-shipment"
|
||||||
export * from "./decline-order-change"
|
export * from "./decline-order-change"
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
import { PaginatedResponse } from "../../common"
|
import { PaginatedResponse } from "../../common"
|
||||||
import { AdminPayment, AdminPaymentProvider, AdminRefund, AdminRefundReason } from "./entities"
|
import {
|
||||||
|
AdminPayment,
|
||||||
|
AdminPaymentCollection,
|
||||||
|
AdminPaymentProvider,
|
||||||
|
AdminRefund,
|
||||||
|
AdminRefundReason,
|
||||||
|
} from "./entities"
|
||||||
|
|
||||||
|
export interface AdminPaymentCollectionResponse {
|
||||||
|
payment_collection: AdminPaymentCollection
|
||||||
|
}
|
||||||
|
|
||||||
export interface AdminPaymentResponse {
|
export interface AdminPaymentResponse {
|
||||||
payment: AdminPayment
|
payment: AdminPayment
|
||||||
@@ -27,4 +37,4 @@ export type RefundReasonsResponse = PaginatedResponse<{
|
|||||||
|
|
||||||
export type AdminPaymentProviderListResponse = PaginatedResponse<{
|
export type AdminPaymentProviderListResponse = PaginatedResponse<{
|
||||||
payment_providers: AdminPaymentProvider[]
|
payment_providers: AdminPaymentProvider[]
|
||||||
}>
|
}>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { MiddlewareRoute } from "@medusajs/framework"
|
||||||
|
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||||
|
import { validateAndTransformQuery } from "../../utils/validate-query"
|
||||||
|
import * as queryConfig from "./query-config"
|
||||||
|
import {
|
||||||
|
AdminCreatePaymentCollection,
|
||||||
|
AdminGetPaymentCollectionParams,
|
||||||
|
} from "./validators"
|
||||||
|
|
||||||
|
export const adminPaymentCollectionsMiddlewares: MiddlewareRoute[] = [
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/admin/payment-collections",
|
||||||
|
middlewares: [
|
||||||
|
validateAndTransformBody(AdminCreatePaymentCollection),
|
||||||
|
validateAndTransformQuery(
|
||||||
|
AdminGetPaymentCollectionParams,
|
||||||
|
queryConfig.retrievePaymentCollectionTransformQueryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export const defaultPaymentCollectionFields = [
|
||||||
|
"id",
|
||||||
|
"currency_code",
|
||||||
|
"amount",
|
||||||
|
"status",
|
||||||
|
"*payment_sessions",
|
||||||
|
]
|
||||||
|
|
||||||
|
export const retrievePaymentCollectionTransformQueryConfig = {
|
||||||
|
defaults: defaultPaymentCollectionFields,
|
||||||
|
isList: false,
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { createOrderPaymentCollectionWorkflow } from "@medusajs/core-flows"
|
||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import {
|
||||||
|
AuthenticatedMedusaRequest,
|
||||||
|
MedusaResponse,
|
||||||
|
} from "../../../types/routing"
|
||||||
|
import { refetchEntity } from "../../utils/refetch-entity"
|
||||||
|
import { AdminCreatePaymentCollectionType } from "./validators"
|
||||||
|
|
||||||
|
export const POST = async (
|
||||||
|
req: AuthenticatedMedusaRequest<AdminCreatePaymentCollectionType>,
|
||||||
|
res: MedusaResponse<HttpTypes.AdminPaymentCollectionResponse>
|
||||||
|
) => {
|
||||||
|
const { result } = await createOrderPaymentCollectionWorkflow(req.scope).run({
|
||||||
|
input: req.body,
|
||||||
|
})
|
||||||
|
|
||||||
|
const paymentCollection = await refetchEntity(
|
||||||
|
"payment_collection",
|
||||||
|
result[0].id,
|
||||||
|
req.scope,
|
||||||
|
req.remoteQueryConfig.fields
|
||||||
|
)
|
||||||
|
|
||||||
|
res.status(200).json({ payment_collection: paymentCollection })
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
import { createSelectParams } from "../../utils/validators"
|
||||||
|
|
||||||
|
export type AdminGetPaymentCollectionParamsType = z.infer<
|
||||||
|
typeof AdminGetPaymentCollectionParams
|
||||||
|
>
|
||||||
|
export const AdminGetPaymentCollectionParams = createSelectParams()
|
||||||
|
|
||||||
|
export type AdminCreatePaymentCollectionType = z.infer<
|
||||||
|
typeof AdminCreatePaymentCollection
|
||||||
|
>
|
||||||
|
export const AdminCreatePaymentCollection = z
|
||||||
|
.object({
|
||||||
|
order_id: z.string(),
|
||||||
|
amount: z.number().optional(),
|
||||||
|
})
|
||||||
|
.strict()
|
||||||
@@ -16,6 +16,7 @@ import { adminInviteRoutesMiddlewares } from "./admin/invites/middlewares"
|
|||||||
import { adminNotificationRoutesMiddlewares } from "./admin/notifications/middlewares"
|
import { adminNotificationRoutesMiddlewares } from "./admin/notifications/middlewares"
|
||||||
import { adminOrderEditRoutesMiddlewares } from "./admin/order-edits/middlewares"
|
import { adminOrderEditRoutesMiddlewares } from "./admin/order-edits/middlewares"
|
||||||
import { adminOrderRoutesMiddlewares } from "./admin/orders/middlewares"
|
import { adminOrderRoutesMiddlewares } from "./admin/orders/middlewares"
|
||||||
|
import { adminPaymentCollectionsMiddlewares } from "./admin/payment-collections/middlewares"
|
||||||
import { adminPaymentRoutesMiddlewares } from "./admin/payments/middlewares"
|
import { adminPaymentRoutesMiddlewares } from "./admin/payments/middlewares"
|
||||||
import { adminPriceListsRoutesMiddlewares } from "./admin/price-lists/middlewares"
|
import { adminPriceListsRoutesMiddlewares } from "./admin/price-lists/middlewares"
|
||||||
import { adminPricePreferencesRoutesMiddlewares } from "./admin/price-preferences/middlewares"
|
import { adminPricePreferencesRoutesMiddlewares } from "./admin/price-preferences/middlewares"
|
||||||
@@ -114,4 +115,5 @@ export default defineMiddlewares([
|
|||||||
...adminExchangeRoutesMiddlewares,
|
...adminExchangeRoutesMiddlewares,
|
||||||
...adminProductVariantRoutesMiddlewares,
|
...adminProductVariantRoutesMiddlewares,
|
||||||
...adminOrderEditRoutesMiddlewares,
|
...adminOrderEditRoutesMiddlewares,
|
||||||
|
...adminPaymentCollectionsMiddlewares,
|
||||||
])
|
])
|
||||||
|
|||||||
Reference in New Issue
Block a user