Files
baya-monorepo/product/payments-and-installments.md
T
2026-06-21 00:05:07 +03:30

38 KiB
Raw Blame History

Balinyaar — Payments, Escrow, Settlement & Installments (BNPL)

Purpose. This is the fintech deep-dive for Balinyaar, an MVP home-nursing marketplace in Iran. It pins down how the platform collects money from families, holds an internal escrow ledger state (not custodied cash), pays nurses weekly minus commission, and integrates Iranian Buy-Now-Pay-Later (خرید اقساطی / BNPL). It answers the two questions the team cares most about — how a BNPL booking is cancelled/refunded mid-plan, and who pays the nurse (and when) under BNPL — and it grounds every decision in verified research, separating VERIFIED facts from CONFIGURABLE / UNCERTAIN items that must be confirmed at contracting. All money is IRR (Rials), stored as BIGINT; Toman is display-only and converted only at the provider API boundary.

Date: 2026-06-20


1. Executive summary

  • Balinyaar legally CANNOT custody buyer funds. In Iran money always flows card → licensed PSP → Shaparak settlement → bank-registered IBANs. A پرداخت‌یار (payment facilitator — the license class an MVP rides on) is explicitly barred from holding deposits, running wallets, paying interest, or moving money between merchants. "Platform holds escrow" must therefore be implemented as an internal double-entry ledger state over funds custodied at a licensed provider — never as cash in a Balinyaar bank account. (VERIFIED — multiple independent sources + CBI/Shaparak directives.)

  • The compliant marketplace primitive is تسهیم (settlement-sharing): one incoming card payment is split by the provider/Shaparak across multiple registered IBANs (the nurse's share, the platform's commission) and deposited directly — the platform never touches the split. Shaparak has separately banned inter-merchant / inter-facilitator transfers and wallet-style holding, which makes a "delay-then-redistribute" pool legally grey-to-prohibited. (VERIFIED.)

  • The decisive BNPL finding: full-upfront settlement. Provider-financed Iranian BNPLs (SnappPay, Digipay, Tara, Torob Pay, ZarinPlus) pay the merchant the FULL amount in ONE lump, minus a merchant commission, and bear 100% of customer-default risk. The customer's installments are owned entirely by the provider and are decoupled from Balinyaar's escrow/EVV/payout cycle. (VERIFIED for SnappPay and Torob Pay from credible sources; consistent for Digipay and Tara.)

  • Consequence — a BNPL order = a card payment landing net-of-fee. Therefore Balinyaar does NOT track customer installments (no installment_entries, no per-installment webhooks, no default propagation). This deletes an entire fragile subsystem.

  • But settlement TIMING is not instant. The "full amount" is true in amount, not timing: cadence is contract-defined (daily / T+13 / weekly / 15-day), and at least one authoritative SnappPay source gates settlement on the customer's first installment (پس از واریز اولین قسط). Model a per-transaction settled_at; never assume instant. (VERIFIED correction to the original research.)

  • Under BNPL the nurse is paid by Balinyaar, on Balinyaar's own weekly schedule, exactly as for a card booking — after EVV check-out and the dispute window. The nurse's payout is computed from gross_price_irr balinyaar_commission_irr, NOT from the BNPL-net amount. The BNPL commission is a platform expense that must never touch the nurse's payout.

  • Avoid Lendo for the MVP. It is bank-financed (Bank Ayandeh): the customer pays ~1823% interest plus a ~5% (often non-refundable) service fee over 612 months — a POS loan, a poor fit for short, cancellable nursing visits.

  • Two corrections the research forced: VAT in Iran is 10% (rose from 9% in 1403, = govt 7% + municipal 3%), not 9% — and make it configurable since it has moved two years running. And the Digipay "24h-pay / collect-in-525-days-from-business" sentence describes the early-settlement/factoring product, not BNPL — right conclusion, wrong evidence; the correct first-party BNPL source is mydigipay.com/bpg/.

  • Provider continuity is a real risk. In Nov 2024 the CBI abruptly cut Toman and Jibit's settlement/withdrawal services with no stated cause, stranding businesses (including millions of Snapp drivers). Design for multi-provider failover and a reconciliation ledger that survives a provider being cut mid-cycle.

  • Recommendation: integrate SnappPay first, Digipay second, avoid Lendo. Onboarding the Balinyaar entity needs both جواز کسب AND eNamad (اینماد). Whether a multi-vendor marketplace re-disbursing to many independent nurses qualifies as a single merchant is publicly UNCONFIRMED — confirm with provider sales before relying on it.


2. The Iranian payment reality

