@techsio/storefront-data
implement-cart-and-checkout-platform-flows
coreLoad this skill when implementing cart and checkout through @techsio/storefront-data with storefront.flows.cart, storefront.flows.checkout, useCart, useAddToCart, useCheckoutShipping, useCheckoutPayment, useCompleteCheckout, and shared cart cache sync. Use it for active cart lifecycle, effective cart state, selected payment-session semantics, and canonical checkout orchestration.
Setup
Use the flow layer as the default app surface for cart and checkout. Drop to low-level hooks only for exceptional cases.
"use client"
import { storefront } from "@/src/lib/storefront"
export function AddToCartButton({
regionId,
variantId,
}: {
regionId: string
variantId: string
}) {
const cart = storefront.flows.cart.useCart({
region_id: regionId,
autoCreate: true,
autoUpdateRegion: true,
})
const addToCart = storefront.flows.cart.useAddToCart()
return (
<button
type="button"
disabled={cart.isLoading || addToCart.isPending}
onClick={() =>
addToCart.mutate({
cartId: cart.cart?.id,
region_id: regionId,
variantId,
quantity: 1,
autoCreate: true,
})
}
>
Add to cart
</button>
)
}Core Patterns
Use checkout shipping through the flow wrapper
"use client"
import { storefront } from "@/src/lib/storefront"
export function ShippingOptions({ cartId }: { cartId: string }) {
const shipping = storefront.flows.checkout.useCheckoutShipping({ cartId })
return (
<ul>
{shipping.shippingOptions.map((option) => (
<li key={option.id}>
<button type="button" onClick={() => shipping.setShipping(option.id)}>
{option.name}
</button>
</li>
))}
</ul>
)
}Use checkout payment through the flow wrapper
"use client"
import { storefront } from "@/src/lib/storefront"
export function PaymentOptions({
cartId,
regionId,
}: {
cartId: string
regionId: string
}) {
const payment = storefront.flows.checkout.useCheckoutPayment({
cartId,
regionId,
})
return (
<ul>
{payment.paymentProviders.map((provider) => (
<li key={provider.id}>
<button
type="button"
onClick={() => payment.initiatePayment(provider.id)}
>
{provider.id}
</button>
</li>
))}
</ul>
)
}Complete checkout through the canonical flow
"use client"
import { storefront } from "@/src/lib/storefront"
export function CompleteCheckoutButton({
cartId,
regionId,
}: {
cartId: string
regionId: string
}) {
const completeCheckout = storefront.flows.checkout.useCompleteCheckout({
cartId,
regionId,
})
return (
<button type="button" onClick={() => completeCheckout.mutate()}>
Complete checkout
</button>
)
}Common Mistakes
HIGH Direct Medusa calls instead of the flow layer
Wrong:
await sdk.store.cart.createLineItem(cartId, payload)
await sdk.store.cart.complete(cartId)Correct:
const addToCart = storefront.flows.cart.useAddToCart()
const completeCheckout = storefront.flows.checkout.useCompleteCheckout({ cartId })
await addToCart.mutateAsync({ cartId, ...payload })
await completeCheckout.mutateAsync()The flow wrappers normalize cache sync, result shapes, and checkout orchestration. Direct SDK calls bypass those shared semantics.
Source: libs/storefront-data/src/medusa/cart-flow.ts, libs/storefront-data/src/medusa/checkout-flow.ts
CRITICAL First payment session wins
Wrong:
const providerId = cart.payment_collection?.payment_sessions?.[0]?.provider_idCorrect:
const providerId = resolveSelectedPaymentProviderId(cart)Checkout now derives the active provider from selected payment-session semantics, not whichever session happens to be first.
Source: libs/storefront-data/src/shared/checkout-flow-utils.ts, libs/storefront-data/tests/medusa.flow.test.tsx
HIGH Latest local cart argument is always authoritative
Wrong:
const complete = () => checkout.mutate({ cart })Correct:
const completeCheckout = storefront.flows.checkout.useCompleteCheckout({
cartId: cart.id,
})
const complete = () => completeCheckout.mutate()The checkout flow resolves effective cart state from the shared caches and selected state. A stale local cart object is not always the right source of truth.
Source: libs/storefront-data/src/shared/checkout-flow-utils.ts, libs/storefront-data/src/medusa/checkout-flow.ts
HIGH App-local active-cart cache heuristics
Wrong:
const activeCartKey = ["shop", "cart", "active"]
queryClient.setQueryData(activeCartKey, cart)Correct:
syncCartCaches(queryClient, storefront.queryKeys.cart, cart)Active-cart matching is now shared platform behavior. Hand-written heuristics drift from the tested cache contract.
Source: libs/storefront-data/src/shared/cart-cache-sync.ts, libs/storefront-data/tests/cart.cache-sync.test.ts
See also: configure-pagination-prefetch-and-cache-policy for shared query-key behavior and cache semantics.