Production is locked until a market agreement is recorded

Orders end to end

The order machine, the three legs that drive it, addresses that a courier can actually find, and cash with change.

Commerce12 min

An order is one aggregate with three legs: the order itself, the payment and the fulfilment. Your integration almost never needs the legs — the phase on every event is already the answer to "what do I tell the person waiting".

Creating an order

A create takes a market, a merchant, lines with item ids and quantities, a place and a payment method. Prices are not sent: the store owns them, and sending one that disagrees answers 409 rather than quietly charging the wrong amount.

POST /v1/orders · Bissau, mobile money
curl https://api.palopgo.com/v1/orders \  -H "Authorization: Bearer $PALOPGO_KEY" \  -H "Idempotency-Key: cmd_office_lunch_2026w41" \  -d '{    "market": "gw-oxb",    "merchant_id": "m-gw-001",    "currency": "XOF",    "fulfilment": "delivery",    "lines": [{ "item_id": "it-gw-001-01", "qty": 40, "options": [{ "group_id": "proteina", "option_id": "frango" }] }],    "place": {      "bairro": "Bandim",      "landmark": "Edifício do banco, segundo portão depois do mercado",      "phone": "+245955221104"    },    "payment_method": "orange_money",    "reference": "PO-2026-0041"  }'

Fulfilment kinds

Fulfilment kinds and what they skip
fulfilmentWhat happens
deliveryA courier is offered the job, collects at the store and proves delivery with an OTP or a photo.
pickupNo courier events at all. The order goes READY and waits; the customer collects.
at_customerA service performed at the address — a hairdresser, a sound system. No pickup leg.
ticketNothing physical moves. Tickets are issued against the event's tiers.

The state machine

States are coarse on purpose. A new state is a new customer-facing sentence, not a new internal step, which is why there are twenty of them and not two hundred.

Order states and the phase they present as
statephaseWhat it means
CREATEDPAYMENT_PENDINGAccepted and queued. Nothing has been charged.
CONFIRMEDCONFIRMEDPayment confirmed, or cash promised. Waiting for the store.
ACCEPTEDACCEPTEDThe store said yes and gave a ready time.
PREPARINGPREPARINGIn the kitchen.
READYREADYWaiting for the courier, or for collection.
NO_COURIERNO_COURIERNobody took the offer inside the lease. Re-offered automatically.
COURIER_ASSIGNEDCOURIER_ASSIGNEDA courier accepted and is heading to the store.
PICKED_UPPICKED_UPCollected.
IN_TRANSITIN_TRANSITOn the way.
ARRIVINGARRIVINGInside the arrival geofence. A hint, never proof.
AT_DROPOFFAT_DROPOFFAt the door, asking for the customer's code.
DELIVEREDDELIVEREDProof accepted. Cash, if any, is collected.
DELIVERY_FAILEDDELIVERY_FAILEDNobody at the address. The order returns to the store.
CANCELLEDCANCELLEDCancelled with a reason code and a refund amount.

Addresses a courier can find

In Luanda, Maputo, Bissau and São Tomé the street is not the address. The pin, the bairro, the landmark and a phone number are, in that order of usefulness. Praia is mixed, and only Lisboa is street-first.

place · the shape that works everywhere
{  "pin": { "lat": -8.9192, "lng": 13.1844 },  "bairro": "Talatona",  "landmark": "Condomínio Jardim de Rosas, portão 3, depois da bomba Sonangol",  "phone": "+244923456789",  "access_notes": "Dizer ao guarda que é para a casa 14"}

Cash, change and the float

Cash is the default rail in most of the platform, not a fallback. A cash order carries what the customer will hand over and what the courier must give back, and the dispatcher only offers the job to a courier whose float can cover it.

cash on an order
{  "payment": { "method": "cash", "state": "PROMISED" },  "cash": {    "amount_minor": 1580000,    "change_for_minor": 2000000,    "change_minor": 420000,    "courier_float_ok": true  }}
  • Nothing is charged before delivery; payment.confirmed fires when the courier marks the cash collected.
  • change_for_minor is the note the customer says they will pay with. Exact change is change_for_minor equal to amount_minor.
  • If no nearby courier can break the note, courier_float_ok is false and the order is still accepted — you decide whether to ask for exact change or switch rail.

Cancelling and refunding

A cancel always carries a reason code, and the answer tells you what will be refunded and when. Cancelling after the store has accepted may be refused, in which case the order continues and you can offer the customer a refund instead.

POST /v1/orders/{id}/cancel
curl https://api.palopgo.com/v1/orders/ord-2026-pt-1079/cancel \  -H "Authorization: Bearer $PALOPGO_KEY" \  -H "Idempotency-Key: cmd_cancel_1079" \  -d '{ "reason_code": "customer_changed_mind" }'

Keep going