Skip to content

Cart & checkout ​

The cart and the checkout are the two storefront pages that turn a visitor into an order. This page covers how a cart is stored and scoped to a visitor, what happens at each step of checkout, every check that runs before a payment is attempted, and the order record that comes out the other end.

How the cart is stored ​

Ambikly does not use PHP sessions or WordPress transients for carts. Every cart is a row in the {prefix}ambikly_carts table, created the first time a visitor touches the cart.

ColumnWhat it holds
cart_key32-character hex key, also written to the visitor's cookie
user_idThe WordPress user ID, stamped on every write when logged in
contentsJSON map of line key → line item
couponsJSON map of coupon code → applied coupon snapshot
shippingJSON: the chosen method_key and the destination address
billingJSON billing address, once checkout or the cart page has collected one
metaJSON extension slot for add-ons (gift cards, store credit)
currencyThe store currency at the time the cart was created
customer_emailBilling email lifted out of the JSON so add-ons can query on it
expires_at14 days from the last write

Session scoping ​

A visitor is matched to a cart by the ambikly_cart_key cookie:

  1. On the first cart request, Ambikly generates a 32-character hex key and sets the ambikly_cart_key cookie for 14 days. The cookie is HTTP-only, and secure when the site is served over HTTPS.
  2. A cookie value that is not exactly 32 hex characters is discarded and replaced, so a tampered or truncated cookie starts a fresh cart rather than erroring.
  3. Every write stamps the current user ID onto the row, and pushes expires_at another 14 days out.
  4. A daily cron event, ambikly_prune_expired_carts, deletes rows whose expires_at has passed.

Carts and logging in ​

The cookie is the primary handle, so a logged-in customer on a new device would otherwise get an empty cart while their real one sat unreachable. On wp_login, Ambikly looks up that user's most recent other cart and folds it in:

  • If the browser's current cart is empty, every line from the older cart is re-added, and any product that is no longer purchasable is skipped rather than failing the merge.
  • If the browser's current cart already has items, nothing is merged — the items the visitor added in this session win.
  • Either way the older row is deleted, so it does not resurface on the next login.

Line keys ​

Each cart line is keyed by an MD5 of the product ID, variation ID, pricing plan ID and the chosen attributes. Two additions that produce the same key merge into one line and their quantities add together; anything that differs (a different size, a different pricing plan) becomes its own line. Add-ons that attach line metadata get that metadata mixed into the key too, so an order-bump copy of a product does not merge with the same product added normally.

The line key is what the REST API and the storefront use to update or remove a line, and it is always 32 hex characters.

What is stored on a line ​

FieldNotes
line_keyThe MD5 described above
product_id, variation_id, pricing_plan_idIdentity of what was added
name, sku, imageSnapshot for display; a pricing plan appends its name to the product name
priceRe-resolved live on every totals calculation — see below
quantity, max_quantitymax_quantity is the current stock ceiling, or empty when stock is not managed
attributesChosen variation attributes
product_type, is_downloadable, requires_shippingA line requires shipping unless it is digital or downloadable
tax_class, tax_status, weightCopied from the product
unavailableSet on every totals run when the product is missing or not published

Quantities are capped at the real stock ceiling as they are added or edited. For a variation, the variation's own stock is the ceiling; otherwise the parent product's. Adding a product whose ceiling is zero throws This product is out of stock. rather than creating a zero-quantity line.

Prices are re-resolved, not frozen ​

Because a cart lives for 14 days, a price captured at add-to-cart time can go stale. Every time totals are computed, each plain product line is re-priced against the product's current effective price, so a sale that ended is not still honored at checkout. Lines that carry a pricing plan, a variation, or line metadata are skipped — their price was computed from something other than the product's base price.

What the cart totals contain ​

Totals are computed on demand and never written to the database. The response contains:

