docs: update recipes and tutorials to support locks and idempotency (#14151)
This commit is contained in:
@@ -732,6 +732,9 @@ export const createSubscriptionWorkflowHighlights = [
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
useQueryGraphStep,
|
||||
acquireLockStep,
|
||||
releaseLockStep,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import {
|
||||
createRemoteLinkStep,
|
||||
@@ -754,6 +757,11 @@ type WorkflowInput = {
|
||||
const createSubscriptionWorkflow = createWorkflow(
|
||||
"create-subscription",
|
||||
(input: WorkflowInput) => {
|
||||
acquireLockStep({
|
||||
key: input.cart_id,
|
||||
timeout: 2,
|
||||
ttl: 10,
|
||||
})
|
||||
const { id } = completeCartWorkflow.runAsStep({
|
||||
input: {
|
||||
id: input.cart_id,
|
||||
@@ -815,14 +823,34 @@ const createSubscriptionWorkflow = createWorkflow(
|
||||
},
|
||||
})
|
||||
|
||||
const { subscription, linkDefs } = createSubscriptionStep({
|
||||
cart_id: input.cart_id,
|
||||
order_id: orders[0].id,
|
||||
customer_id: orders[0].customer_id!,
|
||||
subscription_data: input.subscription_data,
|
||||
const { data: existingLinks } = useQueryGraphStep({
|
||||
entity: subscriptionOrderLink.entryPoint,
|
||||
fields: ["subscription.id"],
|
||||
filters: { order_id: orders[0].id },
|
||||
}).config({ name: "retrieve-existing-links" })
|
||||
|
||||
const subscription = when(
|
||||
"create-subscription-condition",
|
||||
{ existingLinks },
|
||||
(data) => data.existingLinks.length === 0
|
||||
)
|
||||
.then(() => {
|
||||
|
||||
const { subscription, linkDefs } = createSubscriptionStep({
|
||||
cart_id: input.cart_id,
|
||||
order_id: orders[0].id,
|
||||
customer_id: orders[0].customer_id!,
|
||||
subscription_data: input.subscription_data,
|
||||
})
|
||||
|
||||
createRemoteLinkStep(linkDefs)
|
||||
|
||||
return subscription
|
||||
})
|
||||
|
||||
createRemoteLinkStep(linkDefs)
|
||||
releaseLockStep({
|
||||
key: input.cart_id,
|
||||
})
|
||||
|
||||
return new WorkflowResponse({
|
||||
subscription: subscription,
|
||||
@@ -836,10 +864,14 @@ export default createSubscriptionWorkflow
|
||||
|
||||
This workflow accepts the cart’s ID, along with the subscription details. It executes the following steps:
|
||||
|
||||
1. `completeCartWorkflow` from `@medusajs/medusa/core-flows` that completes a cart and creates an order.
|
||||
2. `useQueryGraphStep` from `@medusajs/medusa/core-flows` to retrieve the order's details. [Query](!docs!/learn/fundamentals/module-links/query) is a tool that allows you to retrieve data across modules.
|
||||
3. `createSubscriptionStep`, which is the step you created previously.
|
||||
4. `createRemoteLinkStep` from `@medusajs/medusa/core-flows`, which accepts links to create. These links are in the `linkDefs` array returned by the previous step.
|
||||
1. [acquireLockStep](/references/medusa-workflows/steps/acquireLockStep) to acquire a lock on the cart to prevent race conditions.
|
||||
2. [completeCartWorkflow](/references/medusa-workflows/completeCartWorkflow) that completes a cart and creates an order.
|
||||
3. [useQueryGraphStep](/references/helper-steps/useQueryGraphStep) to retrieve the order's details. [Query](!docs!/learn/fundamentals/module-links/query) is a tool that allows you to retrieve data across modules.
|
||||
4. [useQueryGraphStep](/references/helper-steps/useQueryGraphStep) again to check if a subscription already exists for the order. This is necessary to ensure idempotency in case the workflow is retried.
|
||||
5. Use `when` to check if a subscription already exists for the order. If not, it executes the next steps:
|
||||
1. `createSubscriptionStep`, which is the step you created previously.
|
||||
2. `createRemoteLinkStep` which accepts links to create. These links are in the `linkDefs` array returned by the previous step.
|
||||
6. [releaseLockStep](/references/medusa-workflows/steps/releaseLockStep) to release the lock on the cart.
|
||||
|
||||
The workflow returns the created subscription and order.
|
||||
|
||||
@@ -918,7 +950,7 @@ export const POST = async (
|
||||
|
||||
res.json({
|
||||
type: "order",
|
||||
...result,
|
||||
order: result.order,
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -927,7 +959,7 @@ Since the file exports a `POST` function, you're exposing a `POST` API route at
|
||||
|
||||
In the route handler function, you retrieve the cart to access it's `metadata` property. If the subscription details aren't stored there, you throw an error.
|
||||
|
||||
Then, you use the `createSubscriptionWorkflow` you created to create the order, and return the created order and subscription in the response.
|
||||
Then, you use the `createSubscriptionWorkflow` you created to create the order, and return the created order in the response.
|
||||
|
||||
In the next step, you'll customize the Next.js Starter Storefront, allowing you to test out the subscription feature.
|
||||
|
||||
@@ -965,6 +997,7 @@ In this step, you'll customize the checkout flow in the [Next.js Starter storefr
|
||||
|
||||
1. Add a subscription step to the checkout flow.
|
||||
2. Pass the additional data that Stripe requires to later capture the payment when the subscription renews, as explained in the [Payment Flow Overview](#intermission-payment-flow-overview).
|
||||
3. Change the complete cart action to use the custom API route you created in the previous step.
|
||||
|
||||
### Add Subscription Step
|
||||
|
||||
@@ -1199,6 +1232,30 @@ If you're integrating with a custom payment provider, you can instead pass the r
|
||||
|
||||
The payment method can now be used later to capture the payment when the subscription renews.
|
||||
|
||||
### Change Complete Cart Action
|
||||
|
||||
Finally, you need to change the complete cart action to use the custom API route you created in the previous step.
|
||||
|
||||
In `src/lib/data/cart.ts`, find the `placeOrder` function and change the `await sdk.store.cart.complete` call to the following:
|
||||
|
||||
```ts title="src/lib/data/cart.ts" badgeLabel="Storefront" badgeColor="orange"
|
||||
const cartRes = await sdk.client.fetch<{
|
||||
type: "cart"
|
||||
cart: HttpTypes.StoreCart
|
||||
} | {
|
||||
type: "order"
|
||||
order: HttpTypes.StoreOrder
|
||||
}>(
|
||||
`/store/carts/${id}/subscribe`,
|
||||
{
|
||||
method: "POST",
|
||||
headers,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
You change the request to send a `POST` request to the `/store/carts/[id]/subscribe` endpoint you created earlier. You can keep the rest of the `placeOrder` function as is.
|
||||
|
||||
### Test Cart Completion and Subscription Creation
|
||||
|
||||
To test out the cart completion flow:
|
||||
|
||||
Reference in New Issue
Block a user