# ADR 0013: Model bank-paid, verified, and merchant-fee semantics - Status: Accepted - Date: 2026-08-20 ## Context The source exposes session states `Paid`, `Verified`, `Failed`, and `Reversed`, but names alone did not establish business meaning. Authoritative clarification from ZarinPal's CTO defines a two-step lifecycle: the buyer first completes payment at the bank, then the merchant calls verification. `Paid` means the bank payment succeeded and funds were deducted but merchant verification is incomplete. `Verified` means both steps completed. `Reversed` is possible but extremely rare; the dataset contains one such session. The challenge field `adjusted_fee` was transformed/name-adjusted for participant confidentiality, but the product domain treats it as the merchant's real fee. Profiling found its value constant within every session: 150,450 repeated attempt rows and zero fee conflicts. ## Decision Use these canonical populations: - bank-paid: session status in (`Paid`, `Verified`); - verified/finalized: session status `Verified`; - unverified paid: session status `Paid`; - unsuccessful current outcome: `Failed` or `Reversed` for the MVP success-rate denominator. Define payment success rate as bank-paid sessions divided by sessions in (`Paid`, `Verified`, `Failed`, `Reversed`). Keep unknown future statuses out of numerator/denominator and report their coverage until explicitly classified. Define verification completion rate as `Verified / (Paid + Verified)`. Paid amount is the session amount summed once for (`Paid`, `Verified`). Also expose verified amount and unverified-paid amount as explicit subsets. Never call `Paid` a failure. A retried session has more than one attempt. An eventually paid retry has final session status `Paid` or `Verified` and at least one lower-`try_seq` attempt whose status is neither `Paid` nor `Verified`; CSV row order is irrelevant. Rename source `adjusted_fee` to canonical `fee_amount_rial`, store it once on the session, and present it as a real Rial merchant fee. Paid fee totals sum it once for bank-paid sessions. Fee rate is `sum(paid session fee) / sum(paid session amount)` with numerator, denominator, period, and unit visible. ## Consequences - Dashboard success combines `Paid` and `Verified`, while verification health separates them. - The unverified-paid count/amount is an actionable merchant discrepancy, not a failed-payment metric. - Retry recovery may end in either `Paid` or `Verified`. - Fee aggregation uses session grain and cannot multiply fees by retry count. - Unknown statuses require a metric-catalogue change rather than being guessed into success or failure.