Files
zarinpal-analytics-backend/docs/decisions/0013-payment-lifecycle-and-fee-semantics.md

2.9 KiB

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 CTO data guide states that challenge adjusted_fee is uniformly scaled for commercial confidentiality and is not the actual ZarinPal tariff; relative ordering, trends, and fee share remain valid. The approved product contract nevertheless treats the canonical production field as the merchant's real fee because accepted production data will supply the real value. Profiling found the challenge value constant within every session: 150,450 repeated rows and zero 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.

Map challenge adjusted_fee into canonical fee_amount_rial for the demo and require production writers to provide the real Rial fee under that canonical contract. Store it once on the session and present the canonical product value as the real 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.