Express checkout in themes

Wallet buttons on the product and cart pages: ctx.express, the slot, the product form it reads, and why buttons may not show.

Express checkout shows wallet buttons, such as Apple Pay and Google Pay, on the product page (buying that product, cart untouched), on the cart page and in a cart drawer (buying the cart). The shopper picks an address and delivery in the wallet sheet, confirms, and lands on the order page. The platform owns everything about it: whether the buttons can show, the provider's script, the sheet, pricing and the order. A theme only decides where the buttons go.

Context#

Add express to the ctx_needs of the section (or theme block) that prints the buttons. A product or cart section gets its page's purchase. A cart drawer, or any other global chrome section, gets the cart's purchase (source is cart) on every page, and it is refreshed with the drawer: when the theme re-renders the drawer through section rendering after a cart change, the new HTML carries a new slot, which the platform script replaces whole and mounts, so the buttons always buy the current cart. Other sections on other pages get available false and source null. Quick view is not supported yet: do not print the slot in a quick view.

Field

What it holds

express.available

Whether the buttons can show for this page's purchase. The shopper's device still decides whether it has a wallet.

express.source

product, cart (the cart page and global sections such as a cart drawer), or null on other pages.

express.reason, express.reason_label

When available is false, why: a code (below) and the platform's sentence in the shopper's language. Null when available.

express.wallets

The wallets offered, [{id, label}], id apple_pay or google_pay. For the editor or a note; the buttons themselves come from the slot.

express.variant_reasons

Product page: { "<variant id>": "EXPRESS_REASON_SOLD_OUT" | "EXPRESS_REASON_ORDER_LIMIT" } for variants that cannot use express checkout. The slot already uses it.

express.slot

The platform markup. Empty when available is false.

Full field reference: Cart, wishlist, and reviews.

Offer it as a theme block, "Express checkout", under Add to cart in the product section and under the checkout button in the cart section and in the cart drawer, and include it in the default blocks, in every product and cart template and in the drawer's default blocks so stores get it in place. Merchants move or remove it in the theme editor like any block. The slot arrives with the hidden attribute and the platform script reveals it once the device can pay, so reserve no fixed height and give the wrapper no padding of its own that shows while it is empty.

blocks/express-checkout vascula
{%- if express.available -%}
  <div class="my-express">{{ express.slot }}</div>
{%- elsif design_mode and express.reason_label != blank -%}
  <p class="my-editor-hint">{{ express.reason_label }}</p>
{%- endif -%}

Showing reason_label in the editor (design_mode) tells the merchant why the block is empty. On the live page you may show it as a short note, or print nothing.

The product form#

On a product page the platform script reads the shopper's selection from the page's {% form 'cart_add' %} for product.id. That form must carry product_id, variant_id (when the product has variants) and quantity inputs; when your quantity picker is turned off, keep a hidden quantity input with value 1. The script follows changes to the form: it hides the buttons while the selected variant has an entry in variant_reasons, or while any properties[...] input has a value (personalised items are bought at checkout), and shows the platform's sentence in the slot instead.

vascula
{% form 'cart_add' %}
  <input type="hidden" name="product_id" value="{{ product.id }}">
  <input type="hidden" name="variant_id" value="{{ product.selected_variant.id }}">
  <input type="hidden" name="quantity" value="1">
  <button type="submit">Add to cart</button>
{% endform %}
<div class="my-express">{{ express.slot }}</div>

Why buttons may not show#

The platform checks in this order and reports the first reason. Show reason_label rather than your own words, so the merchant and shopper read the same sentence everywhere.

Reason

When

EXPRESS_REASON_DISABLED

The merchant turned Show express buttons off.

EXPRESS_REASON_CHECKOUT_UNAVAILABLE

Checkout is unavailable (for example the store is not taking orders).

EXPRESS_REASON_NO_PROVIDER

No payment provider enabled for the shopper's market offers express checkout.

EXPRESS_REASON_PROVIDER_SETUP

The provider has not finished setting up express checkout.

EXPRESS_REASON_CURRENCY

The provider's wallet sheet does not take the page's currency.

EXPRESS_REASON_CHECKOUT_FIELD

The store's checkout asks a required question the wallet sheet cannot.

EXPRESS_REASON_SIGN_IN

The store requires shoppers to sign in.

EXPRESS_REASON_PICKUP

The shopper is in pickup mode.

EXPRESS_REASON_EMPTY_CART

The cart is empty (cart page).

EXPRESS_REASON_DISCOUNT_CODE

The cart has a discount code; only automatic discounts apply in the sheet.

EXPRESS_REASON_GIFT_CARD

The product or cart has a gift card.

EXPRESS_REASON_PERSONALISED

The product needs personalisation, or a cart line carries line properties.

EXPRESS_REASON_SOLD_OUT

The product is sold out. Per variant in variant_reasons.

EXPRESS_REASON_ORDER_LIMIT

The total is outside the provider's or the store's order limits. Per variant in variant_reasons.

EXPRESS_REASON_NOT_PURCHASABLE

The product cannot be bought now (for example a pre-order).

EXPRESS_REASON_DEVICE_NO_WALLET

Never in ctx: the platform script shows it in the slot when the shopper's device has no wallet.

Caching#

Product and cart pages stay cacheable. Nothing in express depends on the visitor beyond what the page is already cached by: market, currency, language and shopping mode. Whether the device has a wallet is decided in the shopper's browser, and prices in the sheet come from the platform at tap time.

Theme editor and Theme Store preview#

In theme previews and Theme Store demos, product and cart pages get available: true, both wallets, and a static sample slot without script, so reviewers and merchants see where the buttons go.

Related: Hosted Actions for the express_* actions the platform script calls, and Offer express checkout for payment app developers.

With Fake Pay, the platform confirms the order from the app’s live status answer, including every captured transaction. Returning from the wallet or hosted provider page is navigation only. Provider notifications request a status check; themes must continue using the platform-owned express slot and never implement a payment callback or provider polling loop. Fake Pay is a simulator and moves no real money.

Razorpay Standard Checkout uses the platform browser adapter and is not an express-wallet declaration. Themes must leave payment confirmation to Tringify: provider browser returns and notifications are requests to check, while signed status calls establish payment. The platform handles late captures and repeated notifications. See Razorpay payment behavior.