N
HeadlessCoreHeadless Core
Back to Home

WooCommerce Checkout (real, optional)

Built without a live WooCommerce install to test against. WooCommerce and WPGraphQL for WooCommerce aren't installed in this starter kit's own dev environment, so this flow was verified by build/typecheck only, not end-to-end against a real store. Treat your first real use as a verify-carefully situation — test a full purchase in a sandbox before relying on it in production.

Replaces the previous purely-mock /shop checkout ("Proceed to Checkout" just showed a fake success message) with a real cart-to-order flow via WooCommerce's own Store API — not a URL-parameter hack, this is WooCommerce's officially-supported mechanism for headless/decoupled carts.

Why redirect to WooCommerce's own checkout, not a custom UI

Building custom payment UI in Next.js means either handling card data directly (real PCI compliance scope) or embedding one specific gateway's client SDK (locks the starter kit to that processor). Redirecting to WooCommerce's own hosted /checkout/ page keeps payment UI and PCI scope entirely on the WordPress/WooCommerce side — same reasoning already applied to the Stripe Payment Link integration.

Setup

  1. Install WooCommerce and WPGraphQL for WooCommerce (WooGraphQL) on WordPress.
  2. Set headlesscore_nextjs_url (wp-admin → Headless Core → Settings → Connection) to your Next.js frontend's URL — inc/woocommerce.php reads this same option to configure Store API CORS.
  3. That's it on the config side — no new env vars on the Next.js side; the flow uses NEXT_PUBLIC_WORDPRESS_URL (already required) and the tenant-resolved WordPress URL passed into ShopContainer.

How it works

  1. /shop fetches products via getHeadlessProducts() first; if empty, falls back to getShopProducts() (real WooCommerce via WooGraphQL). ShopContainer receives an isWooCommerce flag — true only when the catalog actually came from WooCommerce (has a wooCommerceId, WPGraphQL's databaseIdnot the opaque id field, which the WooCommerce Store API can't use).
  2. Cart stays entirely client-side, unchanged.
  3. "Proceed to Checkout", when isWooCommerce is true:
    • GET {wordpressUrl}/wp-json/wc/store/v1/cart (credentials: "include") establishes/reads the WooCommerce session cookie and returns a Nonce response header.
    • POST {wordpressUrl}/wp-json/wc/store/v1/cart/add-item once per cart line, with the Nonce header and credentials: "include".
    • window.location.href = "{wordpressUrl}/checkout/" — the customer completes payment on WooCommerce's own checkout page, whatever gateway you've configured there.
  4. When isWooCommerce is false (catalog came from the headless_product CPT or mock data — no real WooCommerce store behind it), the button reads "Proceed to Checkout (Demo)" and keeps the original UI-only confirmation — there's no real order to place.

CORS (required)

The Store API calls above are cross-origin and credentialed (carry cookies) — browsers block this by default. inc/woocommerce.php adds a rest_pre_serve_request filter scoped only to routes under /wc/store/ (not a blanket CORS loosening across the site), setting:

Access-Control-Allow-Origin: {headlesscore_nextjs_url}
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: Nonce

Access-Control-Expose-Headers: Nonce matters specifically — without it, cross-origin fetch() responses hide all but a small set of "simple" headers from JavaScript, and the frontend can't read the Nonce it needs for the add-item call.

Guarded by function_exists('WC'), so this filter is completely inert when WooCommerce isn't installed (the default state of this starter kit).

Testing

# Confirm the cart endpoint responds and CORS headers are present
curl -i "https://your-wp-site.com/wp-json/wc/store/v1/cart" \
  -H "Origin: https://your-nextjs-site.com"

Look for Access-Control-Allow-Origin in the response matching your Next.js URL. Then test a full add-to-cart → checkout → payment flow with a WooCommerce test/sandbox gateway before going live.

Deferred (not in v1)

  • Custom/embedded checkout UI — deliberately out of scope, see rationale above.
  • Cart persistence across devices/sessions — relies entirely on WooCommerce's own session cookie (same-browser only).
  • Multi-tenant-aware WooCommerce — assumes one WooCommerce store, same single-seller assumption as the Stripe integration.