Shop platforms
Sell only what is in their own catalog, so first you migrate your products.
The open-source checkout for brands that already own their product data. Cart, tax, shipping, payment and orders, for your storefront and for AI agents.
Apache-2.0 · Runs in your Cloudflare account
demo.orbcart.dev
Your customer sees

Daypack 30 l
Green · Qty 1
Order placedNG-2026-000142
Behind the scenes
Payments through
Why OrbCart
Sell only what is in their own catalog, so first you migrate your products.
Still want a copy of your SKUs. Current prices and stock mean sync jobs.
Take the payment, and some work out tax. Cart, stock holds and orders are still on you.
Asks your own systems for price and stock while people shop. Your catalog stays where it is.
How it works
Write one function. offers() asks your ERP or PIM for prices and stock while a customer shops.
Set the rules once. Tax, shipping and promotions are worked out on the server, for your shop and for agents.
Deploy to your Cloudflare account. One Worker. Each cart is its own Durable Object, stored in the EU by default.
Start selling. Your storefront, the React checkout kit and AI agents share one checkout. Paid orders go to your ERP.
The parts you would otherwise build and maintain yourself.
Address, contact and consent on one page
All 27 EU countries, daily rates
Per country, with exclusions
Five providers, local methods
Apple Pay, Google Pay and PayPal
Single lines or one piece of a set
Held in your own inventory system
Sent to your ERP, retried until it arrives
End a sale in every open cart at once
React components and hooks
Orders and refunds, server side
orbcart dev on your machine
On the roadmap: invoice payments in Germany, then Unzer.Project status
Developers
Prices come from your API. Swap erp.articles(lines) for a call to your own system.
OrbCart needs four fields. SKU, name, price and tax category. Variants, sets and the rest stay in your system.
Configuration lives in code. Payment methods, tax and shipping per market, in channels, reviewed like any other change.
import { defineCommerce } from "@orbcart/runtime";import { channels } from "./channels";import { erp } from "./erp";export * from "@orbcart/runtime/objects";export default defineCommerce({channels,offers: async (lines, { currency }) => {const articles = await erp.articles(lines);return articles.map((article) => ({reference: article.sku,title: article.name,price: { amount: article.cents, currency },taxCategory: "standard",shipping: { weightInGrams: article.grams },tags: article.promotions,}));},});import { CartProvider, Checkout, useShoppingCart } from "@orbcart/react";import "@orbcart/react/styles.css";<CartProvider commerce={commerce} token={token} onCartChange={saveCart}><App /></CartProvider>;export function AddToBag({ reference }: { reference: string }) {const { add } = useShoppingCart();return <button onClick={() => add(reference)}>Add to bag</button>;}export function CheckoutPage() {const { cart } = useShoppingCart();if (!cart) return null;return <Checkout cart={cart} locale="en-GB" onOrdered={showReceipt} />;}import { createCommerce } from "@orbcart/client";const commerce = createCommerce({ url, key });const cart = await commerce.cart();await cart.add("BACKPACK-30-GREEN");const quote = await cart.quote({contact: { email: "anna@example.com" },shipTo: {name: "Anna Berg",line1: "Bergstraße 12",postalCode: "10115",city: "Berlin",country: "DE",},});const payment = await quote.mountPayment("card", field);const result = await quote.pay({ payment });if (result.status === "ordered") showConfirmation(result.order.number);POST /ucp/checkout-sessions HTTP/1.1Host: commerce.nordgrat.deContent-Type: application/jsonContent-Digest: sha-256=:JUV3e2z40UNI5nFLuNE431Mkt9AwPe0i+Kh8KkvDCN0=:UCP-Agent: profile="https://agent.example/.well-known/ucp"Signature-Input: sig1=("@method" "@authority" "@path" "content-digest" "content-type" "ucp-agent");created=1791538860;keyid="agent-2026"Signature: sig1=:…:{"line_items": [{ "item": { "id": "BACKPACK-30-GREEN" }, "quantity": 1 }],"buyer": { "email": "anna@example.com" },"fulfillment": {"methods": [{"type": "shipping","destinations": [{"first_name": "Anna","last_name": "Berg","street_address": "Bergstraße 12","postal_code": "10115","address_locality": "Berlin","address_country": "DE"}],"groups": [{ "selected_option_id": "standard" }]}]}}Response
201 Created
POST /v1/carts/{cart}/quote{"object": "quote","id": "qt_ZpG1HEl1QmxOSB5FHEs0xI9t86HRUDeC_Ysm9kUzOg8","cart": "cart_01M4G0HN30K4R8W2N6Q9XB3M5T","channel": "de","currency": "EUR","pricesIncludeTax": true,"expiresAt": "2026-10-09T09:56:00.000Z","lines": [{"id": "l_1","reference": "BACKPACK-30-GREEN","quantity": 1,"offer": {"title": "Daypack 30 l, green","price": {"amount": 8990,"currency": "EUR"}},"taxes": [{"percentage": "19","country": "DE","taxCategory": "standard","taxableAmount": {"amount": 7555,"currency": "EUR"},"amount": {"amount": 1435,"currency": "EUR"}}]}],"shipping": {"id": "standard","type": "delivery","title": "Standard shipping (DHL)","price": {"amount": 495,"currency": "EUR"},"deliveryTime": "2-4 business days"},"totals": {"subtotal": {"amount": 8990,"currency": "EUR"},"shipping": {"amount": 495,"currency": "EUR"},"discount": {"amount": 0,"currency": "EUR"},"tax": {"amount": 1514,"currency": "EUR"},"total": {"amount": 9485,"currency": "EUR"}},"signature": "eyJhbGciOiJFUzI1NiIsImtpZCI6…"}{"object": "quote","id": "qt_ZpG1HEl1QmxOSB5FHEs0xI9t86HRUDeC_Ysm9kUzOg8","cart": "cart_01M4G0HN30K4R8W2N6Q9XB3M5T","channel": "de","currency": "EUR","pricesIncludeTax": true,"expiresAt": "2026-10-09T09:56:00.000Z","lines": [{"id": "l_1","reference": "BACKPACK-30-GREEN","quantity": 1,"offer": {"title": "Daypack 30 l, green","price": { "amount": 8990, "currency": "EUR" }},"taxes": [{"percentage": "19","country": "DE","taxCategory": "standard","taxableAmount": {"amount": 7555,"currency": "EUR"},"amount": {"amount": 1435,"currency": "EUR"}}]}],"shipping": {"id": "standard","type": "delivery","title": "Standard shipping (DHL)","price": { "amount": 495, "currency": "EUR" },"deliveryTime": "2-4 business days"},"totals": {"subtotal": { "amount": 8990, "currency": "EUR" },"shipping": { "amount": 495, "currency": "EUR" },"discount": { "amount": 0, "currency": "EUR" },"tax": { "amount": 1514, "currency": "EUR" },"total": { "amount": 9485, "currency": "EUR" }},"signature": "eyJhbGciOiJFUzI1NiIsImtpZCI6…"}
POST /v1/carts/{cart}/lines{"object": "cart","id": "cart_01M4G0HN30K4R8W2N6Q9XB3M5T","channel": "de","version": 2,"lines": [{"id": "l_1","reference": "BACKPACK-30-GREEN","quantity": 1,"status": "available","offer": {"title": "Daypack 30 l, green","price": {"amount": 8990,"currency": "EUR"}}}],"missing": ["ship_to", "email"],"estimate": {"currency": "EUR","pricesIncludeTax": true,"totals": {"subtotal": {"amount": 8990,"currency": "EUR"},"discount": {"amount": 0,"currency": "EUR"},"shipping": {"amount": 0,"currency": "EUR"},"total": {"amount": 8990,"currency": "EUR"}},"excludes": ["shipping"]}}{"object": "cart","id": "cart_01M4G0HN30K4R8W2N6Q9XB3M5T","channel": "de","version": 2,"lines": [{"id": "l_1","reference": "BACKPACK-30-GREEN","quantity": 1,"status": "available","offer": {"title": "Daypack 30 l, green","price": { "amount": 8990, "currency": "EUR" }}}],"missing": ["ship_to", "email"],"estimate": {"currency": "EUR","pricesIncludeTax": true,"totals": {"subtotal": { "amount": 8990, "currency": "EUR" },"discount": { "amount": 0, "currency": "EUR" },"shipping": { "amount": 0, "currency": "EUR" },"total": { "amount": 8990, "currency": "EUR" }},"excludes": ["shipping"]}}
POST /v1/quotes/{quote}/accept{"object": "acceptance","status": "ordered","payment": {"object": "payment","id": "pay_01M4G0HN30K4R8W2N6Q9XB3M5T_1","quote": "qt_ZpG1HEl1QmxOSB5FHEs0xI9t86HRUDeC_Ysm9kUzOg8","amount": {"amount": 9485,"currency": "EUR"},"method": "card","provider": "stripe","status": "authorized"},"order": {"object": "order","id": "ord_01M4G0HN30K4R8W2N6Q9XB3M5T","number": "NG-2026-000142","channel": "de","orderedAt": "2026-10-09T09:42:10.000Z","fulfillment": "unfulfilled","refunds": "none","handoff": "pending"}}{"object": "acceptance","status": "ordered","payment": {"object": "payment","id": "pay_01M4G0HN30K4R8W2N6Q9XB3M5T_1","quote": "qt_ZpG1HEl1QmxOSB5FHEs0xI9t86HRUDeC_Ysm9kUzOg8","amount": { "amount": 9485, "currency": "EUR" },"method": "card","provider": "stripe","status": "authorized"},"order": {"object": "order","id": "ord_01M4G0HN30K4R8W2N6Q9XB3M5T","number": "NG-2026-000142","channel": "de","orderedAt": "2026-10-09T09:42:10.000Z","fulfillment": "unfulfilled","refunds": "none","handoff": "pending"}}
POST /ucp/checkout-sessions{"id": "cart_01M4G0HN30K4R8W2N6Q9XB3M5T","status": "ready_for_complete","currency": "EUR","line_items": [{"id": "l_1","item": {"id": "BACKPACK-30-GREEN","title": "Daypack 30 l, green","price": 8990},"quantity": 1,"totals": [{"type": "subtotal","amount": 8990},{"type": "total","amount": 8990}]}],"totals": [{"type": "subtotal","amount": 8990},{"type": "fulfillment","amount": 495},{ "type": "total", "amount": 9485 }],"expires_at": "2026-10-09T09:56:00.000Z","continue_url": "https://demo.orbcart.dev/de/checkout/continue?handoff=…","quote": {"id": "qt_ZpG1HEl1QmxOSB5FHEs0xI9t86HRUDeC_Ysm9kUzOg8"},"ap2": {"merchant_authorization": "eyJhbGciOiJFUzI1NiIsImtpZCI6…"}}{"id": "cart_01M4G0HN30K4R8W2N6Q9XB3M5T","status": "ready_for_complete","currency": "EUR","line_items": [{"id": "l_1","item": {"id": "BACKPACK-30-GREEN","title": "Daypack 30 l, green","price": 8990},"quantity": 1,"totals": [{ "type": "subtotal", "amount": 8990 },{ "type": "total", "amount": 8990 }]}],"totals": [{ "type": "subtotal", "amount": 8990 },{ "type": "fulfillment", "amount": 495 },{ "type": "total", "amount": 9485 }],"expires_at": "2026-10-09T09:56:00.000Z","continue_url": "https://demo.orbcart.dev/de/checkout/continue?handoff=…","quote": {"id": "qt_ZpG1HEl1QmxOSB5FHEs0xI9t86HRUDeC_Ysm9kUzOg8"},"ap2": {"merchant_authorization": "eyJhbGciOiJFUzI1NiIsImtpZCI6…"}}
Reliability
A payment provider stops answering, a sale ends while someone pays, an address changes inside Apple Pay. Here is what OrbCart does in each case.
Measured in Stripe's sandbox · 25 September 2026
At Stripe: one PaymentIntent, captured once.
Illustrative · Ending a sale from your backend
Nobody is charged a price they did not see.
Illustrative · Express checkout with Apple Pay
The charge always matches what Apple Pay showed.
Illustrative · A partial refund from your backend
Right to the cent, discount included.
Edge and agents
A busy day is just more carts. Each cart is its own object in your Cloudflare account, stored in the EU by default.
$ wrangler deploy✓ commerce · Worker✓ CartObject · one per cartEach box is one cart
AI agents check out like customers do. They get your storefront's prices and rules, plus a quote signed by your shop that they can check.
Order the green 30 l daypack and send it to my home.
cart.add("BACKPACK-30-GREEN")cart.quote({ shipTo, contact })
Daypack 30 lGreen
€89.90verifyQuote(quote, jwks)VerifyingVerifiedOrdered. €94.85 including shipping and VAT. Order NG-2026-000141.
UCP checkoutOpen checkout protocol
AP2 mandatesPayments the customer approved
Questions
No. You write one function, offers(), that asks your ERP, PIM or pricing service for price and stock when a customer needs them. Nothing is copied, so there is nothing to keep in sync.
If your storefront can call an API, yes. Use the React checkout kit, the client for any framework, or plain HTTP with the OpenAPI description. AI agents check out through the same API.
In your own Cloudflare account, as one Worker. Each cart is stored on its own, in the EU by default. There is no OrbCart server in between.
Stripe, Adyen, Mollie, PAYONE and PayPal, with local methods such as Klarna, iDEAL, Bancontact and TWINT, and Apple Pay and Google Pay as express checkout. Moving a method to another provider is a one-word change in the configuration.
For all 27 EU countries, from the EU's daily rates. Places outside EU VAT, such as Heligoland, are caught by postal code. For Switzerland, Norway, the UK and anywhere else, you plug in your own tax function.
Through UCP, an open checkout protocol. The customer approves on your checkout page, or ahead of time with an AP2 mandate in the agent's app. The agent gets a quote signed by your shop and can check it before paying.
The software is free and open source under Apache 2.0. You pay Cloudflare for hosting and your payment provider for transactions, as you use them. There is no paid plan today.
Not yet. OrbCart is pre-release: it is not on npm and the repository is not public yet. The demo shop shows where things stand, and the project status page lists what works today.
The demo shop runs on OrbCart in test mode. Put something in the bag and check out, or read the docs and connect your own data.