Skip to main content

Sandbox preview

Use the merchant App card Sandbox preview row. Do not start from gs://finmatch-p or merchant-router.json.

Idle: Loading…. Then Off, On, On (paused), or Status unavailable. Click confirms, promote vs discard vs turn off: Row states, What happens on each click, Promote, discard, turn off.

Safe config experimentation for a merchant that stays routed to production. Live shoppers are unaffected. Testers open a capability URL on the live storefront and see a forked runtime config from Test.

When to use this​

  • Trying modal / calculator / Pay Monthly changes before they go live
  • Letting a merchant or lender contact review a config change
  • Avoiding the old anti-pattern of switching App → Environment to t (that reroutes all live traffic)

Prerequisite​

App environment must be Production (p). Sandbox forks P → T and discard deletes the stamped T fork. If the merchant is still live on T (or S), enable/fork/discard are rejected (409) so we never overwrite or delete the live storefront row. Move the merchant to p first.

Operator flow​

Live chrome is on the merchant App card, Sandbox preview row (and a bottom dock when a toggle is Sandbox). Idle state: Loading…. There is no button named Generate preview link, Show diff, or Also load T scripts and CSS.

Row states​

StateWhat you seeWhat to do
Loading…First paint / status fetchWait. Do not click Turn on sandbox yet.
Status unavailableSub: Could not load sandbox status. Reload the page to retry. (load-error sub can also read Sandbox status unavailable: plus the error)Reload the merchant page. Grid and actions stay hidden.
OffSub explains a private copy, or Sandbox needs this merchant on the production environment. / sandboxIneligibleReasonEligible: Turn on sandbox. Ineligible: move App environment to Production first. The button is disabled when ineligible.
OnSub: Shoppers still see production. Only your preview link sees the sandbox.Edit, Open preview, promote or discard.
On (paused)Sub: Paused — admin edits and preview links go to production. The draft is kept.Dock Merchant configuration → Sandbox to resume. Open preview alerts instead of minting a sandbox link.

Happy path​

  1. Open the merchant in Admin → Merchants. App environment must stay Production (p).
  2. Click Turn on sandbox. The button disables until the request finishes. That forks the production runtime row into gs://finmatch-t/configs/finmatch-merchant-config.json and stamps _sandbox provenance. Shoppers still see production. Admin writes target T while the row is on (resolveMerchantEnvKey is sandbox-aware).
  3. The grid shows:
    • Your admin edits save to — Production or Sandbox
    • Preview link loads — Production build (config-only, live JS/CSS) or Test build (T scripts and CSS)
    • Sandbox contents — Matches production, a N settings changed chip plus View changes, Unknown plus View changes, or Checking… while the diff loads
  4. Open preview (row or dock) issues a typed sandbox_preview credential (spv_…) via auth-api with a 30-day expiry. Password prompt: Enter admin password to generate a sandbox preview link:. The plaintext URL is Preview link (shown once, expires …) and Copied to clipboard. when copy succeeds. Open it on the merchant domain as https://merchant.domain/?finmatch_sandbox=<token>. Regenerate if lost (do not expect a reveal). This session caches that URL so a later Open preview reuses it.
  5. The storefront shows a bottom bar: FinMatch sandbox: … with Exit sandbox.
  6. The admin bottom dock has Merchant configuration and FinMatch app (each Live / Sandbox) plus Open preview.
  7. Promote or tear down with the action row (see Promote, discard, turn off).

What happens on each click​

Turn on sandbox. POST /admin/sandbox/:id/enable. Failure alert: Could not turn on sandbox: plus the error. If merchant-api returns 409 because this Production merchant still has an older non-sandbox row in T, confirm Replace it with a fresh fork of the current production config? Cancel leaves sandbox Off. Force does not bypass the Production-only eligibility check.

Your admin edits save to. Dock / row Merchant configuration Live pauses sandbox (enabled: false) so admin Saves go to production and the draft is kept. Sandbox resumes. Failure: Could not update merchant configuration: plus the error.

Preview link loads. Dock FinMatch app Live = Production build (live JS/CSS, sandbox config). Sandbox = Test build (T scripts and CSS). Failure: Could not update FinMatch app: plus the error.

View changes. Fills the diff box. Match: The sandbox configuration matches production. Drift adds ⚠ Production changed after this fork was taken. Mismatch lists changed keys. Failure: Diff failed: plus the error.

Open preview while On. Mints or reopens the cached URL as above. Failure: Failed to generate preview link: plus the error.

Open preview while On (paused). Alert: Sandbox is paused. Preview links load production until you resume sandbox editing. It does not mint a token.

