{
"customer": "3d8ec956-de2a-4d43-9c1e-74475b153362", // required — customer id
"card": "41dd8263-288e-4b55-bf2e-6a82995a0441", // required — card id (not a campaign id)
"user": "usr-ext-1042", // opaque id — defaults to the API user
"branch": "brn-ext-07", // opaque id — defaults to the API user's branch
"items": [
{
"id": "4221450", // product opaque id
"name": "Americano",
"quantity": 4,
"amount": 140, // line unit amount — not payment math
"productPrice": 150, // list price — menu sync only
"categories": [
{ "id": "cat-31", "name": "Hot Drinks" } // category opaque id
]
}
],
"amount": 550, // register total: discounts deducted, points not
"orderId": "ORD-2026-000123", // idempotency key
"redeem": {
"coupons": [
{ "id": "906550fd-…", "productOpaqueId": "4221450" } // coupon to redeem (+ the product it was used for)
],
"points": 120 // points to spend
}
}
{
"transactionId": "7f1b6c20-…", // use it to roll the transaction back
"message": "You collected 4 stamps, earned a Free Coffee coupon. Amount to pay: 538",
"executedActions": [
{
"actionName": "collect-stamps",
"transactionCategory": "collect-stamps",
"netCollected": 4,
"previousCollectableValue": 3,
"result": { "value": 2, "total": 12 },
"rewards": ["2f19b6ba-…"],
"milestones": []
}
],
"executedPromotions": [
{
"promotion": "b34466d6-…",
"name": "Weekend double coupon",
"actionType": "issue_coupon",
"coupons": [{ "_id": "5d8a1c2e-…", "coupon": "9a417359-…", "code": "WKND-4821" }]
}
],
"amount": 550,
"discountAmount": 0,
"payableAmount": 538,
"redeemed": {
"couponIds": ["906550fd-…"],
"autoAppliedCouponIds": [],
"points": 120,
"pointsValue": 12
}
}
Semantics
| Concept | Meaning |
|---|---|
| One payload = one basket | Construct and submit one transaction per customer interaction. |
card drives the campaigns | The campaigns that run are the ones attached to the selected card (see find → cards[].campaigns). |
| Opaque ids | items[].id, items[].categories[].id, user, branch, and orderId are your external identifiers; BrandWallet resolves them internally. |
amount is the money truth | The register total with your own discounts already deducted; BrandWallet-applied deductions (promotion discounts, points) not deducted. Those are applied server-side (discountAmount, redeemed.pointsValue); charge payableAmount. |
items[].amount vs productPrice | amount is the line unit amount (campaign condition input); productPrice is the product’s list price, used only by menu sync. Neither enters payment math. |
redeem spends | redeem.coupons are redeemed (a product coupon can carry productOpaqueId — recorded, and its quantity is excluded from earning when the line is in items[]) and redeem.points are deducted from the card’s balance; fails with insufficient_collectable_balance when points exceed the balance. |
orderId deduplicates | A completed transaction with the same orderId is rejected with duplicate_transaction and no side effects. |
executedActions is the outcome | One entry per campaign action that ran: what was collected (netCollected), the balance before (previousCollectableValue) and after (result), any granted coupon ids (rewards), and triggered milestones. |
executedPromotions lists applied promotions | One entry per promotion applied on top of the campaign (omitted when none), e.g. an issue_coupon promotion with the coupons it granted, or an apply_discount promotion with the order discount it applied (deducted from payableAmount, totaled in discountAmount). |
transactionId identifies the transaction | Returned by process (not by validate), used to look it up, list it, and roll it back. |

