Checkout Management Skill
Overview
This skill covers how checkout sessions are created, stored, and managed in the Saleor storefront.
Checkout ID Storage
Checkout IDs are stored in two places:
- Cookie (Primary Storage)
Cookie name: checkoutId-{channel} Example: checkoutId-default-channel
The cookie is set in src/lib/checkout.ts :
export async function saveIdToCookie(channel: string, checkoutId: string) {
const cookieName = checkoutId-${channel};
(await cookies()).set(cookieName, checkoutId, {
sameSite: "lax",
secure: shouldUseHttps,
});
}
- URL Query Parameter
URL: /checkout?checkout=Q2hlY2tvdXQ6YThjN2Y4YjgtZmU0NS00ZTRkLThhZmItZDdjYWI2YTM5MTdm
The checkout ID is a base64-encoded Saleor global ID.
Checkout Lifecycle
Creation
A new checkout is created when:
-
User adds first item to an empty cart
-
No valid checkout ID exists in cookie
-
Existing checkout is not found in Saleor
// src/lib/checkout.ts export async function findOrCreate({ channel, checkoutId }) { if (!checkoutId) { return (await create({ channel })).checkoutCreate?.checkout; } const checkout = await find(checkoutId); return checkout || (await create({ channel })).checkoutCreate?.checkout; }
Persistence
The checkout persists across:
-
Page refreshes
-
Browser sessions (cookie-based)
-
Cart modifications
Completion
When checkoutComplete mutation succeeds:
-
Checkout is converted to an Order
-
The checkout ID becomes invalid
-
A new checkout should be created for future purchases
Common Issues
Hydration Mismatch with Checkout ID
Problem: extractCheckoutIdFromUrl() called during SSR reads an empty URL, causing React hydration mismatch and "PageNotFound" flash.
Symptom: Checkout page briefly shows error then loads correctly on refresh.
Fix: Delay extraction until after client-side mount:
const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); const id = useMemo(() => (mounted ? extractCheckoutIdFromUrl() : null), [mounted]);
See src/checkout/hooks/useCheckout.ts for the full implementation.
Stale Checkout with Failed Transactions
Problem: If payment fails multiple times, the checkout accumulates partial transactions. Subsequent payment attempts may fail with:
CHECKOUT_NOT_FULLY_PAID: The authorized amount doesn't cover the checkout's total amount.
Solutions:
-
Clear cookies - Delete checkoutId-{channel} cookie
-
Use incognito - Test in a private browser window
-
Remove URL param - Navigate to checkout without ?checkout=XXX
Checkout Amount Mismatch
Problem: Checkout total changes after transactions are initialized (e.g., shipping added).
Solution: Always use live checkout data via useCheckout() hook before payment:
const { checkout: liveCheckout } = useCheckout(); const checkout = liveCheckout || initialCheckout; const totalAmount = checkout.totalPrice.gross.amount;
Key Files
File Purpose
src/lib/checkout.ts
Checkout creation, cookie management
src/checkout/hooks/useCheckout.ts
React hook for checkout data
src/checkout/lib/utils/url.ts
URL query param extraction
src/graphql/CheckoutCreate.graphql
Checkout creation mutation
Debugging Checkout Issues
- Check Current Checkout ID
// In browser console document.cookie.split(";").find((c) => c.includes("checkoutId"));
- Decode Checkout ID
// Base64 decode the checkout ID from URL atob("Q2hlY2tvdXQ6YThjN2Y4YjgtZmU0NS00ZTRkLThhZmItZDdjYWI2YTM5MTdm"); // Returns: "Checkout:a8c7f8b8-fe45-4e4d-8afb-d7cab6a3917f"
- Query Checkout in Saleor
Use GraphQL playground to inspect checkout state:
query { checkout(id: "Q2hlY2tvdXQ6...") { id totalPrice { gross { amount currency } } transactions { id chargedAmount { amount } authorizedAmount { amount } } } }
Payment App Issues
Transaction Fails with "AUTHORIZATION_FAILURE"
Symptom: Transaction is created but fails immediately:
{ "transaction": { "id": "...", "actions": [] }, "transactionEvent": { "message": "Failed to delivery request.", "type": "AUTHORIZATION_FAILURE" } }
Cause: The payment app (e.g., Dummy Gateway, Stripe, Adyen) is not responding.
Solutions:
-
Check Saleor Dashboard → Apps - is the payment app active/healthy?
-
Check if the payment app URL is accessible
-
Restart the payment app if self-hosted
-
Check Saleor Cloud status if using cloud-hosted apps
"CHECKOUT_NOT_FULLY_PAID" Error
Symptom: checkoutComplete fails with:
The authorized amount doesn't cover the checkout's total amount.
Causes:
-
Payment app is down - transaction was created but authorization failed
-
Stale checkout - previous partial transactions exist
-
Amount mismatch - checkout total changed after transaction init
Debug steps:
-
Check [Payment] Transaction init result: logs for transactionEvent.type
-
If AUTHORIZATION_FAILURE → payment app is down/unreachable
-
If transaction succeeded but amount is wrong → checkout data is stale
Best Practices
-
Always use live checkout data for payment amounts
-
Handle checkout not found gracefully (create new checkout)
-
Clear checkout after completion to avoid stale data
-
Test with fresh checkouts when debugging payment issues
-
Check payment app health when transactions fail with AUTHORIZATION_FAILURE