cleanup phases 6
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
# Handoff — after backend phase 10 (Payments core: ledger, transactions, webhooks & card capture)
|
||||
|
||||
**The money core is live.** A family can now pay the gross price by card: **initiate → PSP webhook confirms →
|
||||
the balanced card-capture ledger group posts → the booking converts/confirms**. The PSP acquirer, تسهیم split,
|
||||
webhook signature verify, and the distributed lock are **mocked behind seams**; the DB constraints are the
|
||||
authoritative money-path backstops.
|
||||
|
||||
## What f9-b10 can now build
|
||||
- **Summary & pay (C6)** — after the nurse accepts, `POST api/v1/bookings/{bookingRequestId}/payments` (as the
|
||||
customer, with an `Idempotency-Key` header) → `{ transactionId, redirectUrl, gatewayReferenceCode }`. Show
|
||||
the checkout summary (gross + commission/VAT/escrow notice) and send the payer to `redirectUrl`. **Never show
|
||||
the internal `account_type`s** — gross + the commission/VAT breakdown only.
|
||||
- **Card payment redirect + confirmation** — the (mock) redirect completes; the PSP callback hits
|
||||
`POST api/v1/webhooks/payments/{provider}` and confirms. Poll the booking (b9 `bookings/list` /
|
||||
`bookings/get`) → it appears **`confirmed`** once the webhook captures. (A booking exists only on capture.)
|
||||
- **Nurse payable balance** — `GET api/v1/nurses/{nurseId}/payable_balance` (the nurse themself or admin) →
|
||||
`{ nurseId, balanceIrr }` as a digit-string, derived from the ledger.
|
||||
|
||||
## Live endpoints / contracts
|
||||
- Contract: [`dev/contracts/domains/payments.md`](../../contracts/domains/payments.md).
|
||||
- Enums: `payment` status (`pending`/`succeeded`/`failed`), `payment_gateways.type` (`standard`/`bnpl`),
|
||||
`processing_status` (`received`/`processed`/`failed`/`ignored`), the eight `account_type`s (internal).
|
||||
|
||||
## Load-bearing rules the client must honour
|
||||
- **Money is IRR integer, on the wire as a digit-string.** Never coerce to a JS number for math.
|
||||
- **The booking is created & confirmed on capture (the webhook), not on initiate.** After `initiate` you have a
|
||||
redirect only; the `bookings` row appears when the PSP callback confirms.
|
||||
- **Payment is idempotent end-to-end** — a retried `initiate` (same `Idempotency-Key`) reuses the attempt; a
|
||||
replayed webhook is a no-op; a repeat `initiate` after capture is a `409`.
|
||||
- **Internal account types are never exposed to the customer** — checkout shows gross + commission/VAT only.
|
||||
|
||||
## Design note (reconciling b10 with b9's as-built)
|
||||
b9 creates the `bookings` row **on capture** (it never persists a standalone `pending_payment` booking). So b10
|
||||
initiates payment against the `accepted_awaiting_payment` **request**; `payment_transactions.booking_id` is
|
||||
**nullable**, bound only at confirm. Confirm reuses b9's conversion/amount logic through the extracted
|
||||
**`BookingFactory`** (no duplication) and posts the ledger + registers the split. The mock
|
||||
`IPaymentCaptureSimulator` + the `POST bookings/convert` endpoint **stay** (b9's own tests use them); the real
|
||||
capture path uses `BookingFactory` directly, not that seam.
|
||||
|
||||
## Mocked here → make real later (see reports/mocks-registry.md)
|
||||
- **`IPaymentProvider`** (🟡) — deterministic ref + fake redirect; `VerifyAsync` instant-succeeds. Real:
|
||||
ZarinPal/Sadad/Vandar/Jibit acquirer-with-تسهیم, merchant id from encrypted `config_json`, server-side verify.
|
||||
- **`ISettlementSplitProvider`** (🟡) — records split intent, returns `Settled`. Real: split-by-ratio to
|
||||
registered SHEBAs; provider credits IBANs directly.
|
||||
- **`IWebhookVerifier`** (🟡) — signature valid unless a marker; parses a test JSON body. Real: per-provider
|
||||
HMAC / mandatory server-side verify.
|
||||
- **`IDistributedLock`** (🟡) — in-process semaphore. Real: StackExchange.Redis lease, key `booking:{id}:payment`.
|
||||
|
||||
## Consumed by later backend phases
|
||||
- **b11** — refunds/clawbacks/invoices post against the ledger (`refund_payable`/`nurse_clawback_receivable`
|
||||
account types are already defined); `IPaymentProvider.RefundAsync` is ready to call.
|
||||
- **b12** — BNPL settle routes callbacks through the **same** `payment_webhook_events` idempotency store and
|
||||
posts the BNPL-settle group (adds `bnpl_fee_expense`).
|
||||
- **b13** — payouts read `GetNursePayableBalance` (the signed `nurse_payable` ledger sum) and post
|
||||
DEBIT `nurse_payable` / CREDIT `escrow_held`, gated on b9's `dispute_window_ends_at`.
|
||||
Reference in New Issue
Block a user