import { ShopifyRequestedPurchase } from './checkout-types'; /** The live context a claim needs. INTERNAL — the React adapter fills it for you * from provider state; you never construct one. Separate from * `ShopifyCheckoutContext` so the claim and verify seams can diverge without * breaking each other. */ export interface ShopifyClaimContext { /** The consumer's admission credential (the `admissionGrant` from the * consumer-me snapshot). Relayed to the proxy; never persisted in Shopify. */ credential: string; distributionId: string; /** * The cart identifier the gate token is written to. For the React adapter's * headless path this is the real Storefront cart GID you materialized and * handed to `useFanfareClaim().claim({ cartId, purchase })`. This identifies the * cart metafield write target; `requestedPurchase` names what is bought. * (Liquid-theme callers * that build a claimer directly may instead pass an Ajax cart token incl. * `?key=`; the proxy/cart bridge resolves either form to a cart GID.) */ cartId: string; /** * The Shopify cart checkout URL. When present, the SDK signs the relayed DPoP * proof over this URL so the app proxy can ask the admission authority to * verify possession for the eventual checkout target. */ checkoutUrl?: string; /** * The intended purchase. The cart id names WHERE the gate token is written; this * names WHAT is being bought, so the admission authority can apply product/quantity * policy. The server checks the requested quantity against the remaining purchase * allowance. */ requestedPurchase: ShopifyRequestedPurchase; } /** * A machine-readable refusal code on `{ ok: false }`. The codes split into two * groups for the integrator deciding whether to retry: * * - `cart_not_materialized` — RECOVERABLE. The proxy could not find a real cart * behind your `cartId`. Materialize a real Storefront cart, then claim again * with its id. This is the one reason that maps to a concrete retry the * integrator owns. * - `DISTRIBUTION_ORDER_LIMIT` — purchase allowance exceeded; do not offer a retry. * - `dedicated_cart_required` — the admission is priced and needs a cart that * holds nothing but its line, in a market whose currency is the shop currency. * Claim again on the cart `createDedicatedCart()` returns. * - `admission_bound_to_other_cart` — the admission is already bound to another * cart. When the result carries `boundCart`, claim again with its `cartId` and * `checkoutUrl` and redirect to that cart; without it, surface the refusal. * - `no_claim_context` — NOT-YET-READY (not a bug in your `cartId`). The provider * has no admission credential / distribution id to claim with: the shopper * hasn't cleared the drop, the journey hasn't routed, or you're in demo mode * with no `proxyBaseUrl`. Wire up the widget + grant first; re-claiming only * helps once those are in place. * - `claim_malformed` / `claim_unreachable` / `proxy_` — TERMINAL for * this attempt (a bad/unreadable mint response, a transport failure/timeout, or * a non-2xx proxy status other than the 409 above). Surface to the shopper; a * blind re-claim is unlikely to help. */ export type ClaimRefusalReason = "DISTRIBUTION_ORDER_LIMIT" | "dedicated_cart_required" | "admission_bound_to_other_cart" | "cart_not_materialized" | "no_claim_context" | "claim_malformed" | "claim_unreachable" | `proxy_${number}`; /** * The result of a claim. On `{ ok: true }` the gate token was minted and written * to your cart, and `expDate` is the token's ISO-8601 expiry — surface it as a * continuity signal if you like. On `{ ok: false }` the claim was refused and * `reason` is a stable `ClaimRefusalReason` code — see that type for which codes * are retryable (`cart_not_materialized` → re-materialize + re-claim) vs terminal. * * Note: this result does NOT carry a checkout URL. You own the cart mutation and * redirect: on `{ ok: true }`, redirect to the checkout URL for the same cart * after your integration has added any gated lines that were waiting for the * gate token. */ export type ClaimResult = { ok: true; expDate: string; } | { ok: false; reason: ClaimRefusalReason; /** Present only on `admission_bound_to_other_cart`, when the server named the cart the admission is bound to. */ boundCart?: { cartId: string; checkoutUrl: string; }; }; export interface ShopifyClaimConfig { /** App-proxy base, e.g. "/apps/fanfare/api". `claim` POSTs to * `${proxyBaseUrl}/${endpoint}`. */ proxyBaseUrl: string; /** * Supplies the live claim context. Returns `null` when the storefront is not * ready to claim (no credential/cart yet), which `claim` surfaces as a benign * denied result rather than a thrown error. Absent ⇒ always not-ready. */ getClaimContext?: () => ShopifyClaimContext | null | Promise; /** Proxy path under `proxyBaseUrl`. Defaults to the mint-only `admissions/claim` * route. The React adapter's headless buy action overrides this to * `admissions/reserve` (the #29 reserve-fold: validate + mint + write + RESERVE * in one round-trip), which is body-compatible — the claimer reads only * `expDate`. Direct callers (e.g. the Liquid theme) keep the default. */ endpoint?: string; /** Injectable for SSR/test. Defaults to `globalThis.fetch`. */ fetchImpl?: typeof fetch; /** * Abort the proxy request after this many ms so a stalled (connected but * unresponsive) proxy can't leave `claim()` pending — every outcome must * resolve. The abort surfaces as a denied result. Defaults to 10s. */ timeoutMs?: number; } export interface ShopifyCheckoutClaimer { claim(): Promise; } /** * Build a Shopify checkout claimer. The proxy — not this client — is the trust * boundary; a `{ ok:true }` means the authority validated the admission and the * gate token was minted+written for this cart, never a client assertion. * * A 409 `cart_not_materialized` is surfaced verbatim as the reason (not a generic * `proxy_409`) so the storefront can materialize a real cart and re-claim — the * claimer does NOT own materialization, which is context-specific (the Ajax cart * bridge for themes vs the Storefront cart GID for headless) and lives in the * caller that holds that knowledge. For headless, the cart reference identifies * where to write the gate token; it does not replace the intended product/ * variant/quantity context needed to validate purchase policy. */ export declare function createShopifyCheckoutClaimer(config: ShopifyClaimConfig): ShopifyCheckoutClaimer; /** * Read the cart-binding refusals a priced admission's reserve/checkout answers * with `409 {error}`. `admission_bound_to_other_cart` names the bound cart in flat * `cartId`/`checkoutUrl` siblings while the server still holds its id; `boundCart` * is set only when both are non-empty strings. Returns `null` for any other body. */ export declare function cartBindingRefusal(body: unknown): { reason: "dedicated_cart_required"; } | { reason: "admission_bound_to_other_cart"; boundCart?: { cartId: string; checkoutUrl: string; }; } | null; /** * Create a DEDICATED cart through the app proxy: an empty Storefront cart, in a * market whose currency is the shop currency, that a priced admission's claim can * bind to. POSTs `{ dedicated: true }` to `${proxyBaseUrl}/admissions/checkout-url` * and resolves the new cart's `cartId` (a Storefront cart GID) and `checkoutUrl`. * * Return this cart from your `FanfareCartAdapter.createCart()` (reading it back * through your Storefront client if your cart type needs more than these two * fields), or claim on it directly with `claim({ cartId, checkoutUrl, purchase })`. * * Unlike the claim, this REJECTS on failure — a non-2xx proxy response (the * message carries the proxy's `error` code, or `proxy_`), a response * without both fields, or a transport failure/timeout — so a `createCart()` that * awaits it surfaces as `cart_create_failed` from `gatedCheckout`. * * A Fanfare Shopify app version without dedicated-cart support ignores * `dedicated: true` and returns an ordinary empty cart. */ export declare function createDedicatedCart(options: { /** App-proxy base, e.g. "https://shop.example.com/apps/fanfare/api". */ proxyBaseUrl: string; /** Injectable for SSR/test. Defaults to `globalThis.fetch`. */ fetchImpl?: typeof fetch; /** Abort the proxy request after this many ms. Defaults to 10s. */ timeoutMs?: number; }): Promise<{ cartId: string; checkoutUrl: string; }>;