KeyMeaning
subtotalSum of line price × quantity
discount_totalSum of every valid coupon's discount, clamped to the subtotal
shipping_totalCost of the resolved shipping method, or 0 when a coupon grants free shipping
shipping_titleTitle of the resolved method
shipping_resolvedFalse when the stored method key no longer matches an available method
tax_totalSee Tax
totalSubtotal − discount + shipping + tax
total_items, total_quantityLine count and unit count
couponsPer-coupon code, discount and free-shipping flag
requires_shipping, is_digital_only, has_downloadableDerived from the lines
adjustmentsExtension slot filled by Pro add-ons
enable_couponsMirrors the store-wide Enable Coupons setting so the cart page can hide the coupon form

Every applied coupon is fully re-validated on each totals run, not only when it was applied. A coupon that has since expired, been deactivated, hit its usage limit, or no longer meets its minimum stops discounting — but stays listed, so the cart page can tell the customer it is no longer valid instead of silently dropping it.

The checkout flow end to end ​

  1. The checkout page loads the cart and GET /checkout/options together. The options response returns the payment methods actually available to this visitor, the store currency, and whether guest checkout is allowed.
  2. If the cart is empty, or guest checkout is off and the visitor is not signed in, the page shows a short message instead of a form. Both are also enforced server-side.
  3. The customer fills in billing details. Address fields and a shipping-method selector only appear when at least one line requires shipping; a fully digital cart shows a "no shipping required" note instead.
  4. As soon as a usable destination address is entered, the page posts it to POST /checkout/shipping and renders the matching methods with their costs. Choosing one stores the method key on the cart.
  5. The customer picks a payment method, optionally adds an order note, and submits.
  6. POST /checkout runs every validation below, creates the order, and calls the gateway.
  7. If the gateway returned a redirect (Stripe, PayPal) the browser is sent to the gateway. Otherwise it goes straight to the thank-you page.

The submit button is disabled while the request is in flight, so a double-click or a stray Enter key cannot place two orders.

What is validated before payment ​

These run in order, and the first failure aborts the whole checkout with that message. Nothing is created until they all pass.

CheckFailure message
Cart is not emptyCart is empty.
Guest checkout allowed, or the visitor is signed inPlease sign in to check out.
Billing email presentBilling email is required.
Billing first name, last name, email, country presentBilling {field} is required.
Shipping first name, last name, address, city, country present (only when the cart needs shipping)Shipping {field} is required.
The stored shipping method still resolves for that addressPlease select a valid shipping method before placing your order.
The chosen gateway exists, is enabled, and is usable by this customerSelected payment method is not available.
A cart containing a subscription product uses a gateway that can charge renewalsSelected payment method is not available.
Stock check on every non-downloadable lineInsufficient stock: …

The stock check ​

For each line that is not downloadable:

  • The product must still be published. A product unpublished or deleted after it was added reports {name} (no longer available).
  • If the line is a variation and that variation manages its own stock, the variation's quantity must cover the line.
  • Otherwise, if the parent product manages stock, its quantity must cover the line.

All shortfalls are collected and reported together, so a customer fixes every problem in one pass rather than one per attempt. Add-ons can append their own shortfalls through the ambikly_stock_shortfalls filter — Pro's Bundles add-on uses this to reject a bundle whose components are sold out.

Coupon limits ​

Coupon usage is recorded immediately after the order row is created and before the gateway is called. Recording is an atomic conditional update on the coupon row, so two simultaneous checkouts can never both claim the last remaining use. If the limit was already taken, the order is moved to failed, its stock is released, and the customer sees This coupon has reached its usage limit. No payment is attempted.

The same applies to a per-customer limit, which fails with You have already used this coupon the maximum number of times allowed.

Stock is decremented at order creation

Stock comes off the moment the order row is created, not when payment succeeds. A failed or abandoned payment restores it by moving the order to failed, which is what triggers the restore. A customer who abandons a Stripe or PayPal redirect without ever cancelling leaves the order pending — Stripe's session-expiry event eventually fails it; PayPal sends no equivalent event.

Guest versus logged-in checkout ​

Guest checkout is on by default and is controlled by Enable Guest Checkout in Ambikly → Settings → Store Settings.

