Description
The Checkout — payment-only mode (integrating Checkout for payments while owning the rest of the flow), full hosted checkout (checkoutFlow), widget integration via @commercetools/checkout-browser-sdk, configuration, PSP connector setup (Stripe, Adyen, Mollie), and webhook handling. Use when integrating Checkout (the product) into a storefront or headless app — not for generic cart-to-order patterns (see commercetools-storefront).
Installation
In any Claude Code session, run the following commands one at a time:
-
Add the commercetools AI marketplace:
/plugin marketplace add commercetools/commercetools-ai-plugins -
Install the commercetools AI plugin (Skills + Subagents + MCP):
/plugin install commercetools@commercetools
If you've updated the plugin or installed it in another window and need the current session to pick up the latest version:
/reload-plugins
commercetools/commercetools-ai-plugins. Then, click on the plugin and click Install.Instructions Included
Checkout
The Checkout provides hosted payment and checkout experiences via the Browser SDK.
Integration Modes
Three modes — choose based on how much of the checkout flow Checkout should own:
paymentFlow(payment-only) — keep existing address/shipping UI, replace only the payment step with the Checkout widget. Least invasive.checkoutFlow(full hosted) — replace the entire multi-step checkout with a single commercetools-hosted page covering address, shipping, and payment.expressPayment— add Apple Pay / Google Pay express buttons to cart or PDP. Can coexist with either mode above.
Workflow
When this skill is invoked, always follow these steps:
-
Docs search (required, run first) — The first time you use this skill in a session you must run this before answering. It gathers the latest verified documentation as your primary grounding, filtered to the products this skill covers. Use this script for documentation search while working with this skill; the Knowledge MCP covers everything else. Always confirm details against retrieved documentation rather than the skill text alone:
node scripts/docs-search.mjs \ --query "<extract key terms from user's question>" \ --app-name "<host app: claude-code, claude-chat, cursor, codex, copilot — or the host's own name>" \ --model "<current-model>" \ --limit 20 -
Combine with skill references — Cross-reference the analysis output with local references in
./references/for complete context. -
Provide implementation guidance — Synthesize the documentation with the specific integration mode the user is targeting.
Optional scripts
node scripts/graphql-schemata.mjs \
--resource-name "<commercetools resource, e.g. Cart, Product, Order>" \
--app-name "<host app: claude-code, claude-chat, cursor, codex, copilot — or the host's own name>" \
--model "<current-model>"
--resource-name.node scripts/openApi-schemata.mjs \
--resource-name "<commercetools resource, e.g. api-Cart-write, api-Customer-read, checkout-Application>" \
--app-name "<host app: claude-code, claude-chat, cursor, codex, copilot — or the host's own name>" \
--model "<current-model>"
api-Cart-read, api-Cart-write). If the resource name is not recognized, the script prints the list of valid resource names — pick the correct one and re-run. Note: the spec does not include reference-expansion schemas — fetch a referenced resource's schema separately by re-running this script with that resource as --resource-name.References
- Full architecture diagram (Browser SDK → Checkout service → PSP)
- Session creation (
/<api>/checkout/session→ commercetools Sessions API) paymentFlow,checkoutFlow, andexpressPaymentimplementation patterns- PSP connector setup (Stripe, Adyen, Mollie)
- Webhook handling and order confirmation
References
Checkout Integration
Before You Start — Ask the User Two Questions
-
Which checkout mode?
paymentFlow— payment-only: keep existing address/shipping steps, replace onlyStepPayment.tsxwith the commercetools widget. Least invasive.checkoutFlow— full page: replace the entire multi-step checkout with a single commercetools-hosted checkout page (addresses, shipping, and payment all handled by Checkout).expressPayment— express buttons: add Apple Pay / Google Pay buttons to the cart or PDP. Can coexist with either of the above.
-
Which PSP (Payment Service Provider)? The user must configure a Connector in the commercetools Merchant Center that connects to their PSP (e.g., Stripe, Adyen, Mollie, PayPal, ...etc). The skill does not set up the PSP connector — that is done in commercetools Merchant Center or via the Payment Integrations API. Just record the answer so it's clear in your implementation notes.
Architecture Overview
Browser (SDK) → Checkout service → PSP
↑
/<api>/checkout/session (creates Checkout Session from cart)
↑
commercetools Sessions API (POST https://session.{region}.commercetools.com/{projectKey}/sessions)
sessionId to the browser SDK. The SDK handles all payment UI and communication with the PSP. After payment, commercetools creates the order automatically (full/express) or triggers a webhook/redirect (payment-only).Step 0 — Prerequisites
0a. Add environment variables to <root-dir>/.env
# The key of your Checkout Application
CTP_CHECKOUT_APP_KEY=storefront-checkout
CTP_PROJECT_KEY, CTP_API_URL, and CTP_SCOPES are already in <root-dir>/.env. The region is derived automatically from CTP_API_URL at startup — do not add a separate CT_REGION variable. projectKey and region are returned to the browser by /<api>/checkout/session.0b. Install the Browser SDK
cd <root-dir> && npm install @commercetools/checkout-browser-sdk
Step 1 — Session creation API route
<api-dir>/checkout/session. This route:- Reads the current cart ID from the session
- Exchanges an OAuth token
- POSTs to the commercetools Sessions API to create a Checkout Session
- Returns the
sessionIdto the browser
// <root-dir>/<api-dir>/checkout/session/route.ts
const APP_KEY = process.env.CTP_CHECKOUT_APP_KEY!;
// Derive region from CTP_API_URL — no separate CT_REGION variable needed.
// https://api.us-central1.gcp.commercetools.com → us-central1.gcp
const REGION = API_URL.replace(/^https?:\/\/api\./, '').replace(/\.commercetools\.com\/?$/, '');
async function getManageSessionsToken(): Promise<string> {
// fetch token from oauth/token?grant_type=client_credentials
// use the full scope provided in .env vars
}
export async function POST() {
// guard with session's cart ID
try {
const token = await getManageSessionsToken();
const res = await fetch(
`https://session.${REGION}.commercetools.com/${PROJECT_KEY}/sessions`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
cart: { cartRef: { id: session.cartId } },
metadata: { applicationKey: APP_KEY },
}),
},
);
// handle errors here
const data = await res.json();
// Return projectKey and region so the client component needs no frontend facing environment vars
return NextResponse.json({ sessionId: data.id, projectKey: PROJECT_KEY, region: REGION });
} catch (e: unknown) {
// handle error
}
}
Step 2 — Integration by mode
Mode A: paymentFlow (payment-only — recommended starting point)
This is the least invasive change. Keep the existing address and shipping steps. Only the payment step.
2A-1. Implement StepPayment frontend component
Example react/typescript component
// <root-dir>/components/checkout/StepPayment.tsx
'use client';
...
import { paymentFlow } from '@commercetools/checkout-browser-sdk';
export default function StepPayment() {
// fetch checkout session
useEffect(() => {
// handle loading and single initialization
(async () => {
try {
const { sessionId, projectKey, region } = await getCheckoutSession();
paymentFlow({
projectKey,
region,
sessionId,
locale, // from useLocale() — never hardcode
onInfo: (msg) => {
if (msg.code === 'checkout_completed') {
router.push(<checkout-confirmation-path>);
}
},
onError: (err) => {
// SDK Message type has payload/code, not message
setError(String(err.payload ?? err.code ?? 'Payment error'));
},
});
} catch (e: unknown) {
// handle errors
}
})();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<div>
// display loading DOM
{/* Required mount point — without this the widget occupies the full page */}
<div data-ctc />
// display errors
</div>
);
}
projectKey and region are returned by /<api>/checkout/session alongside the sessionId and read from the response in the client component.2A-2. Integrate into the checkout step page
In Checkout page, the payment step should render the PaymentStep.tsx
Note: WithpaymentFlow, Checkout handles the payment AND the order is created by commercetools automatically when payment succeeds (via thepaymentReturnUrlconfigured in the Application).
Step 3 — Order confirmation after Checkout
paymentReturnUrl after a successful payment, appending ?orderId=<id> (or ?orderNumber=<n>). Update the confirmation page to read these params:Step 5 — Styling
styles option (CSS custom properties):checkoutFlow({
// ...
styles: {
'--font-family': 'var(--font-sans)',
'--color-primary': '#2d2d2d', // charcoal
'--color-primary-hover': '#4a4a4a',
'--border-radius': '0.125rem', // rounded-sm
},
});
Available CSS variables are listed in the Checkout theming docs.
Environment Variable Summary
| Variable | Secret? | Purpose |
|---|---|---|
CTP_CHECKOUT_APP_KEY | No | New — key of the Checkout Application |
Checklist
Prerequisites
-
CTP_CHECKOUT_APP_KEYadded to<root-dir>/.env(the only new variable required) -
@commercetools/checkout-browser-sdkinstalled
Session API
- Create
<api-dir>/checkout/sessionendpoint
SDK integration (per mode)
- paymentFlow: Replace
StepPayment.tsxwith commercetools widget mount;
Order confirmation
- Update confirmation page to read
?orderId=from commercetools redirect URL oronInfocallback
Key Engineering Notes
<div data-ctc /> — For paymentFlow, always render <div data-ctc /> in the component's JSX. Without it the commercetools widget mounts at the document root and occupies the full page.PUBLIC_* vars — projectKey and region are server-side config. Return them from /<api>/checkout/session alongside sessionId so the browser never needs environment vars for these values.paymentReturnUrl must be registered — Only one return URL per Connector. The URL must exactly match what's registered in the commercetools Application paymentsConfiguration.paymentReturnUrl.