AgentSkillsCN

checkout-management

修改 GraphQL 查询并重新生成类型。在编辑 .graphql 文件、向查询中添加字段,或遇到关于缺失 GraphQL 类型的 TypeScript 错误时使用。

SKILL.md
--- frontmatter
name: checkout-management
description: Understand checkout session lifecycle and debugging. Use when troubleshooting checkout issues, payment failures, hydration mismatches, or "CHECKOUT_NOT_FULLY_PAID" errors.

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)

code
Cookie name: checkoutId-{channel}
Example: checkoutId-default-channel

The cookie is set in src/lib/checkout.ts:

typescript
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

code
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
typescript
// 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:

tsx
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:

code
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:

typescript
const { checkout: liveCheckout } = useCheckout();
const checkout = liveCheckout || initialCheckout;
const totalAmount = checkout.totalPrice.gross.amount;

Key Files

FilePurpose
src/lib/checkout.tsCheckout creation, cookie management
src/checkout/hooks/useCheckout.tsReact hook for checkout data
src/checkout/lib/utils/url.tsURL query param extraction
src/graphql/CheckoutCreate.graphqlCheckout creation mutation

Debugging Checkout Issues

1. Check Current Checkout ID

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

2. Decode Checkout ID

javascript
// 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:

graphql
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:

json
{
	"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:

code
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