backend phase 10
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user