17 operations. Every schema and example on this page is generated from the platform contract.
Merchant lists their store orders: scope=active is the live fulfilment queue; scope=history is terminal orders newest-first, keyset-paged by the `before` + `before_id` cursor (send both, a mixed basket's parent and legs share a created_at to the microsecond, so the timestamp alone cannot identify a page boundary). Same per-order shape as the fulfilment ops.
Store whose orders to list (also authorises the merchant).
active = the live queue (default); history = terminal orders, newest-first.
activehistoryPage size (default 50, max 100).
Keyset cursor (an order created_at) for history pagination.
The id half of the history cursor, the id of the SAME row `before` came from. Send both together. A mixed basket's parent and legs share a created_at to the microsecond, so created_at alone cannot say where a page ended: omitting this makes the server fall back to an inclusive bound and re-serve the boundary row, which is safe but returns rows you already have (de-dupe by id).
pay_nowbank_transfercashactivehistorycurl -G "https://www.membber.com/api/v1/orders" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
--data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/orders", {
params: { query: { store_id: "6659c139-0000-4000-8000-d0c500000066" } },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.listOrders(
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"orders": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"order_type": "<order_type>",
"fulfilment_status": "<fulfilment_status>",
"collection_code": "EXAMPLE10",
"payment_method": "pay_now",
"order_page_path": "<order_page_path>",
"payment_due_at": "<payment_due_at>",
"collection_mode": "<collection_mode>",
"requested_collection_at": "<requested_collection_at>",
"accepted_at": "<accepted_at>",
"preparing_at": "<preparing_at>",
"ready_at": "<ready_at>",
"collected_at": "<collected_at>",
"customer_name": "Alex Example",
"customer_email": "alex@example.com",
"customer_phone": "+44 7700 900123"
}
],
"scope": "active"
}/api/v1/ordersCustomer creates an in-app order; returns a destination-charge PaymentIntent + ephemeral key for the native Apple Pay / card sheet. Idempotent via the body `idempotency_key` (order-level replay in the service).
Public store code.
Store id.
Client-generated order idempotency key (UUID v4).
Basket lines.
Fulfilment selection (defaults to counter collection).
buy_nowcollectnowscheduledpay_nowbank_transfercashMixed-cart groups: take-now lines + one or more collect groups.
nowcollectnowscheduledpay_nowbank_transfercashCollect children of a £0 mixed order (empty/absent for a normal order).
curl -X POST "https://www.membber.com/api/v1/orders" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_code": "EXAMPLE10",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"idempotency_key": "1f0e2d3c-4b5a-4678-9abc-def012345678",
"items": [
{
"menu_item_id": "ca65a6e7-0000-4000-8000-d0c5000000ca",
"quantity": 1
}
]
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.createOrder({
body: {
store_code: "EXAMPLE10",
store_id: "6659c139-0000-4000-8000-d0c500000066",
idempotency_key: "1f0e2d3c-4b5a-4678-9abc-def012345678",
items: [
{
menu_item_id: "ca65a6e7-0000-4000-8000-d0c5000000ca",
quantity: 1
}
]
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.createOrder(
body: .json(.init(
storeCode: "EXAMPLE10",
storeId: "6659c139-0000-4000-8000-d0c500000066",
idempotencyKey: "1f0e2d3c-4b5a-4678-9abc-def012345678",
items: [.init(
menuItemId: "ca65a6e7-0000-4000-8000-d0c5000000ca",
quantity: 1
)]
))
).ok.body.json
print(response){
"order_id": "4991ffac-0000-4000-8000-d0c500000049",
"order_number": "<order_number>",
"status": "<status>",
"requires_payment": true,
"client_secret": "<client_secret>",
"ephemeral_key": "<ephemeral_key>",
"stripe_customer_id": "847b302a-0000-4000-8000-d0c500000084",
"connected_account_id": "231b6f63-0000-4000-8000-d0c500000023",
"payment_intent_id": "0b6642a5-0000-4000-8000-d0c50000000b",
"amount_pence": 1500,
"currency": "GBP",
"merchant_name": "<merchant_name>",
"collection_code": "EXAMPLE10",
"payment_method": "pay_now",
"order_page_path": "<order_page_path>",
"payment_due_at": "<payment_due_at>",
"children": [
{
"order_id": "4991ffac-0000-4000-8000-d0c500000049",
"order_number": "<order_number>",
"order_type": "<order_type>",
"collection_code": "EXAMPLE10",
"fulfilment_status": "<fulfilment_status>",
"prep_minutes": 1,
"requested_collection_at": "<requested_collection_at>",
"total_pence": 1500
}
]
}Merchant fetches a single order (items, fulfilment state, totals). Store-scoped, a store cannot read another store's order (returns 404).
Store the order belongs to (also authorises the merchant).
A guest order row (Order & Collect).
pay_nowbank_transfercashcurl -G "https://www.membber.com/api/v1/orders/b80cf509-0000-4000-8000-d0c5000000b8" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
--data-urlencode "store_id=6659c139-0000-4000-8000-d0c500000066"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/orders/{orderId}", {
params: { path: { orderId: "b80cf509-0000-4000-8000-d0c5000000b8" }, query: { store_id: "6659c139-0000-4000-8000-d0c500000066" } },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.getOrder(
path: .init(orderId: "b80cf509-0000-4000-8000-d0c5000000b8"),
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"order": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"order_type": "<order_type>",
"fulfilment_status": "<fulfilment_status>",
"collection_code": "EXAMPLE10",
"payment_method": "pay_now",
"order_page_path": "<order_page_path>",
"payment_due_at": "<payment_due_at>",
"collection_mode": "<collection_mode>",
"requested_collection_at": "<requested_collection_at>",
"accepted_at": "<accepted_at>",
"preparing_at": "<preparing_at>",
"ready_at": "<ready_at>",
"collected_at": "<collected_at>",
"customer_name": "Alex Example",
"customer_email": "alex@example.com",
"customer_phone": "+44 7700 900123"
}
}For an order the buyer is paying in cash at collection. Records the payment and the hand-over in ONE transaction, so a crash between them cannot leave a collected order nobody paid for. Sends the receipt, and deliberately not the collection text, the buyer is standing there holding the box.
Store that owns the order.
A guest order row (Order & Collect).
pay_nowbank_transfercashWhat was taken, so the screen can say it back to her.
curl -X POST "https://www.membber.com/api/v1/orders/b80cf509-0000-4000-8000-d0c5000000b8/cash-handover" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/orders/{orderId}/cash-handover", {
params: { path: { orderId: "b80cf509-0000-4000-8000-d0c5000000b8" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.handOverCashOrder(
path: .init(orderId: "b80cf509-0000-4000-8000-d0c5000000b8"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"order": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"order_type": "<order_type>",
"fulfilment_status": "<fulfilment_status>",
"collection_code": "EXAMPLE10",
"payment_method": "pay_now",
"order_page_path": "<order_page_path>",
"payment_due_at": "<payment_due_at>",
"collection_mode": "<collection_mode>",
"requested_collection_at": "<requested_collection_at>",
"accepted_at": "<accepted_at>",
"preparing_at": "<preparing_at>",
"ready_at": "<ready_at>",
"collected_at": "<collected_at>",
"customer_name": "Alex Example",
"customer_email": "alex@example.com",
"customer_phone": "+44 7700 900123"
},
"amount_pence": 1500
}Drop orders only (any other order is a 404). `collected` hands it over: refused with NOT_PAID and the amount when it is still to pay. `didnt_come` closes it after its collection window has ended: refused with COLLECTION_NOT_OVER and `collection_ends_at` before then, on the drop's own clock; an audit row names who said it. Idempotent: the same outcome again changes nothing. Requires the payments permission. People only: an AI agent key is refused.
Store that owns the order.
`collected`: handed over. `didnt_come`: they did not come (after the window).
collecteddidnt_comeA guest order row (Order & Collect).
pay_nowbank_transfercashcollecteddidnt_comecurl -X POST "https://www.membber.com/api/v1/orders/b80cf509-0000-4000-8000-d0c5000000b8/drop-collection" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"outcome": "collected"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/orders/{orderId}/drop-collection", {
params: { path: { orderId: "b80cf509-0000-4000-8000-d0c5000000b8" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
outcome: "collected"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.markDropOrderCollection(
path: .init(orderId: "b80cf509-0000-4000-8000-d0c5000000b8"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
outcome: .collected
))
).ok.body.json
print(response){
"order": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"order_type": "<order_type>",
"fulfilment_status": "<fulfilment_status>",
"collection_code": "EXAMPLE10",
"payment_method": "pay_now",
"order_page_path": "<order_page_path>",
"payment_due_at": "<payment_due_at>",
"collection_mode": "<collection_mode>",
"requested_collection_at": "<requested_collection_at>",
"accepted_at": "<accepted_at>",
"preparing_at": "<preparing_at>",
"ready_at": "<ready_at>",
"collected_at": "<collected_at>",
"customer_name": "Alex Example",
"customer_email": "alex@example.com",
"customer_phone": "+44 7700 900123"
},
"outcome": "collected"
}Staff transition of a collect order: accepted → preparing → ready → collected, or cancelled. Backed by the idempotent advance_order_fulfilment RPC (single writer; outbox event in same tx).
Store that owns the order.
Target fulfilment state.
acceptedpreparingreadycollectedcancelledA guest order row (Order & Collect).
pay_nowbank_transfercashcurl -X PATCH "https://www.membber.com/api/v1/orders/b80cf509-0000-4000-8000-d0c5000000b8/fulfilment" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"to_status": "accepted"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.advanceOrderFulfilment({
params: { path: { orderId: "b80cf509-0000-4000-8000-d0c5000000b8" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
to_status: "accepted"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.advanceOrderFulfilment(
path: .init(orderId: "b80cf509-0000-4000-8000-d0c5000000b8"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
toStatus: .accepted
))
).ok.body.json
print(response){
"order": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"order_type": "<order_type>",
"fulfilment_status": "<fulfilment_status>",
"collection_code": "EXAMPLE10",
"payment_method": "pay_now",
"order_page_path": "<order_page_path>",
"payment_due_at": "<payment_due_at>",
"collection_mode": "<collection_mode>",
"requested_collection_at": "<requested_collection_at>",
"accepted_at": "<accepted_at>",
"preparing_at": "<preparing_at>",
"ready_at": "<ready_at>",
"collected_at": "<collected_at>",
"customer_name": "Alex Example",
"customer_email": "alex@example.com",
"customer_phone": "+44 7700 900123"
}
}Who did what to this order, when, and why, refunds, status corrections and overrides, newest first. Answers the four questions a merchant asks after a refund, from audit rows that were previously written and never read.
A merchant-readable sentence, never a raw action code.
The reason a human typed, when there was one.
Who did it, where a name can be resolved.
curl -G "https://www.membber.com/api/v1/orders/b80cf509-0000-4000-8000-d0c5000000b8/log" \
-H "Authorization: Bearer $MEMBBER_TOKEN"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/orders/{orderId}/log", {
params: { path: { orderId: "b80cf509-0000-4000-8000-d0c5000000b8" } },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.getOrderLog(
path: .init(orderId: "b80cf509-0000-4000-8000-d0c5000000b8")
).ok.body.json
print(response){
"entries": [
{
"at": "<at>",
"title": "Morning class",
"detail": "<detail>",
"actor": "<actor>",
"amount_pence": 1500
}
]
}Staff records that a collect order is running late (delayMinutes) or sets an explicit newReadyAt, with an optional note. Persists a revised ETA on the order (never the actual ready_at or the booked slot), then notifies the customer on the existing channels: push for a linked customer, email fallback for a guest. Writes an append-only order_status_events audit row (kind running_late). Not money-moving; a short per-order dedupe window keeps a double-tap from spamming.
Store that owns the order (also authorises the merchant).
Minutes the order is running late (1-480). New ETA = current ETA + this.
Explicit new expected ready/collection time (ISO 8601). Overrides delayMinutes.
Optional short merchant message shown to the customer with the new time.
pushemailnonecurl -X POST "https://www.membber.com/api/v1/orders/b80cf509-0000-4000-8000-d0c5000000b8/notify-delay" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/orders/{orderId}/notify-delay", {
params: { path: { orderId: "b80cf509-0000-4000-8000-d0c5000000b8" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.notifyOrderDelay(
path: .init(orderId: "b80cf509-0000-4000-8000-d0c5000000b8"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"ok": true,
"newEta": "<newEta>",
"notified": "push",
"channel_detail": "<channel_detail>"
}Puts the order back to awaiting payment. A CORRECTION, not an undo: the receipt and the payment text have already gone and are not recalled, which is why the app asks for a reason. Refused once the order has been collected or handed over, the box is in somebody’s hands, so a refund is the honest instrument.
Store that owns the order.
Why it was wrong. Recorded on the audit row, because the buyer has already had the receipt.
A guest order row (Order & Collect).
pay_nowbank_transfercashcurl -X DELETE "https://www.membber.com/api/v1/orders/b80cf509-0000-4000-8000-d0c5000000b8/offline-payment?store_id=6659c139-0000-4000-8000-d0c500000066" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.DELETE("/api/v1/orders/{orderId}/offline-payment", {
params: { path: { orderId: "b80cf509-0000-4000-8000-d0c5000000b8" }, query: { store_id: "6659c139-0000-4000-8000-d0c500000066" } },
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.unrecordOrderOfflinePayment(
path: .init(orderId: "b80cf509-0000-4000-8000-d0c5000000b8"),
query: .init(storeId: "6659c139-0000-4000-8000-d0c500000066")
).ok.body.json
print(response){
"order": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"order_type": "<order_type>",
"fulfilment_status": "<fulfilment_status>",
"collection_code": "EXAMPLE10",
"payment_method": "pay_now",
"order_page_path": "<order_page_path>",
"payment_due_at": "<payment_due_at>",
"collection_mode": "<collection_mode>",
"requested_collection_at": "<requested_collection_at>",
"accepted_at": "<accepted_at>",
"preparing_at": "<preparing_at>",
"ready_at": "<ready_at>",
"collected_at": "<collected_at>",
"customer_name": "Alex Example",
"customer_email": "alex@example.com",
"customer_phone": "+44 7700 900123"
}
}The shop says the money arrived on an order the buyer is paying by bank transfer or in cash. Sends the confirmation text and the receipt, the window, the address and the collection code the buyer could not be given while the money was outstanding. Idempotent: a second call returns `already_recorded` and sends nothing. Refused on a card order, whose money lives with Stripe. A RELEASED order is accepted and comes back flagged `late_payment_needs_review`, because the boxes went back on sale when it was released.
Store that owns the order.
Who is saying the money arrived. Defaults to the merchant.
merchantsweepThe merchant’s own note, if she left one (a reference, or which account it came into).
A guest order row (Order & Collect).
pay_nowbank_transfercash`already_recorded` when somebody got there first, a second phone in the same kitchen, or a second tap.
recordedalready_recordedThe transfer landed AFTER the order was released and its boxes went back on sale. The money is the shop’s and the order is paid, but the stock may be gone, so the merchant has to decide: make another, or refund.
Whose hand it was, for "Marked paid by Romi".
curl -X POST "https://www.membber.com/api/v1/orders/b80cf509-0000-4000-8000-d0c5000000b8/offline-payment" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/orders/{orderId}/offline-payment", {
params: { path: { orderId: "b80cf509-0000-4000-8000-d0c5000000b8" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.recordOrderOfflinePayment(
path: .init(orderId: "b80cf509-0000-4000-8000-d0c5000000b8"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"order": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"order_type": "<order_type>",
"fulfilment_status": "<fulfilment_status>",
"collection_code": "EXAMPLE10",
"payment_method": "pay_now",
"order_page_path": "<order_page_path>",
"payment_due_at": "<payment_due_at>",
"collection_mode": "<collection_mode>",
"requested_collection_at": "<requested_collection_at>",
"accepted_at": "<accepted_at>",
"preparing_at": "<preparing_at>",
"ready_at": "<ready_at>",
"collected_at": "<collected_at>",
"customer_name": "Alex Example",
"customer_email": "alex@example.com",
"customer_phone": "+44 7700 900123"
},
"outcome": "recorded",
"late_payment_needs_review": true,
"recorded_by_name": "<recorded_by_name>"
}For an order whose money never went through Stripe. Refuses any order carrying a PaymentIntent, that money is Stripe’s to return, and any order that was never paid, which is released rather than refunded. Clamps to what is outstanding. On a full refund it frees the collection slot, puts the stock back on sale and reverses the loyalty in the same transaction, and the buyer is told the money is coming back in cash or by transfer, never "to your card". Idempotent on `attempt_id`.
Store that owns the order.
How much she handed back. Omit for the whole outstanding balance, which is what cancelling means and what the server would work out anyway. Clamped to what is actually left.
Why, in her words. Audited, and kept on the order.
Idempotency key. A retry carrying the same one finds its own earlier work rather than paying twice.
A guest order row (Order & Collect).
pay_nowbank_transfercashrecordedalready_recordedWhat THIS refund returned. Not the running total.
True when nothing is left outstanding, which also ends the order.
Whose hand it was, for "Refunded by Romi".
curl -X POST "https://www.membber.com/api/v1/orders/b80cf509-0000-4000-8000-d0c5000000b8/offline-refund" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/orders/{orderId}/offline-refund", {
params: { path: { orderId: "b80cf509-0000-4000-8000-d0c5000000b8" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.recordOfflineRefund(
path: .init(orderId: "b80cf509-0000-4000-8000-d0c5000000b8"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"order": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"order_type": "<order_type>",
"fulfilment_status": "<fulfilment_status>",
"collection_code": "EXAMPLE10",
"payment_method": "pay_now",
"order_page_path": "<order_page_path>",
"payment_due_at": "<payment_due_at>",
"collection_mode": "<collection_mode>",
"requested_collection_at": "<requested_collection_at>",
"accepted_at": "<accepted_at>",
"preparing_at": "<preparing_at>",
"ready_at": "<ready_at>",
"collected_at": "<collected_at>",
"customer_name": "Alex Example",
"customer_email": "alex@example.com",
"customer_phone": "+44 7700 900123"
},
"outcome": "recorded",
"amount_pence": 1500,
"full_refund": true,
"recorded_by_name": "<recorded_by_name>"
}Staff-initiated refund. Validates the refund window + amount, then creates a Stripe refund, idempotent via a Stripe idempotency key derived from order + refund_attempt_id (a retry of the same tap never double-refunds, while two distinct taps with distinct ids each go through). The amount is exact: more than is still refundable (the order total less what has gone back, including what a lost dispute took) is refused with 409 REFUND_OVER_REMAINING and details.remaining_pence. A refund_attempt_id that already went through is answered with that refund; one still going through, or a refund that moved on the same order while this one was being placed, is 409 REFUND_IN_PROGRESS (retry with the same id); the same id re-sent with another amount is 409 REFUND_ATTEMPT_REUSED. Reverses loyalty + promo on a full refund.
Why the order is being refunded (audited).
Refund amount in whole pence; omit/null to refund everything still left. Refunded exactly: more than is left is refused (409 REFUND_OVER_REMAINING, details.remaining_pence), never reduced.
Optional UUID for ONE merchant refund tap. A fresh id per tap makes two legitimate equal-amount/equal-reason refunds distinct; a retry of the same tap (same id) safely dedups via the Stripe idempotency key.
curl -X POST "https://www.membber.com/api/v1/orders/b80cf509-0000-4000-8000-d0c5000000b8/refund" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"reason": "Added at the front desk"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.refundOrder({
params: { path: { orderId: "b80cf509-0000-4000-8000-d0c5000000b8" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
reason: "Added at the front desk"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.refundOrder(
path: .init(orderId: "b80cf509-0000-4000-8000-d0c5000000b8"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
reason: "Added at the front desk"
))
).ok.body.json
print(response){
"refund": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"status": "<status>",
"amount": 1500
}
}The transfer never arrived. Ends the order (`cancelled`, `released_unpaid`), voids its stock holds and puts the quantities they held back on sale. The buyer is told separately, on the sweep, inside the shop’s sending hours, so a release at half past five in the morning does not wake anybody. Refused on a PAID order: releasing takes the goods back without returning the money, so there the instrument is a refund. Idempotent.
Store that owns the order.
`merchant` when she decides, `stale` when the deadline passed. Defaults to the merchant.
merchantstaleA guest order row (Order & Collect).
pay_nowbank_transfercashreleasedalready_releasedHow many item lines went back on sale.
How many stock holds were released.
curl -X POST "https://www.membber.com/api/v1/orders/b80cf509-0000-4000-8000-d0c5000000b8/release" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/orders/{orderId}/release", {
params: { path: { orderId: "b80cf509-0000-4000-8000-d0c5000000b8" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.releaseUnpaidOrder(
path: .init(orderId: "b80cf509-0000-4000-8000-d0c5000000b8"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"order": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"order_type": "<order_type>",
"fulfilment_status": "<fulfilment_status>",
"collection_code": "EXAMPLE10",
"payment_method": "pay_now",
"order_page_path": "<order_page_path>",
"payment_due_at": "<payment_due_at>",
"collection_mode": "<collection_mode>",
"requested_collection_at": "<requested_collection_at>",
"accepted_at": "<accepted_at>",
"preparing_at": "<preparing_at>",
"ready_at": "<ready_at>",
"collected_at": "<collected_at>",
"customer_name": "Alex Example",
"customer_email": "alex@example.com",
"customer_phone": "+44 7700 900123"
},
"outcome": "released",
"items_restocked": 1,
"holds_voided": 1
}Staff transition of the merchant status axis: ready, fulfilled, or cancelled. Backed by the idempotent updateGuestOrderStatus service (noop on a same-status repeat; store-scoped transition guard). Writes a store_payment_audit_logs row, bridges buy_now status changes onto the order_status_events outbox so the customer is notified, and releases slot + stock holds on cancel. Not money-moving (a refund is refundOrder).
Store that owns the order (also authorises the merchant).
Target business status: ready, fulfilled, or cancelled.
readyfulfilledcancelledOptional staff note, stored on the status change + audit row.
curl -X PATCH "https://www.membber.com/api/v1/orders/b80cf509-0000-4000-8000-d0c5000000b8/status" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"status": "ready"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.PATCH("/api/v1/orders/{orderId}/status", {
params: { path: { orderId: "b80cf509-0000-4000-8000-d0c5000000b8" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
status: "ready"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.updateOrderStatus(
path: .init(orderId: "b80cf509-0000-4000-8000-d0c5000000b8"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
status: .ready
))
).ok.body.json
print(response){
"order": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"status": "<status>",
"order_type": "<order_type>",
"fulfilled_at": "<fulfilled_at>",
"cancelled_at": "<cancelled_at>"
}
}For the box handed to the wrong person. Puts back what the handover changed, in one transaction: the collect stage the order was in, any loyalty stamp the handover earned, and the cash when that same handover took it (money recorded earlier by Mark as paid stays recorded). Refused with UNDO_WINDOW_PASSED once the handover is more than 10 minutes old (server clock), and on a refunded, disputed or cancelled order. Idempotent: a replay returns `already_undone`. Drop orders only. People only: an AI agent key is refused.
Store that owns the order.
A guest order row (Order & Collect).
pay_nowbank_transfercash`already_undone` when this is a replay of an undo that already landed: nothing changed twice.
undonealready_undoneThe stage the order went back to (`preparing` for a drop order).
True when the handover had taken the cash: that money is owed on collection again.
The cash put back to owed, in pence. 0 when the handover took none.
curl -X POST "https://www.membber.com/api/v1/orders/b80cf509-0000-4000-8000-d0c5000000b8/undo-handover" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/orders/{orderId}/undo-handover", {
params: { path: { orderId: "b80cf509-0000-4000-8000-d0c5000000b8" } },
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.undoDropHandover(
path: .init(orderId: "b80cf509-0000-4000-8000-d0c5000000b8"),
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066"
))
).ok.body.json
print(response){
"order": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"order_type": "<order_type>",
"fulfilment_status": "<fulfilment_status>",
"collection_code": "EXAMPLE10",
"payment_method": "pay_now",
"order_page_path": "<order_page_path>",
"payment_due_at": "<payment_due_at>",
"collection_mode": "<collection_mode>",
"requested_collection_at": "<requested_collection_at>",
"accepted_at": "<accepted_at>",
"preparing_at": "<preparing_at>",
"ready_at": "<ready_at>",
"collected_at": "<collected_at>",
"customer_name": "Alex Example",
"customer_email": "alex@example.com",
"customer_phone": "+44 7700 900123"
},
"outcome": "undone",
"back_to": "<back_to>",
"cash_undone": true,
"amount_pence": 1500
}For an order that is ready before its booked slot, moves it onto an earlier open slot. Atomic and first-come-first-served: a full slot returns SLOT_JUST_FILLED (409) and the order keeps its original time; a slot that is not actually earlier returns NOT_EARLIER (400). Idempotent on the same slot.
Store that owns the order (also part of the ownership check).
The customer’s own order to move earlier.
The earlier, open collection slot to move onto.
The slot the order now holds.
The new collection instant (ISO 8601, UTC).
Places still free in the new slot, or null when it is uncapped.
curl -X POST "https://www.membber.com/api/v1/orders/collect-earlier" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"order_id": "4991ffac-0000-4000-8000-d0c500000049",
"slot_id": "820a5efc-0000-4000-8000-d0c500000082"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/orders/collect-earlier", {
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
order_id: "4991ffac-0000-4000-8000-d0c500000049",
slot_id: "820a5efc-0000-4000-8000-d0c500000082"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.collectEarlier(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
orderId: "4991ffac-0000-4000-8000-d0c500000049",
slotId: "820a5efc-0000-4000-8000-d0c500000082"
))
).ok.body.json
print(response){
"to_slot_id": "098d6938-0000-4000-8000-d0c500000009",
"requested_collection_at": "<requested_collection_at>",
"orders_remaining": -9007199254740991
}Resolves the order by PaymentIntent id, verifies ownership + the PI on the platform account, then runs the idempotent reconcile (mark paid + ledger + loyalty award). Safe alongside the Stripe webhook.
The PaymentIntent the sheet reported as succeeded.
Collect children of a mixed order (empty/absent for a normal order).
curl -X POST "https://www.membber.com/api/v1/orders/confirm" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"store_id": "6659c139-0000-4000-8000-d0c500000066",
"payment_intent_id": "0b6642a5-0000-4000-8000-d0c50000000b"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.confirmOrder({
body: {
store_id: "6659c139-0000-4000-8000-d0c500000066",
payment_intent_id: "0b6642a5-0000-4000-8000-d0c50000000b"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.confirmOrder(
body: .json(.init(
storeId: "6659c139-0000-4000-8000-d0c500000066",
paymentIntentId: "0b6642a5-0000-4000-8000-d0c50000000b"
))
).ok.body.json
print(response){
"order_id": "4991ffac-0000-4000-8000-d0c500000049",
"order_number": "<order_number>",
"status": "<status>",
"total_pence": 1500,
"currency": "GBP",
"subtotal_pence": 1500,
"discount_pence": 1500,
"promo_discount_pence": 1500,
"voucher_discount_pence": 1500,
"applied_voucher_id": "e79b214e-0000-4000-8000-d0c5000000e7",
"applied_promo_code": "EXAMPLE10",
"applied_promo_label": "<applied_promo_label>",
"payment_intent_id": "0b6642a5-0000-4000-8000-d0c50000000b",
"order_type": "<order_type>",
"collection_code": "EXAMPLE10",
"fulfilment_status": "<fulfilment_status>",
"prep_minutes": 1,
"collection_mode": "<collection_mode>",
"requested_collection_at": "<requested_collection_at>",
"loyalty_awarded": true,
"stamps_added": 1,
"stamp_count": 1,
"goal": 1,
"voucher_minted": true,
"ask_marketing_opt_in": true,
"reward_title": "<reward_title>",
"is_first_order": true,
"reward_unlock_message": "Added at the front desk",
"welcome_message": "Added at the front desk",
"children": [
{
"order_id": "4991ffac-0000-4000-8000-d0c500000049",
"order_number": "<order_number>",
"order_type": "<order_type>",
"collection_code": "EXAMPLE10",
"fulfilment_status": "<fulfilment_status>",
"prep_minutes": 1,
"requested_collection_at": "<requested_collection_at>",
"total_pence": 1500
}
]
}