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: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 123456.1234657890123456789,
|
||||
amount: 50.25,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -333,7 +333,7 @@ medusaIntegrationTestRunner({
|
||||
prices: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 1000,
|
||||
amount: 15,
|
||||
},
|
||||
],
|
||||
rules: [
|
||||
@@ -359,7 +359,7 @@ medusaIntegrationTestRunner({
|
||||
prices: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 1000,
|
||||
amount: 20,
|
||||
},
|
||||
],
|
||||
rules: [
|
||||
@@ -672,6 +672,40 @@ medusaIntegrationTestRunner({
|
||||
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", () => {
|
||||
|
||||
@@ -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-order-change"
|
||||
export * from "./create-order-change-actions"
|
||||
export * from "./create-order-payment-collection"
|
||||
export * from "./create-orders"
|
||||
export * from "./create-shipment"
|
||||
export * from "./decline-order-change"
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
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 {
|
||||
payment: AdminPayment
|
||||
@@ -27,4 +37,4 @@ export type RefundReasonsResponse = PaginatedResponse<{
|
||||
|
||||
export type AdminPaymentProviderListResponse = PaginatedResponse<{
|
||||
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,
|
||||
}
|
||||
26
packages/medusa/src/api/admin/payment-collections/route.ts
Normal file
26
packages/medusa/src/api/admin/payment-collections/route.ts
Normal file
@@ -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 { adminOrderEditRoutesMiddlewares } from "./admin/order-edits/middlewares"
|
||||
import { adminOrderRoutesMiddlewares } from "./admin/orders/middlewares"
|
||||
import { adminPaymentCollectionsMiddlewares } from "./admin/payment-collections/middlewares"
|
||||
import { adminPaymentRoutesMiddlewares } from "./admin/payments/middlewares"
|
||||
import { adminPriceListsRoutesMiddlewares } from "./admin/price-lists/middlewares"
|
||||
import { adminPricePreferencesRoutesMiddlewares } from "./admin/price-preferences/middlewares"
|
||||
@@ -114,4 +115,5 @@ export default defineMiddlewares([
|
||||
...adminExchangeRoutesMiddlewares,
|
||||
...adminProductVariantRoutesMiddlewares,
|
||||
...adminOrderEditRoutesMiddlewares,
|
||||
...adminPaymentCollectionsMiddlewares,
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user