chore(medusa,core-flows): cart payment collection link (#8457)

This commit is contained in:
Carlos R. L. Rodrigues
2024-08-06 06:12:21 -03:00
committed by GitHub
parent decd830474
commit 5125d1328d
16 changed files with 249 additions and 211 deletions
@@ -165,9 +165,6 @@ export async function createOrderSeeder({ api }) {
const paymentCollection = (
await api.post(`/store/payment-collections`, {
cart_id: cart.id,
region_id: region.id,
currency_code: region.currency_code,
amount: cart.total,
})
).data.payment_collection
@@ -3,6 +3,7 @@ import {
adminHeaders,
createAdminUser,
} from "../../../../helpers/create-admin-user"
import { getProductFixture } from "../../../../helpers/fixtures"
jest.setTimeout(30000)
@@ -17,6 +18,8 @@ medusaIntegrationTestRunner({
describe("POST /admin/payment-collections/:id/payment-sessions", () => {
let region
let product
let cart
beforeEach(async () => {
region = (
@@ -26,15 +29,43 @@ medusaIntegrationTestRunner({
adminHeaders
)
).data.region
product = (
await api.post(
"/admin/products",
getProductFixture({
title: "test",
status: "published",
variants: [
{
title: "Test variant",
manage_inventory: false,
prices: [
{
amount: 150,
currency_code: "usd",
rules: { region_id: region.id },
},
],
},
],
}),
adminHeaders
)
).data.product
cart = (
await api.post("/store/carts", {
region_id: region.id,
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
})
).data.cart
})
it("should create a payment session", async () => {
const paymentCollection = (
await api.post(`/store/payment-collections`, {
region_id: region.id,
cart_id: "cart.id",
amount: 150,
currency_code: "usd",
cart_id: cart.id,
})
).data.payment_collection
@@ -6,6 +6,7 @@ import { setupTaxStructure } from "../../../../modules/__tests__/fixtures"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import { createAdminUser } from "../../../../helpers/create-admin-user"
import { getProductFixture } from "../../../../helpers/fixtures"
import { createOrderSeeder } from "../../fixtures/order"
jest.setTimeout(30000)
@@ -16,20 +17,59 @@ medusaIntegrationTestRunner({
let paymentCollection
let payment
let container
let region
let product
let cart
beforeEach(async () => {
container = getContainer()
paymentModule = container.resolve(ModuleRegistrationName.PAYMENT)
await createAdminUser(dbConnection, adminHeaders, container)
region = (
await api.post(
"/admin/regions",
{ name: "United States", currency_code: "usd", countries: ["us"] },
adminHeaders
)
).data.region
product = (
await api.post(
"/admin/products",
getProductFixture({
title: "test",
status: "published",
variants: [
{
title: "Test variant",
manage_inventory: false,
prices: [
{
amount: 1000,
currency_code: "usd",
rules: { region_id: region.id },
},
],
},
],
}),
adminHeaders
)
).data.product
cart = (
await api.post("/store/carts", {
region_id: region.id,
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
})
).data.cart
const collection = (
await api.post(
"/store/payment-collections",
{
cart_id: "test-cart",
region_id: "test-region",
amount: 1000,
currency_code: "usd",
cart_id: cart.id,
},
adminHeaders
)
@@ -6,7 +6,6 @@ import {
deleteLineItemsStepId,
deleteLineItemsWorkflow,
findOrCreateCustomerStepId,
linkCartAndPaymentCollectionsStepId,
listShippingOptionsForCartWorkflow,
refreshPaymentCollectionForCartWorkflow,
updateLineItemInCartWorkflow,
@@ -1143,9 +1142,6 @@ medusaIntegrationTestRunner({
await createPaymentCollectionForCartWorkflow(appContainer).run({
input: {
cart_id: cart.id,
region_id: defaultRegion.id,
currency_code: "dkk",
amount: 5000,
},
throwOnError: false,
})
@@ -1182,17 +1178,13 @@ medusaIntegrationTestRunner({
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`
)
},
}
)
workflow.addAction("throw", {
invoke: async function failStep() {
throw new Error(
`Failed to do something after linking cart and payment collection`
)
},
})
const region = await regionModuleService.createRegions({
name: "US",
@@ -1214,9 +1206,6 @@ medusaIntegrationTestRunner({
const { errors } = await workflow.run({
input: {
cart_id: cart.id,
region_id: region.id,
currency_code: "usd",
amount: 5000,
},
throwOnError: false,
})
@@ -1280,7 +1269,7 @@ medusaIntegrationTestRunner({
const paymentCollection =
await paymentModule.createPaymentCollections({
amount: 5000,
amount: 5001,
currency_code: "dkk",
region_id: defaultRegion.id,
})
@@ -1288,7 +1277,7 @@ medusaIntegrationTestRunner({
const paymentSession = await paymentModule.createPaymentSession(
paymentCollection.id,
{
amount: 5000,
amount: 5001,
currency_code: "dkk",
data: {},
provider_id: "pp_system_default",
@@ -1400,7 +1400,7 @@ medusaIntegrationTestRunner({
adjustments: [
expect.objectContaining({
code: "PROMOTION_APPLIED",
amount: 180,
amount: 177.86561264822134,
}),
],
}),
@@ -1414,7 +1414,7 @@ medusaIntegrationTestRunner({
expect.objectContaining({
id: expect.not.stringContaining(lineItemAdjustment.id),
code: "PROMOTION_APPLIED",
amount: 120,
amount: 122.13438735177866,
}),
],
}),
@@ -1542,10 +1542,7 @@ medusaIntegrationTestRunner({
})
const response = await api.post(`/store/payment-collections`, {
region_id: region.id,
cart_id: cart.id,
amount: 0,
currency_code: cart.currency_code,
})
expect(response.status).toEqual(200)
@@ -1570,18 +1567,12 @@ medusaIntegrationTestRunner({
const firstCollection = (
await api.post(`/store/payment-collections`, {
region_id: region.id,
cart_id: cart.id,
amount: 0,
currency_code: cart.currency_code,
})
).data.payment_collection
const response = await api.post(`/store/payment-collections`, {
region_id: region.id,
cart_id: cart.id,
amount: 0,
currency_code: cart.currency_code,
})
expect(response.status).toEqual(200)
@@ -1608,19 +1599,13 @@ medusaIntegrationTestRunner({
const firstCollection = (
await api.post(`/store/payment-collections`, {
region_id: region.id,
cart_id: firstCart.id,
amount: 0,
currency_code: firstCart.currency_code,
})
).data.payment_collection
const secondCollection = (
await api.post(`/store/payment-collections`, {
region_id: region.id,
cart_id: secondCart.id,
amount: 0,
currency_code: secondCart.currency_code,
})
).data.payment_collection
@@ -2008,9 +1993,6 @@ medusaIntegrationTestRunner({
const paymentCollection = (
await api.post(`/store/payment-collections`, {
cart_id: cart.id,
region_id: region.id,
currency_code: region.currency_code,
amount: cart.total,
})
).data.payment_collection
@@ -2140,9 +2122,6 @@ medusaIntegrationTestRunner({
await api.post(`/store/payment-collections`, {
cart_id: cart.id,
region_id: region.id,
currency_code: region.currency_code,
amount: cart.total,
})
const error = await api
@@ -2192,9 +2171,6 @@ medusaIntegrationTestRunner({
const paymentCollection = (
await api.post(`/store/payment-collections`, {
cart_id: cart.id,
region_id: region.id,
currency_code: region.currency_code,
amount: cart.total,
})
).data.payment_collection
@@ -1,41 +0,0 @@
import { ContainerRegistrationKeys, Modules } from "@medusajs/utils"
import { createStep, StepResponse } 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(ContainerRegistrationKeys.REMOTE_LINK)
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(ContainerRegistrationKeys.REMOTE_LINK)
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)
}
)
@@ -1,7 +1,6 @@
import {
WorkflowData,
createWorkflow,
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
@@ -97,12 +96,11 @@ export const addShippingMethodToWorkflow = createWorkflow(
shipping_methods: shippingMethodInput,
})
parallelize(
refreshCartPromotionsStep({ id: input.cart_id }),
updateTaxLinesStep({
cart_or_cart_id: input.cart_id,
shipping_methods: shippingMethodsToAdd,
})
)
updateTaxLinesStep({
cart_or_cart_id: input.cart_id,
shipping_methods: shippingMethodsToAdd,
})
refreshCartPromotionsStep({ id: input.cart_id })
}
)
@@ -25,7 +25,7 @@ import {
} from "../utils/fields"
import { prepareLineItemData } from "../utils/prepare-line-item-data"
import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory"
import { refreshPaymentCollectionForCartStep } from "./refresh-payment-collection"
import { refreshPaymentCollectionForCartWorkflow } from "./refresh-payment-collection"
export const addToCartWorkflowId = "add-to-cart"
export const addToCartWorkflow = createWorkflow(
@@ -114,11 +114,17 @@ export const addToCartWorkflow = createWorkflow(
parallelize(
refreshCartShippingMethodsStep({ cart }),
updateTaxLinesStep({ cart_or_cart_id: input.cart.id, items }),
refreshCartPromotionsStep({ id: input.cart.id }),
refreshPaymentCollectionForCartStep({ cart_id: input.cart.id })
updateTaxLinesStep({ cart_or_cart_id: input.cart.id, items })
)
refreshCartPromotionsStep({ id: input.cart.id })
refreshPaymentCollectionForCartWorkflow.runAsStep({
input: {
cart_id: input.cart.id,
},
})
return new WorkflowResponse(items)
}
)
@@ -21,7 +21,7 @@ import { validateVariantPricesStep } from "../steps/validate-variant-prices"
import { productVariantsFields } from "../utils/fields"
import { prepareLineItemData } from "../utils/prepare-line-item-data"
import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory"
import { refreshPaymentCollectionForCartStep } from "./refresh-payment-collection"
import { refreshPaymentCollectionForCartWorkflow } from "./refresh-payment-collection"
// TODO: The createCartWorkflow are missing the following steps:
// - Refresh/delete shipping methods (fulfillment module)
@@ -145,16 +145,18 @@ export const createCartWorkflow = createWorkflow(
const carts = createCartsStep([cartToCreate])
const cart = transform({ carts }, (data) => data.carts?.[0])
parallelize(
refreshCartPromotionsStep({
id: cart.id,
promo_codes: input.promo_codes,
}),
updateTaxLinesStep({ cart_or_cart_id: cart.id }),
refreshPaymentCollectionForCartStep({
updateTaxLinesStep({ cart_or_cart_id: cart.id })
refreshCartPromotionsStep({
id: cart.id,
promo_codes: input.promo_codes,
})
refreshPaymentCollectionForCartWorkflow.runAsStep({
input: {
cart_id: cart.id,
})
)
},
})
return new WorkflowResponse(cart)
}
@@ -1,11 +1,26 @@
import { CreatePaymentCollectionForCartWorkflowInputDTO } from "@medusajs/types"
import {
CartDTO,
CreatePaymentCollectionForCartWorkflowInputDTO,
} from "@medusajs/types"
import { Modules } from "@medusajs/utils"
import {
WorkflowData,
createStep,
createWorkflow,
transform,
} from "@medusajs/workflows-sdk"
import { createRemoteLinkStep } from "../../../common/steps/create-remote-links"
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
import { createPaymentCollectionsStep } from "../steps/create-payment-collection"
import { linkCartAndPaymentCollectionsStep } from "../steps/link-cart-payment-collection"
const validateExistingPaymentCollection = createStep(
"validate-existing-payment-collection",
({ cart }: { cart: CartDTO & { payment_collection?: any } }) => {
if (cart.payment_collection) {
throw new Error(`Cart ${cart.id} already has a payment collection`)
}
}
)
export const createPaymentCollectionForCartWorkflowId =
"create-payment-collection-for-cart"
@@ -14,17 +29,48 @@ export const createPaymentCollectionForCartWorkflow = createWorkflow(
(
input: WorkflowData<CreatePaymentCollectionForCartWorkflowInputDTO>
): WorkflowData<void> => {
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,
},
const cart = useRemoteQueryStep({
entry_point: "cart",
fields: [
"id",
"region_id",
"currency_code",
"total",
"raw_total",
"payment_collection.id",
],
}))
variables: { id: input.cart_id },
throw_if_key_not_found: true,
list: false,
})
linkCartAndPaymentCollectionsStep(link)
validateExistingPaymentCollection({ cart })
const paymentData = transform({ cart }, ({ cart }) => {
return {
cart_id: cart.id,
currency_code: cart.currency_code,
amount: cart.raw_total,
region_id: cart.region_id,
}
})
const created = createPaymentCollectionsStep([paymentData])
const cartPaymentLink = transform(
{ cartId: input.cart_id, created },
(data) => {
return [
{
[Modules.CART]: { cart_id: data.cartId },
[Modules.PAYMENT]: { payment_collection_id: data.created[0].id },
},
]
}
)
createRemoteLinkStep(cartPaymentLink).config({
name: "cart-payment-collection-link",
})
}
)
@@ -1,11 +1,10 @@
import { isPresent } from "@medusajs/utils"
import { MathBN, isPresent } from "@medusajs/utils"
import {
StepResponse,
WorkflowData,
createStep,
createWorkflow,
parallelize,
transform,
when,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
import { updatePaymentCollectionStep } from "../../../payment-collection"
@@ -15,26 +14,6 @@ type WorklowInput = {
cart_id: string
}
interface StepInput {
cart_id: string
}
// We export a step running the workflow too, so that we can use it as a subworkflow e.g. in the update cart workflows
export const refreshPaymentCollectionForCartStepId =
"refresh-payment-collection-for-cart"
export const refreshPaymentCollectionForCartStep = createStep(
refreshPaymentCollectionForCartStepId,
async (data: StepInput, { container }) => {
await refreshPaymentCollectionForCartWorkflow(container).run({
input: {
cart_id: data.cart_id,
},
})
return new StepResponse(null)
}
)
export const refreshPaymentCollectionForCartWorkflowId =
"refresh-payment-collection-for-cart"
export const refreshPaymentCollectionForCartWorkflow = createWorkflow(
@@ -44,9 +23,14 @@ export const refreshPaymentCollectionForCartWorkflow = createWorkflow(
entry_point: "cart",
fields: [
"id",
"total",
"region_id",
"currency_code",
"total",
"raw_total",
"payment_collection.id",
"payment_collection.raw_amount",
"payment_collection.amount",
"payment_collection.currency_code",
"payment_collection.payment_sessions.id",
],
variables: { id: input.cart_id },
@@ -54,37 +38,51 @@ export const refreshPaymentCollectionForCartWorkflow = createWorkflow(
list: false,
})
const deletePaymentSessionInput = transform(
{ paymentCollection: cart.payment_collection },
(data) => {
return {
ids:
data.paymentCollection?.payment_sessions
?.map((ps) => ps.id)
?.flat(1) || [],
when({ cart }, ({ cart }) => {
const valueIsEqual = MathBN.eq(
cart.payment_collection?.raw_amount ?? -1,
cart.raw_total
)
if (valueIsEqual) {
return cart.payment_collection.currency_code !== cart.currency_code
}
return true
}).then(() => {
const deletePaymentSessionInput = transform(
{ paymentCollection: cart.payment_collection },
(data) => {
return {
ids:
data.paymentCollection?.payment_sessions
?.map((ps) => ps.id)
?.flat(1) || [],
}
}
}
)
)
const updatePaymentCollectionInput = transform({ cart }, (data) => {
if (!isPresent(data.cart?.payment_collection?.id)) {
return
}
const updatePaymentCollectionInput = transform({ cart }, ({ cart }) => {
if (!isPresent(cart.payment_collection?.id)) {
return
}
return {
selector: { id: data.cart.payment_collection.id },
update: {
amount: data.cart.total,
currency_code: data.cart.currency_code,
},
}
return {
selector: { id: cart.payment_collection.id },
update: {
amount: cart.total,
currency_code: cart.currency_code,
region_id: cart.region_id,
},
}
})
parallelize(
deletePaymentSessionsWorkflow.runAsStep({
input: deletePaymentSessionInput,
}),
updatePaymentCollectionStep(updatePaymentCollectionInput)
)
})
parallelize(
deletePaymentSessionsWorkflow.runAsStep({
input: deletePaymentSessionInput,
}),
updatePaymentCollectionStep(updatePaymentCollectionInput)
)
}
)
@@ -17,7 +17,7 @@ import {
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
import { updateTaxLinesStep } from "../steps/update-tax-lines"
import { cartFieldsForRefreshSteps } from "../utils/fields"
import { refreshPaymentCollectionForCartStep } from "./refresh-payment-collection"
import { refreshPaymentCollectionForCartWorkflow } from "./refresh-payment-collection"
export const updateCartWorkflowId = "update-cart"
export const updateCartWorkflow = createWorkflow(
@@ -82,15 +82,19 @@ export const updateCartWorkflow = createWorkflow(
parallelize(
refreshCartShippingMethodsStep({ cart }),
updateTaxLinesStep({ cart_or_cart_id: carts[0].id }),
refreshCartPromotionsStep({
id: input.id,
promo_codes: input.promo_codes,
action: PromotionActions.REPLACE,
}),
refreshPaymentCollectionForCartStep({
cart_id: input.id,
})
updateTaxLinesStep({ cart_or_cart_id: carts[0].id })
)
refreshCartPromotionsStep({
id: input.id,
promo_codes: input.promo_codes,
action: PromotionActions.REPLACE,
})
refreshPaymentCollectionForCartWorkflow.runAsStep({
input: {
cart_id: input.id,
},
})
}
)
@@ -3,7 +3,6 @@ import {
WorkflowData,
WorkflowResponse,
createWorkflow,
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
@@ -16,7 +15,7 @@ import {
productVariantsFields,
} from "../utils/fields"
import { confirmVariantInventoryWorkflow } from "./confirm-variant-inventory"
import { refreshPaymentCollectionForCartStep } from "./refresh-payment-collection"
import { refreshPaymentCollectionForCartWorkflow } from "./refresh-payment-collection"
// TODO: The UpdateLineItemsWorkflow are missing the following steps:
// - Validate shipping methods for new items (fulfillment module)
@@ -90,11 +89,13 @@ export const updateLineItemInCartWorkflow = createWorkflow(
list: false,
}).config({ name: "refetchcart" })
parallelize(
refreshCartShippingMethodsStep({ cart }),
refreshCartPromotionsStep({ id: input.cart.id }),
refreshPaymentCollectionForCartStep({ cart_id: input.cart.id })
)
refreshCartShippingMethodsStep({ cart })
refreshCartPromotionsStep({ id: input.cart.id })
refreshPaymentCollectionForCartWorkflow.runAsStep({
input: { cart_id: input.cart.id },
})
const updatedItem = transform({ result }, (data) => data.result?.[0])
+1 -4
View File
@@ -1,9 +1,9 @@
import {
DeleteResponse,
FindParams,
HttpTypes,
PaginatedResponse,
SelectParams,
DeleteResponse,
} from "@medusajs/types"
import { Client } from "../client"
import { ClientHeaders } from "../types"
@@ -289,9 +289,6 @@ export class Store {
if (!paymentCollectionId) {
const collectionBody = {
cart_id: cart.id,
region_id: cart.region_id,
currency_code: cart.currency_code,
amount: cart.total,
}
paymentCollectionId = (
await this.client.fetch<{
@@ -93,9 +93,6 @@ export interface UpdateCartWorkflowInputDTO {
export interface CreatePaymentCollectionForCartWorkflowInputDTO {
cart_id: string
region_id: string
currency_code: string
amount: BigNumberInput
metadata?: Record<string, unknown>
}
@@ -23,8 +23,5 @@ export type StoreCreatePaymentCollectionType = z.infer<
export const StoreCreatePaymentCollection = z
.object({
cart_id: z.string(),
region_id: z.string(),
currency_code: z.string(),
amount: z.number(),
})
.strict()