Guides
Commerce
Add a cart and checkout

Add a cart and checkout

Requires: A publishable or secret key with WRITE_CART; WRITE_SALES for checkout.

The cart lives on Cobuntu, so a buyer can fill it on your site and find the same cart in the community app. Your site holds a cart token; when the buyer moves to another origin, you mint a one-time code for the URL.

POST   /api/v1/communities/:communityTag/cart
GET    /api/v1/communities/:communityTag/cart
POST   /api/v1/communities/:communityTag/cart/items
PATCH  /api/v1/communities/:communityTag/cart/items/:itemId
DELETE /api/v1/communities/:communityTag/cart/items/:itemId
POST   /api/v1/communities/:communityTag/cart/merge
POST   /api/v1/communities/:communityTag/cart/handoff
POST   /api/v1/communities/:communityTag/cart/claim

Scope WRITE_CART, carried by the publishable key. The token travels in the X-Cobuntu-Cart-Token header; the client sends it for you.

At a glance

# Create a cart: answers { cart, token }
curl -X POST "https://api.cobuntu.com/api/v1/communities/your-community/cart" -H "X-API-Key: $COBUNTU_PUBLISHABLE_KEY"
 
# Add a line with the token
curl -X POST "https://api.cobuntu.com/api/v1/communities/your-community/cart/items" \
  -H "X-API-Key: $COBUNTU_PUBLISHABLE_KEY" \
  -H "X-Cobuntu-Cart-Token: $CART_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"productId":"prd_1","tierId":"tier_a5","quantity":1}'

The cart

Every call answers the cart with prices resolved live:

{
  "id": "cart_1", "communityId": "com_1",
  "items": [{
    "id": "ci_1", "productId": "prd_1", "checkoutProductId": "prd_1a", "tierId": "tier_a5",
    "quantity": 2, "name": "Acme Notebook", "tierName": "A5", "sku": "acme-notebook",
    "unitPrice": 2500, "lineTotal": 5000, "currency": "EUR",
    "imageUrl": "https://example.com/n.jpg", "productType": "PHYSICAL", "unavailableReason": null
  }],
  "subtotal": 5000, "currency": "EUR", "totalItems": 2
}
  • The cart stores references, never prices. A price is what the product costs now.
  • A line that can no longer be bought (pulled, or its sales window closed) stays with unavailableReason set and is left out of subtotal. Show it, with the reason, instead of dropping it.
  • checkoutProductId is the id a checkout or quote must use for that line. A variant has its own price row, so sending productId would price the parent.
  • Up to 50 different lines, quantity up to 99 each.

Create and keep the token

const { cart, token } = await cobuntu.cart.create();
// 201 for a guest: store token in your own httpOnly cookie
cookies().set("cart_token", token!, { httpOnly: true, secure: true, sameSite: "lax", maxAge: 60 * 60 * 24 * 90 });

The token is shown once (it is stored only as a hash). It grants that one cart and nothing else. token is null only for a signed-in community app session, whose cart is addressed by the user.

Add, change, remove

const t = cookies().get("cart_token")?.value;
if (!t) throw new Error("no cart yet: call cart.create() first");
 
let cart = await cobuntu.cart.addItem(t, { productId: "prd_1", tierId: "tier_a5", quantity: 1 });
cart = await cobuntu.cart.updateItem(t, "ci_1", 3); // 0 removes the line
cart = await cobuntu.cart.removeItem(t, "ci_1");
cart = await cobuntu.cart.get(t);

Adding a product already in the cart adds to its quantity. Codes and messages:

AnswerMeans
404 Cart not foundThe token does not resolve (purged or wrong). Create a new cart; do not retry the same token.
404 Product not foundNot purchasable here, including members-only products.
404 Item not foundThe line id is not in this cart.
400productId or quantity missing or out of range, an unknown variant, or more than 50 lines.

Checkout

Pass the token instead of items:

const r = await cobuntu.checkout.create({ cartToken: t, successUrl, cancelUrl });
redirect(r.checkoutUrl!);

Send either items or cartToken, never both (400 AMBIGUOUS_BODY). The quote takes the same: checkout.quote({ cartToken }).

Carry the cart to another origin

Never put the cart token in a URL: URLs end up in history, Referer headers and logs. Mint a code instead:

const { code, expiresAt } = await cobuntu.cart.handoff(t); // valid 60 seconds, single use
redirect(`https://your-community.cobuntu.com/checkout?c=${code}`);

The receiving side exchanges it:

const { cart, token } = await cobuntu.cart.claim(code);

A guest claimer gets a fresh token for this browser, and the issuing browser's token keeps working. A signed-in claimer has the cart folded into their own. An expired or used code is 404 That checkout link has expired; a missing one is 400 code is required.

Merge on sign-in

cart.merge(cartToken) folds a guest cart into the signed-in user's cart. It needs the community app's own signed-in session as well as the guest token. A member session from your site is not that session, so from your server it answers 401 Sign in to merge a cart. The community app merges for you when a buyer who arrived with a cart signs in there.