One consumer purchase, two linked ION contracts — a Trade contract between the buyer app and the merchant, and a Logistics contract between the buyer app and the carrier. Joined by linkedContractId, each runs its own Beckn message exchange under the protocols ION ratifies.
Each actor shows up in exactly one lane in the sequence below. Pasar Sapa wears both buyer-app hats — Trade and Logistics — and the ION services stay quietly in the background, providing the catalogue and policy registries everyone consults.
Fourteen phases. Trade messages in rust. Logistics messages in teal. Unsolicited status pushes in amber, dashed. The off-network payment leg in muted grey, also dashed — ION never carries the money itself.
By the time Andi opens Pasar Sapa, the heavy lifting has already happened. BPPs publish to the network and BAPs keep a fresh local index — search runs against the index, not against the live network.
availability.status.Three phases turn a search hit into a Trade contract. The price carries PPN at the rate sourced from current PMK; the address carries the consumer's full Surabaya postal; the QRIS string rides over BI-FAST. ION never touches the money.
Pasar Sapa now wears its Logistics-BAP hat and calls JNE. Three
more phases — the same shape as Trade — but the
/confirm here carries
contractAttributes.linkedContractId, pointing back
at the Trade contract. That single field stitches the two halves
of the transaction together.
logistics-fwa reference (Pasar Sapa × JNE 2026 Q2). The FWA's commercial envelope supersedes per-offer policies.linkedContractId = TRADE/2026/0429/A1F2. JNE returns an AWB; Logistics contract ACTIVE.Three phases of status events stitched between two contracts. Phone numbers are proxied — direct numbers never on the wire. The delivered photo closes both contracts; Andi gets a push.
DISPATCHED with the AWB. JNE collects from the warehouse and pushes PICKED_UP with proxy phone numbers./track session for live GPS in addition to coarse status pushes.DELIVERED with a delivery-proof photo. BAP relays via /update; Trade contract → COMPLETE.Rating, then reconcile. Trade reconciles per-contract with PPN remittance and bukti potong refs. Logistics doesn't scale at per-shipment granularity, so it reconciles monthly batches with FWA rebates applied.
/on_reconcile carries PPN remittance, platform fee, and bukti potong references — everything an auditor needs.isBatch=true, applying FWA volume rebates and weight-dispute adjustments. The link is the whole point. Every Logistics contract
carries contractAttributes.linkedContractId — so when
the BAP queries either side, the network can stitch the full story
together: who bought what, who shipped it, what was charged, and
what was settled.