Checkout widget
Connect finished images to a ready-made review and checkout experience.
Standalone checkout widget
For verified return handling, configure externalRef for your saved checkout revision, returnUrl on your registered website, and an async onCartReady(cart) callback to bind the cart to your customer on your server. The callback is awaited before opening checkout and may run again on retries. See the complete test flow.
The widget uses a public widget_partner_key assigned to your account automatically. Accept partner terms, copy the personalized snippet from your dashboard and connect your editor's finished images. You do not need a secret API key, your own storage or a server cart endpoint for this integration.
Clicking the widget button opens a TCGPlaytest-branded modal with each design's front, matching back, quantity and total card count. The customer reviews the cards, then clicks Proceed to checkout. Only then do uploads and validation begin. Once the artwork is ready, the widget opens checkout inside your page for contact/shipping details and image-rights confirmation. Only the payment step opens Stripe. The modal does not collect card payment credentials.
- Under 3. Add TCGPlaytest to your website, add any additional HTTPS website origins. Your registered website is allowed automatically. Exact origins only: production and preview domains must be added separately; no wildcards or localhost.
- Copy your snippet. Use
data-mode="test"for a simulated checkout, ordata-mode="live"for the configured checkout website. Use the snippet's partner-platform and checkout origins together. - Pass finished PNG/JPEG
Blob/Fileobjects or async functions that return them. Return{ items, sharedBack }to apply one selected back to single-sided designs. Supply an item-specific back for double-sided cards. Omit both to use the TCGPlaytest default back. The widget handles back selection, previews and uploads. Quantities count physical cards.
// Run after the personalized widget script has loaded.
// Export the finished card design, including its text and frame.
TCGPlaytest.configure({
getItems: async () => ({
sharedBack: sharedBackFile,
items: [
{ label: "My card", front: () => renderFinishedFront(), quantity: 4 },
{ label: "Another card", front: () => renderAnotherFront(), quantity: 1 }
]
}),
externalRef: "optional-shop-order-reference"
});
// The default floating button opens the branded front/back review modal.
// For your own button, add data-auto-button="false" to the snippet:
printButton.onclick = () => TCGPlaytest.open().catch(error => {
console.error(error.message); // The widget also shows the error in its dialog.
});
// After editing the deck, reset a previous attempt before starting a new one:
// TCGPlaytest.reset();
// Keep the default dialog to show previews and Proceed to checkout.
// Advanced custom UIs can explicitly opt out with showDialog:false.The widget requests an opaque one-hour customer session, uploads directly to TCGPlaytest-owned private storage, creates one cart and polls every four seconds. It opens embedded checkout only after validation opens the cart. The backend copies your accepted terms and saved affiliate preference. Orders and cart analytics belong to your partner account. The customer certifies image rights at checkout.
The widget uploads up to three files at a time and uploads a reused shared-back Blob only once per attempt. Retrying in the same page keeps successfully uploaded files, including completed fronts whose backs still need uploading. No extra partner configuration is required. Session expiry can require a new attempt. Upload quotas still apply; validation runs in the background after upload.
An item's back takes priority over sharedBack, preserving double-sided designs. Single-sided cards use the shared back when supplied, otherwise the TCGPlaytest default back. Partners do not upload the default back or implement the selection rule. The original array return shape remains supported for individual front/back pairs. A shared back can also be an async Blob factory.
The branded widget also offers Print both faces separately on cards with a supplied back. The customer can turn that front/back pair into two physical cards, each using sharedBack or the TCGPlaytest default. Previews, quantities and the printing estimate update together. Print front and back together restores the original pair. This choice requires no partner-side logic or new configuration; a supplied back is never split automatically. Once uploads start, the order layout stays fixed for retries. Use Use current designs to start a different order.
Previews render up to three faces at once. Low-resolution previews show a warning and remain orderable. The backend records source dimensions and the hosted checkout asks customers to acknowledge reduced quality. Missing or corrupt files still need replacing.
The modal shows an estimated printing subtotal as soon as quantities are known. Call TCGPlaytest.estimate(quantity) to display the same estimate in your editor. It returns {currency, quantity, unit_amount, subtotal_amount, excludes}, with amounts in cents. This uses GET /v1/estimate?quantity=10 and needs no API key. Shipping, tax and discounts are excluded; the final price is recalculated at checkout. An unavailable estimate never prevents checkout.
The optional label names a design in the preview. Front/back image factories are cached for the review attempt, so checkout uploads the same files the customer reviewed. Designs are paginated in groups of six, with quantities shown per design. Closing the modal before proceeding creates no uploads or cart. The preview shows supplied artwork; 2 mm print bleed is prepared by the backend during validation.
Retries reuse the submitted cart, including after a page refresh in the same tab. The modal offers Resume checkout and Use current designs for a previous attempt. A resumed cart after refresh explicitly reports that its previews are unavailable; it never substitutes new images into that cart. To change a submitted order programmatically, call TCGPlaytest.reset() when the widget is closed. Failed or expired carts need a new attempt; no API-key rotation is needed. The widget must receive your images through the frontend connection above; a script cannot discover arbitrary editor data automatically.
| Field / limit | Description |
|---|---|
| POST /api/widget/sessions | JSON {partner_key, mode}. Returns 201 {token, expires_at, mode, max_cards, max_image_bytes}. Browser Origin must match an allowed website. No secret API key. |
| POST /api/widget/uploads | Bearer session token; JSON {request_id: UUID, content_type, size}. Returns 201 {id, upload_url, method: PUT, headers, max_bytes, upload_expires_at}. PUT the bytes to that URL without the session token or cookies. Reuse request_id only for an identical grant retry. |
| POST /api/widget/cart | Bearer session token; JSON {items:[{front_upload_id, back_upload_id?, quantity}], external_ref?}. Only files from this session are accepted. Returns the cart shape documented above, 202 while validating. One immutable cart per session supplies idempotency automatically. |
| GET /api/widget/cart | Bearer session token. Returns only that session's cart; there is no arbitrary cart ID parameter. Polling does not consume upload or session quotas. |
| Limits | 60 new sessions per partner/hour; 2,000 upload grants per session; 10,000 grants per partner/rolling 24 hours. Defaults: 60 / 2,000 / 10,000; configured operator overrides are reflected here. Each distinct front/back upload uses a grant reserving up to 20 MiB; copies may reuse an uploaded image. A deck with 100 distinct fronts and 100 distinct backs needs 200 grants. Normal image and cart limits still apply. Widget quotas are separate from API-key rate limits. |
| Storage lifetime | Storage upload grants last two hours and cannot overwrite an existing object. Unused staging files are removed after the one-hour session plus the grant window. Claimed staging files expire with the cart, subject to that minimum window and legal holds. Validated print files follow the cart retention policy. |
| Errors | 401 widget_session_required/expired: start a new session. 403 origin_not_allowed/widget_unavailable/invalid_upload: fix account, website or file ownership. 409 idempotency_conflict: reset for changed inputs. 429 widget_limit: honor Retry-After; repeated quota failures need the operator. Normal validation errors are returned on the cart. |
The public code is an identifier, not a secret or proof of the caller's identity. Allowed origins restrict browser use; they do not authenticate non-browser callers. The service enforces expiring sessions, upload quotas, partner status and file ownership independently. Keep the session token private to the customer's tab.
Existing snippets without data-partner-key keep the legacy server-launcher behavior using your own cart endpoint. New snippets use the standalone flow above.