Products & orders
Manage a merchant catalog, collect payment through hosted checkout, and connect verified orders to your fulfillment system.
Developer preview. Deploy the updated Worker with migrations through 0011. New catalog orders require a card-only capture-on-delivery configuration. This guide describes source code, not a deployed capability. See Delivery & payment holds and Atlas wallet before enabling payments.
Create and import products
Open Get paid → Products in the dashboard. Add a name, unique SKU, price, currency, product type, and optional description and variant. Each size/color combination is a separate sellable SKU. Products start inactive unless you explicitly enable them. Editing a price affects new orders, not an order already created.
The catalog is isolated by workspace and configured payment environment. The dashboard test-mode display toggle does not switch API credentials or move products between test and live. Only workspace owners can create, edit, or import through the dashboard.
Choose Import CSV, download the template, select your file, review the validated products, and confirm the import. The preview writes nothing. The confirmed batch either succeeds in full or rolls back. Imports are create-only: they never overwrite an existing SKU, even after a timeout or concurrent import. SKUs are case-sensitive.
sku,name,description,variant,price,currency,kind,active
SHIRT-BLUE-M,Classic shirt,Cotton shirt,Blue / Medium,25.00,USD,physical,false
Limits: 50 products per file, 256 KiB, UTF-8, one header row. Required headers are sku, name, price, and currency; the other template columns are optional. Quoted commas, escaped quotes, and embedded newlines are supported. kind is physical, digital, or service; active is true or false. Prices in CSV are decimal display amounts. API prices are integer Stripe charge units: USD 25.00 is 2500, JPY 2500 is 2500. Currency precision rules are enforced before import.
Catalog API
Keep merchant API keys on your server. Add only the permissions your integration needs in Developers; existing payment-only keys do not automatically gain catalog or order access.
| Request | Scope | Behavior |
|---|---|---|
GET /products?q=shirt&after=… | products:read | Search by name/SKU; up to 50 products per page |
GET /products/{id} | products:read | One product in this workspace and mode |
POST /products | products:write | Create one product; duplicate SKU returns 409 |
PUT /products/{id} | products:write | Replace editable fields using product and expected_version |
POST /products/import | products:write | JSON array products, optional dry_run; 1–50 items |
List responses contain data, has_more, and next_cursor. Continue using after=next_cursor; do not treat IDs as timestamps. If an edit returns VERSION_CONFLICT, reload and review the latest product before submitting again. Create/import do not use payment idempotency headers: the unique SKU prevents duplicate creation. After an uncertain result, list or retrieve products to reconcile; do not assume a failed network response means no insert occurred.
Example product body:
{
"sku": "SHIRT-BLUE-M",
"name": "Classic shirt",
"description": "Cotton shirt",
"variant": "Blue / Medium",
"amount": 2500,
"currency": "USD",
"kind": "physical",
"active": true
}
Create a catalog order
Your server chooses the exact product IDs and quantities after the buyer reviews the merchant, variant, delivery details, and total. Atlas Pay calculates merchandise totals from the stored catalog, never from a shopper-supplied price. All items must be active and use one currency. An order stores an immutable item/price/version snapshot before requesting Stripe checkout, so a retry cannot silently use a new catalog price.
curl https://dashboard.atlaspay.sh/api/v1/orders \
-H "Authorization: Bearer $ATLAS_PAY_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: store-order-1042-v1' \
-d '{
"reference": "store-order-1042",
"items": [{"product_id":"prod_0123456789abcdef0123456789abcdef","quantity":2}],
"shipping_amount": 500,
"tax_amount": 250,
"shipping_countries": ["US"],
"success_url": "https://shop.example.com/thank-you",
"cancel_url": "https://shop.example.com/cart"
}'
Replace the example product ID and website with your own. This requires orders:write, enabled Stripe Connect charges, and an Idempotency-Key. Use the exact same key/body for retries. The merchant order reference is unique per workspace and mode; using a new key for the same reference returns 409 rather than creating a second payment. Reconcile expired or abandoned orders before deliberately issuing a new reference.
The response includes data.id (ord_…), checkout_id, checkout_url, item snapshots, amount, currency, payment status, and fulfillment status. Persist the order ID before redirecting to checkout_url. Replays may return a null URL after checkout finishes. There is no card charge solely because an order was created.
Orders support 1–10 distinct product lines, 1–100 units per line, and a total up to 99,999,999 charge units. Physical products require at least one permitted shipping country; Stripe collects the shipping address. Your server must compute tax and shipping before creating the order. Tax and shipping are fixed amounts displayed as separate checkout lines in this preview, not Stripe Tax calculations or carrier quotes.
New orders: authorization, delivery, capture
New catalog orders use capture_policy: on_delivery. Stripe authorizes the total at checkout without capturing it. A fresh payment_status: authorized, known future capture_before, no settlement action, and no refund/dispute are required before dispatch. Confirm your delivery window fits the authorization deadline, and deduplicate dispatch by order ID.
Your trusted delivery service calls POST /orders/{id}/delivery with orders:deliver, a stable Idempotency-Key, and the actual delivery reference/timestamp. Atlas records delivery and then captures the existing full authorization. POST /orders/{id}/cancel with orders:cancel releases uncaptured holds. Neither action accepts a new amount or creates another charge. Unknown or expired authorizations require review. order.delivered does not mean order.paid; capture must still be verified.
Read the complete delivery API, event flow and launch checklist. The external delivery bot is not installed by this API implementation. The legacy /fulfillment endpoint below rejects capture-on-delivery orders, so it cannot trigger early capture.
Legacy automatic-capture fulfillment
The sequence below applies only to existing orders with capture_policy: automatic. Payment collection remains on Stripe's hosted page.
1. Buyer -> Store backend : Confirm item, variant and total
2. Store backend -> Atlas Pay : Create order (idempotency key)
3. Atlas Pay -> D1 : Save product/price snapshot
4. Atlas Pay -> Stripe : Create connected-account checkout
5. Buyer -> Stripe Checkout : Authorize payment / authenticate
6. Stripe -> Atlas Pay : Signed payment event
7. Atlas Pay -> Stripe : Verify current session and charge
8. Atlas Pay -> Store webhook : Signed order.paid event
9. Store backend -> Atlas Pay : Retrieve current order
10. Store backend -> Fulfillment : Queue shipping once, by order ID
11. Fulfillment -> Atlas Pay : Record fulfillment and tracking
Use GET /orders/{id} with orders:read to refresh the current processor status. GET /orders is a paginated stored snapshot, not a fresh Stripe check. Before releasing goods, verify the expected order reference, items, total, currency, mode, and shipping address. Require payment_status = paid, amount_refunded = 0, and your own idempotent fulfillment check. A pending, failed, expired, disputed, refunded, or mismatched order must not automatically ship.
Atlas verifies both the Checkout Session and the succeeded, captured Stripe charge. A dispute creates a sticky hold requiring manual review; this preview has no automatic dispute-release endpoint. Even verified payment does not guarantee it can never be refunded or disputed later. Keep your store’s fulfillment and reconciliation process responsible for late changes.
After your fulfillment system has durably accepted or completed the shipment, record it:
POST /orders/{id}/fulfillment
Authorization: Bearer <server-key-with-orders:write>
Content-Type: application/json
{
"status": "fulfilled",
"carrier": "Your carrier",
"tracking_number": "YOUR-TRACKING-NUMBER",
"tracking_url": "https://carrier.example.com/track/YOUR-TRACKING-NUMBER"
}
Carrier and tracking fields are optional for digital goods/services. Identical fulfillment retries return the same record; a conflicting record returns 409. No separate idempotency header is needed for this transition. Atlas Pay does not buy a shipping label, ship stock, or deliver a digital file for you. The merchant system must prevent two webhook deliveries from creating two shipments.
Order webhook events
| Event | Meaning |
|---|---|
order.authorized | New capture-on-delivery order has a verified card hold; not captured payment |
order.delivered | Trusted delivery attestation recorded; payment capture may still be pending |
order.canceled | Processor canceled the authorization |
order.authorization_expired | Authorization deadline passed; reconcile, do not capture or recharge |
order.authorization_review | Unknown deadline or unsupported authorization; do not dispatch |
order.paid | Current checkout and captured charge verified and current risk decision permits fulfillment; retrieve current state before acting |
order.risk_blocked | Automatic block: stop dispatch, capture and fulfillment; reconcile any existing payment |
order.risk_review | Automatic hold: review required before dispatch, capture or fulfillment |
order.payment_updated | A refund or dispute hold was observed; reconcile before releasing goods |
order.fulfilled | The merchant recorded fulfillment; not proof a carrier delivered it |
These use the existing API key’s signed callback and durable event feed. An order can also produce checkout.updated and refund.updated; use order IDs and stable event IDs to avoid duplicate work. Events may be delayed, duplicated, or arrive out of order. Never ship directly from an old order.paid payload without retrieving the current order. See Webhooks & retries.
Refund catalog orders through POST /refunds using their checkout_id and the refund scope. Orders also refresh refund/dispute state on reads and on configured Stripe charge events. A refund created directly in Stripe still remains outside the separate merchant API refund registry. Order data and callbacks can include customer email and shipping addresses: restrict access, redact logs, and apply your retention policy.
Saved cards and buying through Atlas chat
Add payment_flow: "atlas_wallet" when creating an order to return an Atlas buyer-review link instead of immediately creating Stripe Checkout. The buyer signs in, reviews the immutable total, selects their saved card and authorizes payment on Stripe. The default payment flow remains hosted; both flows use capture-on-delivery for new catalog orders. Already-created orders retain their original capture policy.
The wallet uses one platform Customer per buyer and environment, with an order-specific card copy for a direct connected-account payment. It includes consent, ownership checks, retry protection and removal/cleanup controls. /wallet and /buy <order-link> are private-chat entry points, not in-chat charging commands. Natural-language product discovery and native-app checkout remain follow-up work. Read the full Atlas wallet setup and integration guide before enabling this option.
Preview boundaries
This catalog does not yet reserve inventory, calculate tax, quote carrier shipping, group variants under a parent product, host product images, provide a public marketplace, or synchronize WooCommerce/Wix catalogs. The existing store integrations continue to use their own catalogs and checkout API. CSV and the new server-to-server product API are the import paths provided here. Availability remains subject to merchant country, Stripe capabilities, currency and payment-method eligibility—not unrestricted worldwide acceptance.
Automatic payment risk decisions
Orders include risk_decision: pending, allow, review or block. Only allow permits normal dispatch/fulfillment, alongside every existing payment and delivery check. Retrieve the current order before acting. High processor risk blocks capture and fulfillment; elevated risk, failed checks and unavailable assessments require review. A block does not cancel or refund the payment. order.risk_blocked and order.risk_review callbacks carry the updated order snapshot and stable event IDs. A held order does not emit a new order.authorized or order.paid dispatch signal; previously sent events may still arrive late.