chore(cart): completed at (#8921)

This commit is contained in:
Carlos R. L. Rodrigues
2024-08-31 12:42:58 -03:00
committed by GitHub
parent 89c6ef8fc9
commit 4ffb49efd0
12 changed files with 148 additions and 4 deletions

View File

@@ -1972,6 +1972,10 @@ medusaIntegrationTestRunner({
)
})
afterEach(async () => {
jest.clearAllMocks()
})
it("should create an order and create item reservations", async () => {
const cart = (
await api.post(`/store/carts`, {
@@ -2136,6 +2140,62 @@ medusaIntegrationTestRunner({
})
})
it("should fail to update cart when it is completed", async () => {
const cart = (
await api.post(`/store/carts`, {
currency_code: "usd",
email: "tony@stark-industries.com",
shipping_address: {
address_1: "test address 1",
address_2: "test address 2",
city: "ny",
country_code: "us",
province: "ny",
postal_code: "94016",
},
sales_channel_id: salesChannel.id,
items: [{ quantity: 1, variant_id: product.variants[0].id }],
})
).data.cart
const paymentCollection = (
await api.post(`/store/payment-collections`, {
cart_id: cart.id,
})
).data.payment_collection
await api.post(
`/store/payment-collections/${paymentCollection.id}/payment-sessions`,
{ provider_id: "pp_system_default" }
)
await api.post(`/store/carts/${cart.id}/complete`, {})
const cartRefetch = (await api.get(`/store/carts/${cart.id}`)).data
.cart
expect(cartRefetch.completed_at).toBeTruthy()
await expect(
api.post(`/store/carts/${cart.id}/shipping-methods`, {
option_id: shippingOption.id,
})
).rejects.toThrow()
const error = await api
.post(`/store/carts/${cart.id}/line-items`, {
variant_id: product.variants[0].id,
quantity: 1,
})
.catch((e) => e)
expect(error.response.status).toEqual(400)
expect(error.response.data).toEqual({
type: "invalid_data",
message: `Cart ${cart.id} is already completed.`,
})
})
it("should return cart when payment authorization fails", async () => {
const authorizePaymentSessionSpy = jest.spyOn(
PaymentModuleService.prototype,

View File

@@ -0,0 +1,32 @@
import { CartDTO, CartWorkflowDTO } from "@medusajs/types"
import { MedusaError, isPresent } from "@medusajs/utils"
import { createStep } from "@medusajs/workflows-sdk"
export interface ValidateCartStepInput {
cart: CartWorkflowDTO | CartDTO
}
export const validateCartStepId = "validate-cart"
/**
* This step validates a cart's before editing it.
*/
export const validateCartStep = createStep(
validateCartStepId,
async (data: ValidateCartStepInput) => {
const { cart } = data
if (!isPresent(cart)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cart does not exist`
)
}
if (cart.completed_at) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Cart ${cart.id} is already completed.`
)
}
}
)

View File

@@ -1,9 +1,11 @@
export const cartFieldsForRefreshSteps = [
"id",
"subtotal",
"item_subtotal",
"shipping_subtotal",
"region_id",
"currency_code",
"completed_at",
"region.*",
"items.*",
"items.product.id",
@@ -26,6 +28,7 @@ export const completeCartFields = [
"email",
"created_at",
"updated_at",
"completed_at",
"total",
"subtotal",
"tax_total",

View File

@@ -9,6 +9,7 @@ import {
removeShippingMethodFromCartStep,
validateCartShippingOptionsStep,
} from "../steps"
import { validateCartStep } from "../steps/validate-cart"
import { cartFieldsForRefreshSteps } from "../utils/fields"
import { updateCartPromotionsWorkflow } from "./update-cart-promotions"
import { updateTaxLinesWorkflow } from "./update-tax-lines"
@@ -37,6 +38,8 @@ export const addShippingMethodToWorkflow = createWorkflow(
list: false,
})
validateCartStep({ cart })
const optionIds = transform({ input }, (data) => {
return (data.input.options ?? []).map((i) => i.id)
})

View File

@@ -16,6 +16,7 @@ import {
refreshCartShippingMethodsStep,
updateLineItemsStep,
} from "../steps"
import { validateCartStep } from "../steps/validate-cart"
import { validateVariantPricesStep } from "../steps/validate-variant-prices"
import {
cartFieldsForRefreshSteps,
@@ -34,6 +35,8 @@ export const addToCartWorkflowId = "add-to-cart"
export const addToCartWorkflow = createWorkflow(
addToCartWorkflowId,
(input: WorkflowData<AddToCartWorkflowInputDTO>) => {
validateCartStep(input)
const variantIds = transform({ input }, (data) => {
return (data.input.items ?? []).map((i) => i.variant_id)
})
@@ -67,9 +70,10 @@ export const addToCartWorkflow = createWorkflow(
return prepareLineItemData({
variant: variant,
unitPrice: item.unit_price ||
variant.calculated_price.calculated_amount,
isTaxInclusive: item.is_tax_inclusive ||
unitPrice:
item.unit_price || variant.calculated_price.calculated_amount,
isTaxInclusive:
item.is_tax_inclusive ||
variant.calculated_price.is_calculated_price_tax_inclusive,
quantity: item.quantity,
metadata: item?.metadata ?? {},

View File

@@ -14,8 +14,9 @@ import {
} from "../../common"
import { createOrdersStep } from "../../order/steps/create-orders"
import { authorizePaymentSessionStep } from "../../payment/steps/authorize-payment-session"
import { validateCartPaymentsStep } from "../steps"
import { updateCartsStep, validateCartPaymentsStep } from "../steps"
import { reserveInventoryStep } from "../steps/reserve-inventory"
import { validateCartStep } from "../steps/validate-cart"
import { completeCartFields } from "../utils/fields"
import { prepareConfirmInventoryInput } from "../utils/prepare-confirm-inventory-input"
import {
@@ -44,6 +45,8 @@ export const completeCartWorkflow = createWorkflow(
list: false,
})
validateCartStep({ cart })
const paymentSessions = validateCartPaymentsStep({ cart })
authorizePaymentSessionStep({
@@ -158,6 +161,13 @@ export const completeCartWorkflow = createWorkflow(
({ createdOrders }) => createdOrders[0]
)
const updateCompletedAt = transform({ cart }, ({ cart }) => {
return {
id: cart.id,
completed_at: new Date(),
}
})
parallelize(
createRemoteLinkStep([
{
@@ -171,6 +181,7 @@ export const completeCartWorkflow = createWorkflow(
},
},
]),
updateCartsStep([updateCompletedAt]),
emitEventStep({
eventName: OrderWorkflowEvents.PLACED,
data: { id: order.id },

View File

@@ -12,6 +12,7 @@ import {
import { createRemoteLinkStep } from "../../common/steps/create-remote-links"
import { useRemoteQueryStep } from "../../common/steps/use-remote-query"
import { createPaymentCollectionsStep } from "../steps/create-payment-collection"
import { validateCartStep } from "../steps/validate-cart"
/**
* This step validates that a cart doesn't have a payment collection.
@@ -40,6 +41,7 @@ export const createPaymentCollectionForCartWorkflow = createWorkflow(
fields: [
"id",
"region_id",
"completed_at",
"currency_code",
"total",
"raw_total",
@@ -50,6 +52,8 @@ export const createPaymentCollectionForCartWorkflow = createWorkflow(
list: false,
})
validateCartStep({ cart })
validateExistingPaymentCollectionStep({ cart })
const paymentData = transform({ cart }, ({ cart }) => {

View File

@@ -8,6 +8,7 @@ import {
import { useRemoteQueryStep } from "../../common/steps/use-remote-query"
import { updateLineItemsStepWithSelector } from "../../line-item/steps"
import { refreshCartShippingMethodsStep } from "../steps"
import { validateCartStep } from "../steps/validate-cart"
import { validateVariantPricesStep } from "../steps/validate-variant-prices"
import {
cartFieldsForRefreshSteps,
@@ -27,6 +28,8 @@ export const updateLineItemInCartWorkflowId = "update-line-item-in-cart"
export const updateLineItemInCartWorkflow = createWorkflow(
updateLineItemInCartWorkflowId,
(input: WorkflowData<UpdateLineItemInCartWorkflowInputDTO>) => {
validateCartStep(input)
const variantIds = transform({ input }, (data) => {
return [data.input.item.variant_id]
})

View File

@@ -782,6 +782,11 @@ export interface CartDTO {
*/
metadata?: Record<string, unknown> | null
/**
* When the cart was completed.
*/
completed_at?: string | Date
/**
* When the cart was created.
*/

View File

@@ -4,6 +4,7 @@ export const defaultStoreCartFields = [
"email",
"created_at",
"updated_at",
"completed_at",
"total",
"subtotal",
"tax_total",

View File

@@ -0,0 +1,15 @@
import { Migration } from "@mikro-orm/migrations"
export class Migration20240831125857 extends Migration {
async up(): Promise<void> {
this.addSql(
'alter table if exists "cart" add column if not exists "completed_at" timestamptz null;'
)
}
async down(): Promise<void> {
this.addSql(
'alter table if exists "cart" drop column if exists "completed_at";'
)
}
}

View File

@@ -147,6 +147,9 @@ export default class Cart {
})
shipping_methods = new Collection<Rel<ShippingMethod>>(this)
@Property({ columnType: "timestamptz", nullable: true })
completed_at: Date | null = null
@Property({
onCreate: () => new Date(),
columnType: "timestamptz",