backend phase 10

This commit is contained in:
hamid
2026-07-06 21:17:00 +03:30
parent 12c7e51c32
commit aae056b4e5
70 changed files with 8124 additions and 73 deletions
+36
View File
@@ -287,6 +287,42 @@ Load-bearing rules:
real card capture replaces it by calling `ConvertRequestToBooking` directly on a `succeeded` transaction. The no-show
sweep (`DetectNoShowSessions`) is admin/test-triggered; its recurring cron is DEFERRED (like b8's expiry sweep).
**Payments core — ledger, transactions, webhooks & card capture (backend-phase-10).** A new **`payments`
schema** holds the money core: `PaymentGateways` (config per PSP; **encrypted `config_json`**;
selection by `type`+`priority`), `PaymentTransactions` (every attempt; the **two filtered uniques** —
`UNIQUE(gateway_reference_code) WHERE NOT NULL` and `UNIQUE(booking_id) WHERE status='succeeded'` — are the
anti-double-capture backstop), `PaymentWebhookEvents` (the idempotency store; **`UNIQUE(provider_code,
external_event_id)`**), and the **append-only** `LedgerEntries` (double-entry source of truth). Entities in
`Domain/Entities/Payments/` (+ `LedgerPosting` balanced-group builder, `LedgerAccountType`/`PaymentTransactionStatus`/
`WebhookProcessingStatus`/`PaymentGatewayType` code sets); configs in `Persistence/Configuration/PaymentsConfig/`;
one migration (`PaymentsCoreLedger`). Features under `Baya.Application/Features/Payments/{Commands|Queries}/`
(`InitiatePayment`, `HandlePaymentWebhook`, `ConfirmPaymentAndPostLedger`, `GetNursePayableBalance`);
`IPaymentRepository` on `IUnitOfWork`; controllers `PaymentsController` (`POST bookings/{id}/payments`),
`WebhooksController` (public `POST webhooks/payments/{provider}`), `NursePayableBalanceController`
(`GET nurses/{id}/payable_balance`). Load-bearing rules:
- **A `bookings` row exists only on capture (b9).** So a payment is initiated against the
`accepted_awaiting_payment` **request**; `payment_transactions.booking_id` is **nullable**, bound only when
the confirm creates/loads the booking. Confirm reuses b9 via the extracted **`BookingFactory`** (shared
conversion/amount logic) rather than re-implementing it — the mock `IPaymentCaptureSimulator` Convert path
stays for b9's own tests.
- **Idempotency ordering:** `HandlePaymentWebhook` **upserts the webhook event first** on `(provider,
external_event_id)` and **no-ops on a duplicate**; on a new success event it **re-verifies server-side**
(`IPaymentProvider.VerifyAsync`) then dispatches `ConfirmPaymentAndPostLedger`, all under
`IDistributedLock(booking-request:{id}:payment)`. A unique-violation on confirm is treated as an
**idempotent no-op success**, not an error.
- **The card-capture group is balanced:** `LedgerPosting.CardCapture` posts DEBIT `escrow_held` gross =
CREDIT `platform_revenue` commission + `nurse_payable` payout under one `transaction_group_id`
(Σdebit = Σcredit; throws if the three frozen amounts don't reconcile). `ledger_entries` is **append-only**
(implements `IEntity` only — no `ITimeModification`, so the audit interceptor never stamps it; no soft-delete).
- **Escrow IS the ledger.** `GetNursePayableBalance` is the **signed sum** over `nurse_payable` legs — never a
stored column. The lawful split is **تسهیم via `ISettlementSplitProvider`** to registered IBANs (the platform
never moves money).
- **Four money-path seams** in `Application/Contracts/Payments/` — `IPaymentProvider`,
`ISettlementSplitProvider`, `IWebhookVerifier`, `IDistributedLock` — with faithful mocks in
`CrossCutting/Seams/` (`MockPaymentProvider`, `MockSettlementSplitProvider`, `MockWebhookVerifier`,
`InProcessDistributedLock`), registered by `AddCrossCuttingSeams`. `payment_gateways.config_json` is
encrypted through the b0 `IFieldEncryptor` (converter wired in `ApplicationDbContext`).
**Keeping the Project map current.** When a change touches the architecture — adds, removes, or
renames a project/assembly, a Clean-Architecture layer, or a major folder, or changes a cross-layer
dependency — you **must** update this Project map (and the dependency rule above, if affected) in the