Single Product Purchase
This page is the flow-level walkthrough for Branded Commerce. For a runnable script that executes the same multi-step /api/v2 sequence end-to-end, see the Advanced Checkout Guide — the API calls are identical across all solutions; this page covers the Branded-specific framing.
The five canonical phases
Every Firmly purchase moves through five phases. In Branded Commerce, the experience runs on Firmly-hosted partner-branded pages (the partner’s brand identity layered on Firmly’s commerce backend).
| Phase | What happens | What you do |
|---|---|---|
| 1. Hosting | Firmly-hosted partner-branded pages — your domain, your brand, Firmly’s commerce backend | One-time configuration with Firmly |
| 2. Authentication | A Firmly session is established | POST /api/v1/browser-session (or use S2S directly) |
| 3. Discovery | Buyer clicks an ad and lands on a Firmly-hosted page for a specific product or curated landing | Page-load context resolves the product/SKU; usually no live discovery/search call — the link itself names the SKU |
| 4. Checkout | Cart, address, shipping, payment, place order | Five sub-calls (see below) |
| 5. Order placement | Order lands in the merchant’s existing OMS as a native order, with attribution tagged | POST /payment/complete-order |
Sequence diagram
The implementation steps (Phase 4 broken out)
Ad click captured
Buyer clicks an ad and lands on a Firmly-hosted branded page (e.g., partner.example.com/holiday-deals/hoodie). The page captures attribution params (utm_source, utm_campaign, utm_content, fbclid, gclid, etc.) from the URL and persists them client-side for the session. No API call yet.
Discover (Phase 3) — page-load context
For Branded Commerce, the landing URL usually names the SKU directly, so an explicit discovery/search call isn’t always required. If you do need search (e.g., a curated landing with multiple options):
curl -X POST https://api.firmly.work/api/v1/discovery/search \-H "x-firmly-authorization: $TOKEN" \-H "Content-Type: application/json" \-d '{"query": "hoodie", "page_size": 5}'
Response contains products[] (across all merchants your App ID can reach). Each product has a domain field identifying its merchant, plus variants[]. Each variant has an add_to_cart_ref — keep it as an opaque handle.
Add to cart (Phase 4 begins)
Buyer clicks Buy Now on the branded PDP.
curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/line-items \-H "x-firmly-authorization: $TOKEN" \-H "Content-Type: application/json" \-d '{"add_to_cart_ref": {...}, "quantity": 1}'
Cart is created implicitly. Response is the full cart object.
Set shipping address (Phase 4)
Buyer fills the address on the branded checkout page.
curl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/shipping-info \-H "x-firmly-authorization: $TOKEN" \-H "Content-Type: application/json" \-d '{"first_name": "Consumer", "last_name": "Tester","email": "consumer@example.com", "phone": "+15551234567","address1": "500 Howard St", "city": "San Francisco","state_or_province": "CA", "country": "US", "postal_code": "94105"}'
Response includes updated shipments[] with shipment_ids.
Optimization tip: pre-set the postal code from the buyer’s geo IP via Set Postal Code — Firmly stores it session-wide and the merchant often has shipping rates pre-warmed by the time the full address is submitted.
Pick a shipping method (Phase 4)
# Read available methods from cart.shipments[].shipping_method_options (set in step 4),# then record the choicecurl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/shipment/methods \-H "x-firmly-authorization: $TOKEN" \-H "Content-Type: application/json" \-d '{"shipment_id": "01e232ec-6202-5cb1-25fa-6fe46cc569a9", "shipping_method_id": "method_standard"}'# Optional: for scheduled-delivery / pickup, fetch delivery dates & time slots firstcurl -X POST https://api.firmly.work/api/v2/domains/staging.luma.gift/cart/shipments/get-availability \-H "x-firmly-authorization: $TOKEN" \-H "Content-Type: application/json" \-d '{"shipment_id": "01e232ec-6202-5cb1-25fa-6fe46cc569a9"}'
(If required) Capture merchant consents
Some merchants require the buyer to agree to terms-of-sale, warranty, or delivery acknowledgments before order placement. Before complete-order:
GET /api/v2/domains/{domain}/cart/consents— returns an array of required consents withid,html/textto display,required,explicitflags- If the array is non-empty, surface each
texton the branded checkout page, get explicit agreement, thenPUT /api/v2/domains/{domain}/cart/consentswith{consents: [{id, revoke: false}, ...]}
If you skip this and the merchant required consents, complete-order returns RequiredConsentsNotSigned (412). See Get Consents and Set Consents.
Encrypt payment + complete order (Phase 4 ends)
Get the payment public key, JWE-encrypt the card, then submit. Branded Commerce checkout supports card via JWE encryption, Click to Pay (Visa/Mastercard), and PayPal as live payment methods; Google Pay is planned. Pick what aligns with the partner’s audience.
Payment calls go to the cc.firmly.work host, not the api.firmly.work host every prior call used — the payment surface is served from its own hostname.
curl -X POST https://cc.firmly.work/api/v2/payment/domains/staging.luma.gift/complete-order \-H "x-firmly-authorization: $TOKEN" \-H "Idempotency-Key: $(uuidgen)" \-H "Content-Type: application/json" \-d '{"encrypted_card": "eyJh...","billing_info": { ... }}'
Why complete-order and not place-order? The cart was built over the previous calls (add line item → shipping info → shipping method → consents), so complete-order finalizes that existing cart — the body carries only encrypted_card and billing_info (shipping was already set earlier). Use place-order instead only for the one-shot path: it creates a cart and places the order in a single call, and requires an items[] array plus shipping_info because there’s no prior cart to finalize. Verify the request body against Complete Order.
Response includes cart_status: "submitted", cart_id, and urls.order_status_page for the branded thank-you page. The merchant’s native order number is returned as platform_order_number.
Attribution note: Pass UTM params and ad-click IDs (fbclid, gclid, etc.) in the order metadata so the merchant’s downstream attribution reporting can reconcile. See Partner Onboarding → Attribution metadata fields for the fields the merchant expects.
Render branded thank-you page
Firmly’s scope ends at order placement. The branded thank-you page typically:
- Confirms the order with the merchant’s order number from
platform_order_number - Maintains the partner’s brand identity (logo, colors, copy)
- Links to the merchant’s own order status or support if the buyer needs post-order help
Anything post-order — tracking, returns, refunds — lives in the merchant’s native OMS. The buyer contacts the merchant directly through the merchant’s existing channels.
What can go wrong at each step
| Step | Most common failure | Recovery |
|---|---|---|
| Discover | Empty results, StoreUnavailable, DomainNotFound |
Check domain spelling, confirm merchant is enabled for Branded |
| Add to cart | ProductNotFound, NotEnoughStockError (409) |
Re-fetch product or surface “out of stock” on the branded page |
| Set shipping | InvalidInputBody (400) |
Validate address fields client-side first |
| Shipping methods | No methods returned | Address may not be deliverable — ask buyer for an alternative |
| Place order | CreditCardDeclined (422), NotEnoughStockError (race with another buyer) |
Offer alternative payment, or re-quote with new stock |
See Errors & Conventions for the full catalog and the exact PascalCase error names.
Attribution — keeping the partner’s identity intact
The Branded Commerce flow’s identity is the partner’s brand on the front end, the merchant’s order on the back end, and attribution data flowing both ways so reporting reconciles correctly.
| Data | Direction | Why |
|---|---|---|
UTM params (utm_source, utm_campaign, utm_content) |
Ad click → branded page → order metadata → merchant OMS | Lets the merchant’s own attribution reports reflect partner-driven orders |
Click IDs (fbclid, gclid, ttclid, etc.) |
Ad click → branded page → order metadata → ad platform postback | Allows the ad platform to credit the conversion |
| Partner identifier | Branded page session → Firmly’s attribution service → merchant OMS | Lets the merchant see “orders from Partner X” without manual reconciliation |
Pass-through is automatic when the partner page sets these on the cart session. See Partner Onboarding → Attribution metadata fields for the field mapping.
What’s in a cart response
Every cart-mutation endpoint returns the full cart object in the response, so the partner’s page doesn’t have to re-fetch. The shape is identical across all Firmly solutions — see Cart Lifecycle for the full field reference.
Runnable end-to-end
The Advanced Checkout Guide is the same multi-step /api/v2 sequence as a complete Node script you can run today. Start there if you want working code, return here for the conceptual walkthrough framed for Branded Commerce.
Related
- Buyer Purchase Flow — the ad → PDP → checkout → thank-you walkthrough from the buyer’s point of view
- Multi-Product Purchase — multiple items in one branded cart
- Why Firmly for Branded Commerce — what makes Branded Commerce different
- Operations — onboarding partners, dashboard, operational workflow