Open preview while Off. Confirm: Sandbox is off. Turn it on and fork the production configuration now? Cancel does nothing.

Promote, discard, turn off​

The action row never shows Discard changes and Turn off sandbox together.

ControlWhen it appearsConfirmSuccess / failure
Promote to production (label becomes Promote N change(s) when the diff has keys)Contents driftedPromote the sandbox configuration to production? Live shoppers will see these changes within about a minute.Alert Sandbox promoted to production. Failure: Promote failed: plus the error.
Discard changesContents are not Matches production (including Unknown / still checking)Discard the draft? Its unpromoted changes will be lost. Production is not affected. Unknown-diff confirm warns it could not verify whether the draft differs.Deletes the T draft and clears preview-link cache. Failure: Discard failed: plus the error.
Turn off sandboxContents Matches productionTurn off sandbox? The draft matches production, so nothing will be lost. Preview links stop working.Same discard endpoint. Production is unchanged.

Production changed after you forked. Review View changes before promoting, or the sandbox overwrite will replace those later production edits.

Do not use Discard changes / Turn off sandbox to “undo” a promote that already succeeded — that promote already wrote P.

What stays the same​

GCS and router notes. Live shoppers stay on production while the sandbox row is on.

ConcernBehaviour
App environmentRemains p in merchant-router.json
Live shoppersAlways get gs://finmatch-p config
Merchant IDUnchanged — one identity, forked runtime row
SnippetSame finmatch-sdk.js?merchantID=…

Modelled on Shopify theme share previews: expiring, plural, labelled preview links with a persistent on-page exit bar.

Headless browsers visit the cart with an empty session, so a bare /cart URL produces an empty screenshot. A cart permalink is a URL that pre-populates the cart with line items as the page loads.

Shopify format: store /cart/ plus VARIANT_ID:QUANTITY, comma-separated for multiple lines. Find a variant id in the product JSON on the page source, or Shopify admin → Products → Variants. Tick Save as default to write merchant.config.testUrls.cart. Other platforms: paste any URL that loads the cart with at least one item.

Credential model​

Merchant auth identities (role: merchant in auth-credentials.json) carry a typed keys[] array:

TypePrefixUI verbLifetime
finance_assistantsk_live_Copy / reveal (step-up)Long-lived
sandbox_previewspv_Open preview (show-once)30 days, plural

The Users page expands a merchant row into a credential registry (type, label, status, created, expiry, revoke).

apiKey remains a back-compat alias for the active Finance Assistant key so finance-assistant and the Cloudflare KV mirror keep working. auth-api writes that KV pair; operators do not paste it by hand.

Technical path​

Preview URL ?finmatch_sandbox=spv_…
→ SDK sessionStorage + fresh=1
→ getMerchantConfig?merchantID=&sandbox=&fresh=1
→ hash token, lookup gs://finmatch-secrets/sandbox-tokens.json
→ read gs://finmatch-t/.../finmatch-merchant-config.json
→ response { …merchant, environment: "p", sandbox: true, sandboxConfigEnv: "t" }
→ SDK preview bar; separate localStorage key (never poisons live cache)

Fail-closed: expired, revoked, wrong-merchant, missing projection entries, or router sandbox not enabled (including after Discard changes / Turn off sandbox) fall through to the normal production path with no error page. A valid token alone is not enough — discard clears the router block so leftover preview links stop forcing the T bucket. If the T fork row is missing while sandbox is still marked enabled, getMerchantConfig also falls through to live instead of 404ing. The SDK clears the preview session only on a successful non-sandbox response (discard / expired token). Transient fetch errors keep the session and any cached sandbox config so in-site navigation can recover.

Admin / API endpoints​

ServiceEndpointPurpose
merchant-apiGET /admin/sandbox/:id/statusRouter sandbox + fork + drift
merchant-apiPOST /admin/sandbox/:id/enableFork P→T + set router sandbox
merchant-apiGET /admin/sandbox/:id/diffChanged keys + drift warning
merchant-apiPOST /admin/sandbox/:id/promoteApply T row onto P
merchant-apiPOST /admin/sandbox/:id/discardRemove T row + clear sandbox
auth-apiPOST /admin/merchant-keys/issueMint typed key (show-once)
auth-apiPOST /admin/merchant-keys/revokeRevoke
auth-apiPOST /admin/sandbox-tokens/reconcileRebuild projection

Naming discipline​

  • Merchant ID — public identifier, never masked
  • Finance Assistant key — long-lived API key
  • Sandbox preview link — human-facing capability URL, not an API key