feat: Add payment collection creation for cart (#6527)
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import {
|
||||
addToCartWorkflow,
|
||||
createCartWorkflow,
|
||||
createPaymentCollectionForCartWorkflow,
|
||||
deleteLineItemsStepId,
|
||||
deleteLineItemsWorkflow,
|
||||
findOrCreateCustomerStepId,
|
||||
linkCartAndPaymentCollectionsStepId,
|
||||
updateLineItemInCartWorkflow,
|
||||
updateLineItemsStepId,
|
||||
} from "@medusajs/core-flows"
|
||||
@@ -11,6 +13,7 @@ import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
ICartModuleService,
|
||||
ICustomerModuleService,
|
||||
IPaymentModuleService,
|
||||
IPricingModuleService,
|
||||
IProductModuleService,
|
||||
IRegionModuleService,
|
||||
@@ -37,7 +40,8 @@ describe("Carts workflows", () => {
|
||||
let customerModule: ICustomerModuleService
|
||||
let productModule: IProductModuleService
|
||||
let pricingModule: IPricingModuleService
|
||||
let remoteLink
|
||||
let paymentModule: IPaymentModuleService
|
||||
let remoteLink, remoteQuery
|
||||
|
||||
let defaultRegion
|
||||
|
||||
@@ -52,7 +56,9 @@ describe("Carts workflows", () => {
|
||||
customerModule = appContainer.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
productModule = appContainer.resolve(ModuleRegistrationName.PRODUCT)
|
||||
pricingModule = appContainer.resolve(ModuleRegistrationName.PRICING)
|
||||
paymentModule = appContainer.resolve(ModuleRegistrationName.PAYMENT)
|
||||
remoteLink = appContainer.resolve("remoteLink")
|
||||
remoteQuery = appContainer.resolve("remoteQuery")
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -668,4 +674,142 @@ describe("Carts workflows", () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("createPaymentCollectionForCart", () => {
|
||||
it("should create a payment collection and link it to cart", async () => {
|
||||
const region = await regionModuleService.create({
|
||||
name: "US",
|
||||
currency_code: "usd",
|
||||
})
|
||||
|
||||
const cart = await cartModuleService.create({
|
||||
currency_code: "usd",
|
||||
region_id: region.id,
|
||||
items: [
|
||||
{
|
||||
quantity: 1,
|
||||
unit_price: 5000,
|
||||
title: "Test item",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
await createPaymentCollectionForCartWorkflow(appContainer).run({
|
||||
input: {
|
||||
cart_id: cart.id,
|
||||
region_id: region.id,
|
||||
currency_code: "usd",
|
||||
amount: 5000,
|
||||
},
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
const result = await remoteQuery(
|
||||
{
|
||||
cart: {
|
||||
fields: ["id"],
|
||||
payment_collection: {
|
||||
fields: ["id", "amount", "currency_code"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cart: {
|
||||
id: cart.id,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(result).toEqual([
|
||||
expect.objectContaining({
|
||||
id: cart.id,
|
||||
payment_collection: expect.objectContaining({
|
||||
amount: 5000,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
describe("compensation", () => {
|
||||
it("should dismiss cart <> payment collection link and delete created payment collection", async () => {
|
||||
const workflow = createPaymentCollectionForCartWorkflow(appContainer)
|
||||
|
||||
workflow.appendAction("throw", linkCartAndPaymentCollectionsStepId, {
|
||||
invoke: async function failStep() {
|
||||
throw new Error(
|
||||
`Failed to do something after linking cart and payment collection`
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const region = await regionModuleService.create({
|
||||
name: "US",
|
||||
currency_code: "usd",
|
||||
})
|
||||
|
||||
const cart = await cartModuleService.create({
|
||||
currency_code: "usd",
|
||||
region_id: region.id,
|
||||
items: [
|
||||
{
|
||||
quantity: 1,
|
||||
unit_price: 5000,
|
||||
title: "Test item",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const { errors } = await workflow.run({
|
||||
input: {
|
||||
cart_id: cart.id,
|
||||
region_id: region.id,
|
||||
currency_code: "usd",
|
||||
amount: 5000,
|
||||
},
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
action: "throw",
|
||||
handlerType: "invoke",
|
||||
error: new Error(
|
||||
`Failed to do something after linking cart and payment collection`
|
||||
),
|
||||
},
|
||||
])
|
||||
|
||||
const carts = await remoteQuery(
|
||||
{
|
||||
cart: {
|
||||
fields: ["id"],
|
||||
payment_collection: {
|
||||
fields: ["id", "amount", "currency_code"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cart: {
|
||||
id: cart.id,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const payCols = await remoteQuery({
|
||||
payment_collection: {
|
||||
fields: ["id"],
|
||||
},
|
||||
})
|
||||
|
||||
expect(carts).toEqual([
|
||||
expect.objectContaining({
|
||||
id: cart.id,
|
||||
payment_collection: undefined,
|
||||
}),
|
||||
])
|
||||
expect(payCols.length).toEqual(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -669,4 +669,35 @@ describe("Store Carts API", () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /store/carts/:id/payment-collections", () => {
|
||||
it("should create a payment collection for the cart", async () => {
|
||||
const region = await regionModuleService.create({
|
||||
name: "US",
|
||||
currency_code: "usd",
|
||||
})
|
||||
|
||||
const cart = await cartModuleService.create({
|
||||
currency_code: "usd",
|
||||
region_id: region.id,
|
||||
})
|
||||
|
||||
const api = useApi() as any
|
||||
const response = await api.post(
|
||||
`/store/carts/${cart.id}/payment-collections`
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.cart).toEqual(
|
||||
expect.objectContaining({
|
||||
id: cart.id,
|
||||
currency_code: "usd",
|
||||
payment_collection: expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
amount: 0,
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IPaymentModuleService } from "@medusajs/types"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
type StepInput = {
|
||||
region_id: string
|
||||
currency_code: string
|
||||
amount: number
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const createPaymentCollectionsStepId = "create-payment-collections"
|
||||
export const createPaymentCollectionsStep = createStep(
|
||||
createPaymentCollectionsStepId,
|
||||
async (data: StepInput[], { container }) => {
|
||||
const service = container.resolve<IPaymentModuleService>(
|
||||
ModuleRegistrationName.PAYMENT
|
||||
)
|
||||
|
||||
const created = await service.createPaymentCollections(data)
|
||||
|
||||
return new StepResponse(
|
||||
created,
|
||||
created.map((collection) => collection.id)
|
||||
)
|
||||
},
|
||||
async (createdIds, { container }) => {
|
||||
if (!createdIds?.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const service = container.resolve<IPaymentModuleService>(
|
||||
ModuleRegistrationName.PAYMENT
|
||||
)
|
||||
|
||||
await service.deletePaymentCollections(createdIds)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
type StepInput = {
|
||||
links: {
|
||||
cart_id: string
|
||||
payment_collection_id: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export const linkCartAndPaymentCollectionsStepId =
|
||||
"link-cart-payment-collection"
|
||||
export const linkCartAndPaymentCollectionsStep = createStep(
|
||||
linkCartAndPaymentCollectionsStepId,
|
||||
async (data: StepInput, { container }) => {
|
||||
const remoteLink = container.resolve("remoteLink")
|
||||
|
||||
const links = data.links.map((d) => ({
|
||||
[Modules.CART]: { cart_id: d.cart_id },
|
||||
[Modules.PAYMENT]: { payment_collection_id: d.payment_collection_id },
|
||||
}))
|
||||
|
||||
await remoteLink.create(links)
|
||||
|
||||
return new StepResponse(void 0, data)
|
||||
},
|
||||
async (data, { container }) => {
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
|
||||
const remoteLink = container.resolve("remoteLink")
|
||||
|
||||
const links = data.links.map((d) => ({
|
||||
[Modules.CART]: { cart_id: d.cart_id },
|
||||
[Modules.PAYMENT]: { payment_collection_id: d.payment_collection_id },
|
||||
}))
|
||||
|
||||
await remoteLink.dismiss(links)
|
||||
}
|
||||
)
|
||||
@@ -4,7 +4,7 @@ import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||
|
||||
interface StepInput {
|
||||
id: string
|
||||
config: FindConfig<CartDTO>
|
||||
config?: FindConfig<CartDTO>
|
||||
}
|
||||
|
||||
export const retrieveCartStepId = "retrieve-cart"
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
CartDTO,
|
||||
CreatePaymentCollectionForCartWorkflowInputDTO,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
WorkflowData,
|
||||
createWorkflow,
|
||||
transform,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { retrieveCartStep } from "../steps"
|
||||
import { createPaymentCollectionsStep } from "../steps/create-payment-collection"
|
||||
import { linkCartAndPaymentCollectionsStep } from "../steps/link-cart-payment-collection"
|
||||
|
||||
export const createPaymentCollectionForCartWorkflowId =
|
||||
"create-payment-collection-for-cart"
|
||||
export const createPaymentCollectionForCartWorkflow = createWorkflow(
|
||||
createPaymentCollectionForCartWorkflowId,
|
||||
(
|
||||
input: WorkflowData<CreatePaymentCollectionForCartWorkflowInputDTO>
|
||||
): WorkflowData<CartDTO> => {
|
||||
const created = createPaymentCollectionsStep([input])
|
||||
|
||||
const link = transform({ cartId: input.cart_id, created }, (data) => ({
|
||||
links: [
|
||||
{
|
||||
cart_id: data.cartId,
|
||||
payment_collection_id: data.created[0].id,
|
||||
},
|
||||
],
|
||||
}))
|
||||
|
||||
linkCartAndPaymentCollectionsStep(link)
|
||||
|
||||
const cart = retrieveCartStep({ id: input.cart_id })
|
||||
|
||||
return cart
|
||||
}
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
export * from "./add-to-cart"
|
||||
export * from "./create-carts"
|
||||
export * from "./create-payment-collection-for-cart"
|
||||
export * from "./update-cart"
|
||||
export * from "./update-cart-promotions"
|
||||
export * from "./update-line-item-in-cart"
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { createPaymentCollectionForCartWorkflow } from "@medusajs/core-flows"
|
||||
import { UpdateCartDataDTO } from "@medusajs/types"
|
||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||
|
||||
import { MedusaRequest, MedusaResponse } from "../../../../../types/routing"
|
||||
import { defaultStoreCartFields } from "../../query-config"
|
||||
|
||||
export const POST = async (
|
||||
req: MedusaRequest<UpdateCartDataDTO>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const workflow = createPaymentCollectionForCartWorkflow(req.scope)
|
||||
|
||||
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||
|
||||
let [cart] = await remoteQuery(
|
||||
{
|
||||
cart: {
|
||||
fields: ["id", "currency_code", "region_id", "total"],
|
||||
},
|
||||
},
|
||||
{
|
||||
cart: { id: req.params.id },
|
||||
}
|
||||
)
|
||||
|
||||
const { errors } = await workflow.run({
|
||||
input: {
|
||||
cart_id: req.params.id,
|
||||
region_id: cart.region_id,
|
||||
currency_code: cart.currency_code,
|
||||
amount: cart.total ?? 0, // TODO: This should be calculated from the cart when totals decoration is introduced
|
||||
},
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
const query = remoteQueryObjectFromString({
|
||||
entryPoint: "cart",
|
||||
variables: { id: req.params.id },
|
||||
fields: defaultStoreCartFields,
|
||||
})
|
||||
|
||||
;[cart] = await remoteQuery(query)
|
||||
|
||||
res.status(200).json({ cart })
|
||||
}
|
||||
@@ -42,6 +42,11 @@ export const defaultStoreCartFields = [
|
||||
"region.name",
|
||||
"region.currency_code",
|
||||
"sales_channel_id",
|
||||
|
||||
// TODO: To be updated when payment sessions are introduces in the Rest API
|
||||
"payment_collection.id",
|
||||
"payment_collection.amount",
|
||||
"payment_collection.payment_sessions",
|
||||
]
|
||||
|
||||
export const defaultStoreCartRelations = [
|
||||
|
||||
@@ -23,10 +23,18 @@ export const joinerConfig: ModuleJoinerConfig = {
|
||||
serviceName: Modules.PAYMENT,
|
||||
primaryKeys: ["id"],
|
||||
linkableKeys: LinkableKeys,
|
||||
alias: {
|
||||
name: ["payment", "payments"],
|
||||
args: {
|
||||
entity: Payment.name,
|
||||
alias: [
|
||||
{
|
||||
name: ["payment", "payments"],
|
||||
args: {
|
||||
entity: Payment.name,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["payment_collection", "payment_collections"],
|
||||
args: {
|
||||
entity: PaymentCollection.name,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -83,3 +83,11 @@ export interface UpdateCartWorkflowInputDTO {
|
||||
currency_code?: string
|
||||
metadata?: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
export interface CreatePaymentCollectionForCartWorkflowInputDTO {
|
||||
cart_id: string
|
||||
region_id: string
|
||||
currency_code: string
|
||||
amount: number
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user