2.1 The rails: card → PSP → Shaparak → registered IBANs

Every card payment in Iran is acquired by a licensed PSP and cleared through Shaparak (the national switch), which settles to bank-registered IBANs (شِبا) of the merchant/beneficiaries. There is no native marketplace-escrow construct the way a US/EU platform would hold buyer cash in trust. The platform does not — and legally may not — sit in the money path as a custodian.

2.2 The license class: پرداخت‌یار (payment facilitator) and the custody prohibition

An MVP marketplace like Balinyaar rides on a پرداخت‌یار (payment facilitator / aggregator) arrangement under a contracted PSP and the CBI/Shaparak agreement. A facilitator is explicitly forbidden from:

  • holding customer deposits,
  • operating wallets,
  • paying interest,
  • granting credit / guarantees,
  • temporarily using merchant balances.

Settlement must go only to merchant-registered bank accounts, and only Shaparak can withdraw from the special facilitator settlement account (حساب ویژه پرداخت‌یاری). Unauthorized fund-holding draws penalties, license suspension, and AML exposure. This is the single load-bearing constraint of the whole design: Balinyaar cannot be the custodian of buyer funds. (VERIFIED — way2pay, Zibal legal blog, finolaw, peivast.)

2.3 تسهیم (settlement-sharing) — the compliant marketplace primitive

The legitimate way to pay many providers is تسهیم / تسویه اشتراکی (settlement-sharing): a single incoming card payment is split across multiple registered IBANs (the nurse's net share + the platform's commission) and credited directly by Shaparak/the provider to each party. The platform never touches the actual split. ZarinPal markets this for marketplaces (بازارگاه) with split-by-ratio to each partner's registered Sheba; Zibal, Sadad, SizPay, Vandar, Jibit, Zibal, PayPing, IDPay offer variants. (VERIFIED.)

Caveat (CONFIGURABLE): ZarinPal's تسهیم appears gated to a "golden" (طلایی) membership tier; all timing/minimum/limit numbers (e.g. ~100,000 IRR minimum, processing windows, "no beneficiary limit") came from single doc reads and must be reconfirmed at contracting.

2.4 The banned move: inter-merchant / inter-facilitator transfers and held pools

A tempting design — "collect into a platform pool, hold until EVV check-out, then redistribute" — is regulatory grey-to-prohibited. Shaparak explicitly banned inter-facilitator and inter-merchant fund transfers and wallet-style holding. A delayed but pre-fixed split to the same registered IBANs may be tolerable; moving/holding funds in a platform-controlled pool to release conditionally is the banned behavior. The only clean hold/release/refund mechanism is a bank-grade escrow product (e.g. Vandar میندو / معاملات امن — buyer pays into trust, released to seller on confirmation, refundable on seller failure), but even that is flagged as regulatorily fragile and its API-level EVV trigger is unverified. Practical conclusion: model escrow as an internal ledger over whichever provider primitive you contract; do not assume you can lawfully custody cash yourself. (VERIFIED ban; the "delay-then-hold" pattern is the OVERSTATED part of the original research.)

2.5 Provider cut-off continuity risk (Toman / Jibit, Nov 2024)

