Order Lifecycle Guide
Audience: Engineers building order flows in a Dr Green-backed store. TL;DR: Orders pass through five conceptual stages — placed, admin-reviewed, paid, dispatched, delivered. Each stage updates one of three independent status fields. Polling is your only signal for transitions.
The three status fields
Every order has three independent statuses. They evolve in parallel, not strictly sequentially.| Field | Set by | Values | Meaning |
|---|---|---|---|
orderStatus | Dr Green internal logic | PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED | Where the order is in the fulfilment pipeline |
adminApproval | Dr Green admin team | PENDING, VERIFIED, REJECTED | Manual review outcome |
paymentStatus | Payment processor webhook (CoinRemitter / Payinn / PGPay) | PENDING, PROCESSING, COMPLETED, FAILED, REFUNDED | Did the customer pay |
adminApproval=VERIFIED while paymentStatus=PENDING (admin pre-approves, customer hasn’t paid yet) and vice versa. Always check all three before considering an order “complete.”
🔒 The complete enum lists above are partly inferred. Verified live values: orderStatus=PENDING|DELIVERED, adminApproval=PENDING|VERIFIED|REJECTED, paymentStatus=PENDING. Other values are likely supported but not yet seen in production data — defensively render unknown values as the raw string.
The lifecycle visualised
Reading the order through this lifecycle
Stage 1 — placed
Right afterPOST /dapp/orders, every status is PENDING:
invoiceNumber as the customer-facing reference (Dr Green generates it; don’t make your own).
Stage 2 — admin review
A Dr Green admin reviews each order before it can proceed. This is a manual step and can take hours to days, especially during business off-hours. After review,adminApproval flips:
| New value | What it means | Your action |
|---|---|---|
VERIFIED | Approved | Continue to payment if not already paid |
REJECTED | Not approved | Refund any payment via Dr Green; notify customer with reason if Dr Green provides one |
🔒 Reason for rejection — whether Dr Green surfaces a reason in the order detail isn’t formally documented. Defensively render: if you find a rejectionReason field, surface it; otherwise, “Your order could not be approved at this time.”
Stage 3 — payment
Payment is collected via the processor specified inpaymentMethod at order time:
CRYPTO→ CoinRemitter (BTC, ETH, USDT, etc.)FIAT→ Payinn (card / bank transfer depending on country)PGPAY→ PGPay (alternate fiat in supported regions)
🔒 Customer-facing payment UX — whether Dr Green provides a hosted checkout, sends a payment link via email, or relies on your store to render a checkout isn’t formally documented from the API surface. Confirm with Dr Green which model applies. The most common pattern is “Dr Green emails a payment link to the customer” — but verify before building.When payment completes (the processor’s webhook fires to Dr Green),
paymentStatus flips:
paymentStatus | Meaning |
|---|---|
COMPLETED | Paid; order proceeds to dispatch |
FAILED | Payment failed; customer can retry |
PROCESSING | In flight (e.g. crypto confirmations pending) |
REFUNDED | Money returned to customer |
Stage 4 — dispatch
When the order ships,orderStatus flips to SHIPPED. Dr Green handles the dispatch from their distribution partner (e.g. Takoda1 in the UK).
🔒 Tracking number visibility — whether the order detail exposes a courier tracking number or transactions[] array entries are not yet captured live. The order detail does include a transactions array which is empty in observed data; this may populate with shipping info, payment info, or both.
Stage 5 — delivered
orderStatus = DELIVERED is the terminal happy-path state. At this point, the holder’s commission for this order accrues — visible via /dapp/commissions.
How to detect transitions in your store
There are no outbound webhooks, so polling is your signal. The pattern:Per-order polling (active orders)
Notedata.orderDetails(notdatadirectly) — order detail wraps in an extraorderDetailsfield, unlike most other endpoints.
Bulk polling (all active orders across customers)
For at-scale stores, use/dapp/orders/recent as a heartbeat:
recent returns the most recent ~10 orders without pagination — ideal for catching changes across customers cheaply.
For older orders (>1 day old, not in recent anymore), poll them individually but at lower frequency (e.g. every 30 min until terminal).
Recommended polling cadence
| Order age / state | Cadence |
|---|---|
Just placed, not yet VERIFIED | Every 5 min |
VERIFIED, awaiting payment | Every 5 min |
paymentStatus=COMPLETED, awaiting SHIPPED | Every 30 min |
SHIPPED, awaiting DELIVERED | Every 60 min |
Terminal (DELIVERED, CANCELLED, adminApproval=REJECTED) | Stop polling |
Customer notifications by transition
Suggested template for what to email/notify the customer at each transition:| Transition | Notify | Tone |
|---|---|---|
| Order placed | ”We’ve received your order. Reference: <invoiceNumber>. Awaiting review.” | Confirmation |
adminApproval=VERIFIED, awaiting payment | ”Your order is approved. Please complete payment via the link sent by Dr Green.” | Action required |
paymentStatus=COMPLETED | ”Payment confirmed. Your order will ship shortly.” | Confirmation |
orderStatus=SHIPPED | ”Your order has been dispatched.” (include tracking if available) | Update |
orderStatus=DELIVERED | ”Delivered. Thank you!” | Closure |
adminApproval=REJECTED | ”We were unable to approve this order. Please contact support.” | Apology + escalation |
paymentStatus=FAILED | ”Your payment didn’t complete. You can retry via the link sent by Dr Green.” | Retry prompt |
Idempotency and retry semantics
POST /dapp/orders is not idempotent. There’s no Idempotency-Key header support. Naive retries produce duplicate orders.
The pattern that works
-
Generate a correlation ID client-side at checkout, store it in your DB before the API call:
-
POST the order:
-
For any retry, first check Dr Green’s side:
Idempotency-Key support is a reasonable backend ask.
Pricing and currency
The order detail returns prices in two currencies:totalAmount(USD) — accounting / reporting figurelocalPrice.totalAmount(customer’s local currency) — what the customer was charged
localPrice.currency + localPrice.totalAmount.
For internal reporting / accounting: use totalAmount (USD).
The exchange rate is captured at order time and frozen for that order — even if FX moves, the customer is charged the locked-in localPrice.totalAmount.
Anti-patterns to avoid
| Anti-pattern | Why bad |
|---|---|
Treating orderStatus=PENDING as “no progress” | The order is awaiting admin review — that IS progress |
| Polling every order every minute | Wastes API calls; your customers don’t need second-by-second updates |
Showing customer the USD totalAmount | They paid in their local currency; show them localPrice.totalAmount |
Auto-cancelling orders that sit in PENDING for 24h | Dr Green admin may legitimately take 24+ hours |
| Sending status notification on every poll | Only notify on transitions (use the fingerprint pattern above) |
| Creating an order, then later POSTing to “update status” | There’s no PATCH for orders; status changes are server-side only |
Trusting data directly on order-detail responses | It wraps in orderDetails — response.data.orderDetails, not response.data |
Where to next
- reference/orders.md — full endpoint detail
- reference/commissions.md — what happens after
DELIVERED - 04-errors.md § Idempotency — the no-
Idempotency-Keyworkaround - 06-webhooks.md § The polling pattern — battle-tested polling at scale