feat: Add payment collection creation for cart (#6527)
This commit is contained in:
1 parent
71ed21de4a
commit
883cb0dca7
11 files changed
+367
-4
No files matched your search
@@ -1,9 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
addToCartWorkflow,
|
addToCartWorkflow,
|
||||||
createCartWorkflow,
|
createCartWorkflow,
|
||||||
|
createPaymentCollectionForCartWorkflow,
|
||||||
deleteLineItemsStepId,
|
deleteLineItemsStepId,
|
||||||
deleteLineItemsWorkflow,
|
deleteLineItemsWorkflow,
|
||||||
findOrCreateCustomerStepId,
|
findOrCreateCustomerStepId,
|
||||||
|
linkCartAndPaymentCollectionsStepId,
|
||||||
updateLineItemInCartWorkflow,
|
updateLineItemInCartWorkflow,
|
||||||
updateLineItemsStepId,
|
updateLineItemsStepId,
|
||||||
} from "@medusajs/core-flows"
|
} from "@medusajs/core-flows"
|
||||||
@@ -11,6 +13,7 @@ import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
|||||||
import {
|
import {
|
||||||
ICartModuleService,
|
ICartModuleService,
|
||||||
ICustomerModuleService,
|
ICustomerModuleService,
|
||||||
|
IPaymentModuleService,
|
||||||
IPricingModuleService,
|
IPricingModuleService,
|
||||||
IProductModuleService,
|
IProductModuleService,
|
||||||
IRegionModuleService,
|
IRegionModuleService,
|
||||||
@@ -37,7 +40,8 @@ describe("Carts workflows", () => {
|
|||||||
let customerModule: ICustomerModuleService
|
let customerModule: ICustomerModuleService
|
||||||
let productModule: IProductModuleService
|
let productModule: IProductModuleService
|
||||||
let pricingModule: IPricingModuleService
|
let pricingModule: IPricingModuleService
|
||||||
let remoteLink
|
let paymentModule: IPaymentModuleService
|
||||||
|
let remoteLink, remoteQuery
|
||||||
|
|
||||||
let defaultRegion
|
let defaultRegion
|
||||||
|
|
||||||
@@ -52,7 +56,9 @@ describe("Carts workflows", () => {
|
|||||||
customerModule = appContainer.resolve(ModuleRegistrationName.CUSTOMER)
|
customerModule = appContainer.resolve(ModuleRegistrationName.CUSTOMER)
|
||||||
productModule = appContainer.resolve(ModuleRegistrationName.PRODUCT)
|
productModule = appContainer.resolve(ModuleRegistrationName.PRODUCT)
|
||||||
pricingModule = appContainer.resolve(ModuleRegistrationName.PRICING)
|
pricingModule = appContainer.resolve(ModuleRegistrationName.PRICING)
|
||||||
|
paymentModule = appContainer.resolve(ModuleRegistrationName.PAYMENT)
|
||||||
remoteLink = appContainer.resolve("remoteLink")
|
remoteLink = appContainer.resolve("remoteLink")
|
||||||
|
remoteQuery = appContainer.resolve("remoteQuery")
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
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 {
|
interface StepInput {
|
||||||
id: string
|
id: string
|
||||||
config: FindConfig<CartDTO>
|
config?: FindConfig<CartDTO>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const retrieveCartStepId = "retrieve-cart"
|
export const retrieveCartStepId = "retrieve-cart"
|
||||||
|
|||||||
+38
@@ -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 "./add-to-cart"
|
||||||
export * from "./create-carts"
|
export * from "./create-carts"
|
||||||
|
export * from "./create-payment-collection-for-cart"
|
||||||
export * from "./update-cart"
|
export * from "./update-cart"
|
||||||
export * from "./update-cart-promotions"
|
export * from "./update-cart-promotions"
|
||||||
export * from "./update-line-item-in-cart"
|
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.name",
|
||||||
"region.currency_code",
|
"region.currency_code",
|
||||||
"sales_channel_id",
|
"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 = [
|
export const defaultStoreCartRelations = [
|
||||||
|
|||||||
@@ -23,10 +23,18 @@ export const joinerConfig: ModuleJoinerConfig = {
|
|||||||
serviceName: Modules.PAYMENT,
|
serviceName: Modules.PAYMENT,
|
||||||
primaryKeys: ["id"],
|
primaryKeys: ["id"],
|
||||||
linkableKeys: LinkableKeys,
|
linkableKeys: LinkableKeys,
|
||||||
alias: {
|
alias: [
|
||||||
|
{
|
||||||
name: ["payment", "payments"],
|
name: ["payment", "payments"],
|
||||||
args: {
|
args: {
|
||||||
entity: Payment.name,
|
entity: Payment.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: ["payment_collection", "payment_collections"],
|
||||||
|
args: {
|
||||||
|
entity: PaymentCollection.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
@@ -83,3 +83,11 @@ export interface UpdateCartWorkflowInputDTO {
|
|||||||
currency_code?: string
|
currency_code?: string
|
||||||
metadata?: Record<string, unknown> | null
|
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