GuestLogged in
WordPress user accountNone is createdExisting account
Ambikly customer recordCreated or matched by billing emailMatched by email, linked to the user ID
user_id on the orderEmptyThe WordPress user ID
Cart across devicesCookie onlyMerged on login
Thank-you page accessNeeds the signed key in the URLAlso allowed by matching user ID or email

Turning guest checkout off makes the checkout page render a sign-in prompt rather than a form, and the server rejects an anonymous submission regardless.

The order that gets created ​

The order is created before the gateway runs, with status pending, payment_status pending and fulfillment_status unfulfilled. Creation writes:

  • The order row, with a generated order number, the cart's totals copied field by field, the payment and shipping method identifiers and titles, the comma-joined coupon codes, the customer note, the client IP, the user agent, and a hash of the cart contents.
  • One order item row per cart line, carrying the product type, requires_shipping and is_downloadable flags so downloads are granted correctly later.
  • A billing address row, and a shipping address row when one was supplied.
  • An order note: Order placed via checkout.
  • The ambikly_order_created action, which is what decrements stock.

The gateway then runs and sets the real status. See Payments overview for the status each gateway produces.

When a payment genuinely settles, Ambikly marks the order paid: payment_status becomes paid, total_paid is set to the order total, and an order sitting in pending or on-hold moves to processing. That fires ambikly_order_paid, which issues download grants and license keys, and auto-completes an order whose contents are entirely digital. A mixed cart stays in processing so you can ship the physical part.

Where the customer lands ​

For a gateway that settles in-process (cash on delivery, bank transfer, check, manual, net terms) the browser goes straight to the thank-you page, carrying order_id and a signed key. That key is an HMAC over the order — without it, the page would be enumerable by order ID.

For Stripe and PayPal the browser goes to the gateway first. Both come back to the thank-you page only after the payment has actually been confirmed. The cart is deliberately not cleared when a gateway returns a redirect, so a customer who cancels or abandons still has their cart intact. It is cleared once on the thank-you page for a genuinely paid order, guarded by order metadata so a refresh does not wipe a cart the customer has since started refilling.

Handlers of ambikly_checkout_completed must null-check

For redirect gateways the completion action fires from the thank-you page with null as its second argument, because there is no payment result object at that point.

Cart and checkout on the storefront ​

Both pages are rendered from a block or a shortcode — they are interchangeable, and both mount the same JavaScript app against the REST API.

PageBlockShortcodeNotes
Cartambikly/cart[ambikly_cart]Block attributes: layout (two-column or stacked), showCoupons, showShipping, checkoutLabel
Checkoutambikly/checkout[ambikly_checkout]No attributes
Thank you—[ambikly_thank_you]Reads order_id and key from the URL

The setup wizard creates these pages for you. See Blocks & store pages for the full block reference and Shortcodes for the shortcode list.

Cart REST endpoints ​

Every cart route is public — the visitor's cookie identifies the cart.

MethodRouteNotes
GET/cartFull cart with computed totals
POST/cart/itemsproduct_id, quantity, variation_id, pricing_plan_id, attributes
PUT/cart/items/{line_key}quantity; zero or less removes the line
DELETE/cart/items/{line_key}Remove a line
POST/cart/clearEmpty the cart, its coupons and its shipping selection
POST/cart/couponscode
DELETE/cart/coupons/{code}Remove an applied coupon
POST/cart/shippingmethod_key and address
POST/cart/billingaddress
GET/checkout/optionsAvailable gateways, currency, guest-checkout flag
POST/checkout/shippingShipping methods for an address
POST/checkoutPlace the order

Price overrides and line metadata are server-only. The public add-to-cart route strips them, so a crafted request cannot set its own price.

PROCheckout add-ons

Ambikly Pro replaces the multi-step flow with a single page, adds accept-in-one-click offers inside the checkout summary, and offers upsells after the order is paid by re-charging the card already on file.

See pricing →

Post-purchase upsells re-charge the stored card without a second checkout, which only works on a gateway that saved a reusable payment method. In practice that means Stripe.

Next ​

Payments overview covers all seven gateways, how to enable them, and the order status each one produces.