docs: correct payment and fee semantics
This commit is contained in:
35
docs/decisions/0013-payment-lifecycle-and-fee-semantics.md
Normal file
35
docs/decisions/0013-payment-lifecycle-and-fee-semantics.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user