Supercommerce API Docs
Store API

Guest Checkout Module — Storefront

HTTP surface for guest (no-account) checkout — contact capture for an anonymous cart, a non-blocking "you already have an account" hint, and public order tracking after the order…

HTTP surface for guest (no-account) checkout — contact capture for an anonymous cart, a non-blocking "you already have an account" hint, and public order tracking after the order is placed. Delivered as a removable plugin built on better-auth's anonymous plugin: a guest is a throwaway anonymous user, so the core cart / place-order flow runs unchanged.

Source: api-modules/guest-checkout/src/controllers/store-guest-checkout.controller.ts, api-modules/guest-checkout/src/controllers/store-guest-order-lookup.controller.ts.

This is a customer-facing-only surface — no admin or vendor controller. Removing GuestCheckoutModule.forRoot() from app.module disables every endpoint below, the guest confirmation email, and the guest→account conversion. The better-auth POST /auth/sign-in/anonymous endpoint remains but is inert without this module.


The guest flow

  1. Start a guest sessionPOST /auth/sign-in/anonymous (better-auth, not this module). This mints an anonymous user whose user.email is an unroutable temp address.
  2. Build the cart — the standard store/cart endpoints, using the same x-cart-token handle.
  3. Capture contactPOST /store/guest/contact stores the guest's real email (used for the confirmation + tracking link) against the active cart.
  4. Place the order — the standard store/orders place-order flow, unchanged.
  5. Confirmation — on order.placed, this module mints an unguessable lookup token, binds it to the contact, and emails a tokenized order-status link. (The default order-placed notification is suppressed for anonymous users, so this is the only confirmation a guest receives.)
  6. Track — the guest opens the tokenized link (GET /store/guest/orders/:token) or uses the email + order-number form (POST /store/guest/orders/lookup).
  7. (Optional) Convert — if the guest later signs up / signs in, better-auth links the anonymous account and this module re-keys their orders, addresses, and discount usage onto the real account before the anonymous user is deleted.

Conventions

Authentication

EndpointAuth
POST /store/guest/contactanonymous session required (guard is OptionalAuth, but the handler 400s without session.user.id)
GET /store/guest/account-existsnone (public)
POST /store/guest/orders/lookupnone (public)
GET /store/guest/orders/:tokennone (public)

All controllers run BetterAuthGuard with @OptionalAuth() — a session is read when present but never mandated by the guard. The order-tracking endpoints are deliberately public so a guest can track without ever logging in.

Headers (contact endpoint)

Mirrors the cart/checkout controllers; forwarded to the cart layer verbatim:

HeaderDirectionNotes
x-cart-tokenrequest (optional) + responseOpaque cart handle. The response always re-emits the active cart's token.
x-platformrequest (optional)WEB / APP (case-insensitive). Defaults to WEB.

Response envelope

{
  "data": <payload>,
  "message": "Success",
  "statusCode": 200
}

Error envelope

statusCodeerrorCode examples
400BAD_REQUEST (no guest session on contact), VALIDATION_ERROR
404GUEST_ORDER_NOT_FOUND (order tracking; neutral — see below)
500INTERNAL_SERVER_ERROR, DATABASE_ERROR

Neutral 404 on tracking

Both order-tracking endpoints raise the same 404 GUEST_ORDER_NOT_FOUND on any miss (unknown token, wrong email, unknown order number, email/order mismatch) so the endpoints can't be used to enumerate orders or probe which emails placed orders.


Domain types

GuestContactResponsePOST /store/guest/contact

type GuestContactResponse = {
  email: string;
  accountExists: boolean;   // non-blocking hint; never gates checkout
};

GuestAccountExistsResponseGET /store/guest/account-exists

type GuestAccountExistsResponse = { exists: boolean };

Order detail

The two order-tracking endpoints return the order module's OrderResponse shape (see order.md) — the same payload an authenticated customer gets for their own order.


Endpoints

POST /store/guest/contact — Capture the guest's contact details

Requires an anonymous session (step 1 above). Resolves the active cart from x-cart-token (minting one if absent), stores the contact email/name/phone against that cart, and returns a non-blocking accountExists hint. The response sets x-cart-token.

Headersx-cart-token (optional), x-platform (optional).

Body

{ "email": "guest@example.com", "name": "Jane Doe", "phone": "+15551234567" }
FieldTypeConstraints
emailstringTrimmed, valid email. The real contact address (the anonymous user.email is unroutable)
namestring?Trimmed, 1..255 chars
phonestring?Trimmed, 1..32 chars

Response 200GuestContactResponse.

Errors

StatusCodeWhen
400BAD_REQUESTNo guest session — "Start a guest session (POST /auth/sign-in/anonymous) before capturing contact details"
400VALIDATION_ERRORBody fails zod (bad email, etc.)

GET /store/guest/account-exists — Does a registered account use this email?

Non-blocking lookup driving the "you already have an account — log in to keep your orders together" hint. Never blocks guest checkout.

Query

NameTypeConstraints
emailstringTrimmed, valid email

Response 200GuestAccountExistsResponse.


POST /store/guest/orders/lookup — Track by email + order number

The WooCommerce/Magento-style guest tracking form.

Body

{ "email": "guest@example.com", "orderNumber": "SC-100245" }
FieldTypeConstraints
emailstringTrimmed, valid email
orderNumberstringTrimmed, min 1 char

Response 200OrderResponse (see order.md).

Errors

StatusCodeWhen
404GUEST_ORDER_NOT_FOUNDNo order with that number, or the email doesn't match the captured contact

The unguessable token (32 bytes of entropy → 43-char base64url) embedded in the confirmation email. Built into a URL of the form <STOREFRONT_URL>/order-status/<token>.

Path params

NameNotes
tokenOrder-status lookup token from the confirmation email

Response 200OrderResponse (see order.md).

Errors

StatusCodeWhen
404GUEST_ORDER_NOT_FOUNDUnknown token, or no order is bound to it yet

Side effects (not direct HTTP)

These run off the event bus, not from a client call, but shape what the endpoints above return:

TriggerBehavior
order.placed (guest order)Mints the lookup token, binds it to the contact + order, and emails the tokenized status link to the captured contact address. Idempotent — a re-delivered event does not re-mint or re-send. Failures are swallowed (the order is already placed; the email + order-number path still works).
guest_checkout.anon_account_linked (better-auth onLinkAccount)Re-keys the guest's order, customer_address (forced to isDefault=false), discount_usage, order_event, and non-active cart rows onto the new account, in a single transaction that completes before better-auth deletes the anonymous user (so a failure aborts the deletion rather than orphaning data). Guest-earned reward points are intentionally not migrated in v1.

  • auth — the better-auth anonymous plugin mints the guest session; the onLinkAccount hook drives conversion. POST /auth/sign-in/anonymous is the prerequisite for POST /store/guest/contact.
  • cartPOST /store/guest/contact resolves the active cart via CartService using the same x-cart-token / x-platform handles as the cart endpoints. See cart.md.
  • order — order tracking returns the order module's OrderResponse; the confirmation flow listens on order.placed. See order.md.
  • notifications — the default order-placed notification is suppressed for anonymous users (recipient resolver), leaving this module's guest confirmation email as the sole notice.

On this page