Skip to content

Checkout and orders

View .mdllms.txt

One order buys one listing. Checkout is three calls and Stripe’s payment form; after that, the order moves on by transitions until the buyer has it and the seller is paid. Every call sends the buyer’s session.

Show what the buyer will pay before they pay it:

POST /v1/me/orders/quote
{
"listing_id": "8f3c…",
"currency": "EUR",
"delivery": {
"method": "shipping",
"option": "standard",
"address": { "name": "Mia Holm", "line1": "Hallvägen 29", "postal_code": "121 62", "city": "Johanneshov", "country": "SE", "phone": "+46…" }
},
"voucher_code": "SPRING10"
}
{
"currency": "EUR",
"total": 61900,
"line_items": [
{ "code": "item", "label": "Eames lounge chair", "unit_price": 59000, "quantity": 1, "amount": 59000 },
{ "code": "shipping", "label": "Shipping", "unit_price": 8800, "quantity": 1, "amount": 8800 },
{ "code": "voucher", "label": "Voucher SPRING10", "unit_price": -5900, "quantity": 1, "amount": -5900 }
]
}
  • Show line_items as they come, by label, and total as the amount to pay. Don’t add things up yourself.
  • Quote again whenever the delivery, address or voucher changes. Placing the order prices it the same way.
  • currency is what the buyer pays in: one of the marketplace’s, the listing’s when left out.

Line item codes:

Code Meaning
item The listing.
shipping Delivery, priced.
collect Collected at the showroom or warehouse where it’s stored. Free.
shipping_address_needed Ask for the address; the price comes with it. Placing without one fails.
shipping_quote Delivery can’t be priced here: the marketplace sends a quote after the order.
voucher A discount (negative).

A voucher that doesn’t apply gives 422 with the reason on body.voucher_code. Show it next to the voucher field.

POST /v1/me/orders
Idempotency-Key: 6f1d3c5e-…
{ the same body as the quote }
{
"order": { "id": "1a2b…", "state": "pending_payment", "total": 61900, "currency": "EUR", "available_transitions": [] },
"payment": { "client_secret": "pi_3Q…_secret_…" }
}
  • This reserves the listing and starts the payment. The buyer has 15 minutes to pay; then the order becomes payment_expired and the listing is released.
  • Send an Idempotency-Key (a new UUID for each press of “Pay”), so a retried request can’t place two orders.
  • 409 means it can’t be bought right now: sold, reserved by someone else, or the delivery isn’t possible. Show the detail and go back to the listing.

Payments run on the marketplace’s Stripe account. Use Stripe’s Payment Element with the marketplace’s Stripe publishable key (the team gives it to you; it’s public) and the order’s client_secret. Cards, Klarna and the other methods the marketplace has turned on in Stripe appear by themselves.

import { loadStripe } from '@stripe/stripe-js'
const stripe = await loadStripe(STRIPE_PUBLISHABLE_KEY)
const elements = stripe.elements({ clientSecret: payment.client_secret })
elements.create('payment').mount('#payment')
// On "Pay":
const { error } = await stripe.confirmPayment({
elements,
confirmParams: { return_url: `${location.origin}/orders/${order.id}` },
})
if (error) showError(error.message) // otherwise Stripe sends the buyer to return_url

When Stripe confirms the payment, mongrl moves the order to purchased. That happens in the background, a moment after the buyer lands on your order page: read the order (GET /v1/me/orders/{id}) and, while it’s still pending_payment, check again every couple of seconds.

In the test environment, pay with Stripe’s test card 4242 4242 4242 4242, any future date and any CVC.

(new) ─request-payment─▶ pending_payment ─confirm-payment─▶ purchased
└─expire-payment (15 min)─▶ payment_expired
purchased ─mark-delivered─▶ delivered ─mark-received / auto (14 days)─▶ received
purchased ─mark-received-early─▶ received
purchased ─cancel / auto-cancel (14 days)─▶ canceled
delivered ─dispute─▶ disputed ─resolve-received─▶ received
└─resolve-canceled / auto (60 days)─▶ canceled

payment_expired, received and canceled are final. Each order has history: every transition with when it happened, for a timeline on the order page.

Each order has available_transitions: what this user may do now. Show a button for each and nothing else.

POST /v1/me/orders/{id}/transitions/mark-delivered
{ "data": { "tracking_number": "JJSE00012345", "carrier": "PostNord" } }
Transition Who When
mark-delivered the seller It’s sent or handed over. data can carry tracking_number and carrier; they appear in order.delivery.
mark-received the buyer They have it. The seller is paid.
mark-received-early the buyer They have it before it was marked sent (e.g. picked up). The seller is paid.
dispute the buyer Something’s wrong after delivery. The marketplace’s team resolves it.

When the marketplace delivers a listing itself (fulfilment: marketplace, e.g. stored listings), its team marks the order sent in the console. The timers keep orders moving if nobody acts: an order that’s never sent is canceled and refunded after 14 days; a delivered order counts as received after 14 days.

  • GET /v1/me/orders lists the buyer’s purchases; ?as=seller lists their sales. Filter with state; page with limit and offset.
  • Each order has line_items, total, delivery and, for the seller, payout: subtotal, commission, storage_fee, amount and its status (pending until the buyer has it, then paid; waiting_for_payout_details until the seller sets up payouts).

Buyers and sellers get emails when an order is paid, sent and received, each linking to https://<your shop>/<lang>/orders/<id>. Build that page. See Pages your shop needs.

  • Checkout: delivery choice, address, voucher, live quote, “Pay” with the Payment Element.
  • Idempotency-Key on placeOrder.
  • The order page waits for purchased after Stripe returns, then shows the timeline and the buttons from available_transitions.
  • A delivery-time picker when the order offers one. See Shipping and delivery.