checkout-management

📁 saleor/storefront 📅 9 days ago
3
总安装量
3
周安装量
#57360
全站排名
安装命令
npx skills add https://github.com/saleor/storefront --skill checkout-management

Agent 安装分布

amp 3
gemini-cli 3
github-copilot 3
codex 3
kimi-cli 3
cursor 3

Skill 文档

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:

1. 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,
	});
}

2. 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:

  1. Clear cookies – Delete checkoutId-{channel} cookie
  2. Use incognito – Test in a private browser window
  3. 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

1. Check Current Checkout ID

// In browser console
document.cookie.split(";").find((c) => c.includes("checkoutId"));

2. Decode Checkout ID

// Base64 decode the checkout ID from URL
atob("Q2hlY2tvdXQ6YThjN2Y4YjgtZmU0NS00ZTRkLThhZmItZDdjYWI2YTM5MTdm");
// Returns: "Checkout:a8c7f8b8-fe45-4e4d-8afb-d7cab6a3917f"

3. 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:

  1. Check Saleor Dashboard → Apps – is the payment app active/healthy?
  2. Check if the payment app URL is accessible
  3. Restart the payment app if self-hosted
  4. 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:

  1. Payment app is down – transaction was created but authorization failed
  2. Stale checkout – previous partial transactions exist
  3. Amount mismatch – checkout total changed after transaction init

Debug steps:

  1. Check [Payment] Transaction init result: logs for transactionEvent.type
  2. If AUTHORIZATION_FAILURE → payment app is down/unreachable
  3. If transaction succeeded but amount is wrong → checkout data is stale

Best Practices

  1. Always use live checkout data for payment amounts
  2. Handle checkout not found gracefully (create new checkout)
  3. Clear checkout after completion to avoid stale data
  4. Test with fresh checkouts when debugging payment issues
  5. Check payment app health when transactions fail with AUTHORIZATION_FAILURE