docs: fixes to type errors in guides (#13797)
This commit is contained in:
1 parent
d9249be6e6
commit
0d83918348
19 files changed
+922
-682
No files matched your search
+422
-302
File diff suppressed because it is too large.
Load diff
@@ -112,7 +112,7 @@ const { data: [order] } = await query.graph({
|
|||||||
fields: ["*", "transactions.*"],
|
fields: ["*", "transactions.*"],
|
||||||
filters: {
|
filters: {
|
||||||
id: "order_123",
|
id: "order_123",
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const refundTransactions = order.transactions?.filter(
|
const refundTransactions = order.transactions?.filter(
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ export default class AgenticCommerceService {
|
|||||||
// ...
|
// ...
|
||||||
async verifySignature({
|
async verifySignature({
|
||||||
signature,
|
signature,
|
||||||
payload
|
payload,
|
||||||
}: {
|
}: {
|
||||||
// base64 encoded signature
|
// base64 encoded signature
|
||||||
signature: string
|
signature: string
|
||||||
@@ -205,18 +205,18 @@ export default class AgenticCommerceService {
|
|||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
// Decode the base64 signature
|
// Decode the base64 signature
|
||||||
const receivedSignature = Buffer.from(signature, 'base64')
|
const receivedSignature = Buffer.from(signature, "base64")
|
||||||
|
|
||||||
// Create HMAC-SHA256 signature using your signing key
|
// Create HMAC-SHA256 signature using your signing key
|
||||||
const expectedSignature = crypto
|
const expectedSignature = crypto
|
||||||
.createHmac('sha256', this.options.signatureKey)
|
.createHmac("sha256", this.options.signatureKey)
|
||||||
.update(JSON.stringify(payload), 'utf8')
|
.update(JSON.stringify(payload), "utf8")
|
||||||
.digest()
|
.digest()
|
||||||
|
|
||||||
// Compare signatures using constant-time comparison to prevent timing attacks
|
// Compare signatures using constant-time comparison to prevent timing attacks
|
||||||
return crypto.timingSafeEqual(receivedSignature, expectedSignature)
|
return crypto.timingSafeEqual(receivedSignature, expectedSignature)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Signature verification failed:', error)
|
console.error("Signature verification failed:", error)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,8 +237,8 @@ Add the following method to the `AgenticCommerceService` class:
|
|||||||
export default class AgenticCommerceService {
|
export default class AgenticCommerceService {
|
||||||
// ...
|
// ...
|
||||||
async getSignature(data: any) {
|
async getSignature(data: any) {
|
||||||
return Buffer.from(crypto.createHmac('sha256', this.options.signatureKey)
|
return Buffer.from(crypto.createHmac("sha256", this.options.signatureKey)
|
||||||
.update(JSON.stringify(data), 'utf8').digest()).toString('base64')
|
.update(JSON.stringify(data), "utf8").digest()).toString("base64")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -276,7 +276,7 @@ export default class AgenticCommerceService {
|
|||||||
// ...
|
// ...
|
||||||
async sendWebhookEvent({
|
async sendWebhookEvent({
|
||||||
type,
|
type,
|
||||||
data
|
data,
|
||||||
}: AgenticCommerceWebhookEvent) {
|
}: AgenticCommerceWebhookEvent) {
|
||||||
// Create signature
|
// Create signature
|
||||||
const signature = this.getSignature(data)
|
const signature = this.getSignature(data)
|
||||||
@@ -328,7 +328,7 @@ module.exports = defineConfig({
|
|||||||
resolve: "./src/modules/agentic-commerce",
|
resolve: "./src/modules/agentic-commerce",
|
||||||
options: {
|
options: {
|
||||||
signatureKey: process.env.AGENTIC_COMMERCE_SIGNATURE_KEY || "supersecret",
|
signatureKey: process.env.AGENTIC_COMMERCE_SIGNATURE_KEY || "supersecret",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@@ -487,7 +487,7 @@ export const getProductFeedItemsStep = createStep(
|
|||||||
do {
|
do {
|
||||||
const {
|
const {
|
||||||
data: products,
|
data: products,
|
||||||
metadata
|
metadata,
|
||||||
} = await query.graph({
|
} = await query.graph({
|
||||||
entity: "product",
|
entity: "product",
|
||||||
fields: [
|
fields: [
|
||||||
@@ -503,7 +503,7 @@ export const getProductFeedItemsStep = createStep(
|
|||||||
"sales_channels.*",
|
"sales_channels.*",
|
||||||
"sales_channels.stock_locations.*",
|
"sales_channels.stock_locations.*",
|
||||||
"sales_channels.stock_locations.address.*",
|
"sales_channels.stock_locations.address.*",
|
||||||
"categories.*"
|
"categories.*",
|
||||||
],
|
],
|
||||||
filters: {
|
filters: {
|
||||||
status: "published",
|
status: "published",
|
||||||
@@ -513,19 +513,19 @@ export const getProductFeedItemsStep = createStep(
|
|||||||
calculated_price: QueryContext({
|
calculated_price: QueryContext({
|
||||||
currency_code: currencyCode,
|
currency_code: currencyCode,
|
||||||
}),
|
}),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
pagination: {
|
pagination: {
|
||||||
take: limit,
|
take: limit,
|
||||||
skip: offset,
|
skip: offset,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
count = metadata?.count ?? 0
|
count = metadata?.count ?? 0
|
||||||
offset += limit
|
offset += limit
|
||||||
|
|
||||||
for (const product of products) {
|
for (const product of products) {
|
||||||
if (!product.variants.length) continue
|
if (!product.variants.length) {continue}
|
||||||
const salesChannel = product.sales_channels?.find((channel) => {
|
const salesChannel = product.sales_channels?.find((channel) => {
|
||||||
return channel?.stock_locations?.some((location) => {
|
return channel?.stock_locations?.some((location) => {
|
||||||
return location?.address?.country_code.toLowerCase() === countryCode
|
return location?.address?.country_code.toLowerCase() === countryCode
|
||||||
@@ -752,11 +752,11 @@ export const sendProductFeedWorkflow = createWorkflow(
|
|||||||
const { items: feedItems } = getProductFeedItemsStep(input)
|
const { items: feedItems } = getProductFeedItemsStep(input)
|
||||||
|
|
||||||
const xml = buildProductFeedXmlStep({
|
const xml = buildProductFeedXmlStep({
|
||||||
items: feedItems
|
items: feedItems,
|
||||||
})
|
})
|
||||||
|
|
||||||
sendProductFeedStep({
|
sendProductFeedStep({
|
||||||
productFeed: xml
|
productFeed: xml,
|
||||||
})
|
})
|
||||||
|
|
||||||
return new WorkflowResponse({ xml })
|
return new WorkflowResponse({ xml })
|
||||||
@@ -786,9 +786,9 @@ Create the file `src/jobs/sync-product-feed.ts` with the following content:
|
|||||||
|
|
||||||
```ts title="src/jobs/sync-product-feed.ts"
|
```ts title="src/jobs/sync-product-feed.ts"
|
||||||
import {
|
import {
|
||||||
MedusaContainer
|
MedusaContainer,
|
||||||
} from "@medusajs/framework/types";
|
} from "@medusajs/framework/types"
|
||||||
import sendProductFeedWorkflow from "../workflows/send-product-feed";
|
import sendProductFeedWorkflow from "../workflows/send-product-feed"
|
||||||
|
|
||||||
export default async function syncProductFeed(container: MedusaContainer) {
|
export default async function syncProductFeed(container: MedusaContainer) {
|
||||||
const logger = container.resolve("logger")
|
const logger = container.resolve("logger")
|
||||||
@@ -816,7 +816,7 @@ export default async function syncProductFeed(container: MedusaContainer) {
|
|||||||
export const config = {
|
export const config = {
|
||||||
name: "sync-product-feed",
|
name: "sync-product-feed",
|
||||||
schedule: "*/15 * * * *", // Every 15 minutes
|
schedule: "*/15 * * * *", // Every 15 minutes
|
||||||
};
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In a scheduled job file, you must export:
|
In a scheduled job file, you must export:
|
||||||
@@ -890,11 +890,11 @@ To create the workflow, create the file `src/workflows/prepare-checkout-session-
|
|||||||
import {
|
import {
|
||||||
createWorkflow,
|
createWorkflow,
|
||||||
transform,
|
transform,
|
||||||
WorkflowResponse
|
WorkflowResponse,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
import {
|
import {
|
||||||
listShippingOptionsForCartWithPricingWorkflow,
|
listShippingOptionsForCartWithPricingWorkflow,
|
||||||
useQueryGraphStep
|
useQueryGraphStep,
|
||||||
} from "@medusajs/medusa/core-flows"
|
} from "@medusajs/medusa/core-flows"
|
||||||
|
|
||||||
export type PrepareCheckoutSessionDataWorkflowInput = {
|
export type PrepareCheckoutSessionDataWorkflowInput = {
|
||||||
@@ -960,21 +960,21 @@ const { data: carts } = useQueryGraphStep({
|
|||||||
"original_item_total",
|
"original_item_total",
|
||||||
"shipping_total",
|
"shipping_total",
|
||||||
"metadata",
|
"metadata",
|
||||||
"order.id"
|
"order.id",
|
||||||
],
|
],
|
||||||
filters: {
|
filters: {
|
||||||
id: input.cart_id,
|
id: input.cart_id,
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
throwIfKeyNotFound: true
|
throwIfKeyNotFound: true,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Retrieve shipping options
|
// Retrieve shipping options
|
||||||
const shippingOptions = listShippingOptionsForCartWithPricingWorkflow.runAsStep({
|
const shippingOptions = listShippingOptionsForCartWithPricingWorkflow.runAsStep({
|
||||||
input: {
|
input: {
|
||||||
cart_id: carts[0].id,
|
cart_id: carts[0].id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO prepare response
|
// TODO prepare response
|
||||||
@@ -1022,7 +1022,7 @@ const responseData = transform({
|
|||||||
item: {
|
item: {
|
||||||
id: item?.variant_id,
|
id: item?.variant_id,
|
||||||
quantity: item?.quantity,
|
quantity: item?.quantity,
|
||||||
}
|
},
|
||||||
})),
|
})),
|
||||||
fulfillment_address: data.input.fulfillment_address,
|
fulfillment_address: data.input.fulfillment_address,
|
||||||
fulfillment_options: data.shippingOptions?.map((option) => ({
|
fulfillment_options: data.shippingOptions?.map((option) => ({
|
||||||
@@ -1080,7 +1080,7 @@ const responseData = transform({
|
|||||||
display_name: "Total",
|
display_name: "Total",
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
amount: data.carts[0].total,
|
amount: data.carts[0].total,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
messages: data.input.messages || [],
|
messages: data.input.messages || [],
|
||||||
links: [
|
links: [
|
||||||
@@ -1095,8 +1095,8 @@ const responseData = transform({
|
|||||||
{
|
{
|
||||||
type: "seller_shop_policy",
|
type: "seller_shop_policy",
|
||||||
value: "https://www.medusa-commerce.com/seller-shop-policy", // TODO: replace with actual seller shop policy
|
value: "https://www.medusa-commerce.com/seller-shop-policy", // TODO: replace with actual seller shop policy
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1215,7 +1215,7 @@ import {
|
|||||||
createWorkflow,
|
createWorkflow,
|
||||||
transform,
|
transform,
|
||||||
when,
|
when,
|
||||||
WorkflowResponse
|
WorkflowResponse,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
import {
|
import {
|
||||||
addShippingMethodToCartWorkflow,
|
addShippingMethodToCartWorkflow,
|
||||||
@@ -1223,10 +1223,10 @@ import {
|
|||||||
CreateCartWorkflowInput,
|
CreateCartWorkflowInput,
|
||||||
createCustomersWorkflow,
|
createCustomersWorkflow,
|
||||||
listShippingOptionsForCartWithPricingWorkflow,
|
listShippingOptionsForCartWithPricingWorkflow,
|
||||||
useQueryGraphStep
|
useQueryGraphStep,
|
||||||
} from "@medusajs/medusa/core-flows"
|
} from "@medusajs/medusa/core-flows"
|
||||||
import {
|
import {
|
||||||
prepareCheckoutSessionDataWorkflow
|
prepareCheckoutSessionDataWorkflow,
|
||||||
} from "./prepare-checkout-session-data"
|
} from "./prepare-checkout-session-data"
|
||||||
|
|
||||||
type WorkflowInput = {
|
type WorkflowInput = {
|
||||||
@@ -1270,7 +1270,7 @@ Replace the `TODO` in the workflow with the following:
|
|||||||
```ts title="src/workflows/create-checkout-session.ts"
|
```ts title="src/workflows/create-checkout-session.ts"
|
||||||
// validate item IDs
|
// validate item IDs
|
||||||
const variantIds = transform({
|
const variantIds = transform({
|
||||||
input
|
input,
|
||||||
}, (data) => {
|
}, (data) => {
|
||||||
return data.input.items.map((item) => item.id)
|
return data.input.items.map((item) => item.id)
|
||||||
})
|
})
|
||||||
@@ -1280,11 +1280,11 @@ useQueryGraphStep({
|
|||||||
entity: "variant",
|
entity: "variant",
|
||||||
fields: ["id"],
|
fields: ["id"],
|
||||||
filters: {
|
filters: {
|
||||||
id: variantIds
|
id: variantIds,
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
throwIfKeyNotFound: true
|
throwIfKeyNotFound: true,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO retrieve region and sales channel
|
// TODO retrieve region and sales channel
|
||||||
@@ -1305,9 +1305,9 @@ const { data: regions } = useQueryGraphStep({
|
|||||||
fields: ["id"],
|
fields: ["id"],
|
||||||
filters: {
|
filters: {
|
||||||
countries: {
|
countries: {
|
||||||
iso_2: "us"
|
iso_2: "us",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}).config({ name: "find-region" })
|
}).config({ name: "find-region" })
|
||||||
|
|
||||||
// get sales channel
|
// get sales channel
|
||||||
@@ -1338,7 +1338,7 @@ const { data: customers } = useQueryGraphStep({
|
|||||||
fields: ["id"],
|
fields: ["id"],
|
||||||
filters: {
|
filters: {
|
||||||
email: input.buyer?.email,
|
email: input.buyer?.email,
|
||||||
}
|
},
|
||||||
}).config({ name: "find-customer" })
|
}).config({ name: "find-customer" })
|
||||||
|
|
||||||
// create customer if it does not exist
|
// create customer if it does not exist
|
||||||
@@ -1354,9 +1354,9 @@ const createdCustomers = when ({ customers }, ({ customers }) =>
|
|||||||
first_name: input.buyer?.first_name,
|
first_name: input.buyer?.first_name,
|
||||||
phone: input.buyer?.phone_number,
|
phone: input.buyer?.phone_number,
|
||||||
has_account: false,
|
has_account: false,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1397,7 +1397,7 @@ const cartInput = transform({
|
|||||||
return {
|
return {
|
||||||
items: data.input.items.map((item) => ({
|
items: data.input.items.map((item) => ({
|
||||||
variant_id: item.id,
|
variant_id: item.id,
|
||||||
quantity: item.quantity
|
quantity: item.quantity,
|
||||||
})),
|
})),
|
||||||
region_id: data.regions[0]?.id,
|
region_id: data.regions[0]?.id,
|
||||||
email: data.input.buyer?.email,
|
email: data.input.buyer?.email,
|
||||||
@@ -1416,12 +1416,12 @@ const cartInput = transform({
|
|||||||
sales_channel_id: data.salesChannels[0]?.id,
|
sales_channel_id: data.salesChannels[0]?.id,
|
||||||
metadata: {
|
metadata: {
|
||||||
is_checkout_session: true,
|
is_checkout_session: true,
|
||||||
}
|
},
|
||||||
} as CreateCartWorkflowInput
|
} as CreateCartWorkflowInput
|
||||||
})
|
})
|
||||||
|
|
||||||
const createdCart = createCartWorkflow.runAsStep({
|
const createdCart = createCartWorkflow.runAsStep({
|
||||||
input: cartInput
|
input: cartInput,
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO retrieve shipping options
|
// TODO retrieve shipping options
|
||||||
@@ -1445,7 +1445,7 @@ when(input, (input) => !!input.fulfillment_address)
|
|||||||
const shippingOptions = listShippingOptionsForCartWithPricingWorkflow.runAsStep({
|
const shippingOptions = listShippingOptionsForCartWithPricingWorkflow.runAsStep({
|
||||||
input: {
|
input: {
|
||||||
cart_id: createdCart.id,
|
cart_id: createdCart.id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const shippingMethodData = transform({
|
const shippingMethodData = transform({
|
||||||
@@ -1460,11 +1460,11 @@ when(input, (input) => !!input.fulfillment_address)
|
|||||||
cart_id: data.createdCart.id,
|
cart_id: data.createdCart.id,
|
||||||
options: [{
|
options: [{
|
||||||
id: cheapestShippingOption.id,
|
id: cheapestShippingOption.id,
|
||||||
}]
|
}],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
addShippingMethodToCartWorkflow.runAsStep({
|
addShippingMethodToCartWorkflow.runAsStep({
|
||||||
input: shippingMethodData
|
input: shippingMethodData,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1488,7 +1488,7 @@ const responseData = prepareCheckoutSessionDataWorkflow.runAsStep({
|
|||||||
buyer: input.buyer,
|
buyer: input.buyer,
|
||||||
fulfillment_address: input.fulfillment_address,
|
fulfillment_address: input.fulfillment_address,
|
||||||
cart_id: createdCart.id,
|
cart_id: createdCart.id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return new WorkflowResponse(responseData)
|
return new WorkflowResponse(responseData)
|
||||||
@@ -1555,7 +1555,7 @@ export const POST = async (
|
|||||||
input: req.validatedBody,
|
input: req.validatedBody,
|
||||||
context: {
|
context: {
|
||||||
idempotencyKey: req.headers["idempotency-key"] as string,
|
idempotencyKey: req.headers["idempotency-key"] as string,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
res.set(responseHeaders).json(result)
|
res.set(responseHeaders).json(result)
|
||||||
@@ -1569,8 +1569,8 @@ export const POST = async (
|
|||||||
code: "invalid",
|
code: "invalid",
|
||||||
content_type: "plain",
|
content_type: "plain",
|
||||||
content: medusaError.message,
|
content: medusaError.message,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1593,8 +1593,8 @@ Next, you'll create a [middleware](!docs!/learn/fundamentals/api-routes/middlewa
|
|||||||
Create the file `src/api/middlewares/validate-agentic-request.ts` with the following content:
|
Create the file `src/api/middlewares/validate-agentic-request.ts` with the following content:
|
||||||
|
|
||||||
```ts title="src/api/middlewares/validate-agentic-request.ts"
|
```ts title="src/api/middlewares/validate-agentic-request.ts"
|
||||||
import { MedusaNextFunction, MedusaRequest, MedusaResponse } from "@medusajs/framework";
|
import { MedusaNextFunction, MedusaRequest, MedusaResponse } from "@medusajs/framework"
|
||||||
import { AGENTIC_COMMERCE_MODULE } from "../../modules/agentic-commerce";
|
import { AGENTIC_COMMERCE_MODULE } from "../../modules/agentic-commerce"
|
||||||
|
|
||||||
export async function validateAgenticRequest(
|
export async function validateAgenticRequest(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
@@ -1609,12 +1609,12 @@ export async function validateAgenticRequest(
|
|||||||
const isTokenValid = await apiKeyModuleService.authenticate(apiKey || "")
|
const isTokenValid = await apiKeyModuleService.authenticate(apiKey || "")
|
||||||
const isSignatureValid = !!req.body || await agenticCommerceModuleService.verifySignature({
|
const isSignatureValid = !!req.body || await agenticCommerceModuleService.verifySignature({
|
||||||
signature,
|
signature,
|
||||||
payload: req.body
|
payload: req.body,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!isTokenValid || !isSignatureValid) {
|
if (!isTokenValid || !isSignatureValid) {
|
||||||
return res.status(401).json({
|
return res.status(401).json({
|
||||||
message: "Unauthorized"
|
message: "Unauthorized",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1642,24 +1642,24 @@ You apply middlewares in the `src/api/middlewares.ts` file. Create this file wit
|
|||||||
import {
|
import {
|
||||||
defineMiddlewares,
|
defineMiddlewares,
|
||||||
validateAndTransformBody,
|
validateAndTransformBody,
|
||||||
} from "@medusajs/framework/http";
|
} from "@medusajs/framework/http"
|
||||||
import { validateAgenticRequest } from "./middlewares/validate-agentic-request";
|
import { validateAgenticRequest } from "./middlewares/validate-agentic-request"
|
||||||
import { PostCreateSessionSchema } from "./checkout_sessions/route";
|
import { PostCreateSessionSchema } from "./checkout_sessions/route"
|
||||||
|
|
||||||
export default defineMiddlewares({
|
export default defineMiddlewares({
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
matcher: "/checkout_sessions*",
|
matcher: "/checkout_sessions*",
|
||||||
middlewares: [
|
middlewares: [
|
||||||
validateAgenticRequest
|
validateAgenticRequest,
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
matcher: "/checkout_sessions",
|
matcher: "/checkout_sessions",
|
||||||
method: ["POST"],
|
method: ["POST"],
|
||||||
middlewares: [validateAndTransformBody(PostCreateSessionSchema)]
|
middlewares: [validateAndTransformBody(PostCreateSessionSchema)],
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1678,7 +1678,7 @@ In `src/api/middlewares.ts`, add the following import at the top of the file:
|
|||||||
```ts title="src/api/middlewares.ts"
|
```ts title="src/api/middlewares.ts"
|
||||||
import {
|
import {
|
||||||
errorHandler,
|
errorHandler,
|
||||||
} from "@medusajs/framework/http";
|
} from "@medusajs/framework/http"
|
||||||
|
|
||||||
const originalErrorHandler = errorHandler()
|
const originalErrorHandler = errorHandler()
|
||||||
```
|
```
|
||||||
@@ -1702,8 +1702,8 @@ export default defineMiddlewares({
|
|||||||
code: "invalid",
|
code: "invalid",
|
||||||
content_type: "plain",
|
content_type: "plain",
|
||||||
content: error.message,
|
content: error.message,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -1935,16 +1935,16 @@ import {
|
|||||||
createWorkflow,
|
createWorkflow,
|
||||||
transform,
|
transform,
|
||||||
when,
|
when,
|
||||||
WorkflowResponse
|
WorkflowResponse,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
import {
|
import {
|
||||||
addShippingMethodToCartWorkflow,
|
addShippingMethodToCartWorkflow,
|
||||||
createCustomersWorkflow,
|
createCustomersWorkflow,
|
||||||
updateCartWorkflow,
|
updateCartWorkflow,
|
||||||
useQueryGraphStep
|
useQueryGraphStep,
|
||||||
} from "@medusajs/medusa/core-flows"
|
} from "@medusajs/medusa/core-flows"
|
||||||
import {
|
import {
|
||||||
prepareCheckoutSessionDataWorkflow
|
prepareCheckoutSessionDataWorkflow,
|
||||||
} from "./prepare-checkout-session-data"
|
} from "./prepare-checkout-session-data"
|
||||||
|
|
||||||
type WorkflowInput = {
|
type WorkflowInput = {
|
||||||
@@ -1980,7 +1980,7 @@ export const updateCheckoutSessionWorkflow = createWorkflow(
|
|||||||
fields: ["id", "customer.*", "email"],
|
fields: ["id", "customer.*", "email"],
|
||||||
filters: {
|
filters: {
|
||||||
id: input.cart_id,
|
id: input.cart_id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO retrieve or create customer
|
// TODO retrieve or create customer
|
||||||
@@ -2003,7 +2003,7 @@ const { data: customers } = useQueryGraphStep({
|
|||||||
fields: ["id"],
|
fields: ["id"],
|
||||||
filters: {
|
filters: {
|
||||||
email: input.buyer?.email,
|
email: input.buyer?.email,
|
||||||
}
|
},
|
||||||
}).config({ name: "find-customer" })
|
}).config({ name: "find-customer" })
|
||||||
|
|
||||||
const createdCustomers = when({ customers }, ({ customers }) =>
|
const createdCustomers = when({ customers }, ({ customers }) =>
|
||||||
@@ -2017,9 +2017,9 @@ const createdCustomers = when({ customers }, ({ customers }) =>
|
|||||||
email: input.buyer?.email,
|
email: input.buyer?.email,
|
||||||
first_name: input.buyer?.first_name,
|
first_name: input.buyer?.first_name,
|
||||||
phone: input.buyer?.phone_number,
|
phone: input.buyer?.phone_number,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2057,7 +2057,7 @@ when(input, (input) => !!input.items)
|
|||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
throwIfKeyNotFound: true,
|
throwIfKeyNotFound: true,
|
||||||
}
|
},
|
||||||
}).config({ name: "find-variant" })
|
}).config({ name: "find-variant" })
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2133,7 +2133,7 @@ const responseData = prepareCheckoutSessionDataWorkflow.runAsStep({
|
|||||||
cart_id: updateData.id,
|
cart_id: updateData.id,
|
||||||
buyer: input.buyer,
|
buyer: input.buyer,
|
||||||
fulfillment_address: input.fulfillment_address,
|
fulfillment_address: input.fulfillment_address,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return new WorkflowResponse(responseData)
|
return new WorkflowResponse(responseData)
|
||||||
@@ -2199,7 +2199,7 @@ export const POST = async (
|
|||||||
},
|
},
|
||||||
context: {
|
context: {
|
||||||
idempotencyKey: req.headers["idempotency-key"] as string,
|
idempotencyKey: req.headers["idempotency-key"] as string,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
res.set(responseHeaders).json(result)
|
res.set(responseHeaders).json(result)
|
||||||
@@ -2209,7 +2209,7 @@ export const POST = async (
|
|||||||
await refreshPaymentCollectionForCartWorkflow(req.scope).run({
|
await refreshPaymentCollectionForCartWorkflow(req.scope).run({
|
||||||
input: {
|
input: {
|
||||||
cart_id: req.params.id,
|
cart_id: req.params.id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { result } = await prepareCheckoutSessionDataWorkflow(req.scope)
|
const { result } = await prepareCheckoutSessionDataWorkflow(req.scope)
|
||||||
@@ -2224,8 +2224,8 @@ export const POST = async (
|
|||||||
"payment_declined" : "invalid",
|
"payment_declined" : "invalid",
|
||||||
content_type: "plain",
|
content_type: "plain",
|
||||||
content: medusaError.message,
|
content: medusaError.message,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2249,7 +2249,7 @@ Finally, you'll apply the validation middleware to the `POST /checkout_sessions/
|
|||||||
In `src/api/middlewares.ts`, add the following import at the top of the file:
|
In `src/api/middlewares.ts`, add the following import at the top of the file:
|
||||||
|
|
||||||
```ts title="src/api/middlewares.ts"
|
```ts title="src/api/middlewares.ts"
|
||||||
import { PostUpdateSessionSchema } from "./checkout_sessions/[id]/route";
|
import { PostUpdateSessionSchema } from "./checkout_sessions/[id]/route"
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, add a new route configuration in `defineMiddlewares`:
|
Then, add a new route configuration in `defineMiddlewares`:
|
||||||
@@ -2261,7 +2261,7 @@ export default defineMiddlewares({
|
|||||||
{
|
{
|
||||||
matcher: "/checkout_sessions/:id",
|
matcher: "/checkout_sessions/:id",
|
||||||
method: ["POST"],
|
method: ["POST"],
|
||||||
middlewares: [validateAndTransformBody(PostUpdateSessionSchema)]
|
middlewares: [validateAndTransformBody(PostUpdateSessionSchema)],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// ...
|
// ...
|
||||||
@@ -2346,7 +2346,7 @@ export const GET = async (
|
|||||||
},
|
},
|
||||||
context: {
|
context: {
|
||||||
idempotencyKey: req.headers["idempotency-key"] as string,
|
idempotencyKey: req.headers["idempotency-key"] as string,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
res.set(responseHeaders).status(201).json(result)
|
res.set(responseHeaders).status(201).json(result)
|
||||||
@@ -2499,7 +2499,7 @@ import {
|
|||||||
createWorkflow,
|
createWorkflow,
|
||||||
transform,
|
transform,
|
||||||
when,
|
when,
|
||||||
WorkflowResponse
|
WorkflowResponse,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
import {
|
import {
|
||||||
completeCartWorkflow,
|
completeCartWorkflow,
|
||||||
@@ -2507,11 +2507,11 @@ import {
|
|||||||
createPaymentSessionsWorkflow,
|
createPaymentSessionsWorkflow,
|
||||||
refreshPaymentCollectionForCartWorkflow,
|
refreshPaymentCollectionForCartWorkflow,
|
||||||
updateCartWorkflow,
|
updateCartWorkflow,
|
||||||
useQueryGraphStep
|
useQueryGraphStep,
|
||||||
} from "@medusajs/medusa/core-flows"
|
} from "@medusajs/medusa/core-flows"
|
||||||
import {
|
import {
|
||||||
prepareCheckoutSessionDataWorkflow,
|
prepareCheckoutSessionDataWorkflow,
|
||||||
PrepareCheckoutSessionDataWorkflowInput
|
PrepareCheckoutSessionDataWorkflowInput,
|
||||||
} from "./prepare-checkout-session-data"
|
} from "./prepare-checkout-session-data"
|
||||||
|
|
||||||
type WorkflowInput = {
|
type WorkflowInput = {
|
||||||
@@ -2547,7 +2547,7 @@ export const completeCheckoutSessionWorkflow = createWorkflow(
|
|||||||
"id",
|
"id",
|
||||||
"region.*",
|
"region.*",
|
||||||
"region.payment_providers.*",
|
"region.payment_providers.*",
|
||||||
"shipping_address.*"
|
"shipping_address.*",
|
||||||
],
|
],
|
||||||
filters: {
|
filters: {
|
||||||
id: input.cart_id,
|
id: input.cart_id,
|
||||||
@@ -2589,7 +2589,7 @@ when(input, (input) => !!input.payment_data.billing_address)
|
|||||||
postal_code: data.input.payment_data.billing_address!.postal_code,
|
postal_code: data.input.payment_data.billing_address!.postal_code,
|
||||||
country_code: data.input.payment_data.billing_address!.country,
|
country_code: data.input.payment_data.billing_address!.country,
|
||||||
phone: data.input.payment_data.billing_address!.phone_number,
|
phone: data.input.payment_data.billing_address!.phone_number,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return updateCartWorkflow.runAsStep({
|
return updateCartWorkflow.runAsStep({
|
||||||
@@ -2628,7 +2628,7 @@ const preparationInput = transform({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const paymentProviderId = transform({
|
const paymentProviderId = transform({
|
||||||
input
|
input,
|
||||||
}, (data) => {
|
}, (data) => {
|
||||||
switch (data.input.payment_data.provider) {
|
switch (data.input.payment_data.provider) {
|
||||||
case "stripe":
|
case "stripe":
|
||||||
@@ -2640,7 +2640,7 @@ const paymentProviderId = transform({
|
|||||||
|
|
||||||
const completeCartResponse = when({
|
const completeCartResponse = when({
|
||||||
carts,
|
carts,
|
||||||
paymentProviderId
|
paymentProviderId,
|
||||||
}, (data) => {
|
}, (data) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return !!data.carts[0].region?.payment_providers?.find((provider) => provider?.id === data.paymentProviderId)
|
return !!data.carts[0].region?.payment_providers?.find((provider) => provider?.id === data.paymentProviderId)
|
||||||
@@ -2649,7 +2649,7 @@ const completeCartResponse = when({
|
|||||||
const paymentCollection = createPaymentCollectionForCartWorkflow.runAsStep({
|
const paymentCollection = createPaymentCollectionForCartWorkflow.runAsStep({
|
||||||
input: {
|
input: {
|
||||||
cart_id: carts[0].id,
|
cart_id: carts[0].id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
createPaymentSessionsWorkflow.runAsStep({
|
createPaymentSessionsWorkflow.runAsStep({
|
||||||
@@ -2658,14 +2658,14 @@ const completeCartResponse = when({
|
|||||||
provider_id: paymentProviderId,
|
provider_id: paymentProviderId,
|
||||||
data: {
|
data: {
|
||||||
shared_payment_token: input.payment_data.token,
|
shared_payment_token: input.payment_data.token,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
completeCartWorkflow.runAsStep({
|
completeCartWorkflow.runAsStep({
|
||||||
input: {
|
input: {
|
||||||
id: carts[0].id,
|
id: carts[0].id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return prepareCheckoutSessionDataWorkflow.runAsStep({
|
return prepareCheckoutSessionDataWorkflow.runAsStep({
|
||||||
@@ -2695,7 +2695,7 @@ Replace the `TODO` in the workflow with the following:
|
|||||||
```ts title="src/workflows/complete-checkout-session.ts"
|
```ts title="src/workflows/complete-checkout-session.ts"
|
||||||
const invalidPaymentResponse = when({
|
const invalidPaymentResponse = when({
|
||||||
carts,
|
carts,
|
||||||
paymentProviderId
|
paymentProviderId,
|
||||||
}, (data) => {
|
}, (data) => {
|
||||||
return !data.carts[0].region?.payment_providers?.find((provider) => provider?.id === data.paymentProviderId)
|
return !data.carts[0].region?.payment_providers?.find((provider) => provider?.id === data.paymentProviderId)
|
||||||
})
|
})
|
||||||
@@ -2703,7 +2703,7 @@ const invalidPaymentResponse = when({
|
|||||||
refreshPaymentCollectionForCartWorkflow.runAsStep({
|
refreshPaymentCollectionForCartWorkflow.runAsStep({
|
||||||
input: {
|
input: {
|
||||||
cart_id: carts[0].id,
|
cart_id: carts[0].id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
const prepareDataWithMessages = transform({
|
const prepareDataWithMessages = transform({
|
||||||
prepareData: preparationInput,
|
prepareData: preparationInput,
|
||||||
@@ -2716,12 +2716,12 @@ const invalidPaymentResponse = when({
|
|||||||
code: "invalid",
|
code: "invalid",
|
||||||
content_type: "plain",
|
content_type: "plain",
|
||||||
content: "Invalid payment provider",
|
content: "Invalid payment provider",
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
} as PrepareCheckoutSessionDataWorkflowInput
|
} as PrepareCheckoutSessionDataWorkflowInput
|
||||||
})
|
})
|
||||||
return prepareCheckoutSessionDataWorkflow.runAsStep({
|
return prepareCheckoutSessionDataWorkflow.runAsStep({
|
||||||
input: prepareDataWithMessages
|
input: prepareDataWithMessages,
|
||||||
}).config({ name: "prepare-checkout-session-data-with-messages" })
|
}).config({ name: "prepare-checkout-session-data-with-messages" })
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2739,7 +2739,7 @@ Finally, you'll return the appropriate response based on whether the cart was co
|
|||||||
```ts title="src/workflows/complete-checkout-session.ts"
|
```ts title="src/workflows/complete-checkout-session.ts"
|
||||||
const responseData = transform({
|
const responseData = transform({
|
||||||
completeCartResponse,
|
completeCartResponse,
|
||||||
invalidPaymentResponse
|
invalidPaymentResponse,
|
||||||
}, (data) => {
|
}, (data) => {
|
||||||
return data.completeCartResponse || data.invalidPaymentResponse
|
return data.completeCartResponse || data.invalidPaymentResponse
|
||||||
})
|
})
|
||||||
@@ -2804,7 +2804,7 @@ export const POST = async (
|
|||||||
},
|
},
|
||||||
context: {
|
context: {
|
||||||
idempotencyKey: req.headers["idempotency-key"] as string,
|
idempotencyKey: req.headers["idempotency-key"] as string,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
res.set(responseHeaders).json(result)
|
res.set(responseHeaders).json(result)
|
||||||
@@ -2814,7 +2814,7 @@ export const POST = async (
|
|||||||
await refreshPaymentCollectionForCartWorkflow(req.scope).run({
|
await refreshPaymentCollectionForCartWorkflow(req.scope).run({
|
||||||
input: {
|
input: {
|
||||||
cart_id: req.params.id,
|
cart_id: req.params.id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
const { result } = await prepareCheckoutSessionDataWorkflow(req.scope)
|
const { result } = await prepareCheckoutSessionDataWorkflow(req.scope)
|
||||||
.run({
|
.run({
|
||||||
@@ -2828,8 +2828,8 @@ export const POST = async (
|
|||||||
"payment_declined" : "invalid",
|
"payment_declined" : "invalid",
|
||||||
content_type: "plain",
|
content_type: "plain",
|
||||||
content: medusaError.message,
|
content: medusaError.message,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2853,7 +2853,7 @@ Finally, you'll apply the validation middleware to the `POST /checkout_sessions/
|
|||||||
In `src/api/middlewares.ts`, add the following import at the top of the file:
|
In `src/api/middlewares.ts`, add the following import at the top of the file:
|
||||||
|
|
||||||
```ts title="src/api/middlewares.ts"
|
```ts title="src/api/middlewares.ts"
|
||||||
import { PostCompleteSessionSchema } from "./checkout_sessions/[id]/complete/route";
|
import { PostCompleteSessionSchema } from "./checkout_sessions/[id]/complete/route"
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, add a new route configuration in `defineMiddlewares`:
|
Then, add a new route configuration in `defineMiddlewares`:
|
||||||
@@ -2865,7 +2865,7 @@ export default defineMiddlewares({
|
|||||||
{
|
{
|
||||||
matcher: "/checkout_sessions/:id/complete",
|
matcher: "/checkout_sessions/:id/complete",
|
||||||
method: ["POST"],
|
method: ["POST"],
|
||||||
middlewares: [validateAndTransformBody(PostCompleteSessionSchema)]
|
middlewares: [validateAndTransformBody(PostCompleteSessionSchema)],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// ...
|
// ...
|
||||||
@@ -3190,14 +3190,14 @@ export const cancelCheckoutSessionWorkflow = createWorkflow(
|
|||||||
"id",
|
"id",
|
||||||
"payment_collection.*",
|
"payment_collection.*",
|
||||||
"payment_collection.payment_sessions.*",
|
"payment_collection.payment_sessions.*",
|
||||||
"order.id"
|
"order.id",
|
||||||
],
|
],
|
||||||
filters: {
|
filters: {
|
||||||
id: input.cart_id,
|
id: input.cart_id,
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
throwIfKeyNotFound: true,
|
throwIfKeyNotFound: true,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
validateCartCancelationStep({
|
validateCartCancelationStep({
|
||||||
@@ -3217,11 +3217,11 @@ Next, you'll cancel the payment sessions if there are any. Replace the `TODO` in
|
|||||||
|
|
||||||
```ts title="src/workflows/cancel-checkout-session.ts"
|
```ts title="src/workflows/cancel-checkout-session.ts"
|
||||||
when({
|
when({
|
||||||
carts
|
carts,
|
||||||
}, (data) => !!data.carts[0].payment_collection?.payment_sessions?.length)
|
}, (data) => !!data.carts[0].payment_collection?.payment_sessions?.length)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const paymentSessionIds = transform({
|
const paymentSessionIds = transform({
|
||||||
carts
|
carts,
|
||||||
}, (data) => {
|
}, (data) => {
|
||||||
return data.carts[0].payment_collection?.payment_sessions?.map((session) => session!.id)
|
return data.carts[0].payment_collection?.payment_sessions?.map((session) => session!.id)
|
||||||
})
|
})
|
||||||
@@ -3235,8 +3235,8 @@ updateCartWorkflow.runAsStep({
|
|||||||
id: carts[0].id,
|
id: carts[0].id,
|
||||||
metadata: {
|
metadata: {
|
||||||
checkout_session_canceled: true,
|
checkout_session_canceled: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO prepare and return response
|
// TODO prepare and return response
|
||||||
@@ -3252,7 +3252,7 @@ Finally, you'll prepare and return the checkout session response. Replace the `T
|
|||||||
const responseData = prepareCheckoutSessionDataWorkflow.runAsStep({
|
const responseData = prepareCheckoutSessionDataWorkflow.runAsStep({
|
||||||
input: {
|
input: {
|
||||||
cart_id: carts[0].id,
|
cart_id: carts[0].id,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return new WorkflowResponse(responseData)
|
return new WorkflowResponse(responseData)
|
||||||
@@ -3287,7 +3287,7 @@ export const POST = async (
|
|||||||
},
|
},
|
||||||
context: {
|
context: {
|
||||||
idempotencyKey: req.headers["idempotency-key"] as string,
|
idempotencyKey: req.headers["idempotency-key"] as string,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
res.set(responseHeaders).json(result)
|
res.set(responseHeaders).json(result)
|
||||||
@@ -3300,8 +3300,8 @@ export const POST = async (
|
|||||||
code: "invalid",
|
code: "invalid",
|
||||||
content_type: "plain",
|
content_type: "plain",
|
||||||
content: medusaError.message,
|
content: medusaError.message,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3382,7 +3382,7 @@ export default async function orderWebhookHandler({
|
|||||||
],
|
],
|
||||||
filters: {
|
filters: {
|
||||||
id: orderId,
|
id: orderId,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// only send webhook if order is associated with a checkout session
|
// only send webhook if order is associated with a checkout session
|
||||||
@@ -3404,7 +3404,7 @@ export default async function orderWebhookHandler({
|
|||||||
type: "original_payment",
|
type: "original_payment",
|
||||||
amount: transaction!.amount * -1,
|
amount: transaction!.amount * -1,
|
||||||
})) || [],
|
})) || [],
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// set status based on order, fulfillments and transactions
|
// set status based on order, fulfillments and transactions
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const key = await cachingModuleService.computeKey(data)
|
|||||||
await cachingModuleService.set({
|
await cachingModuleService.set({
|
||||||
key,
|
key,
|
||||||
tags: ["Product:prod_123", "Product:list:*"],
|
tags: ["Product:prod_123", "Product:list:*"],
|
||||||
data
|
data,
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ const key = await cachingModuleService.computeKey(data)
|
|||||||
await cachingModuleService.set({
|
await cachingModuleService.set({
|
||||||
key,
|
key,
|
||||||
tags: ["Product:list:*", "Product:prod_123"],
|
tags: ["Product:list:*", "Product:prod_123"],
|
||||||
data
|
data,
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ const key = await cachingModuleService.computeKey(data)
|
|||||||
await cachingModuleService.set({
|
await cachingModuleService.set({
|
||||||
key,
|
key,
|
||||||
tags: ["Product:prod_123", "Product:list:*"],
|
tags: ["Product:prod_123", "Product:list:*"],
|
||||||
data
|
data,
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -108,14 +108,14 @@ export default async function connection({
|
|||||||
serverUrls = ["127.0.0.1:11211"],
|
serverUrls = ["127.0.0.1:11211"],
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
options: clientOptions
|
options: clientOptions,
|
||||||
} = options || {}
|
} = options || {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.info("Connecting to Memcached...")
|
logger.info("Connecting to Memcached...")
|
||||||
|
|
||||||
// Create Memcached client
|
// Create Memcached client
|
||||||
const client = memjs.Client.create(serverUrls.join(','), {
|
const client = memjs.Client.create(serverUrls.join(","), {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
...clientOptions,
|
...clientOptions,
|
||||||
@@ -271,9 +271,9 @@ class MemcachedCachingProviderService implements ICachingProviderService {
|
|||||||
return { data, compressed: false }
|
return { data, compressed: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = Buffer.from(data, 'utf8')
|
const buffer = Buffer.from(data, "utf8")
|
||||||
const compressed = await deflateAsync(buffer)
|
const compressed = await deflateAsync(buffer)
|
||||||
const compressedData = compressed.toString('base64')
|
const compressedData = compressed.toString("base64")
|
||||||
|
|
||||||
// Only use compression if it actually reduces size
|
// Only use compression if it actually reduces size
|
||||||
if (compressedData.length < data.length) {
|
if (compressedData.length < data.length) {
|
||||||
@@ -291,9 +291,9 @@ class MemcachedCachingProviderService implements ICachingProviderService {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = Buffer.from(data, 'base64')
|
const buffer = Buffer.from(data, "base64")
|
||||||
const decompressed = await inflateAsync(buffer)
|
const decompressed = await inflateAsync(buffer)
|
||||||
return decompressed.toString('utf8')
|
return decompressed.toString("utf8")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -411,12 +411,12 @@ class MemcachedCachingProviderService implements ICachingProviderService {
|
|||||||
// Compress data if enabled
|
// Compress data if enabled
|
||||||
const {
|
const {
|
||||||
data: finalData,
|
data: finalData,
|
||||||
compressed
|
compressed,
|
||||||
} = await this.compressData(serializedData)
|
} = await this.compressData(serializedData)
|
||||||
|
|
||||||
// Batch operations for better performance
|
// Batch operations for better performance
|
||||||
const operations: Promise<any>[] = [
|
const operations: Promise<any>[] = [
|
||||||
this.client.set(prefixedKey, finalData, setOptions)
|
this.client.set(prefixedKey, finalData, setOptions),
|
||||||
]
|
]
|
||||||
|
|
||||||
// Always store options (including compression flag) to allow checking them later
|
// Always store options (including compression flag) to allow checking them later
|
||||||
@@ -484,9 +484,9 @@ class MemcachedCachingProviderService implements ICachingProviderService {
|
|||||||
const storedTags = JSON.parse(keyTagsResult.value.toString())
|
const storedTags = JSON.parse(keyTagsResult.value.toString())
|
||||||
|
|
||||||
// Batch all namespace checks for better performance
|
// Batch all namespace checks for better performance
|
||||||
const tagKeys = Object.keys(storedTags).map(tag => this.TAG_PREFIX + tag)
|
const tagKeys = Object.keys(storedTags).map((tag) => this.TAG_PREFIX + tag)
|
||||||
const tagResults = await Promise.all(
|
const tagResults = await Promise.all(
|
||||||
tagKeys.map(tagKey => this.client.get(tagKey))
|
tagKeys.map((tagKey) => this.client.get(tagKey))
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check if any tag namespace is missing or changed
|
// Check if any tag namespace is missing or changed
|
||||||
@@ -536,7 +536,7 @@ class MemcachedCachingProviderService implements ICachingProviderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get all keys associated with each tag
|
// Get all keys associated with each tag
|
||||||
const tagKeysOperations = tags.map(tag => {
|
const tagKeysOperations = tags.map((tag) => {
|
||||||
const tagKeysKey = `${this.TAG_KEYS_PREFIX}${tag}`
|
const tagKeysKey = `${this.TAG_KEYS_PREFIX}${tag}`
|
||||||
return this.client.get(tagKeysKey)
|
return this.client.get(tagKeysKey)
|
||||||
})
|
})
|
||||||
@@ -548,7 +548,7 @@ class MemcachedCachingProviderService implements ICachingProviderService {
|
|||||||
for (const result of tagKeysResults) {
|
for (const result of tagKeysResults) {
|
||||||
if (result.value) {
|
if (result.value) {
|
||||||
const keys = JSON.parse(result.value.toString()) as string[]
|
const keys = JSON.parse(result.value.toString()) as string[]
|
||||||
keys.forEach(key => allKeys.add(key))
|
keys.forEach((key) => allKeys.add(key))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,7 +557,7 @@ class MemcachedCachingProviderService implements ICachingProviderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get all cached data for the collected keys
|
// Get all cached data for the collected keys
|
||||||
const dataOperations = Array.from(allKeys).map(async key => {
|
const dataOperations = Array.from(allKeys).map(async (key) => {
|
||||||
const prefixedKey = this.CACHE_PREFIX + key
|
const prefixedKey = this.CACHE_PREFIX + key
|
||||||
const result = await this.client.get(prefixedKey)
|
const result = await this.client.get(prefixedKey)
|
||||||
|
|
||||||
@@ -717,7 +717,7 @@ class MemcachedCachingProviderService implements ICachingProviderService {
|
|||||||
let keys: string[] = JSON.parse(tagKeysResult.value.toString()) as string[]
|
let keys: string[] = JSON.parse(tagKeysResult.value.toString()) as string[]
|
||||||
|
|
||||||
// Remove the specified keys
|
// Remove the specified keys
|
||||||
keys = keys.filter(key => !keysToRemove.includes(key))
|
keys = keys.filter((key) => !keysToRemove.includes(key))
|
||||||
|
|
||||||
if (keys.length === 0) {
|
if (keys.length === 0) {
|
||||||
// If no keys left, delete the tag keys entry
|
// If no keys left, delete the tag keys entry
|
||||||
@@ -755,7 +755,7 @@ class MemcachedCachingProviderService implements ICachingProviderService {
|
|||||||
const operations: Promise<any>[] = [
|
const operations: Promise<any>[] = [
|
||||||
this.client.delete(this.CACHE_PREFIX + key),
|
this.client.delete(this.CACHE_PREFIX + key),
|
||||||
this.client.delete(this.OPTIONS_PREFIX + key),
|
this.client.delete(this.OPTIONS_PREFIX + key),
|
||||||
this.client.delete(keyTagsKey)
|
this.client.delete(keyTagsKey),
|
||||||
]
|
]
|
||||||
|
|
||||||
// If the key has tags, remove it from tag key lists
|
// If the key has tags, remove it from tag key lists
|
||||||
@@ -764,7 +764,7 @@ class MemcachedCachingProviderService implements ICachingProviderService {
|
|||||||
const tagNames = Object.keys(storedTags)
|
const tagNames = Object.keys(storedTags)
|
||||||
|
|
||||||
// Batch tag cleanup operations
|
// Batch tag cleanup operations
|
||||||
const tagCleanupOperations = tagNames.map(async tag => {
|
const tagCleanupOperations = tagNames.map(async (tag) => {
|
||||||
await this.removeKeysFromTag(tag, [key])
|
await this.removeKeysFromTag(tag, [key])
|
||||||
})
|
})
|
||||||
operations.push(...tagCleanupOperations)
|
operations.push(...tagCleanupOperations)
|
||||||
@@ -794,7 +794,7 @@ Add the following method to the `MemcachedCachingProviderService` class:
|
|||||||
class MemcachedCachingProviderService implements ICachingProviderService {
|
class MemcachedCachingProviderService implements ICachingProviderService {
|
||||||
// ...
|
// ...
|
||||||
private async clearByTags(tags: string[]): Promise<void> {
|
private async clearByTags(tags: string[]): Promise<void> {
|
||||||
const operations = tags.map(async tag => {
|
const operations = tags.map(async (tag) => {
|
||||||
const tagKey = this.TAG_PREFIX + tag
|
const tagKey = this.TAG_PREFIX + tag
|
||||||
const result = await this.client.increment(tagKey, 1)
|
const result = await this.client.increment(tagKey, 1)
|
||||||
if (result === null) {
|
if (result === null) {
|
||||||
@@ -972,7 +972,7 @@ module.exports = defineConfig({
|
|||||||
resolve: "@medusajs/medusa/caching",
|
resolve: "@medusajs/medusa/caching",
|
||||||
options: {
|
options: {
|
||||||
in_memory: {
|
in_memory: {
|
||||||
enable: true
|
enable: true,
|
||||||
},
|
},
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
@@ -981,16 +981,16 @@ module.exports = defineConfig({
|
|||||||
// Optional, makes this the default caching provider
|
// Optional, makes this the default caching provider
|
||||||
is_default: true,
|
is_default: true,
|
||||||
options: {
|
options: {
|
||||||
serverUrls: process.env.MEMCACHED_SERVERS?.split(',') ||
|
serverUrls: process.env.MEMCACHED_SERVERS?.split(",") ||
|
||||||
["127.0.0.1:11211"],
|
["127.0.0.1:11211"],
|
||||||
// add other optional options here...
|
// add other optional options here...
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// other caching providers...
|
// other caching providers...
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1051,7 +1051,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
|||||||
key: "test-cache-products",
|
key: "test-cache-products",
|
||||||
// If you didn't set is_default to true, uncomment the following line
|
// If you didn't set is_default to true, uncomment the following line
|
||||||
// providers: ["caching-memcached"],
|
// providers: ["caching-memcached"],
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
|
|||||||
@@ -74,10 +74,10 @@ module.exports = defineConfig({
|
|||||||
// more options...
|
// more options...
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ For example:
|
|||||||
```ts highlights={[["14"], ["15"], ["16"]]}
|
```ts highlights={[["14"], ["15"], ["16"]]}
|
||||||
import {
|
import {
|
||||||
createWorkflow,
|
createWorkflow,
|
||||||
WorkflowResponse
|
WorkflowResponse,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ export const getBrandStep = createStep(
|
|||||||
|
|
||||||
const cacheKey = await cachingModuleService.computeKey(input.filters)
|
const cacheKey = await cachingModuleService.computeKey(input.filters)
|
||||||
const cachedValue = await cachingModuleService.get({
|
const cachedValue = await cachingModuleService.get({
|
||||||
tags: ["brand"]
|
tags: ["brand"],
|
||||||
})
|
})
|
||||||
|
|
||||||
if (cachedValue) {
|
if (cachedValue) {
|
||||||
@@ -213,7 +213,7 @@ export const getBrandStep = createStep(
|
|||||||
await cachingModuleService.set({
|
await cachingModuleService.set({
|
||||||
key: cacheKey,
|
key: cacheKey,
|
||||||
tags: [`Brand:${brand.id}`],
|
tags: [`Brand:${brand.id}`],
|
||||||
data: brand
|
data: brand,
|
||||||
})
|
})
|
||||||
|
|
||||||
return new StepResponse(brand)
|
return new StepResponse(brand)
|
||||||
|
|||||||
@@ -102,11 +102,11 @@ module.exports = defineConfig({
|
|||||||
options: {
|
options: {
|
||||||
// Memcached options...
|
// Memcached options...
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ const { data: products } = useQueryGraphStep({
|
|||||||
options: {
|
options: {
|
||||||
cache: {
|
cache: {
|
||||||
enable: true,
|
enable: true,
|
||||||
providers: ["caching-memcached"] // Specify Memcached provider
|
providers: ["caching-memcached"], // Specify Memcached provider
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -145,7 +145,7 @@ const cachingModuleService = container.resolve(Modules.CACHING)
|
|||||||
await cachingModuleService.set({
|
await cachingModuleService.set({
|
||||||
key: "product-list",
|
key: "product-list",
|
||||||
data: products,
|
data: products,
|
||||||
providers: ["caching-memcached"] // Specify Memcached provider
|
providers: ["caching-memcached"], // Specify Memcached provider
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ For example, you can create a workflow in `src/workflows/cache-products.ts` that
|
|||||||
```ts title="src/workflows/cache-products.ts" highlights={[["14"], ["15"], ["16"], ["17"]]}
|
```ts title="src/workflows/cache-products.ts" highlights={[["14"], ["15"], ["16"], ["17"]]}
|
||||||
import {
|
import {
|
||||||
createWorkflow,
|
createWorkflow,
|
||||||
WorkflowResponse
|
WorkflowResponse,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||||
|
|
||||||
|
|||||||
@@ -406,6 +406,7 @@ If you get a type error on resolving the Meilisearch Module, run the Medusa appl
|
|||||||
import { ProductDTO } from "@medusajs/framework/types"
|
import { ProductDTO } from "@medusajs/framework/types"
|
||||||
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||||
import { MEILISEARCH_MODULE } from "../../modules/meilisearch"
|
import { MEILISEARCH_MODULE } from "../../modules/meilisearch"
|
||||||
|
import MeilisearchModuleService from "../../modules/meilisearch/service"
|
||||||
|
|
||||||
export type SyncProductsStepInput = {
|
export type SyncProductsStepInput = {
|
||||||
products: ProductDTO[]
|
products: ProductDTO[]
|
||||||
@@ -414,7 +415,7 @@ export type SyncProductsStepInput = {
|
|||||||
export const syncProductsStep = createStep(
|
export const syncProductsStep = createStep(
|
||||||
"sync-products",
|
"sync-products",
|
||||||
async ({ products }: SyncProductsStepInput, { container }) => {
|
async ({ products }: SyncProductsStepInput, { container }) => {
|
||||||
const meilisearchModuleService = container.resolve(
|
const meilisearchModuleService = container.resolve<MeilisearchModuleService>(
|
||||||
MEILISEARCH_MODULE
|
MEILISEARCH_MODULE
|
||||||
)
|
)
|
||||||
const existingProducts = await meilisearchModuleService.retrieveFromIndex(
|
const existingProducts = await meilisearchModuleService.retrieveFromIndex(
|
||||||
@@ -470,7 +471,7 @@ export const syncProductsStep = createStep(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const meilisearchModuleService = container.resolve(
|
const meilisearchModuleService = container.resolve<MeilisearchModuleService>(
|
||||||
MEILISEARCH_MODULE
|
MEILISEARCH_MODULE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1200,13 +1200,14 @@ Create the file `src/workflows/send-order-confirmation.ts` with the following co
|
|||||||
|
|
||||||
export const workflowHighlights = [
|
export const workflowHighlights = [
|
||||||
["13", "sendOrderConfirmationWorkflow", "Create the workflow that sends an order confirmation email."],
|
["13", "sendOrderConfirmationWorkflow", "Create the workflow that sends an order confirmation email."],
|
||||||
["15", "useQueryGraphStep", "Retrieve the order's details."],
|
["16", "useQueryGraphStep", "Retrieve the order's details."],
|
||||||
["43", "sendNotificationStep", "Send the order confirmation email."]
|
["48", "sendNotificationStep", "Send the order confirmation email."]
|
||||||
]
|
]
|
||||||
|
|
||||||
```ts title="src/workflows/send-order-confirmation.ts" highlights={workflowHighlights}
|
```ts title="src/workflows/send-order-confirmation.ts" highlights={workflowHighlights}
|
||||||
import {
|
import {
|
||||||
createWorkflow,
|
createWorkflow,
|
||||||
|
when,
|
||||||
WorkflowResponse,
|
WorkflowResponse,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||||
@@ -1244,18 +1245,26 @@ export const sendOrderConfirmationWorkflow = createWorkflow(
|
|||||||
filters: {
|
filters: {
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
|
options: {
|
||||||
|
throwIfKeyNotFound: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const notification = sendNotificationStep([{
|
const notification = when({ orders }, (data) => !!data.orders[0].email)
|
||||||
to: orders[0].email,
|
.then(() => {
|
||||||
channel: "email",
|
return sendNotificationStep([{
|
||||||
template: "order-placed",
|
to: orders[0].email!,
|
||||||
data: {
|
channel: "email",
|
||||||
order: orders[0],
|
template: "order-placed",
|
||||||
},
|
data: {
|
||||||
}])
|
order: orders[0],
|
||||||
|
},
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
return new WorkflowResponse(notification)
|
return new WorkflowResponse({
|
||||||
|
notification,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
@@ -1265,7 +1274,7 @@ You create a workflow using `createWorkflow` from the Workflows SDK. It accepts
|
|||||||
It accepts as a second parameter a constructor function, which is the workflow's implementation. The workflow has the following steps:
|
It accepts as a second parameter a constructor function, which is the workflow's implementation. The workflow has the following steps:
|
||||||
|
|
||||||
1. `useQueryGraphStep`, which is a step implemented by Medusa that uses [Query](!docs!/learn/fundamentals/module-links/query), a tool that allows you to retrieve data across modules. You use it to retrieve the order's details.
|
1. `useQueryGraphStep`, which is a step implemented by Medusa that uses [Query](!docs!/learn/fundamentals/module-links/query), a tool that allows you to retrieve data across modules. You use it to retrieve the order's details.
|
||||||
2. `sendNotificationStep` which is the step you implemented. You pass it an array with one object, which is the notification's details having following properties:
|
2. Ensure that the order has an email address by using `when-then`. If so, you send the notification using the `sendNotificationStep` you implemented earlier. You pass it an object with the following properties:
|
||||||
- `to`: The address to send the email to. You pass the customer's email that is stored in the order.
|
- `to`: The address to send the email to. You pass the customer's email that is stored in the order.
|
||||||
- `channel`: The channel to send the notification through, which is `email`. Since you specified `email` in the Resend Module Provider's `channel` option, the Notification Module will delegate the sending to the Resend Module Provider's service.
|
- `channel`: The channel to send the notification through, which is `email`. Since you specified `email` in the Resend Module Provider's `channel` option, the Notification Module will delegate the sending to the Resend Module Provider's service.
|
||||||
- `template`: The email's template type. You retrieve the template content in the `ResendNotificationProviderService`'s `send` method based on the template specified here.
|
- `template`: The email's template type. You retrieve the template content in the `ResendNotificationProviderService`'s `send` method based on the template specified here.
|
||||||
@@ -1273,7 +1282,7 @@ It accepts as a second parameter a constructor function, which is the workflow's
|
|||||||
|
|
||||||
<Note title="Tip">
|
<Note title="Tip">
|
||||||
|
|
||||||
A workflow's constructor function has some constraints in implementation. Learn more about them in [this documentation](!docs!/learn/fundamentals/workflows/constructor-constraints).
|
`when` allows you to perform steps based on a condition during execution. Learn more in the [Conditions in Workflows](!docs!/learn/fundamentals/workflows/conditions) documentation.
|
||||||
|
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ export const createReturnRequest = async (
|
|||||||
return_shipping: {
|
return_shipping: {
|
||||||
option_id: returnShippingOptionId,
|
option_id: returnShippingOptionId,
|
||||||
},
|
},
|
||||||
location_id: locationId
|
location_id: locationId,
|
||||||
},
|
},
|
||||||
headers,
|
headers,
|
||||||
})
|
})
|
||||||
@@ -671,7 +671,7 @@ export const returnShippingSelectorHighlights = [
|
|||||||
|
|
||||||
import React, {
|
import React, {
|
||||||
useEffect,
|
useEffect,
|
||||||
useState
|
useState,
|
||||||
} from "react"
|
} from "react"
|
||||||
import { RadioGroup } from "@headlessui/react"
|
import { RadioGroup } from "@headlessui/react"
|
||||||
import { HttpTypes } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
@@ -911,7 +911,7 @@ const ReturnRequestTemplate: React.FC<ReturnRequestTemplateProps> = ({
|
|||||||
formData.append("items", JSON.stringify(selectedItems))
|
formData.append("items", JSON.stringify(selectedItems))
|
||||||
formData.append("return_shipping_option_id", selectedShippingOption)
|
formData.append("return_shipping_option_id", selectedShippingOption)
|
||||||
// @ts-expect-error issue in HTTP types
|
// @ts-expect-error issue in HTTP types
|
||||||
const locationId = shippingOptions.find(opt => opt.id === selectedShippingOption)?.service_zone.fulfillment_set.location.id
|
const locationId = shippingOptions.find((opt) => opt.id === selectedShippingOption)?.service_zone.fulfillment_set.location.id
|
||||||
formData.append("location_id", locationId)
|
formData.append("location_id", locationId)
|
||||||
formAction(formData)
|
formAction(formData)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ export const validateVariantOutOfStockStep = createStep(
|
|||||||
sales_channel_id,
|
sales_channel_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (availability[variant_id].availability > 0) {
|
if ((availability[variant_id].availability || 0) > 0) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.INVALID_DATA,
|
MedusaError.Types.INVALID_DATA,
|
||||||
"Variant isn't out of stock."
|
"Variant isn't out of stock."
|
||||||
@@ -1212,7 +1212,7 @@ export const getRestockedStep = createStep(
|
|||||||
sales_channel_id: restockSubscription.sales_channel_id,
|
sales_channel_id: restockSubscription.sales_channel_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (variantAvailability[restockSubscription.variant_id].availability > 0) {
|
if ((variantAvailability[restockSubscription.variant_id].availability || 0) > 0) {
|
||||||
restocked.push(restockSubscription)
|
restocked.push(restockSubscription)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -461,7 +461,7 @@ Create the file `src/workflows/create-digital-product/steps/create-digital-produ
|
|||||||
export const createDpHighlights = [
|
export const createDpHighlights = [
|
||||||
["19", "createDigitalProducts", "Create the digital product."],
|
["19", "createDigitalProducts", "Create the digital product."],
|
||||||
["24", "digital_product", "Pass the digital product to the compensation function."],
|
["24", "digital_product", "Pass the digital product to the compensation function."],
|
||||||
["31", "deleteDigitalProducts", "Delete the digital product if an error occurs in the workflow."]
|
["34", "deleteDigitalProducts", "Delete the digital product if an error occurs in the workflow."]
|
||||||
]
|
]
|
||||||
|
|
||||||
```ts title="src/workflows/create-digital-product/steps/create-digital-product.ts" highlights={createDpHighlights} collapsibleLines="1-7" expandMoreLabel="Show Imports"
|
```ts title="src/workflows/create-digital-product/steps/create-digital-product.ts" highlights={createDpHighlights} collapsibleLines="1-7" expandMoreLabel="Show Imports"
|
||||||
@@ -491,12 +491,15 @@ const createDigitalProductStep = createStep(
|
|||||||
digital_product: digitalProduct,
|
digital_product: digitalProduct,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async ({ digital_product }, { container }) => {
|
async (data, { container }) => {
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const digitalProductModuleService: DigitalProductModuleService =
|
const digitalProductModuleService: DigitalProductModuleService =
|
||||||
container.resolve(DIGITAL_PRODUCT_MODULE)
|
container.resolve(DIGITAL_PRODUCT_MODULE)
|
||||||
|
|
||||||
await digitalProductModuleService.deleteDigitalProducts(
|
await digitalProductModuleService.deleteDigitalProducts(
|
||||||
digital_product.id
|
data.digital_product.id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -515,7 +518,7 @@ Create the file `src/workflows/create-digital-product/steps/create-digital-produ
|
|||||||
export const createDigitalProductMediaHighlights = [
|
export const createDigitalProductMediaHighlights = [
|
||||||
["29", "createDigitalProductMedias", "Create the digital product medias."],
|
["29", "createDigitalProductMedias", "Create the digital product medias."],
|
||||||
["34", "digital_product_medias", "Pass the digital product medias to the compensation function."],
|
["34", "digital_product_medias", "Pass the digital product medias to the compensation function."],
|
||||||
["41", "deleteDigitalProductMedias", "Delete the digital product medias if an error occurs in the workflow."]
|
["44", "deleteDigitalProductMedias", "Delete the digital product medias if an error occurs in the workflow."]
|
||||||
]
|
]
|
||||||
|
|
||||||
```ts title="src/workflows/create-digital-product/steps/create-digital-product-medias.ts" highlights={createDigitalProductMediaHighlights} collapsibleLines="1-8" expandMoreLabel="Show Imports"
|
```ts title="src/workflows/create-digital-product/steps/create-digital-product-medias.ts" highlights={createDigitalProductMediaHighlights} collapsibleLines="1-8" expandMoreLabel="Show Imports"
|
||||||
@@ -555,12 +558,15 @@ const createDigitalProductMediasStep = createStep(
|
|||||||
digital_product_medias: digitalProductMedias,
|
digital_product_medias: digitalProductMedias,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async ({ digital_product_medias }, { container }) => {
|
async (data, { container }) => {
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const digitalProductModuleService: DigitalProductModuleService =
|
const digitalProductModuleService: DigitalProductModuleService =
|
||||||
container.resolve(DIGITAL_PRODUCT_MODULE)
|
container.resolve(DIGITAL_PRODUCT_MODULE)
|
||||||
|
|
||||||
await digitalProductModuleService.deleteDigitalProductMedias(
|
await digitalProductModuleService.deleteDigitalProductMedias(
|
||||||
digital_product_medias.map((media) => media.id)
|
data.digital_product_medias.map((media) => media.id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1883,7 +1889,7 @@ export const createDpoHighlights = [
|
|||||||
["18", "InferTypeOf", "Infer the type of the `DigitalProduct` data model since it's a variable."],
|
["18", "InferTypeOf", "Infer the type of the `DigitalProduct` data model since it's a variable."],
|
||||||
["33", "createDigitalProductOrders", "Create the digital product order."],
|
["33", "createDigitalProductOrders", "Create the digital product order."],
|
||||||
["41", "digital_product_order", "Pass the created digital product order to the compensation function."],
|
["41", "digital_product_order", "Pass the created digital product order to the compensation function."],
|
||||||
["48", "deleteDigitalProductOrders", "Delete the digital product order if an error occurs in the workflow."]
|
["51", "deleteDigitalProductOrders", "Delete the digital product order if an error occurs in the workflow."]
|
||||||
]
|
]
|
||||||
|
|
||||||
```ts title="src/workflows/create-digital-product-order/steps/create-digital-product-order.ts" highlights={createDpoHighlights} collapsibleLines="1-14" expandMoreLabel="Show Imports"
|
```ts title="src/workflows/create-digital-product-order/steps/create-digital-product-order.ts" highlights={createDpoHighlights} collapsibleLines="1-14" expandMoreLabel="Show Imports"
|
||||||
@@ -1901,7 +1907,7 @@ import DigitalProductModuleService from "../../../modules/digital-product/servic
|
|||||||
import { DIGITAL_PRODUCT_MODULE } from "../../../modules/digital-product"
|
import { DIGITAL_PRODUCT_MODULE } from "../../../modules/digital-product"
|
||||||
import DigitalProduct from "../../../modules/digital-product/models/digital-product"
|
import DigitalProduct from "../../../modules/digital-product/models/digital-product"
|
||||||
|
|
||||||
type StepInput = {
|
export type CreateDigitalProductOrderStepInput = {
|
||||||
items: (OrderLineItemDTO & {
|
items: (OrderLineItemDTO & {
|
||||||
variant: ProductVariantDTO & {
|
variant: ProductVariantDTO & {
|
||||||
digital_product: InferTypeOf<typeof DigitalProduct>
|
digital_product: InferTypeOf<typeof DigitalProduct>
|
||||||
@@ -1911,7 +1917,7 @@ type StepInput = {
|
|||||||
|
|
||||||
const createDigitalProductOrderStep = createStep(
|
const createDigitalProductOrderStep = createStep(
|
||||||
"create-digital-product-order",
|
"create-digital-product-order",
|
||||||
async ({ items }: StepInput, { container }) => {
|
async ({ items }: CreateDigitalProductOrderStepInput, { container }) => {
|
||||||
const digitalProductModuleService: DigitalProductModuleService =
|
const digitalProductModuleService: DigitalProductModuleService =
|
||||||
container.resolve(DIGITAL_PRODUCT_MODULE)
|
container.resolve(DIGITAL_PRODUCT_MODULE)
|
||||||
|
|
||||||
@@ -1929,12 +1935,15 @@ const createDigitalProductOrderStep = createStep(
|
|||||||
digital_product_order: digitalProductOrder,
|
digital_product_order: digitalProductOrder,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async ({ digital_product_order }, { container }) => {
|
async (data, { container }) => {
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const digitalProductModuleService: DigitalProductModuleService =
|
const digitalProductModuleService: DigitalProductModuleService =
|
||||||
container.resolve(DIGITAL_PRODUCT_MODULE)
|
container.resolve(DIGITAL_PRODUCT_MODULE)
|
||||||
|
|
||||||
await digitalProductModuleService.deleteDigitalProductOrders(
|
await digitalProductModuleService.deleteDigitalProductOrders(
|
||||||
digital_product_order.id
|
data.digital_product_order.id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1951,14 +1960,14 @@ In the compensation function, you delete the digital product order.
|
|||||||
Create the file `src/workflows/create-digital-product-order/index.ts` with the following content:
|
Create the file `src/workflows/create-digital-product-order/index.ts` with the following content:
|
||||||
|
|
||||||
export const createDpoWorkflowHighlights = [
|
export const createDpoWorkflowHighlights = [
|
||||||
["27", "completeCartWorkflow", "Create an order for the cart."],
|
["29", "completeCartWorkflow", "Create an order for the cart."],
|
||||||
["33", "useQueryGraphStep", "Retrieve the order's items and their associated variants and linked digital products."],
|
["35", "useQueryGraphStep", "Retrieve the order's items and their associated variants and linked digital products."],
|
||||||
["58", "when", "Check whether the order has any digital products."],
|
["60", "when", "Check whether the order has any digital products."],
|
||||||
["64", "then", "Perform the callback function if an order has digital products."],
|
["66", "then", "Perform the callback function if an order has digital products."],
|
||||||
["67", "createDigitalProductOrderStep", "Create the digital product order."],
|
["69", "createDigitalProductOrderStep", "Create the digital product order."],
|
||||||
["71", "createRemoteLinkStep", "Link the digital product order to the Medusa order."],
|
["73", "createRemoteLinkStep", "Link the digital product order to the Medusa order."],
|
||||||
["80", "createOrderFulfillmentWorkflow", "Create a fulfillment for the digital products in the order."],
|
["82", "createOrderFulfillmentWorkflow", "Create a fulfillment for the digital products in the order."],
|
||||||
["94", "emitEventStep", "Emit the `digital_product_order.created` event."]
|
["96", "emitEventStep", "Emit the `digital_product_order.created` event."]
|
||||||
]
|
]
|
||||||
|
|
||||||
```ts title="src/workflows/create-digital-product-order/index.ts" highlights={createDpoWorkflowHighlights} collapsibleLines="1-17" expandMoreLabel="Show Imports"
|
```ts title="src/workflows/create-digital-product-order/index.ts" highlights={createDpoWorkflowHighlights} collapsibleLines="1-17" expandMoreLabel="Show Imports"
|
||||||
@@ -1978,7 +1987,9 @@ import {
|
|||||||
import {
|
import {
|
||||||
Modules,
|
Modules,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import createDigitalProductOrderStep from "./steps/create-digital-product-order"
|
import createDigitalProductOrderStep, {
|
||||||
|
CreateDigitalProductOrderStepInput,
|
||||||
|
} from "./steps/create-digital-product-order"
|
||||||
import { DIGITAL_PRODUCT_MODULE } from "../../modules/digital-product"
|
import { DIGITAL_PRODUCT_MODULE } from "../../modules/digital-product"
|
||||||
|
|
||||||
type WorkflowInput = {
|
type WorkflowInput = {
|
||||||
@@ -2015,7 +2026,7 @@ const createDigitalProductOrderWorkflow = createWorkflow(
|
|||||||
orders,
|
orders,
|
||||||
},
|
},
|
||||||
(data) => {
|
(data) => {
|
||||||
return data.orders[0].items.filter((item) => item.variant.digital_product !== undefined)
|
return data.orders[0].items?.filter((item) => item?.variant?.digital_product !== undefined)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2023,14 +2034,14 @@ const createDigitalProductOrderWorkflow = createWorkflow(
|
|||||||
"create-digital-product-order-condition",
|
"create-digital-product-order-condition",
|
||||||
itemsWithDigitalProducts,
|
itemsWithDigitalProducts,
|
||||||
(itemsWithDigitalProducts) => {
|
(itemsWithDigitalProducts) => {
|
||||||
return itemsWithDigitalProducts.length
|
return !!itemsWithDigitalProducts?.length
|
||||||
}
|
}
|
||||||
).then(() => {
|
).then(() => {
|
||||||
const {
|
const {
|
||||||
digital_product_order,
|
digital_product_order,
|
||||||
} = createDigitalProductOrderStep({
|
} = createDigitalProductOrderStep({
|
||||||
items: orders[0].items,
|
items: orders[0].items,
|
||||||
})
|
} as unknown as CreateDigitalProductOrderStepInput)
|
||||||
|
|
||||||
createRemoteLinkStep([{
|
createRemoteLinkStep([{
|
||||||
[DIGITAL_PRODUCT_MODULE]: {
|
[DIGITAL_PRODUCT_MODULE]: {
|
||||||
@@ -2047,9 +2058,9 @@ const createDigitalProductOrderWorkflow = createWorkflow(
|
|||||||
items: transform({
|
items: transform({
|
||||||
itemsWithDigitalProducts,
|
itemsWithDigitalProducts,
|
||||||
}, (data) => {
|
}, (data) => {
|
||||||
return data.itemsWithDigitalProducts.map((item) => ({
|
return data.itemsWithDigitalProducts!.map((item) => ({
|
||||||
id: item.id,
|
id: item!.id,
|
||||||
quantity: item.quantity,
|
quantity: item!.quantity,
|
||||||
}))
|
}))
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -2225,10 +2236,14 @@ import {
|
|||||||
INotificationModuleService,
|
INotificationModuleService,
|
||||||
IFileModuleService,
|
IFileModuleService,
|
||||||
} from "@medusajs/framework/types"
|
} from "@medusajs/framework/types"
|
||||||
import { ModuleRegistrationName } from "@medusajs/framework/utils"
|
import {
|
||||||
|
MedusaError,
|
||||||
|
ModuleRegistrationName,
|
||||||
|
promiseAll,
|
||||||
|
} from "@medusajs/framework/utils"
|
||||||
import { DigitalProductOrder, MediaType } from "../../../modules/digital-product/types"
|
import { DigitalProductOrder, MediaType } from "../../../modules/digital-product/types"
|
||||||
|
|
||||||
type SendDigitalOrderNotificationStepInput = {
|
export type SendDigitalOrderNotificationStepInput = {
|
||||||
digital_product_order: DigitalProductOrder
|
digital_product_order: DigitalProductOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2244,6 +2259,20 @@ export const sendDigitalOrderNotificationStep = createStep(
|
|||||||
ModuleRegistrationName.FILE
|
ModuleRegistrationName.FILE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!digitalProductOrder.order) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"Digital product order is missing associated order."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!digitalProductOrder.order.email) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"Order is missing email."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO assemble notification
|
// TODO assemble notification
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -2251,16 +2280,16 @@ export const sendDigitalOrderNotificationStep = createStep(
|
|||||||
|
|
||||||
This creates the `sendDigitalOrderNotificationStep` step that receives a digital product order as an input.
|
This creates the `sendDigitalOrderNotificationStep` step that receives a digital product order as an input.
|
||||||
|
|
||||||
In the step, so far you resolve the main services of the Notification and File Modules.
|
In the step, so far you resolve the main services of the Notification and File Modules. You also check that the digital product order has an associated order and that the order has an email.
|
||||||
|
|
||||||
Replace the `TODO` with the following:
|
Next, you'll prepare the data to pass in the notification. Replace the `TODO` with the following:
|
||||||
|
|
||||||
```ts title="src/workflows/fulfill-digital-order/steps/send-digital-order-notification.ts"
|
```ts title="src/workflows/fulfill-digital-order/steps/send-digital-order-notification.ts"
|
||||||
const notificationData = await Promise.all(
|
const notificationData = await promiseAll(
|
||||||
digitalProductOrder.products.map(async (product) => {
|
digitalProductOrder.products.map(async (product) => {
|
||||||
const medias = []
|
const medias: string[] = []
|
||||||
|
|
||||||
await Promise.all(
|
await promiseAll(
|
||||||
product.medias
|
product.medias
|
||||||
.filter((media) => media.type === MediaType.MAIN)
|
.filter((media) => media.type === MediaType.MAIN)
|
||||||
.map(async (media) => {
|
.map(async (media) => {
|
||||||
@@ -2304,12 +2333,12 @@ You use the `createNotifications` method of the Notification Module's main servi
|
|||||||
Create the workflow in the file `src/workflows/fulfill-digital-order/index.ts`:
|
Create the workflow in the file `src/workflows/fulfill-digital-order/index.ts`:
|
||||||
|
|
||||||
export const fulfillWorkflowHighlights = [
|
export const fulfillWorkflowHighlights = [
|
||||||
["18", "useQueryGraphStep", "Retrieve the digital product order's details."],
|
["21", "useQueryGraphStep", "Retrieve the digital product order's details."],
|
||||||
["35", "sendDigitalOrderNotificationStep", "Send a notification to the customer."],
|
["38", "sendDigitalOrderNotificationStep", "Send a notification to the customer."],
|
||||||
["39", "markOrderFulfillmentAsDeliveredWorkflow", "Mark the order's fulfillment as delivered."],
|
["44", "markOrderFulfillmentAsDeliveredWorkflow", "Mark the order's fulfillment as delivered."],
|
||||||
]
|
]
|
||||||
|
|
||||||
```ts title="src/workflows/fulfill-digital-order/index.ts" highlights={fulfillWorkflowHighlights} collapsibleLines="1-10" expandMoreLabel="Show Imports"
|
```ts title="src/workflows/fulfill-digital-order/index.ts" highlights={fulfillWorkflowHighlights} collapsibleLines="1-13" expandMoreLabel="Show Imports"
|
||||||
import {
|
import {
|
||||||
createWorkflow,
|
createWorkflow,
|
||||||
WorkflowResponse,
|
WorkflowResponse,
|
||||||
@@ -2318,7 +2347,10 @@ import {
|
|||||||
markOrderFulfillmentAsDeliveredWorkflow,
|
markOrderFulfillmentAsDeliveredWorkflow,
|
||||||
useQueryGraphStep,
|
useQueryGraphStep,
|
||||||
} from "@medusajs/medusa/core-flows"
|
} from "@medusajs/medusa/core-flows"
|
||||||
import { sendDigitalOrderNotificationStep } from "./steps/send-digital-order-notification"
|
import {
|
||||||
|
sendDigitalOrderNotificationStep,
|
||||||
|
SendDigitalOrderNotificationStepInput,
|
||||||
|
} from "./steps/send-digital-order-notification"
|
||||||
|
|
||||||
type FulfillDigitalOrderWorkflowInput = {
|
type FulfillDigitalOrderWorkflowInput = {
|
||||||
id: string
|
id: string
|
||||||
@@ -2346,14 +2378,17 @@ export const fulfillDigitalOrderWorkflow = createWorkflow(
|
|||||||
|
|
||||||
sendDigitalOrderNotificationStep({
|
sendDigitalOrderNotificationStep({
|
||||||
digital_product_order: digitalProductOrders[0],
|
digital_product_order: digitalProductOrders[0],
|
||||||
})
|
} as unknown as SendDigitalOrderNotificationStepInput)
|
||||||
|
|
||||||
markOrderFulfillmentAsDeliveredWorkflow.runAsStep({
|
when({ digitalProductOrders }, (data) => !!data.digitalProductOrders[0].order?.fulfillments?.length)
|
||||||
input: {
|
.then(() => {
|
||||||
orderId: digitalProductOrders[0].order.id,
|
markOrderFulfillmentAsDeliveredWorkflow.runAsStep({
|
||||||
fulfillmentId: digitalProductOrders[0].order.fulfillments[0].id,
|
input: {
|
||||||
},
|
orderId: digitalProductOrders[0].order!.id,
|
||||||
})
|
fulfillmentId: digitalProductOrders[0].order!.fulfillments![0]!.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
return new WorkflowResponse(
|
return new WorkflowResponse(
|
||||||
digitalProductOrders[0]
|
digitalProductOrders[0]
|
||||||
@@ -2366,7 +2401,13 @@ In the workflow, you:
|
|||||||
|
|
||||||
1. Retrieve the digital product order's details using `useQueryGraphStep` from Medusa's core workflows.
|
1. Retrieve the digital product order's details using `useQueryGraphStep` from Medusa's core workflows.
|
||||||
2. Send a notification to the customer with the digital product download links using the `sendDigitalOrderNotificationStep`.
|
2. Send a notification to the customer with the digital product download links using the `sendDigitalOrderNotificationStep`.
|
||||||
3. Mark the order's fulfillment as delivered using `markOrderFulfillmentAsDeliveredWorkflow` from Medusa's core workflows.
|
3. Check whether the Medusa order has fulfillments. If so, mark the order's fulfillment as delivered using `markOrderFulfillmentAsDeliveredWorkflow` from Medusa's core workflows.
|
||||||
|
|
||||||
|
<Note title="Tip">
|
||||||
|
|
||||||
|
`when` allows you to perform steps based on a condition during execution. Learn more in the [Conditions in Workflows](!docs!/learn/fundamentals/workflows/conditions) documentation.
|
||||||
|
|
||||||
|
</Note>
|
||||||
|
|
||||||
### Configure Notification Module Provider
|
### Configure Notification Module Provider
|
||||||
|
|
||||||
@@ -2555,8 +2596,11 @@ export const GET = async (
|
|||||||
|
|
||||||
const digitalProducts = {}
|
const digitalProducts = {}
|
||||||
|
|
||||||
customer.orders.forEach((order) => {
|
customer.orders?.forEach((order) => {
|
||||||
order.digital_product_order.products.forEach((product) => {
|
order?.digital_product_order?.products.forEach((product) => {
|
||||||
|
if (!product) {
|
||||||
|
return
|
||||||
|
}
|
||||||
digitalProducts[product.id] = product
|
digitalProducts[product.id] = product
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -2613,9 +2657,9 @@ export const POST = async (
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const customerDigitalOrderIds = customer.orders
|
const customerDigitalOrderIds = customer.orders?.filter(
|
||||||
.filter((order) => order.digital_product_order !== undefined)
|
(order) => order?.digital_product_order !== undefined
|
||||||
.map((order) => order.digital_product_order.id)
|
).map((order) => order!.digital_product_order!.id)
|
||||||
|
|
||||||
const { data: dpoResult } = await query.graph({
|
const { data: dpoResult } = await query.graph({
|
||||||
entity: "digital_product_order",
|
entity: "digital_product_order",
|
||||||
@@ -2634,11 +2678,11 @@ export const POST = async (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let foundMedia = undefined
|
let foundMedia: any | undefined = undefined
|
||||||
|
|
||||||
dpoResult[0].products.some((product) => {
|
dpoResult[0].products.some((product) => {
|
||||||
return product.medias.some((media) => {
|
return product?.medias.some((media) => {
|
||||||
foundMedia = media.id === req.params.mediaId ? media : undefined
|
foundMedia = media?.id === req.params.mediaId ? media : undefined
|
||||||
|
|
||||||
return foundMedia !== undefined
|
return foundMedia !== undefined
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -231,15 +231,9 @@ export enum DeliveryStatus {
|
|||||||
IN_TRANSIT = "in_transit",
|
IN_TRANSIT = "in_transit",
|
||||||
DELIVERED = "delivered",
|
DELIVERED = "delivered",
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "@medusajs/framework/types" {
|
|
||||||
export interface ModuleImplementations {
|
|
||||||
deliveryModuleService: DeliveryModuleService;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This adds an enum that is used by the data models. It also adds a type for `deliveryModuleService` in `ModuleImplementations` so that when you resolve it from the Medusa container, it has the correct typing.
|
This adds an enum that is used by the data models.
|
||||||
|
|
||||||
### Create Delivery Data Models
|
### Create Delivery Data Models
|
||||||
|
|
||||||
@@ -489,15 +483,9 @@ import { Restaurant } from "../models/restaurant"
|
|||||||
export type CreateRestaurant = Omit<
|
export type CreateRestaurant = Omit<
|
||||||
InferTypeOf<typeof Restaurant>, "id" | "admins"
|
InferTypeOf<typeof Restaurant>, "id" | "admins"
|
||||||
>
|
>
|
||||||
|
|
||||||
declare module "@medusajs/framework/types" {
|
|
||||||
export interface ModuleImplementations {
|
|
||||||
restaurantModuleService: RestaurantModuleService;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This adds a type used for inputs in creating a restaurant. It also adds a type for `restaurantModuleService` in `ModuleImplementations` so that when you resolve it from the Medusa container, it has the correct typing.
|
This adds a type used for inputs in creating a restaurant.
|
||||||
|
|
||||||
<Note title="Tip">
|
<Note title="Tip">
|
||||||
|
|
||||||
@@ -920,14 +908,16 @@ const { user, authUserInput } = transform({ input, restaurantUser, driverUser },
|
|||||||
authUserInput: {
|
authUserInput: {
|
||||||
authIdentityId: data.input.auth_identity_id,
|
authIdentityId: data.input.auth_identity_id,
|
||||||
actorType: data.input.user.actor_type,
|
actorType: data.input.user.actor_type,
|
||||||
value: user.id,
|
value: user?.id || "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
setAuthAppMetadataStep(authUserInput)
|
setAuthAppMetadataStep(authUserInput)
|
||||||
|
|
||||||
return new WorkflowResponse(user)
|
return new WorkflowResponse({
|
||||||
|
user,
|
||||||
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
In the workflow, you:
|
In the workflow, you:
|
||||||
@@ -987,7 +977,7 @@ export const POST = async (
|
|||||||
} as CreateUserWorkflowInput,
|
} as CreateUserWorkflowInput,
|
||||||
})
|
})
|
||||||
|
|
||||||
res.status(201).json({ user: result })
|
res.status(201).json({ user: result.user })
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -1133,12 +1123,15 @@ export const deleteRestaurantAdminStep = createStep(
|
|||||||
|
|
||||||
return new StepResponse(undefined, { admin })
|
return new StepResponse(undefined, { admin })
|
||||||
},
|
},
|
||||||
async ({ admin }, { container }) => {
|
async (data, { container }) => {
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const restaurantModuleService: RestaurantModuleService = container.resolve(
|
const restaurantModuleService: RestaurantModuleService = container.resolve(
|
||||||
RESTAURANT_MODULE
|
RESTAURANT_MODULE
|
||||||
)
|
)
|
||||||
|
|
||||||
const { restaurant: _, ...adminData } = admin
|
const { restaurant: _, ...adminData } = data.admin
|
||||||
|
|
||||||
await restaurantModuleService.createRestaurantAdmins(adminData)
|
await restaurantModuleService.createRestaurantAdmins(adminData)
|
||||||
}
|
}
|
||||||
@@ -1190,6 +1183,7 @@ const { data: authIdentities } = useQueryGraphStep({
|
|||||||
entity: "auth_identity",
|
entity: "auth_identity",
|
||||||
fields: ["id"],
|
fields: ["id"],
|
||||||
filters: {
|
filters: {
|
||||||
|
// @ts-ignore
|
||||||
app_metadata: {
|
app_metadata: {
|
||||||
restaurant_id: input.id,
|
restaurant_id: input.id,
|
||||||
},
|
},
|
||||||
@@ -1498,7 +1492,7 @@ Next, create the file `src/workflows/delivery/steps/create-delivery.ts` with the
|
|||||||
|
|
||||||
export const createDeliveryStepHighlights = [
|
export const createDeliveryStepHighlights = [
|
||||||
["11", "createDeliveries", "Create the delivery."],
|
["11", "createDeliveries", "Create the delivery."],
|
||||||
["21", "softDeleteDeliveries", "Delete the delivery if an error occurs in the workflow."]
|
["24", "softDeleteDeliveries", "Delete the delivery if an error occurs in the workflow."]
|
||||||
]
|
]
|
||||||
|
|
||||||
```ts title="src/workflows/delivery/steps/create-delivery.ts" highlights={createDeliveryStepHighlights}
|
```ts title="src/workflows/delivery/steps/create-delivery.ts" highlights={createDeliveryStepHighlights}
|
||||||
@@ -1518,11 +1512,14 @@ export const createDeliveryStep = createStep(
|
|||||||
delivery_id: delivery.id,
|
delivery_id: delivery.id,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async function ({ delivery_id }, { container }) {
|
async function (data, { container }) {
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const deliverModuleService: DeliveryModuleService =
|
const deliverModuleService: DeliveryModuleService =
|
||||||
container.resolve(DELIVERY_MODULE)
|
container.resolve(DELIVERY_MODULE)
|
||||||
|
|
||||||
deliverModuleService.softDeleteDeliveries(delivery_id)
|
deliverModuleService.softDeleteDeliveries(data.delivery_id)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1535,10 +1532,10 @@ This step creates a delivery and returns it. In the compensation function, it de
|
|||||||
Finally, create the workflow in `src/workflows/delivery/workflows/create-delivery.ts`:
|
Finally, create the workflow in `src/workflows/delivery/workflows/create-delivery.ts`:
|
||||||
|
|
||||||
export const createDeliveryWorkflowHighlights = [
|
export const createDeliveryWorkflowHighlights = [
|
||||||
["23", "validateRestaurantStep", "Ensure that the specified restaurant exists before creating the delivery."],
|
["24", "validateRestaurantStep", "Ensure that the specified restaurant exists before creating the delivery."],
|
||||||
["26", "createDeliveryStep", "Create the delivery."],
|
["27", "createDeliveryStep", "Create the delivery."],
|
||||||
["28", "transform", "Define an array of link objects to create."],
|
["29", "transform", "Define an array of link objects to create."],
|
||||||
["50", "createRemoteLinkStep", "Create the links between the delivery and cart and restaurant."]
|
["51", "createRemoteLinkStep", "Create the links between the delivery and cart and restaurant."]
|
||||||
]
|
]
|
||||||
|
|
||||||
```ts title="src/workflows/delivery/workflows/create-delivery.ts" highlights={createDeliveryWorkflowHighlights} collapsibleLines="1-13" expandButtonLabel="Show Imports"
|
```ts title="src/workflows/delivery/workflows/create-delivery.ts" highlights={createDeliveryWorkflowHighlights} collapsibleLines="1-13" expandButtonLabel="Show Imports"
|
||||||
@@ -1548,6 +1545,7 @@ import {
|
|||||||
createWorkflow,
|
createWorkflow,
|
||||||
transform,
|
transform,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
|
import { LinkDefinition } from "@medusajs/framework/types"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
|
import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
|
||||||
import { DELIVERY_MODULE } from "../../../modules/delivery"
|
import { DELIVERY_MODULE } from "../../../modules/delivery"
|
||||||
@@ -1589,7 +1587,7 @@ export const createDeliveryWorkflow = createWorkflow(
|
|||||||
delivery_id: data.delivery.id,
|
delivery_id: data.delivery.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]))
|
] as LinkDefinition[]))
|
||||||
|
|
||||||
createRemoteLinkStep(links)
|
createRemoteLinkStep(links)
|
||||||
|
|
||||||
@@ -1787,7 +1785,7 @@ export const notifyRestaurantStep = createStep(
|
|||||||
await eventBus.emit({
|
await eventBus.emit({
|
||||||
name: "notify.restaurant",
|
name: "notify.restaurant",
|
||||||
data: {
|
data: {
|
||||||
restaurant_id: delivery.restaurant.id,
|
restaurant_id: delivery.restaurant?.id,
|
||||||
delivery_id: delivery.id,
|
delivery_id: delivery.id,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -1837,7 +1835,7 @@ This step is async and its only purpose is to wait until it’s marked as succes
|
|||||||
Create the file `src/workflows/delivery/steps/create-order.ts` with the following content:
|
Create the file `src/workflows/delivery/steps/create-order.ts` with the following content:
|
||||||
|
|
||||||
export const createOrderStepHighlights1 = [
|
export const createOrderStepHighlights1 = [
|
||||||
["15", "deliveryQuery", "Retrieve the delivery with its linked cart's details."]
|
["16", "deliveryQuery", "Retrieve the delivery with its linked cart's details."]
|
||||||
]
|
]
|
||||||
|
|
||||||
```ts title="src/workflows/delivery/steps/create-order.ts" highlights={createOrderStepHighlights1} collapsibleLines="1-9" expandButtonLabel="Show Imports"
|
```ts title="src/workflows/delivery/steps/create-order.ts" highlights={createOrderStepHighlights1} collapsibleLines="1-9" expandButtonLabel="Show Imports"
|
||||||
@@ -1845,6 +1843,7 @@ import { CreateOrderShippingMethodDTO } from "@medusajs/framework/types"
|
|||||||
import {
|
import {
|
||||||
Modules,
|
Modules,
|
||||||
ContainerRegistrationKeys,
|
ContainerRegistrationKeys,
|
||||||
|
MedusaError,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk"
|
||||||
import { DELIVERY_MODULE } from "../../../modules/delivery"
|
import { DELIVERY_MODULE } from "../../../modules/delivery"
|
||||||
@@ -1871,7 +1870,7 @@ export const createOrderStep = createStep(
|
|||||||
|
|
||||||
// TODO create order
|
// TODO create order
|
||||||
},
|
},
|
||||||
async ({ orderId }, { container }) => {
|
async (data, { container }) => {
|
||||||
// TODO add compensation
|
// TODO add compensation
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1883,26 +1882,66 @@ This creates the `createOrderStep`, which so far only retrieves the delivery wit
|
|||||||
Replace the `TODO` with the following to create the order:
|
Replace the `TODO` with the following to create the order:
|
||||||
|
|
||||||
export const createOrderStepHighlights2 = [
|
export const createOrderStepHighlights2 = [
|
||||||
["5", "createOrders", "Create the order."],
|
["12", "createOrders", "Create the order."],
|
||||||
["18", "linkDef", "Define an array of links to be created."]
|
["58", "linkDef", "Define an array of links to be created."]
|
||||||
]
|
]
|
||||||
|
|
||||||
```ts title="src/workflows/delivery/steps/create-order.ts" highlights={createOrderStepHighlights2}
|
```ts title="src/workflows/delivery/steps/create-order.ts" highlights={createOrderStepHighlights2}
|
||||||
const { cart } = delivery
|
const { cart } = delivery
|
||||||
|
|
||||||
|
if (!cart) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Cart for delivery with id: ${deliveryId} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const orderModuleService = container.resolve(Modules.ORDER)
|
const orderModuleService = container.resolve(Modules.ORDER)
|
||||||
|
|
||||||
const order = await orderModuleService.createOrders({
|
const order = await orderModuleService.createOrders({
|
||||||
currency_code: cart.currency_code,
|
currency_code: cart.currency_code,
|
||||||
email: cart.email,
|
email: cart.email,
|
||||||
shipping_address: cart.shipping_address,
|
shipping_address: {
|
||||||
billing_address: cart.billing_address,
|
first_name: cart.shipping_address?.first_name || "",
|
||||||
items: cart.items,
|
last_name: cart.shipping_address?.last_name || "",
|
||||||
region_id: cart.region_id,
|
address_1: cart.shipping_address?.address_1 || "",
|
||||||
customer_id: cart.customer_id,
|
address_2: cart.shipping_address?.address_2 || "",
|
||||||
sales_channel_id: cart.sales_channel_id,
|
city: cart.shipping_address?.city || "",
|
||||||
|
province: cart.shipping_address?.province || "",
|
||||||
|
postal_code: cart.shipping_address?.postal_code || "",
|
||||||
|
country_code: cart.shipping_address?.country_code || "",
|
||||||
|
phone: cart.shipping_address?.phone || "",
|
||||||
|
},
|
||||||
|
billing_address: {
|
||||||
|
first_name: cart.billing_address?.first_name || "",
|
||||||
|
last_name: cart.billing_address?.last_name || "",
|
||||||
|
address_1: cart.billing_address?.address_1 || "",
|
||||||
|
address_2: cart.billing_address?.address_2 || "",
|
||||||
|
city: cart.billing_address?.city || "",
|
||||||
|
province: cart.billing_address?.province || "",
|
||||||
|
postal_code: cart.billing_address?.postal_code || "",
|
||||||
|
country_code: cart.billing_address?.country_code || "",
|
||||||
|
phone: cart.billing_address?.phone || "",
|
||||||
|
},
|
||||||
|
items: cart.items.map((item) => ({
|
||||||
|
title: item!.title,
|
||||||
|
quantity: item!.quantity,
|
||||||
|
variant_id: item!.variant_id || "",
|
||||||
|
unit_price: item!.unit_price,
|
||||||
|
metadata: item!.metadata || undefined,
|
||||||
|
product_id: item!.product_id || "",
|
||||||
|
})),
|
||||||
|
region_id: cart.region_id || "",
|
||||||
|
customer_id: cart.customer_id || "",
|
||||||
|
sales_channel_id: cart.sales_channel_id || "",
|
||||||
shipping_methods:
|
shipping_methods:
|
||||||
cart.shipping_methods as unknown as CreateOrderShippingMethodDTO[],
|
cart.shipping_methods?.map((sm): CreateOrderShippingMethodDTO => ({
|
||||||
|
shipping_option_id: sm?.shipping_option_id || "",
|
||||||
|
data: sm?.data || {},
|
||||||
|
name: sm?.name || "",
|
||||||
|
amount: sm?.amount || 0,
|
||||||
|
order_id: "", // will be set internally
|
||||||
|
})) || [],
|
||||||
})
|
})
|
||||||
|
|
||||||
const linkDef = [{
|
const linkDef = [{
|
||||||
@@ -1927,9 +1966,12 @@ You create the order using the Order Module’s main service. Then, you create a
|
|||||||
Then, replace the `TODO` in the compensation function with the following:
|
Then, replace the `TODO` in the compensation function with the following:
|
||||||
|
|
||||||
```ts title="src/workflows/delivery/steps/create-order.ts"
|
```ts title="src/workflows/delivery/steps/create-order.ts"
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const orderService = container.resolve(Modules.ORDER)
|
const orderService = container.resolve(Modules.ORDER)
|
||||||
|
|
||||||
await orderService.softDeleteOrders([orderId])
|
await orderService.softDeleteOrders([data.orderId])
|
||||||
```
|
```
|
||||||
|
|
||||||
You delete the order in the compensation function.
|
You delete the order in the compensation function.
|
||||||
@@ -2374,7 +2416,7 @@ So, start by creating the first step at `src/workflows/delivery/steps/update-del
|
|||||||
export const updateDeliveryStepHighlights = [
|
export const updateDeliveryStepHighlights = [
|
||||||
["16", "prevDeliveryData", "Retrieve the previous data of the delivery for the compensation."],
|
["16", "prevDeliveryData", "Retrieve the previous data of the delivery for the compensation."],
|
||||||
["19", "updateDeliveries", "Update the delivery."],
|
["19", "updateDeliveries", "Update the delivery."],
|
||||||
["35", "updateDeliveries", "Update the delivery with the old data if an error occurs in the workflow."]
|
["38", "updateDeliveries", "Update the delivery with the old data if an error occurs in the workflow."]
|
||||||
]
|
]
|
||||||
|
|
||||||
```ts title="src/workflows/delivery/steps/update-delivery.ts" highlights={updateDeliveryStepHighlights}
|
```ts title="src/workflows/delivery/steps/update-delivery.ts" highlights={updateDeliveryStepHighlights}
|
||||||
@@ -2403,14 +2445,17 @@ export const updateDeliveryStep = createStep(
|
|||||||
prevDeliveryData,
|
prevDeliveryData,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async ({ prevDeliveryData }, { container }) => {
|
async (data, { container }) => {
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const deliverModuleService: DeliveryModuleService =
|
const deliverModuleService: DeliveryModuleService =
|
||||||
container.resolve(DELIVERY_MODULE)
|
container.resolve(DELIVERY_MODULE)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
driver,
|
driver,
|
||||||
...prevDeliveryDataWithoutDriver
|
...prevDeliveryDataWithoutDriver
|
||||||
} = prevDeliveryData
|
} = data.prevDeliveryData
|
||||||
|
|
||||||
await deliverModuleService.updateDeliveries(prevDeliveryDataWithoutDriver)
|
await deliverModuleService.updateDeliveries(prevDeliveryDataWithoutDriver)
|
||||||
}
|
}
|
||||||
@@ -2452,7 +2497,7 @@ export const setStepSuccessStep = createStep(
|
|||||||
await engineService.setStepSuccess({
|
await engineService.setStepSuccess({
|
||||||
idempotencyKey: {
|
idempotencyKey: {
|
||||||
action: TransactionHandlerType.INVOKE,
|
action: TransactionHandlerType.INVOKE,
|
||||||
transactionId: updatedDelivery.transaction_id,
|
transactionId: updatedDelivery.transaction_id || "",
|
||||||
stepId,
|
stepId,
|
||||||
workflowId: handleDeliveryWorkflowId,
|
workflowId: handleDeliveryWorkflowId,
|
||||||
},
|
},
|
||||||
@@ -2500,7 +2545,7 @@ export const setStepFailedStep = createStep(
|
|||||||
await engineService.setStepFailure({
|
await engineService.setStepFailure({
|
||||||
idempotencyKey: {
|
idempotencyKey: {
|
||||||
action: TransactionHandlerType.INVOKE,
|
action: TransactionHandlerType.INVOKE,
|
||||||
transactionId: updatedDelivery.transaction_id,
|
transactionId: updatedDelivery.transaction_id || "",
|
||||||
stepId,
|
stepId,
|
||||||
workflowId: handleDeliveryWorkflowId,
|
workflowId: handleDeliveryWorkflowId,
|
||||||
},
|
},
|
||||||
@@ -2551,7 +2596,7 @@ export const updateDeliveryWorkflow = createWorkflow(
|
|||||||
when(input, ({ stepIdToSucceed }) => stepIdToSucceed !== undefined)
|
when(input, ({ stepIdToSucceed }) => stepIdToSucceed !== undefined)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setStepSuccessStep({
|
setStepSuccessStep({
|
||||||
stepId: input.stepIdToSucceed,
|
stepId: input.stepIdToSucceed!,
|
||||||
updatedDelivery,
|
updatedDelivery,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -2560,7 +2605,7 @@ export const updateDeliveryWorkflow = createWorkflow(
|
|||||||
when(input, ({ stepIdToFail }) => stepIdToFail !== undefined)
|
when(input, ({ stepIdToFail }) => stepIdToFail !== undefined)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setStepFailedStep({
|
setStepFailedStep({
|
||||||
stepId: input.stepIdToFail,
|
stepId: input.stepIdToFail!,
|
||||||
updatedDelivery,
|
updatedDelivery,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -2688,7 +2733,7 @@ export const isDeliveryRestaurant = async (
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (delivery.restaurant.id !== restaurantAdmin.restaurant.id) {
|
if (delivery.restaurant?.id !== restaurantAdmin.restaurant.id) {
|
||||||
return res.status(403).json({
|
return res.status(403).json({
|
||||||
message: "unauthorized",
|
message: "unauthorized",
|
||||||
})
|
})
|
||||||
@@ -2738,7 +2783,7 @@ import deliveriesMiddlewares from "./deliveries/[id]/middlewares"
|
|||||||
export default defineMiddlewares({
|
export default defineMiddlewares({
|
||||||
routes: [
|
routes: [
|
||||||
// ...
|
// ...
|
||||||
...deliveriesMiddlewares.routes,
|
...deliveriesMiddlewares.routes!,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
@@ -3387,7 +3432,7 @@ res.write(
|
|||||||
"data: " +
|
"data: " +
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
message: "Subscribed to workflow",
|
message: "Subscribed to workflow",
|
||||||
transactionId: delivery.transaction_id,
|
transactionId: delivery.transaction_id || undefined,
|
||||||
}) +
|
}) +
|
||||||
"\n\n"
|
"\n\n"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -975,6 +975,7 @@ import {
|
|||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
import {
|
import {
|
||||||
createProductsWorkflow,
|
createProductsWorkflow,
|
||||||
|
CreateProductsWorkflowInput,
|
||||||
createRemoteLinkStep,
|
createRemoteLinkStep,
|
||||||
useQueryGraphStep,
|
useQueryGraphStep,
|
||||||
} from "@medusajs/medusa/core-flows"
|
} from "@medusajs/medusa/core-flows"
|
||||||
@@ -1014,7 +1015,7 @@ const createVendorProductWorkflow = createWorkflow(
|
|||||||
})
|
})
|
||||||
|
|
||||||
const createdProducts = createProductsWorkflow.runAsStep({
|
const createdProducts = createProductsWorkflow.runAsStep({
|
||||||
input: productData,
|
input: productData as CreateProductsWorkflowInput,
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO link vendor and products
|
// TODO link vendor and products
|
||||||
@@ -1333,26 +1334,28 @@ import {
|
|||||||
createStep,
|
createStep,
|
||||||
StepResponse,
|
StepResponse,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
import { CartDTO, CartLineItemDTO } from "@medusajs/framework/types"
|
import { CartLineItemDTO } from "@medusajs/framework/types"
|
||||||
import { ContainerRegistrationKeys, promiseAll } from "@medusajs/framework/utils"
|
import { ContainerRegistrationKeys, promiseAll } from "@medusajs/framework/utils"
|
||||||
|
|
||||||
type StepInput = {
|
export type GroupVendorItemsStepInput = {
|
||||||
cart: CartDTO
|
cart: {
|
||||||
|
items?: CartLineItemDTO[]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupVendorItemsStep = createStep(
|
const groupVendorItemsStep = createStep(
|
||||||
"group-vendor-items",
|
"group-vendor-items",
|
||||||
async ({ cart }: StepInput, { container }) => {
|
async ({ cart }: GroupVendorItemsStepInput, { container }) => {
|
||||||
const query = container.resolve(ContainerRegistrationKeys.QUERY)
|
const query = container.resolve(ContainerRegistrationKeys.QUERY)
|
||||||
|
|
||||||
const vendorsItems: Record<string, CartLineItemDTO[]> = {}
|
const vendorsItems: Record<string, CartLineItemDTO[]> = {}
|
||||||
|
|
||||||
await promiseAll(cart.items?.map(async (item) => {
|
await promiseAll((cart.items || []).map(async (item) => {
|
||||||
const { data: [product] } = await query.graph({
|
const { data: [product] } = await query.graph({
|
||||||
entity: "product",
|
entity: "product",
|
||||||
fields: ["vendor.*"],
|
fields: ["vendor.*"],
|
||||||
filters: {
|
filters: {
|
||||||
id: [item.product_id],
|
id: item.product_id || "",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1451,7 +1454,7 @@ const createVendorOrdersStep = createStep(
|
|||||||
created_orders: createdOrders,
|
created_orders: createdOrders,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async ({ created_orders }, { container, context }) => {
|
async (data, { container, context }) => {
|
||||||
// TODO add compensation function
|
// TODO add compensation function
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1586,19 +1589,19 @@ function prepareOrderData(
|
|||||||
// item/vendor. This requires changes in the storefront to commodate that
|
// item/vendor. This requires changes in the storefront to commodate that
|
||||||
// and passing the item/vendor ID in the `data` property, for example.
|
// and passing the item/vendor ID in the `data` property, for example.
|
||||||
// For simplicity here we just use the same shipping method.
|
// For simplicity here we just use the same shipping method.
|
||||||
shipping_methods: parentOrder.shipping_methods.map((shippingMethod) => ({
|
shipping_methods: parentOrder.shipping_methods?.map((shippingMethod) => ({
|
||||||
name: shippingMethod.name,
|
name: shippingMethod.name,
|
||||||
amount: shippingMethod.amount,
|
amount: shippingMethod.amount,
|
||||||
shipping_option_id: shippingMethod.shipping_option_id,
|
shipping_option_id: shippingMethod.shipping_option_id,
|
||||||
data: shippingMethod.data,
|
data: shippingMethod.data,
|
||||||
tax_lines: shippingMethod.tax_lines.map((taxLine) => ({
|
tax_lines: shippingMethod.tax_lines?.map((taxLine) => ({
|
||||||
code: taxLine.code,
|
code: taxLine.code,
|
||||||
rate: taxLine.rate,
|
rate: taxLine.rate,
|
||||||
provider_id: taxLine.provider_id,
|
provider_id: taxLine.provider_id,
|
||||||
tax_rate_id: taxLine.tax_rate_id,
|
tax_rate_id: taxLine.tax_rate_id,
|
||||||
description: taxLine.description,
|
description: taxLine.description,
|
||||||
})),
|
})),
|
||||||
adjustments: shippingMethod.adjustments.map((adjustment) => ({
|
adjustments: shippingMethod.adjustments?.map((adjustment) => ({
|
||||||
code: adjustment.code,
|
code: adjustment.code,
|
||||||
amount: adjustment.amount,
|
amount: adjustment.amount,
|
||||||
description: adjustment.description,
|
description: adjustment.description,
|
||||||
@@ -1621,7 +1624,10 @@ When creating the child orders, the shipping method of the parent is used as-is
|
|||||||
Finally, replace the `TODO` in the compensation function with the following:
|
Finally, replace the `TODO` in the compensation function with the following:
|
||||||
|
|
||||||
```ts title="src/workflows/marketplace/create-vendor-orders/steps/create-vendor-orders.ts"
|
```ts title="src/workflows/marketplace/create-vendor-orders/steps/create-vendor-orders.ts"
|
||||||
await Promise.all(created_orders.map((createdOrder) => {
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await promiseAll(data.created_orders.map((createdOrder) => {
|
||||||
return cancelOrderWorkflow(container).run({
|
return cancelOrderWorkflow(container).run({
|
||||||
input: {
|
input: {
|
||||||
order_id: createdOrder.id,
|
order_id: createdOrder.id,
|
||||||
@@ -1687,7 +1693,7 @@ const createVendorOrdersWorkflow = createWorkflow(
|
|||||||
|
|
||||||
const { vendorsItems } = groupVendorItemsStep({
|
const { vendorsItems } = groupVendorItemsStep({
|
||||||
cart: carts[0],
|
cart: carts[0],
|
||||||
})
|
} as unknown as GroupVendorItemsStepInput)
|
||||||
|
|
||||||
const order = getOrderDetailWorkflow.runAsStep({
|
const order = getOrderDetailWorkflow.runAsStep({
|
||||||
input: {
|
input: {
|
||||||
@@ -1869,7 +1875,7 @@ export const GET = async (
|
|||||||
],
|
],
|
||||||
variables: {
|
variables: {
|
||||||
filters: {
|
filters: {
|
||||||
id: vendorAdmin.vendor.orders.map((order) => order.id),
|
id: vendorAdmin.vendor.orders?.map((order) => order?.id),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -646,7 +646,7 @@ const createSubscriptionStep = createStep(
|
|||||||
}, {
|
}, {
|
||||||
subscription: subscription[0],
|
subscription: subscription[0],
|
||||||
})
|
})
|
||||||
}, async ({ subscription }, { container }) => {
|
}, async (data, { container }) => {
|
||||||
// TODO implement compensation
|
// TODO implement compensation
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -706,10 +706,13 @@ This adds links between:
|
|||||||
The step also has a compensation function to undo the step’s changes if an error occurs. So, replace the second `TODO` with the following:
|
The step also has a compensation function to undo the step’s changes if an error occurs. So, replace the second `TODO` with the following:
|
||||||
|
|
||||||
```ts title="src/workflows/create-subscription/steps/create-subscription.ts"
|
```ts title="src/workflows/create-subscription/steps/create-subscription.ts"
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const subscriptionModuleService: SubscriptionModuleService =
|
const subscriptionModuleService: SubscriptionModuleService =
|
||||||
container.resolve(SUBSCRIPTION_MODULE)
|
container.resolve(SUBSCRIPTION_MODULE)
|
||||||
|
|
||||||
await subscriptionModuleService.cancelSubscriptions(subscription.id)
|
await subscriptionModuleService.cancelSubscriptions(data.subscription.id)
|
||||||
```
|
```
|
||||||
|
|
||||||
The compensation function receives the subscription as a parameter. It cancels the subscription.
|
The compensation function receives the subscription as a parameter. It cancels the subscription.
|
||||||
@@ -815,7 +818,7 @@ const createSubscriptionWorkflow = createWorkflow(
|
|||||||
const { subscription, linkDefs } = createSubscriptionStep({
|
const { subscription, linkDefs } = createSubscriptionStep({
|
||||||
cart_id: input.cart_id,
|
cart_id: input.cart_id,
|
||||||
order_id: orders[0].id,
|
order_id: orders[0].id,
|
||||||
customer_id: orders[0].customer_id,
|
customer_id: orders[0].customer_id!,
|
||||||
subscription_data: input.subscription_data,
|
subscription_data: input.subscription_data,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -859,9 +862,9 @@ In this step, you’ll create a custom API route similar to the [Complete Cart A
|
|||||||
Create the file `src/api/store/carts/[id]/subscribe/route.ts` with the following content:
|
Create the file `src/api/store/carts/[id]/subscribe/route.ts` with the following content:
|
||||||
|
|
||||||
export const completeCartHighlights = [
|
export const completeCartHighlights = [
|
||||||
["17", "graph", "Retrieve the cart to retrieve the subscription details from the `metadata`."],
|
["18", "graph", "Retrieve the cart to retrieve the subscription details from the `metadata`."],
|
||||||
["29", "", "If the subscription data isn't set in the cart's `metadata`, throw an error"],
|
["30", "", "If the subscription data isn't set in the cart's `metadata`, throw an error"],
|
||||||
["36", "createSubscriptionWorkflow", "Execute the workflow created in the previous step."]
|
["37", "createSubscriptionWorkflow", "Execute the workflow created in the previous step."]
|
||||||
]
|
]
|
||||||
|
|
||||||
```ts title="src/api/store/carts/[id]/subscribe/route.ts" highlights={completeCartHighlights} collapsibleLines="1-10" expandMoreLabel="Show Imports"
|
```ts title="src/api/store/carts/[id]/subscribe/route.ts" highlights={completeCartHighlights} collapsibleLines="1-10" expandMoreLabel="Show Imports"
|
||||||
@@ -874,6 +877,7 @@ import {
|
|||||||
MedusaError,
|
MedusaError,
|
||||||
} from "@medusajs/framework/utils"
|
} from "@medusajs/framework/utils"
|
||||||
import createSubscriptionWorkflow from "../../../../../workflows/create-subscription"
|
import createSubscriptionWorkflow from "../../../../../workflows/create-subscription"
|
||||||
|
import { SubscriptionInterval } from "../../../../../modules/subscription/types"
|
||||||
|
|
||||||
export const POST = async (
|
export const POST = async (
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
@@ -906,8 +910,8 @@ export const POST = async (
|
|||||||
input: {
|
input: {
|
||||||
cart_id: req.params.id,
|
cart_id: req.params.id,
|
||||||
subscription_data: {
|
subscription_data: {
|
||||||
interval: metadata.subscription_interval,
|
interval: metadata.subscription_interval as SubscriptionInterval,
|
||||||
period: metadata.subscription_period,
|
period: metadata.subscription_period as number,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -1247,7 +1251,7 @@ export const GET = async (
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
data: subscriptions,
|
data: subscriptions,
|
||||||
metadata: { count, take, skip },
|
metadata: { count, take, skip } = {},
|
||||||
} = await query.graph({
|
} = await query.graph({
|
||||||
entity: "subscription",
|
entity: "subscription",
|
||||||
...req.queryConfig,
|
...req.queryConfig,
|
||||||
@@ -1743,7 +1747,7 @@ import { AccountHolderDTO, CustomerDTO, PaymentMethodDTO } from "@medusajs/frame
|
|||||||
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||||
|
|
||||||
export interface GetPaymentMethodStepInput {
|
export interface GetPaymentMethodStepInput {
|
||||||
customer: CustomerDTO & {
|
customer?: CustomerDTO & {
|
||||||
account_holder: AccountHolderDTO
|
account_holder: AccountHolderDTO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1776,7 +1780,7 @@ export const getPaymentMethodStep = createStep(
|
|||||||
async ({ customer }: GetPaymentMethodStepInput, { container }) => {
|
async ({ customer }: GetPaymentMethodStepInput, { container }) => {
|
||||||
const paymentModuleService = container.resolve(Modules.PAYMENT)
|
const paymentModuleService = container.resolve(Modules.PAYMENT)
|
||||||
|
|
||||||
if (!customer.account_holder) {
|
if (!customer?.account_holder) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.INVALID_DATA,
|
MedusaError.Types.INVALID_DATA,
|
||||||
"No account holder found for the customer while retrieving payment method"
|
"No account holder found for the customer while retrieving payment method"
|
||||||
@@ -1840,7 +1844,7 @@ import { createOrderWorkflow } from "@medusajs/medusa/core-flows"
|
|||||||
import { SubscriptionData } from "../../../modules/subscription/types"
|
import { SubscriptionData } from "../../../modules/subscription/types"
|
||||||
import { SUBSCRIPTION_MODULE } from "../../../modules/subscription"
|
import { SUBSCRIPTION_MODULE } from "../../../modules/subscription"
|
||||||
|
|
||||||
type StepInput = {
|
export type CreateSubscriptionOrderStepInput = {
|
||||||
subscription: SubscriptionData
|
subscription: SubscriptionData
|
||||||
cart: CartWorkflowDTO
|
cart: CartWorkflowDTO
|
||||||
payment_collection: PaymentCollectionDTO
|
payment_collection: PaymentCollectionDTO
|
||||||
@@ -1854,7 +1858,7 @@ const createSubscriptionOrderStep = createStep(
|
|||||||
"create-subscription-order",
|
"create-subscription-order",
|
||||||
async ({
|
async ({
|
||||||
subscription, cart, payment_collection,
|
subscription, cart, payment_collection,
|
||||||
}: StepInput,
|
}: CreateSubscriptionOrderStepInput,
|
||||||
{ container, context }) => {
|
{ container, context }) => {
|
||||||
const linkDefs: LinkDefinition[] = []
|
const linkDefs: LinkDefinition[] = []
|
||||||
|
|
||||||
@@ -1873,7 +1877,7 @@ const createSubscriptionOrderStep = createStep(
|
|||||||
order,
|
order,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async ({ order }, { container }) => {
|
async (data, { container }) => {
|
||||||
// TODO add compensation function
|
// TODO add compensation function
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1904,20 +1908,20 @@ function getOrderData(cart: CartWorkflowDTO) {
|
|||||||
id: null,
|
id: null,
|
||||||
},
|
},
|
||||||
items: cart.items,
|
items: cart.items,
|
||||||
shipping_methods: cart.shipping_methods.map((method) => ({
|
shipping_methods: cart.shipping_methods?.map((method) => ({
|
||||||
name: method.name,
|
name: method.name,
|
||||||
amount: method.amount,
|
amount: method.amount,
|
||||||
is_tax_inclusive: method.is_tax_inclusive,
|
is_tax_inclusive: method.is_tax_inclusive,
|
||||||
shipping_option_id: method.shipping_option_id,
|
shipping_option_id: method.shipping_option_id,
|
||||||
data: method.data,
|
data: method.data,
|
||||||
tax_lines: method.tax_lines.map((taxLine) => ({
|
tax_lines: method.tax_lines?.map((taxLine) => ({
|
||||||
description: taxLine.description,
|
description: taxLine.description,
|
||||||
tax_rate_id: taxLine.tax_rate_id,
|
tax_rate_id: taxLine.tax_rate_id,
|
||||||
code: taxLine.code,
|
code: taxLine.code,
|
||||||
rate: taxLine.rate,
|
rate: taxLine.rate,
|
||||||
provider_id: taxLine.provider_id,
|
provider_id: taxLine.provider_id,
|
||||||
})),
|
})),
|
||||||
adjustments: method.adjustments.map((adjustment) => ({
|
adjustments: method.adjustments?.map((adjustment) => ({
|
||||||
code: adjustment.code,
|
code: adjustment.code,
|
||||||
amount: adjustment.amount,
|
amount: adjustment.amount,
|
||||||
description: adjustment.description,
|
description: adjustment.description,
|
||||||
@@ -1962,11 +1966,14 @@ This adds links to be created into the `linkDefs` array between the new order an
|
|||||||
Finally, replace the `TODO` in the compensation function to cancel the order in case of an error:
|
Finally, replace the `TODO` in the compensation function to cancel the order in case of an error:
|
||||||
|
|
||||||
```ts title="src/workflows/create-subscription-order/steps/create-subscription-order.ts"
|
```ts title="src/workflows/create-subscription-order/steps/create-subscription-order.ts"
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const orderModuleService: IOrderModuleService = container.resolve(
|
const orderModuleService: IOrderModuleService = container.resolve(
|
||||||
Modules.ORDER
|
Modules.ORDER
|
||||||
)
|
)
|
||||||
|
|
||||||
await orderModuleService.cancel(order.id)
|
await orderModuleService.cancel(data.order.id)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Create updateSubscriptionStep (Ninth Step)
|
### Create updateSubscriptionStep (Ninth Step)
|
||||||
@@ -2046,7 +2053,7 @@ const updateSubscriptionStep = createStep(
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
async ({
|
async ({
|
||||||
prev_data,
|
data,
|
||||||
}, { container }) => {
|
}, { container }) => {
|
||||||
// TODO add compensation
|
// TODO add compensation
|
||||||
}
|
}
|
||||||
@@ -2062,15 +2069,18 @@ Before updating the subscription, the step retrieves the old data and passes it
|
|||||||
So, replace the `TODO` in the compensation function with the following:
|
So, replace the `TODO` in the compensation function with the following:
|
||||||
|
|
||||||
```ts title="src/workflows/create-subscription-order/steps/update-subscription.ts"
|
```ts title="src/workflows/create-subscription-order/steps/update-subscription.ts"
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const subscriptionModuleService: SubscriptionModuleService =
|
const subscriptionModuleService: SubscriptionModuleService =
|
||||||
container.resolve(
|
container.resolve(
|
||||||
SUBSCRIPTION_MODULE
|
SUBSCRIPTION_MODULE
|
||||||
)
|
)
|
||||||
|
|
||||||
await subscriptionModuleService.updateSubscriptions({
|
await subscriptionModuleService.updateSubscriptions({
|
||||||
id: prev_data.id,
|
id: data.prev_data.id,
|
||||||
last_order_date: prev_data.last_order_date,
|
last_order_date: data.prev_data.last_order_date,
|
||||||
next_order_date: prev_data.next_order_date,
|
next_order_date: data.prev_data.next_order_date,
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -2081,19 +2091,19 @@ This updates the subscription’s `last_order_date` and `next_order_date` proper
|
|||||||
Finally, create the file `src/workflows/create-subscription-order/index.ts` with the following content:
|
Finally, create the file `src/workflows/create-subscription-order/index.ts` with the following content:
|
||||||
|
|
||||||
export const createSubscriptionOrderWorkflowHighlights = [
|
export const createSubscriptionOrderWorkflowHighlights = [
|
||||||
["26", "useQueryGraphStep", "Retrieve the cart linked to the subscription."],
|
["31", "useQueryGraphStep", "Retrieve the cart linked to the subscription."],
|
||||||
["63", "createPaymentCollectionsStep", "Create a payment collection using the same information in the cart."],
|
["68", "createPaymentCollectionsStep", "Create a payment collection using the same information in the cart."],
|
||||||
["67", "getPaymentMethodStep", "Get the customer's saved payment method."],
|
["72", "getPaymentMethodStep", "Get the customer's saved payment method."],
|
||||||
["80", "data", "Pass data required by Stripe to capture the payment."],
|
["85", "data", "Pass data required by Stripe to capture the payment."],
|
||||||
["89", "createPaymentSessionsWorkflow", "Create a payment session in the payment collection from the previous step."],
|
["94", "createPaymentSessionsWorkflow", "Create a payment session in the payment collection from the previous step."],
|
||||||
["93", "authorizePaymentSessionStep", "Authorize the payment session created from the first step."],
|
["98", "authorizePaymentSessionStep", "Authorize the payment session created from the first step."],
|
||||||
["98", "createSubscriptionOrderStep", "Create the new order for the subscription."],
|
["103", "createSubscriptionOrderStep", "Create the new order for the subscription."],
|
||||||
["104", "createRemoteLinkStep", "Create links returned by the previous step."],
|
["109", "createRemoteLinkStep", "Create links returned by the previous step."],
|
||||||
["106", "capturePaymentStep", "Capture the order’s payment."],
|
["111", "capturePaymentStep", "Capture the order’s payment."],
|
||||||
["111", "updateSubscriptionStep", "Update the subscription’s `last_order_date` and `next_order_date`."]
|
["116", "updateSubscriptionStep", "Update the subscription’s `last_order_date` and `next_order_date`."]
|
||||||
]
|
]
|
||||||
|
|
||||||
```ts title="src/workflows/create-subscription-order/index.ts" highlights={createSubscriptionOrderWorkflowHighlights} collapsibleLines="1-18" expandMoreLabel="Show Imports"
|
```ts title="src/workflows/create-subscription-order/index.ts" highlights={createSubscriptionOrderWorkflowHighlights} collapsibleLines="1-23" expandMoreLabel="Show Imports"
|
||||||
import { createWorkflow, transform, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
|
import { createWorkflow, transform, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
|
||||||
import {
|
import {
|
||||||
useQueryGraphStep,
|
useQueryGraphStep,
|
||||||
@@ -2108,9 +2118,14 @@ import {
|
|||||||
authorizePaymentSessionStep,
|
authorizePaymentSessionStep,
|
||||||
createPaymentCollectionsStep,
|
createPaymentCollectionsStep,
|
||||||
} from "@medusajs/medusa/core-flows"
|
} from "@medusajs/medusa/core-flows"
|
||||||
import createSubscriptionOrderStep from "./steps/create-subscription-order"
|
import createSubscriptionOrderStep, {
|
||||||
|
CreateSubscriptionOrderStepInput,
|
||||||
|
} from "./steps/create-subscription-order"
|
||||||
import updateSubscriptionStep from "./steps/update-subscription"
|
import updateSubscriptionStep from "./steps/update-subscription"
|
||||||
import { getPaymentMethodStep } from "./steps/get-payment-method"
|
import {
|
||||||
|
getPaymentMethodStep,
|
||||||
|
GetPaymentMethodStepInput,
|
||||||
|
} from "./steps/get-payment-method"
|
||||||
|
|
||||||
type WorkflowInput = {
|
type WorkflowInput = {
|
||||||
subscription: SubscriptionData
|
subscription: SubscriptionData
|
||||||
@@ -2150,9 +2165,9 @@ const createSubscriptionOrderWorkflow = createWorkflow(
|
|||||||
}, (data) => {
|
}, (data) => {
|
||||||
const cart = data.subscriptions[0].cart
|
const cart = data.subscriptions[0].cart
|
||||||
return {
|
return {
|
||||||
currency_code: cart.currency_code,
|
currency_code: cart?.currency_code || "",
|
||||||
amount: cart.payment_collection.amount,
|
amount: cart?.payment_collection?.amount || 0,
|
||||||
metadata: cart.payment_collection.metadata,
|
metadata: cart?.payment_collection?.metadata || undefined,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2172,7 +2187,7 @@ const createSubscriptionOrderWorkflow = createWorkflow(
|
|||||||
return {
|
return {
|
||||||
payment_collection_id: data.payment_collection.id,
|
payment_collection_id: data.payment_collection.id,
|
||||||
provider_id: "pp_stripe_stripe",
|
provider_id: "pp_stripe_stripe",
|
||||||
customer_id: data.subscriptions[0].cart.customer.id,
|
customer_id: data.subscriptions[0].cart?.customer?.id,
|
||||||
data: {
|
data: {
|
||||||
payment_method: data.defaultPaymentMethod.id,
|
payment_method: data.defaultPaymentMethod.id,
|
||||||
off_session: true,
|
off_session: true,
|
||||||
@@ -2195,7 +2210,7 @@ const createSubscriptionOrderWorkflow = createWorkflow(
|
|||||||
subscription: input.subscription,
|
subscription: input.subscription,
|
||||||
cart: carts[0],
|
cart: carts[0],
|
||||||
payment_collection,
|
payment_collection,
|
||||||
})
|
} as unknown as CreateSubscriptionOrderStepInput)
|
||||||
|
|
||||||
createRemoteLinkStep(linkDefs)
|
createRemoteLinkStep(linkDefs)
|
||||||
|
|
||||||
|
|||||||
@@ -1542,7 +1542,7 @@ return (
|
|||||||
placeholder="Quantity"
|
placeholder="Quantity"
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
max={selectedVariant?.inventory_quantity}
|
max={selectedVariant?.inventory_quantity || undefined}
|
||||||
value={quantity}
|
value={quantity}
|
||||||
onChange={(e) => setQuantity(parseInt(e.target.value))}
|
onChange={(e) => setQuantity(parseInt(e.target.value))}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -138,9 +138,9 @@ const queryClient = new QueryClient({
|
|||||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
export default queryClient;
|
export default queryClient
|
||||||
|
|
||||||
// product.ts
|
// product.ts
|
||||||
// Used in components that are children of QueryClientProvider
|
// Used in components that are children of QueryClientProvider
|
||||||
@@ -149,10 +149,10 @@ import { sdk } from "../lib/sdk"
|
|||||||
|
|
||||||
export const useProduct = (id: string) => {
|
export const useProduct = (id: string) => {
|
||||||
return useQuery(["product", id], async () => {
|
return useQuery(["product", id], async () => {
|
||||||
const response = await sdk.store.products.retrieve(id);
|
const response = await sdk.store.products.retrieve(id)
|
||||||
return response.data;
|
return response.data
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
export const useProductPrice = (id: string) => {
|
export const useProductPrice = (id: string) => {
|
||||||
return useQuery(
|
return useQuery(
|
||||||
@@ -160,14 +160,14 @@ export const useProductPrice = (id: string) => {
|
|||||||
async () => {
|
async () => {
|
||||||
const response = await sdk.store.products.retrieve(id, {
|
const response = await sdk.store.products.retrieve(id, {
|
||||||
fields: "*variants.calculated_price",
|
fields: "*variants.calculated_price",
|
||||||
});
|
})
|
||||||
return response.data.variants[0].price;
|
return response.data.variants[0].price
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
staleTime: 0, // Always fetch fresh data
|
staleTime: 0, // Always fetch fresh data
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Invalidate Queries
|
### Invalidate Queries
|
||||||
@@ -181,19 +181,19 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"
|
|||||||
import { sdk } from "../lib/sdk"
|
import { sdk } from "../lib/sdk"
|
||||||
|
|
||||||
export const useAddToCart = () => {
|
export const useAddToCart = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
return useMutation(
|
return useMutation(
|
||||||
async (item) => {
|
async (item) => {
|
||||||
await sdk.store.cart.createLineItem("cart_id", item);
|
await sdk.store.cart.createLineItem("cart_id", item)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(["cart"]);
|
queryClient.invalidateQueries(["cart"])
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -209,7 +209,7 @@ For example, if you only need the product's `id`, `title`, and `variants.calcula
|
|||||||
```ts
|
```ts
|
||||||
const response = await sdk.store.products.retrieve("product_id", {
|
const response = await sdk.store.products.retrieve("product_id", {
|
||||||
fields: "*variants.calculated_price, id, title",
|
fields: "*variants.calculated_price, id, title",
|
||||||
});
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
This query will return only the specified fields, reducing the response size and improving performance.
|
This query will return only the specified fields, reducing the response size and improving performance.
|
||||||
@@ -221,11 +221,11 @@ const useProduct = (id: string, fields?: string) => {
|
|||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["product", id, fields],
|
queryKey: ["product", id, fields],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await sdk.store.products.retrieve(id, { fields });
|
const response = await sdk.store.products.retrieve(id, { fields })
|
||||||
return response.data;
|
return response.data
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Learn more about the `fields` parameter in the [Store API reference](!api!/store#select-fields-and-relations).
|
Learn more about the `fields` parameter in the [Store API reference](!api!/store#select-fields-and-relations).
|
||||||
@@ -366,7 +366,7 @@ export const addItemOptimistically = (
|
|||||||
|
|
||||||
// Check if item already exists in cart
|
// Check if item already exists in cart
|
||||||
const existingItemIndex = currentCart.items?.findIndex(
|
const existingItemIndex = currentCart.items?.findIndex(
|
||||||
item => item.variant_id === newItem.variant_id
|
(item) => item.variant_id === newItem.variant_id
|
||||||
)
|
)
|
||||||
|
|
||||||
let updatedItems: HttpTypes.StoreCartLineItem[]
|
let updatedItems: HttpTypes.StoreCartLineItem[]
|
||||||
@@ -459,7 +459,7 @@ export const updateLineItemOptimistically = (
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedItems = (currentCart.items || []).map(item => {
|
const updatedItems = (currentCart.items || []).map((item) => {
|
||||||
if (item.id === lineId) {
|
if (item.id === lineId) {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
@@ -513,7 +513,7 @@ export const removeLineItemOptimistically = (
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedItems = (currentCart.items || []).filter(item => item.id !== lineId)
|
const updatedItems = (currentCart.items || []).filter((item) => item.id !== lineId)
|
||||||
|
|
||||||
const optimisticCart: OptimisticCart = {
|
const optimisticCart: OptimisticCart = {
|
||||||
...currentCart,
|
...currentCart,
|
||||||
@@ -633,7 +633,7 @@ export const createOptimisticCart = (region: HttpTypes.StoreRegion): OptimisticC
|
|||||||
export const getCurrentCart = (queryClient: QueryClient, fields?: string): HttpTypes.StoreCart | null => {
|
export const getCurrentCart = (queryClient: QueryClient, fields?: string): HttpTypes.StoreCart | null => {
|
||||||
return queryClient.getQueryData<HttpTypes.StoreCart | null>(queryKeys.cart.current(fields)) ||
|
return queryClient.getQueryData<HttpTypes.StoreCart | null>(queryKeys.cart.current(fields)) ||
|
||||||
queryClient.getQueriesData<HttpTypes.StoreCart | null>({
|
queryClient.getQueriesData<HttpTypes.StoreCart | null>({
|
||||||
predicate: queryKeys.cart.predicate
|
predicate: queryKeys.cart.predicate,
|
||||||
})[0]?.[1] || null
|
})[0]?.[1] || null
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -702,7 +702,7 @@ export const useCart = ({ fields }: { fields?: string } = {}) => {
|
|||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: queryKeys.cart.current(fields),
|
queryKey: queryKeys.cart.current(fields),
|
||||||
queryFn: () => retrieveCart({ fields }),
|
queryFn: () => retrieveCart({ fields }),
|
||||||
staleTime: 0
|
staleTime: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -751,7 +751,7 @@ export const useUpdateCart = () => {
|
|||||||
mutationFn: updateCart,
|
mutationFn: updateCart,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ predicate: queryKeys.cart.predicate })
|
queryClient.invalidateQueries({ predicate: queryKeys.cart.predicate })
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -906,7 +906,7 @@ export const useAddToCart = ({ fields }: { fields?: string } = {}) => {
|
|||||||
onSettled: (data) => {
|
onSettled: (data) => {
|
||||||
// Always refetch after error or success to ensure we have the latest data
|
// Always refetch after error or success to ensure we have the latest data
|
||||||
queryClient.invalidateQueries({ predicate:
|
queryClient.invalidateQueries({ predicate:
|
||||||
(query) => queryKeys.cart.predicate(query, fields && data ? [fields] : undefined)
|
(query) => queryKeys.cart.predicate(query, fields && data ? [fields] : undefined),
|
||||||
})
|
})
|
||||||
if (data) {
|
if (data) {
|
||||||
queryClient.setQueryData(queryKeys.cart.current(fields), data)
|
queryClient.setQueryData(queryKeys.cart.current(fields), data)
|
||||||
@@ -970,7 +970,7 @@ export const useUpdateLineItem = ({ fields }: { fields?: string } = {}) => {
|
|||||||
onMutate: async (variables) => {
|
onMutate: async (variables) => {
|
||||||
// Cancel any outgoing refetches
|
// Cancel any outgoing refetches
|
||||||
await queryClient.cancelQueries({
|
await queryClient.cancelQueries({
|
||||||
predicate: (query) => queryKeys.cart.predicate(query, fields ? [fields] : undefined)
|
predicate: (query) => queryKeys.cart.predicate(query, fields ? [fields] : undefined),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Snapshot the previous value
|
// Snapshot the previous value
|
||||||
@@ -993,7 +993,7 @@ export const useUpdateLineItem = ({ fields }: { fields?: string } = {}) => {
|
|||||||
onSettled: (data) => {
|
onSettled: (data) => {
|
||||||
// Always refetch after error or success to ensure we have the latest data
|
// Always refetch after error or success to ensure we have the latest data
|
||||||
queryClient.invalidateQueries({ predicate:
|
queryClient.invalidateQueries({ predicate:
|
||||||
(query) => queryKeys.cart.predicate(query, fields && data ? [fields] : undefined)
|
(query) => queryKeys.cart.predicate(query, fields && data ? [fields] : undefined),
|
||||||
})
|
})
|
||||||
if (data) {
|
if (data) {
|
||||||
queryClient.setQueryData(queryKeys.cart.current(fields), data)
|
queryClient.setQueryData(queryKeys.cart.current(fields), data)
|
||||||
@@ -1057,7 +1057,7 @@ export const useDeleteLineItem = ({ fields }: { fields?: string } = {}) => {
|
|||||||
onMutate: async (variables) => {
|
onMutate: async (variables) => {
|
||||||
// Cancel any outgoing refetches
|
// Cancel any outgoing refetches
|
||||||
await queryClient.cancelQueries({ predicate:
|
await queryClient.cancelQueries({ predicate:
|
||||||
(query) => queryKeys.cart.predicate(query, fields ? [fields] : undefined)
|
(query) => queryKeys.cart.predicate(query, fields ? [fields] : undefined),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Snapshot the previous value
|
// Snapshot the previous value
|
||||||
@@ -1080,7 +1080,7 @@ export const useDeleteLineItem = ({ fields }: { fields?: string } = {}) => {
|
|||||||
onSettled: (data) => {
|
onSettled: (data) => {
|
||||||
// Always refetch after error or success to ensure we have the latest data
|
// Always refetch after error or success to ensure we have the latest data
|
||||||
queryClient.invalidateQueries({ predicate:
|
queryClient.invalidateQueries({ predicate:
|
||||||
(query) => queryKeys.cart.predicate(query, fields && data ? [fields] : undefined)
|
(query) => queryKeys.cart.predicate(query, fields && data ? [fields] : undefined),
|
||||||
})
|
})
|
||||||
if (data) {
|
if (data) {
|
||||||
queryClient.setQueryData(queryKeys.cart.current(fields), data)
|
queryClient.setQueryData(queryKeys.cart.current(fields), data)
|
||||||
|
|||||||
@@ -110,17 +110,17 @@ export const generatedEditDates = {
|
|||||||
"app/nextjs-starter/page.mdx": "2025-02-26T11:37:47.137Z",
|
"app/nextjs-starter/page.mdx": "2025-02-26T11:37:47.137Z",
|
||||||
"app/recipes/b2b/page.mdx": "2025-05-20T07:51:40.718Z",
|
"app/recipes/b2b/page.mdx": "2025-05-20T07:51:40.718Z",
|
||||||
"app/recipes/commerce-automation/page.mdx": "2025-05-20T07:51:40.719Z",
|
"app/recipes/commerce-automation/page.mdx": "2025-05-20T07:51:40.719Z",
|
||||||
"app/recipes/digital-products/examples/standard/page.mdx": "2025-04-24T15:41:05.364Z",
|
"app/recipes/digital-products/examples/standard/page.mdx": "2025-10-21T10:31:35.296Z",
|
||||||
"app/recipes/digital-products/page.mdx": "2025-05-20T07:51:40.719Z",
|
"app/recipes/digital-products/page.mdx": "2025-05-20T07:51:40.719Z",
|
||||||
"app/recipes/ecommerce/page.mdx": "2025-06-24T08:50:10.116Z",
|
"app/recipes/ecommerce/page.mdx": "2025-06-24T08:50:10.116Z",
|
||||||
"app/recipes/marketplace/examples/vendors/page.mdx": "2025-07-16T09:58:59.793Z",
|
"app/recipes/marketplace/examples/vendors/page.mdx": "2025-10-21T10:47:23.415Z",
|
||||||
"app/recipes/marketplace/page.mdx": "2025-05-20T07:51:40.721Z",
|
"app/recipes/marketplace/page.mdx": "2025-05-20T07:51:40.721Z",
|
||||||
"app/recipes/multi-region-store/page.mdx": "2025-05-20T07:51:40.721Z",
|
"app/recipes/multi-region-store/page.mdx": "2025-05-20T07:51:40.721Z",
|
||||||
"app/recipes/omnichannel/page.mdx": "2025-05-20T07:51:40.722Z",
|
"app/recipes/omnichannel/page.mdx": "2025-05-20T07:51:40.722Z",
|
||||||
"app/recipes/oms/page.mdx": "2025-05-20T07:51:40.722Z",
|
"app/recipes/oms/page.mdx": "2025-05-20T07:51:40.722Z",
|
||||||
"app/recipes/personalized-products/page.mdx": "2025-07-22T06:52:29.419Z",
|
"app/recipes/personalized-products/page.mdx": "2025-07-22T06:52:29.419Z",
|
||||||
"app/recipes/pos/page.mdx": "2025-05-20T07:51:40.723Z",
|
"app/recipes/pos/page.mdx": "2025-05-20T07:51:40.723Z",
|
||||||
"app/recipes/subscriptions/examples/standard/page.mdx": "2025-05-20T07:51:40.723Z",
|
"app/recipes/subscriptions/examples/standard/page.mdx": "2025-10-21T12:36:28.745Z",
|
||||||
"app/recipes/subscriptions/page.mdx": "2025-05-20T07:51:40.723Z",
|
"app/recipes/subscriptions/page.mdx": "2025-05-20T07:51:40.723Z",
|
||||||
"app/recipes/page.mdx": "2025-05-20T07:51:40.722Z",
|
"app/recipes/page.mdx": "2025-05-20T07:51:40.722Z",
|
||||||
"app/service-factory-reference/methods/create/page.mdx": "2025-09-01T15:54:53.385Z",
|
"app/service-factory-reference/methods/create/page.mdx": "2025-09-01T15:54:53.385Z",
|
||||||
@@ -568,7 +568,7 @@ export const generatedEditDates = {
|
|||||||
"app/medusa-cli/commands/start/page.mdx": "2025-10-13T10:27:25.345Z",
|
"app/medusa-cli/commands/start/page.mdx": "2025-10-13T10:27:25.345Z",
|
||||||
"app/medusa-cli/commands/telemtry/page.mdx": "2025-01-16T09:51:24.323Z",
|
"app/medusa-cli/commands/telemtry/page.mdx": "2025-01-16T09:51:24.323Z",
|
||||||
"app/medusa-cli/commands/user/page.mdx": "2025-09-01T15:36:38.978Z",
|
"app/medusa-cli/commands/user/page.mdx": "2025-09-01T15:36:38.978Z",
|
||||||
"app/recipes/marketplace/examples/restaurant-delivery/page.mdx": "2025-08-25T07:30:37.299Z",
|
"app/recipes/marketplace/examples/restaurant-delivery/page.mdx": "2025-10-21T12:04:46.051Z",
|
||||||
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminCreateCustomerGroup/page.mdx": "2024-12-09T13:21:33.569Z",
|
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminCreateCustomerGroup/page.mdx": "2024-12-09T13:21:33.569Z",
|
||||||
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminCreateReservation/page.mdx": "2025-04-11T09:04:47.498Z",
|
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminCreateReservation/page.mdx": "2025-04-11T09:04:47.498Z",
|
||||||
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminCustomerGroup/page.mdx": "2025-05-20T07:51:41.059Z",
|
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminCustomerGroup/page.mdx": "2025-05-20T07:51:41.059Z",
|
||||||
@@ -5520,7 +5520,7 @@ export const generatedEditDates = {
|
|||||||
"references/workflows/classes/workflows.WorkflowResponse/page.mdx": "2025-04-11T09:04:53.140Z",
|
"references/workflows/classes/workflows.WorkflowResponse/page.mdx": "2025-04-11T09:04:53.140Z",
|
||||||
"references/workflows/interfaces/workflows.ApplyStepOptions/page.mdx": "2025-09-12T14:10:46.875Z",
|
"references/workflows/interfaces/workflows.ApplyStepOptions/page.mdx": "2025-09-12T14:10:46.875Z",
|
||||||
"references/workflows/types/workflows.WorkflowData/page.mdx": "2024-12-23T13:57:08.059Z",
|
"references/workflows/types/workflows.WorkflowData/page.mdx": "2024-12-23T13:57:08.059Z",
|
||||||
"app/integrations/guides/resend/page.mdx": "2025-08-01T12:09:08.483Z",
|
"app/integrations/guides/resend/page.mdx": "2025-10-21T11:08:19.352Z",
|
||||||
"references/api_key_models/variables/api_key_models.ApiKey/page.mdx": "2025-10-21T08:10:52.876Z",
|
"references/api_key_models/variables/api_key_models.ApiKey/page.mdx": "2025-10-21T08:10:52.876Z",
|
||||||
"references/cart/ICartModuleService/methods/cart.ICartModuleService.updateShippingMethods/page.mdx": "2025-10-21T08:10:43.389Z",
|
"references/cart/ICartModuleService/methods/cart.ICartModuleService.updateShippingMethods/page.mdx": "2025-10-21T08:10:43.389Z",
|
||||||
"references/cart/interfaces/cart.UpdateShippingMethodDTO/page.mdx": "2025-06-25T10:11:35.052Z",
|
"references/cart/interfaces/cart.UpdateShippingMethodDTO/page.mdx": "2025-06-25T10:11:35.052Z",
|
||||||
@@ -5560,7 +5560,7 @@ export const generatedEditDates = {
|
|||||||
"references/modules/sales_channel_models/page.mdx": "2024-12-10T14:55:13.205Z",
|
"references/modules/sales_channel_models/page.mdx": "2024-12-10T14:55:13.205Z",
|
||||||
"references/types/DmlTypes/types/types.DmlTypes.KnownDataTypes/page.mdx": "2024-12-17T16:57:19.922Z",
|
"references/types/DmlTypes/types/types.DmlTypes.KnownDataTypes/page.mdx": "2024-12-17T16:57:19.922Z",
|
||||||
"references/types/DmlTypes/types/types.DmlTypes.RelationshipTypes/page.mdx": "2024-12-10T14:54:55.435Z",
|
"references/types/DmlTypes/types/types.DmlTypes.RelationshipTypes/page.mdx": "2024-12-10T14:54:55.435Z",
|
||||||
"app/recipes/commerce-automation/restock-notification/page.mdx": "2025-09-29T15:35:49.320Z",
|
"app/recipes/commerce-automation/restock-notification/page.mdx": "2025-10-21T12:06:58.024Z",
|
||||||
"app/integrations/guides/shipstation/page.mdx": "2025-05-20T07:51:40.717Z",
|
"app/integrations/guides/shipstation/page.mdx": "2025-05-20T07:51:40.717Z",
|
||||||
"app/nextjs-starter/guides/customize-stripe/page.mdx": "2025-07-15T08:50:51.997Z",
|
"app/nextjs-starter/guides/customize-stripe/page.mdx": "2025-07-15T08:50:51.997Z",
|
||||||
"references/core_flows/Cart/Workflows_Cart/functions/core_flows.Cart.Workflows_Cart.listShippingOptionsForCartWithPricingWorkflow/page.mdx": "2025-09-18T17:04:38.644Z",
|
"references/core_flows/Cart/Workflows_Cart/functions/core_flows.Cart.Workflows_Cart.listShippingOptionsForCartWithPricingWorkflow/page.mdx": "2025-09-18T17:04:38.644Z",
|
||||||
@@ -5722,7 +5722,7 @@ export const generatedEditDates = {
|
|||||||
"references/types/StockLocationTypes/interfaces/types.StockLocationTypes.FilterableStockLocationAddressProps/page.mdx": "2025-01-13T18:05:55.410Z",
|
"references/types/StockLocationTypes/interfaces/types.StockLocationTypes.FilterableStockLocationAddressProps/page.mdx": "2025-01-13T18:05:55.410Z",
|
||||||
"references/types/StockLocationTypes/types/types.StockLocationTypes.UpdateStockLocationAddressInput/page.mdx": "2025-01-07T12:54:23.057Z",
|
"references/types/StockLocationTypes/types/types.StockLocationTypes.UpdateStockLocationAddressInput/page.mdx": "2025-01-07T12:54:23.057Z",
|
||||||
"references/types/StockLocationTypes/types/types.StockLocationTypes.UpsertStockLocationAddressInput/page.mdx": "2025-01-07T12:54:23.058Z",
|
"references/types/StockLocationTypes/types/types.StockLocationTypes.UpsertStockLocationAddressInput/page.mdx": "2025-01-07T12:54:23.058Z",
|
||||||
"app/storefront-development/guides/express-checkout/page.mdx": "2025-05-20T07:51:40.724Z",
|
"app/storefront-development/guides/express-checkout/page.mdx": "2025-10-21T10:35:36.273Z",
|
||||||
"app/commerce-modules/inventory/inventory-kit/page.mdx": "2025-05-20T07:51:40.708Z",
|
"app/commerce-modules/inventory/inventory-kit/page.mdx": "2025-05-20T07:51:40.708Z",
|
||||||
"app/commerce-modules/api-key/workflows/page.mdx": "2025-01-09T13:41:46.573Z",
|
"app/commerce-modules/api-key/workflows/page.mdx": "2025-01-09T13:41:46.573Z",
|
||||||
"app/commerce-modules/api-key/js-sdk/page.mdx": "2025-01-09T12:04:39.787Z",
|
"app/commerce-modules/api-key/js-sdk/page.mdx": "2025-01-09T12:04:39.787Z",
|
||||||
@@ -6604,7 +6604,7 @@ export const generatedEditDates = {
|
|||||||
"references/core_flows/Locking/Steps_Locking/variables/core_flows.Locking.Steps_Locking.acquireLockStepId/page.mdx": "2025-09-15T09:52:14.218Z",
|
"references/core_flows/Locking/Steps_Locking/variables/core_flows.Locking.Steps_Locking.acquireLockStepId/page.mdx": "2025-09-15T09:52:14.218Z",
|
||||||
"references/core_flows/Locking/Steps_Locking/variables/core_flows.Locking.Steps_Locking.releaseLockStepId/page.mdx": "2025-09-15T09:52:14.219Z",
|
"references/core_flows/Locking/Steps_Locking/variables/core_flows.Locking.Steps_Locking.releaseLockStepId/page.mdx": "2025-09-15T09:52:14.219Z",
|
||||||
"references/core_flows/Locking/core_flows.Locking.Steps_Locking/page.mdx": "2025-09-15T09:52:14.217Z",
|
"references/core_flows/Locking/core_flows.Locking.Steps_Locking/page.mdx": "2025-09-15T09:52:14.217Z",
|
||||||
"app/integrations/guides/meilisearch/page.mdx": "2025-10-09T11:30:25.084Z",
|
"app/integrations/guides/meilisearch/page.mdx": "2025-10-21T10:51:27.358Z",
|
||||||
"app/nextjs-starter/guides/storefront-returns/page.mdx": "2025-09-22T06:02:00.580Z",
|
"app/nextjs-starter/guides/storefront-returns/page.mdx": "2025-09-22T06:02:00.580Z",
|
||||||
"references/js_sdk/admin/Admin/properties/js_sdk.admin.Admin.views/page.mdx": "2025-10-21T08:10:55.384Z",
|
"references/js_sdk/admin/Admin/properties/js_sdk.admin.Admin.views/page.mdx": "2025-10-21T08:10:55.384Z",
|
||||||
"app/data-model-repository-reference/methods/create/page.mdx": "2025-10-09T11:42:23.826Z",
|
"app/data-model-repository-reference/methods/create/page.mdx": "2025-10-09T11:42:23.826Z",
|
||||||
|
|||||||
Reference in new issue
Block a user