In November 2024 the CBI abruptly cut Toman's and Jibit's settlement/withdrawal services with no stated cause, disrupting businesses including millions of Snapp drivers who could not settle. Wallet/balance facilitator models have been blocked and re-permitted before (Vandar's gateway was blocked then unblocked by Shaparak). Design for multi-provider failover and a reconciliation ledger that survives a provider being cut off mid-cycle. (VERIFIED — zoomit, way2pay, ecoiran.)

2.6 Tax: سامانه مودیان and VAT = 10%

  • Iran's سامانه مودیان (taxpayer / e-invoicing system) requires electronic invoices (صورتحساب الکترونیکی) with a 22-digit number, a memory tax-id (شناسه یکتای حافظه مالیاتی), and a digital signature. The SELLER issues the invoice and remits VAT; the buyer cannot. For a marketplace this maps cleanly: each nurse is the taxable seller of the nursing service; Balinyaar's taxable supply is ONLY its commission (the Snapp/Tapsi precedent). (VERIFIED.)
  • VAT is 10%, not 9%: the standard rate rose from 9% to 10% in 1403 (govt 7% + municipal 3%) and remains 10% in 1404. Any VAT field hardcoded at 9% is stale — use 10% and make it a configurable parameter, since it has changed two years running. (VERIFIED correction.)
  • مودیان enrollment is phased in by revenue threshold (individuals with sales above ~144bn IRR through end of 1404 must issue e-invoices from Tir 1405). Whether the home-nursing service itself is VAT-exempt (medical exemption) is UNCERTAIN — do not assume; model a config-driven VAT rate that can be 0.

3. Escrow as an internal ledger, not held cash

Because Balinyaar cannot custody buyer funds (§2.2), "escrow" must be a software construct: a double-entry ledger STATE over money that legally sits at a licensed provider/bank. The original ~45-table model had no ledger — escrow was only inferable by joining bookings.status, bookings.payout_released, payment_transactions.status, and refunds, with no single answer to "how much do we owe nurses right now?" Three independent critiques rated this a critical gap. The fix is one append-only table.

3.1 ledger_entries — the financial source of truth

Append-only, never updated or deleted. Every money event posts balanced rows sharing a transaction_group_id (Σ debit = Σ credit). Per-nurse balances are derived by filter, never cached in a drifting wallet-balance column.

Account types:

account_type Meaning
escrow_held Funds received and held (over provider custody) not yet released or refunded
platform_revenue Balinyaar's own commission income
nurse_payable What the platform owes the nurse (accrued, awaiting weekly payout)
refund_payable Amount owed back to the customer / in-flight reversal
bnpl_fee_expense The BNPL provider's merchant commission — a platform expense
nurse_clawback_receivable Money a nurse owes back after a refund-after-payout

3.2 The postings

Amounts are positive; direction carries the sign. The three-amount split (gross_price_irr, balinyaar_commission_irr, bnpl_commission_irr) is defined in §7.

(a) Card payment capture (inbound):

DEBIT  escrow_held         gross_price_irr
  CREDIT platform_revenue    balinyaar_commission_irr
  CREDIT nurse_payable       nurse_payout_amount        (= gross  balinyaar_commission)

(b) BNPL settle (inbound) — identical to a card capture, plus the provider-fee leg:

DEBIT  escrow_held         gross_price_irr
  CREDIT platform_revenue    balinyaar_commission_irr
  CREDIT nurse_payable       nurse_payout_amount
DEBIT  bnpl_fee_expense    bnpl_commission_irr
  CREDIT escrow_held         bnpl_commission_irr        (escrow reflects NET cash actually received)

Posted once, idempotently, keyed on the settling transaction. No installment-level postings — the customer's repayment schedule is SnappPay's ledger, not ours.

(c) Refund — BEFORE the nurse is paid out (clean reversal):

DEBIT  platform_revenue    platform_fee_refunded_irr
DEBIT  nurse_payable       nurse_payout_refunded_irr
  CREDIT refund_payable      (sum)

Clear refund_payable when the PSP / SnappPay confirms the customer cash-back. Nothing leaves Balinyaar toward the nurse — the nurse_payable accrual is simply reversed.

(d) Clawback — refund AFTER the nurse was already paid: The nurse's nurse_payable was already drained by a processed payout batch, so there is nothing left to reverse. Instead the platform books a receivable:

DEBIT  nurse_clawback_receivable   amount_irr   (nurse_id set; nurse now owes the platform)
  CREDIT refund_payable               amount_irr

Recovered by netting against the nurse's next nurse_payable at batch time, or marked written_off if uncollectable. A nurse_clawbacks row carries the lifecycle (pending / recovered / written_off). This is unavoidable because Iranian payouts are real bank transfers — hard/impossible to reverse — so the right defense is gating payout on the dispute window, with clawback as the fallback.

3.3 Why the ledger, not more columns

A marketplace that holds escrow, pays out weekly minus commission, and handles refunds + clawbacks has exactly the shape double-entry was invented for. The MVP cost is one table + posting discipline. The alternative (more money columns on bookings/payouts) cannot answer "how much is held but unreleased" without fragile joins and makes bank/Shaparak reconciliation nearly impossible. Keep the per-booking fee snapshot as the pricing record; the ledger is the financial-truth / reconciliation layer posted alongside.


4. BNPL landscape — comparison

All six are Iranian. The structurally important fact (full-upfront-to-merchant, provider bears default risk) holds for every provider-financed option; Lendo is the outlier (bank-financed, customer pays interest).

Provider Settlement model Who bears financing cost Customer plan (installments / tenor / interest) Credit ceiling Merchant fee Service / marketplace eligibility Integration Confidence
SnappPay (اسنپ‌پی) Full-upfront, single lump minus commission; provider bears default risk Merchant (commission) 4 installments / 4 months (1 at purchase + 3 monthly); interest-free ~20M Toman (→~50M good payers); separate bank-credit product up to 100M Toman, 1224 mo, carries bank interest Undocumented (anecdotal ~715% + ~10% VAT; one merchant cited 15%); per-contract config Services incl. "بعضی خدمات پزشکی" eligible; in-person/appointment OK. Multi-vendor re-disbursement UNCONFIRMED API + IPG redirect (OAuth → eligible → token → verify → settle → revert/cancel/update/status) High (model) / Medium (fee, eligibility)
Digipay (دیجی‌پی) Full-upfront to contracted merchant; provider bears default risk Customer bears markup; merchant pays acquiring commission 1-month (interest-free) + 4-installment (~48% on installments 24) + 3/6/9/12-mo loan Varies widely by product/campaign (monthly ~130M Toman; 4-inst up to ~50200M; loan up to 2bn IRR) توافقی (negotiable), settlement-speed dependent; sells early settlement as a paid add-on Explicit services (travel, hotels, insurance, dental); single contracted merchant, no sub-merchant split UPG: ticket → redirect → callback → verify; deliver-confirm then refund. Type codes IPG=0/Wallet=11/Credit=5/BNPL=13/Credit-Card=24 High (model, API) / Uncertain (fee, mid-plan mechanics)
Tara (تارا) Provider-financed, full amount to seller Merchant (interest-free to customer) 2 interest-free installments, starting 1 month after purchase Up to 20M Toman (research's "10M / 2-month" is OUTDATED); other tiers 5M / 10M / 100M / 150M Per-contract Interest-free (بدون سود) standard tier API/gateway Medium
Torob Pay Full-upfront, cash to seller Merchant 4 equal installments, 25% down, interest-free New users ~12M Toman → 35M Concrete: 6% + VAT = 6.6% per order Third-party explainers (not first-party docs) Gateway Medium (firm fee)
ZarinPlus (ZarinPal) Provider-financed BNPL inside the ZarinPal gateway; standard ZarinPal merchant settlement ~T+1 Merchant 4 installments ~2M Toman (ID only) → 515M with history → up to 20M with cheque/promissory note ZarinPal gateway ~1% (cap ~4000 Toman) + per-txn; BNPL-specific terms not confirmed Inside ZarinPal IPG Gateway / تسهیم Medium
Lendo Bank-financed (Bank Ayandeh); merchant effectively gets full value CUSTOMER (~1823% interest + ~5% upfront service fee, often non-refundable) 6 / 9 / 12 months — a POS loan, not interest-free BNPL Bank-set POS loan AVOID for MVP

Key contrast: Torob Pay's 6.6% (6% + VAT) is the only published rate; SnappPay's true rate cannot be inferred from it and must be treated as negotiated per-contract config. Lendo's customer-borne interest + non-refundable fee make mid-engagement cancellations leave the customer out of pocket — a poor fit for short, cancellable nursing visits.


5. The decisive finding: full-upfront settlement

The provider pays the merchant the full amount minus commission in ONE lump and bears default risk. The customer's installments are owned by the provider and are decoupled from Balinyaar's escrow/payout. (VERIFIED — SnappPay CEO: "ارایه‌دهنده سرویس تمام پول پذیرنده را پرداخت کرده و هیچ ریسکی سمت پذیرنده نیست"; Digipay first-party bpg page: "مبلغ حاصل از فروش را یک‌جا دریافت کنید … نگران ریسک عدم بازپرداخت اقساط نباشید". No source described any tranched-to-merchant model among provider-financed BNPLs.)

Therefore, in Balinyaar's books, a BNPL order is identical to a card payment that lands net-of-fee in one inbound settlement. We DO NOT model customer-installment tracking (installment_entries, per-installment webhooks, default propagation). This removes the fragile subsystem the original model flagged as unresolved.

Verified caveats that must remain CONFIGURABLE / UNCERTAIN

  • Settlement TIMING is NOT instant. Cadence is contract-defined (daily / T+13 / weekly / 15-day / occasionally longer — one merchant alleged ~4 months), and the most concrete SnappPay statement gates settlement on the customer's first installment ("پس از واریز اولین قسط"). Digipay even sells early settlement as a paid feature, implying its baseline is delayed. Model a per-transaction settled_at and a per-provider settlement-lag; gate weekly nurse payouts on settlement actually received, so you never pay a nurse before you hold the cash.
  • Commission rate is per-contract config, never hardcoded. SnappPay publishes no public rate; read the actual deducted amount from each settlement record.
  • Marketplace re-disbursement eligibility is publicly UNCONFIRMED. SnappPay's and Digipay's documented model is single-merchant-receiver. Design so the provider pays Balinyaar (the merchant-of-record) one lump, and Balinyaar does the internal escrow→nurse allocation itself. Confirm any per-nurse routing directly with provider sales. Even Snapp Doctor's own home-nursing page does not advertise SnappPay installments — service eligibility is plausible, not confirmed for an in-home individual-nurse marketplace.
  • Onboarding requires BOTH جواز کسب AND eNamad (اینماد) for the Balinyaar entity (the original research omitted eNamad). For Digipay, an activity license is mandatory only for "sensitive trades" (صنوف حساس); home-healthcare may be treated as one — confirm.

6. Q1 — Cancellation / refund of a BNPL booking mid-plan

Decisive rule: money ALWAYS flows customer ↔ SnappPay ↔ Balinyaar. Never refund the customer directly, and never route a nurse→customer refund. Balinyaar initiates the reversal through SnappPay's API using the stored payment token/transaction id:

  • Full cancel/refund → revert (full amount).
  • Partial / shortened-visit → update (new amount must be strictly lower than the original settled amount) — or cancel per the provider's partial semantics.

SnappPay then, on its own ledger and asynchronously:

  1. cancels the customer's remaining UNPAID installments and credits their equivalent back to the customer's credit wallet (reusable BNPL credit — not merely "wiped"),
  2. refunds any already-PAID installment to the customer's bank account in ~710 business days.

The merchant's only role is to authorize/cancel; SnappPay owns the unwind. (VERIFIED verbatim: "اقساط پرداخت‌نشده لغو و معادل آن به موجودی حساب اعتباری شما برگشت داده می‌شود"; "مبلغ قسط پرداخت‌شده به حساب بانکی شما برگشت داده خواهد شد (۷ تا ۱۰ روز کاری)".)

Balinyaar's internal bookkeeping

  1. Record a refund row with refund_channel = 'bnpl_revert', external_revert_reference, expected_customer_refund_eta, and a refund_status that stays processing until SnappPay confirms (a reconciliation job clears it). Surface the asynchronous 710-day window in the UI and reconciliation — never assume instant.
  2. Decompose the refund across the two fee legs: platform_fee_refunded_irr and nurse_payout_refunded_irr (the booking gross = platform fee + nurse payout; the refund must say how much of each is reversed).
  3. Post balanced ledger entries (§3.2c/d): debit the decomposed platform_revenue / nurse_payable, credit refund_payable; record the revert reference on the bnpl_transactions row (reverted_amount_irr, reverted_at, refund_channel).
  4. If the nurse has NOT been paid (booking still inside the dispute window / not in a processed batch): reverse the nurse_payable accrual — clean, nothing leaves Balinyaar. (This is the common case if you gate payout on the dispute window.)
  5. If the nurse HAS been paid (refund-after-payout): take the clawback path — a nurse_clawbacks row + a nurse_clawback_receivable ledger leg (§3.2d), recovered from the next payout batch or written off.

Partial / shortened-visit maps to the update endpoint with a reduced amount: record refund_delta_irr, reduce settled_amount_irr on the bnpl_transactions row, and apply the same fee-leg decomposition.

UNCERTAIN (confirm at contracting): whether the provider returns its merchant commission on a full vs partial refund (full / pro-rata / not at all) is undocumented and directly affects platform P&L on cancellations. Model provider_commission_reversed_amount as nullable and reconcile from the provider's refund response — do not hardcode. Digipay's exact mid-installment proration mechanics are likewise undocumented and contract-dependent.


7. Q2 — Under BNPL, who pays the nurse, and when?

Balinyaar pays the nurse, on Balinyaar's own normal weekly payout schedule, after EVV check-out and after the dispute window closes — exactly the same path as a card-funded booking. SnappPay never pays the nurse and is indifferent to Balinyaar's internal split. The customer's BNPL repayment timeline is completely decoupled from the nurse payout cycle.

The crucial accounting rule — the three-amount split

The nurse's payout is computed from the booking's own price and Balinyaar's own commission, NOT from the BNPL-net amount. SnappPay's commission is a cost of accepting BNPL, borne by Balinyaar, and must never be passed through to the nurse. Store three separate amounts so the two fee deductions are never conflated:

Amount Meaning Drives
gross_price_irr What the customer is charged (booking price) The invoice; the inbound escrow_held debit
balinyaar_commission_irr Balinyaar's own cut (was platform_fee_amount) platform_revenue; the nurse payout
bnpl_commission_irr The BNPL provider's merchant discount bnpl_fee_expense (platform expense) — never the nurse
nurse_payout_amount = gross_price_irr  balinyaar_commission_irr

The nurse receives the identical amount whether the family paid by card or by SnappPay, and on the identical weekly timing (batch, gated on dispute_window_ends_at < now()). The only difference a BNPL order makes to the books is the extra bnpl_fee_expense leg that reduces Balinyaar's margin — not the nurse's pay.

Worked example (illustrative; rates are config): gross 5,000,000 IRR, Balinyaar commission 15% = 750,000, nurse payout = 4,250,000. If paid via SnappPay at a 10% merchant commission, bnpl_commission_irr = 500,000 is a Balinyaar expense; SnappPay settles 4,500,000 net to Balinyaar; the nurse still receives 4,250,000, and Balinyaar's net margin is 750,000 500,000 = 250,000 (before PSP/VAT). The nurse payout is invariant to the payment method.

Timing guard (CONFIGURABLE): because BNPL settlement can lag, optionally key weekly-payout eligibility off bnpl_transactions.settled_at (settlement actually received) in addition to EVV + dispute window, so the platform never advances a nurse before it holds the cash.


8. Integration notes

8.1 SnappPay (اسنپ‌پی) — primary

API-based with an IPG redirect. Endpoint paths are VERIFIED against the open-source Laravel package and match exactly:

POST  api/online/v1/oauth/token          → OAuth bearer token
GET   api/online/offer/v1/eligible       → eligibility / credit check on the customer
POST  api/online/payment/v1/token        → payment token  → redirect customer to SnappPay
POST  api/online/payment/v1/verify       → verify after callback
POST  api/online/payment/v1/settle       → settle (capture the merchant lump)
POST  api/online/payment/v1/revert       → full reversal
POST  api/online/payment/v1/cancel       → cancel
POST  api/online/payment/v1/update       → partial (new amount strictly lower)
GET   api/online/payment/v1/status       → status

Credentials issued only after a signed contract + business-license review: user_name, password, client_id, client_secret, merchant/customer number, security code, base_url. Sandbox availability is plausible (issued by sales) but TO BE CONFIRMED — the public package does not evidence it.

WARNING: the SnapPayInc/open-api-java-sdk GitHub repo is the unrelated CANADIAN SnapPay (snappay.ca, CAD) — do NOT use it. Likewise, English searches for "digipay split payment" return DigiPay.Guru, an unrelated white-label vendor — not the Iranian Digipay.

8.2 Digipay (دیجی‌پی) — secondary / fallback

Unified UPG gateway, server-side + hosted redirect:

POST  /digipay/api/tickets/business?type=…   → ticket + redirectUrl  (type MUST match product)
        (callback to merchant)
POST  /digipay/api/purchases/verify          → verify (re-check amount + providerId before trusting)
POST  /digipay/api/purchases/deliver?type=…  → delivery confirmation (Credit=5 / BNPL=13) — GATE ON EVV CHECK-OUT
POST  /digipay/api/refunds?type=…            → refund (providerId, amount, saleTrackingCode)
GET   /digipay/api/refunds/{InquiryId}       → poll refund status
POST  /digipay/api/reverse                   → manual reverse (~25 min, IPG/DPG only)

Type codes (VERIFIED, first-party): IPG=0, Wallet=11, Credit=5, BNPL=13, Credit-Card=24 — persist the gateway type per transaction; deliver/refund calls must carry the matching code. Each purchase supports EITHER refund OR manual reverse, not both — store a mutually-exclusive reversal-mode flag. For a service, the "delivery" is the completed visit, so gate deliver on the nurse's EVV check-out. A BNPL refund returns to the customer's Digipay credit/wallet (or bank/SHEBA), not the original card.

8.3 Cross-cutting integration rules

  • Webhook idempotency: every PSP/BNPL callback is at-least-once and retried. Upsert into payment_webhook_events keyed UNIQUE(external_event_id) first, inside the same transaction that mutates money state, and no-op on duplicate — prevents double-confirm / double-settle / double-refund.
  • Never trust the callback alone — always verify server-side and re-check amount + providerId/reference before treating funds as captured.
  • Amounts in IRR Rials as BIGINT everywhere; SnappPay/Digipay quote in Toman at the API boundary — store a currency field on the BNPL row and convert only at the boundary, never internally.
  • State-machine guard on BNPL status transitions (eligible → token_issued → verified → settled → reverted) so callbacks/retries cannot double-settle or double-refund.

9. Schema touchpoints

Final, aligned table/field names (these supersede installment_plans / installment_entries):

  • bnpl_transactions (new, replaces installment_plans; installment_entries CUT) — 1:1 with a payment_transaction. Fields: payment_transaction_id FK UNIQUE, provider_code, merchant_of_record, external_payment_token, external_transaction_id, eligibility_status, order_amount_irr, settled_amount_irr (net of provider commission), bnpl_commission_irr, currency (IRR/TOMAN), status (eligible/token_issued/verified/settled/reverted/cancelled/failed), installment_count (default 4, informational only), settled_at, revert_transaction_id, reverted_amount_irr, reverted_at, refund_channel, callback_payload_json.
  • payment_transactions — keep full gateway response + Shaparak reference; ADD a filtered UNIQUE(gateway_reference_code) WHERE NOT NULL and a filtered UNIQUE(booking_id) WHERE status='succeeded' (single capture per booking; idempotent retries).
  • payment_webhook_events (new) — provider_code, event_type, external_event_id UNIQUE, payload_json, signature_valid, processing_status (received/processed/failed/ignored), related_payment_transaction_id NULL, received_at, processed_at.
  • refunds1:N per payment_transaction (the original "1:1" claim is wrong); ADD platform_fee_refunded_irr, nurse_payout_refunded_irr (fee-leg decomposition), refund_channel (psp_card/bnpl_revert/manual_bank), external_revert_reference, expected_customer_refund_eta; app invariant Σ refunded ≤ captured.
  • ledger_entries (new) — transaction_group_id, account_type (escrow_held/platform_revenue/nurse_payable/refund_payable/bnpl_fee_expense/nurse_clawback_receivable), nurse_id NULL, direction, amount_irr, booking_id NULL, source_ref_type, source_ref_id, memo, created_at. Append-only; balanced per group.
  • nurse_clawbacks (new) — nurse_id, booking_id, refund_id, amount_irr, status (pending/recovered/written_off), recovered_in_payout_id NULL, created_at, resolved_at.
  • payment_gateways — encrypted provider config in config_json / secrets: SnappPay client_id, client_secret/username+password, merchant number, security code, base_url, sandbox flag. Never store credentials per-transaction.

Supporting changes: bookings gets the three-way split (gross_price_irr, balinyaar_commission_irr, nurse_payout_amount) and dispute_window_ends_at; payout_released BIT is CUT (derive from nurse_payout_booking_links + ledger). nurse_payouts gets gross_earnings_irr, clawback_applied_irr, net_amount_irr. An invoices table (minimal) captures the commission VAT line.


10. Recommendations & open questions to confirm at contracting

Recommendations

  1. Integrate SnappPay first, Digipay second, avoid Lendo. SnappPay has the largest reach, explicit service-merchant support, true full-upfront settlement, full default-risk transfer, and a coded API. Digipay is the redundancy/fallback with the broadest healthcare/service coverage. Lendo's customer-borne interest + non-refundable fee is wrong for short, cancellable visits.
  2. Treat a BNPL order as one net inbound settlement identical to a card payment net-of-fee. Do not build customer-installment tracking.
  3. Make escrow an internal double-entry ledger over funds custodied at a single licensed provider; abstract the provider behind config so it can be swapped if blocked (Toman/Jibit precedent).
  4. Pay the nurse from gross balinyaar_commission, weekly, after EVV + dispute window — identical for card and BNPL; the BNPL commission is a platform expense only.
  5. Gate payout on the dispute window (default 72h) rather than relying on clawback — Iranian bank transfers are effectively irreversible; keep clawback as the modeled fallback.
  6. Build webhook idempotency before touching real money, and store all amounts in IRR BIGINT, converting from Toman only at the API boundary.
  7. Use 10% VAT, configurable. Treat each nurse as the taxable seller; invoice only Balinyaar's commission.

Open questions to confirm with provider sales / at contracting

  • Marketplace eligibility: does the provider's merchant contract permit a multi-vendor home-services marketplace that re-disburses to many independent nurses as a single merchant-of-record? (Publicly undocumented; their known model is single-receiver.)
  • Commission rate (%): the actual rate for the health/home-services category (SnappPay publishes none; ~715% is anecdotal; Torob Pay's 6.6% is not a proxy).
  • Settlement SLA / timing: daily vs T+13 vs weekly vs 15-day, and whether it is gated on the customer's first installment. Get it in writing; do not assume same-day.
  • Commission-clawback-on-refund behavior: on a full vs partial refund, does the provider return its merchant commission fully, pro-rata, or not at all?
  • Onboarding documents: confirm جواز کسب and eNamad suffice for the Balinyaar entity, and whether home-healthcare is a "sensitive trade" needing a sectoral license.
  • Sandbox credentials: request early; confirm availability (not evidenced publicly).
  • Settlement-provider (تسهیم/payout) choice for the card leg: which licensed provider (ZarinPal تسهیم / Vandar / Jibit), its fee schedule, batch caps, minimums, and whether delayed settlement / a bank-grade escrow product (Vandar میندو) is permissible for the EVV-gated hold.

Sources

Iranian payment-facilitator / escrow / settlement legality

  • finolaw.net — مقررات پرداخت‌یاری (facilitator rules): https://finolaw.net/مقررات-پرداخت-یاری/
  • way2pay.ir — CBI facilitator framework: https://way2pay.ir/480525/, https://way2pay.ir/484056/
  • Zibal legal blog — internet-payment rules: https://zibal.ir/blog/قوانین-پرداخت-اینترنتی-درگاه-پرداخت-ک/
  • peivast.com — Shaparak inter-merchant/wallet ban: https://peivast.com/p/148655
  • ZarinPal تسهیم (split-payment): https://zarinpal.com/split-payment.html, https://www.zarinpal.com/blog/درگاه-پرداخت-اشتراکی-چیست؟/, https://next.zarinpal.com/paymentGateway/setshare.html
  • Vandar — facilitator / میندو escrow / Bank Ayandeh custody: https://vandar.io/blog/پرداختیاری-چیست-و-پرداختیار-کیست؟/, https://vandar.io/miando/, https://docs.vandar.io/payout_service/settlement
  • Jibit transferor / payout: https://www.jibit.io/transferor/
  • Toman/Jibit Nov-2024 cut-off: https://www.zoomit.ir/tech-iran/429145-banning-payment-services-on-toman-and-jibit/, https://way2pay.ir/389544/

Tax / مودیان / VAT (10%)

  • systemgroup.net — مودیان registration: https://www.systemgroup.net/knowledge-network/registration-in-the-tax-system/
  • hesabandish.com — taxpayer rules: https://hesabandish.com/rules-taxpayer-system/
  • sepidarsystem.com — VAT rate: https://www.sepidarsystem.com/blog/vat-rate/
  • Tapsi/Snapp commission-tax precedent: https://ip30.ir/tapsi-taxation-challenge/, https://drhesaab.ir/how-is-digital-platform-tax-calculated/

SnappPay

  • Merchant settlement (full-upfront, risk): https://limoo.host/blog/snap-pay-merchant-settlement/, https://www.portal.ir/snappay-payment-method, https://way2pay.ir/278219/
  • Product / CEO revenue model: https://see5.net/blog/what-is-snappay, https://ideaagency.net/snapppay-the-correct-revenue-model-landtechs/, https://snapppay.ir/, https://pay.snapp.ir/
  • Refund/cancel FAQ (710 business days): https://allsport.ir/faq/5/8.html, https://sourmeh.ir/common-question-about-snapppay/
  • API (Laravel package) + eligibility: https://github.com/backendprogramer/laravel-snapp-pay, https://payzito.net/docs/gateways/snapppay, https://snapppay.ir/merchant-acquisition/

Digipay

  • BNPL full-upfront (credit gateway): https://www.mydigipay.com/bpg/, https://matson.online/digipay-seller/, https://digiato.com/tech/digipay-business-solutions-pr
  • Services / merchants: https://www.mydigipay.com/credit/merchants/, https://www.mydigipay.com/credit/c-credit/, https://www.mydigipay.com/bnpl/c-bnpl/
  • UPG dev docs (type codes, deliver/refund/reverse): https://www.mydigipay.com/developers/docs/upg/
  • Onboarding: https://limoo.host/blog/signup-on-digipay/

Tara / Torob Pay / ZarinPlus / Lendo

  • Tara: https://tara360.ir/bnpl/, https://tara360.ir/, https://itresan.com/384039/
  • Torob Pay (6% + VAT): https://blupoz.com/, https://ranginstore.com/
  • ZarinPlus: https://www.zarinpal.com/blog/bnpl-زرین-پلاس/, https://www.zarinpal.com/payment-gateway
  • Lendo (bank-financed): https://lendo.ir/blog/, https://lendo.ir/

Internal

  • Existing research: c:\Users\Lenovo\Desktop\balinyaar\product\Home-Nursing-Platform-Research.md
  • Database model to refine: c:\Users\Lenovo\Desktop\balinyaar\product\database-model.md

Confidence legend. VERIFIED = survived adversarial verification against multiple/first-party sources. CONFIGURABLE = real but contract-/campaign-dependent (store as config, read actuals from provider). UNCERTAIN = plausible but unconfirmed publicly — confirm at contracting before depending on it.