Runbook: Escrow Dispute Arbitration
1. Context & Incident Classification
| Dispute Type | Common Causes | Investigation SLA | Arbitration Authority |
|---|---|---|---|
| Non-Delivery / Hostage PIN | Vendor claims delivery; buyer denies receiving item or refuses PIN handover. | $< 12\text{ hours}$ | Tier 1 Dispute Specialist |
| Defective / Damaged Item | Item arrived broken, counterfeit, or with wrong specifications. | $< 24\text{ hours}$ | Tier 2 Support Lead |
| High-Value / Suspected Fraud | Transaction $> ₦100,000$, fake courier tracking, or off-platform coercion detected by ChatGuard. | $< 4\text{ hours}$ | Senior Arbiter + Maker-Checker Review |
2. Evidence Gathering Protocol
Before executing any fund disposition, arbiters must collect and log evidence across four independent telemetry channels:
graph TD
subgraph Evidence Ingestion Channels
CHAT[1. ChatGuard & In-App Transcripts]
PIN[2. Delivery PIN & Verification History]
LOGISTICS[3. Campus Hub / Courier Dispatch Waybill]
MEDIA[4. Buyer Unboxing Photo / Defect Evidence]
end
subgraph Forensic Synthesis
ARBITER[Dispute Arbiter Case Review]
end
CHAT --> ARBITER
PIN --> ARBITER
LOGISTICS --> ARBITER
MEDIA --> ARBITER
ARBITER --> DECISION{Decision Matrix}
DECISION -->|Vendor at Fault| REFUND[100% Buyer Refund via ReviewedWalletRefundService]
DECISION -->|Buyer Unresponsive / Fraud| RELEASE[100% Vendor Release via OrderService]
DECISION -->|Mutual Compromise| SPLIT[Split Settlement / Partial Credit]Channel 1: In-App Chat Transcript & ChatGuard Logs
Query chat history between the buyer and vendor:
SELECT
cm.id, cm.sender_id, cm.message, cm.created_at,
cs.flagged, cs.reasons
FROM public.chat_messages cm
LEFT JOIN public.chat_scan_results cs ON cs.message_id = cm.id
WHERE cm.conversation_id = '<CONVERSATION_ID>'
ORDER BY cm.created_at ASC;Look for: Attempts by either party to move off-platform, abusive language, or admission of item handover/receipt.
Channel 2: Delivery PIN Forensics
Inspect the PIN state in public.order_verification:
SELECT
ov.order_id,
ov.delivery_code,
o.status,
o.payment_status,
o.created_at,
o.updated_at
FROM public.order_verification ov
JOIN public.orders o ON o.id = ov.order_id
WHERE o.id = '<ORDER_ID>';Critical Forensic Invariant: If the vendor successfully entered the correct 4-digit PIN prior to dispute filing, the burden of proof shifts heavily to the buyer to demonstrate non-receipt (since the PIN is exclusively displayed inside the buyer's authenticated account).
Channel 3: Campus Hub Logistics Verification
- If fulfilled via Campus Hub: Contact the designated Campus Hub Representative to confirm if the package was physically logged into the hub locker and whether a student courier was dispatched.
- If fulfilled via Third-Party Courier: Verify external waybill tracking on GIG Logistics, Kwik, or Speedaf portals.
3. Arbitration Decision Matrix
| Scenario | Substantiated Evidence | Final Disposition | System Execution |
|---|---|---|---|
| Vendor Non-Fulfillment | No courier tracking after 48h; vendor uncontactable in chat. | 100% Refund to Buyer | Issue full wallet refund; apply Strike 1 to vendor. |
| Damaged / Incorrect Item | Buyer photo verified vs product listing; return case opened. | Return & Full Refund | Dispatch campus runner via AtomicReturnCaseService; refund executed upon vendor restock inspection. |
| Buyer False Claim | Vendor entered correct PIN; courier provides GPS timestamp at buyer hostel. | 100% Release to Vendor | Dismiss dispute; trigger process_order_fund_release(). |
| Partial Defect / Compromise | Item functionally intact but missing non-essential accessory. | Split Settlement | 20% partial refund to buyer wallet; 80% released to vendor. |
4. Execution via Command Services
All dispute fund adjustments are executed through audit-logged command services enforcing Maker-Checker rules:
Pathway A: Executing Buyer Refund (ReviewedWalletRefundService)
For full or partial refunds back to the buyer's wallet:
await ReviewedWalletRefundService.propose({
orderId: targetOrderId,
buyerId: targetBuyerId,
amountMinor: refundAmountMinor,
reason: "Dispute Arbitrated in Buyer Favor: Item defective (Case #" + disputeCaseId + ")",
actorId: arbiterStaffId
});Note: For refunds exceeding ₦50,000, a second senior support lead must review and execute the approval in the admin portal.
Pathway B: Executing Vendor Escrow Release (OrderService)
If the dispute is ruled in favor of the vendor:
await OrderService.forceReleaseEscrow(
targetOrderId,
arbiterStaffId,
"Dispute Arbitrated in Vendor Favor: Proof of delivery verified via campus hub receipt"
);5. Post-Arbitration Actions & Strike Enforcement
- Vendor Strike Allocation:
- If ruled against the vendor for intentional fraud, counterfeit goods, or failure to fulfill, log a formal Strike via
VendorService:
sqlUPDATE public.vendor_profiles SET strike_count = strike_count + 1, last_strike_at = now(), strike_reason = 'Dispute Case #<DISPUTE_ID>: Failed fulfillment' WHERE id = '<VENDOR_ID>'; - If ruled against the vendor for intentional fraud, counterfeit goods, or failure to fulfill, log a formal Strike via
- Buyer Fraud Monitoring:
- If a buyer exhibits a pattern of filing false disputes on verified deliveries ($> 2$ false claims), set
profiles.buying_disabled = truepending KYC re-verification.
- If a buyer exhibits a pattern of filing false disputes on verified deliveries ($> 2$ false claims), set
- Automated Notification Dispatch:
- System dispatches itemized arbitration ruling emails to both buyer and vendor with legal rationale.