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:
Riqwan Thamir
2024-08-16 10:16:40 +02:00
committed by GitHub
parent bb72fda9ae
commit adcd25650c
9 changed files with 264 additions and 5 deletions

View File

@@ -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", () => {

View File

@@ -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)
}
)

View File

@@ -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"

View File

@@ -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[]
}>
}>

View File

@@ -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
),
],
},
]

View File

@@ -0,0 +1,12 @@
export const defaultPaymentCollectionFields = [
"id",
"currency_code",
"amount",
"status",
"*payment_sessions",
]
export const retrievePaymentCollectionTransformQueryConfig = {
defaults: defaultPaymentCollectionFields,
isList: false,
}

View 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 })
}

View File

@@ -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()

View File

@@ -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,
])