Connect customer quotations and payment execution to Sendico (#50)
This commit is contained in:
1 parent
5d67c25c8e
commit
7a6510d1a8
45 files changed
+2696
-45
No files matched your search
@@ -0,0 +1,162 @@
|
||||
# Customer quotations and payment execution
|
||||
|
||||
Task [SXCHNGE #50](https://git.sendico.io/tech/sxchnge/issues/50), branch
|
||||
`payments-50`, based on `identity-verification-48` / PR #49. Provider prerequisite:
|
||||
[Sendico #2896](https://git.sendico.io/tech/sendico/issues/2896).
|
||||
|
||||
## Boundary and behavior
|
||||
|
||||
The authenticated app uses `BackendWalletService` whenever backend authentication
|
||||
is configured. Explicit service injection remains available for tests; demo mode
|
||||
continues to use the isolated in-memory wallet. Missing provider configuration
|
||||
returns an unavailable wallet, never demonstration balances or fake settlements.
|
||||
|
||||
The path is Flutter DTO → mapper → service → domain model → provider → UI, then
|
||||
SXCHNGE's authenticated customer BFF → Sendico public HTTP. The existing confirmed
|
||||
identity binding resolves each signed-in customer to an independent subject.
|
||||
Neither a browser-selected organization nor an integration-login-owned balance
|
||||
is used as that customer's financial resource.
|
||||
|
||||
Consumed public endpoints, below the existing configured API prefix:
|
||||
|
||||
- `GET /payments/fx/currencies/{organization}` for accounting scales;
|
||||
- `GET /ledger_accounts/{organization}` and
|
||||
`GET /ledger_accounts/{organization}/{ledger}/balance`;
|
||||
- `POST /payments/quote/{organization}` with `external_subject_ref`;
|
||||
- `POST /payments/by-quote/{organization}`;
|
||||
- `GET /payments/by-ref/{payment}` for authoritative status.
|
||||
|
||||
The adapter requires the exact `externalSubjectRef` acknowledgement on the quote
|
||||
response. An old deployment that ignores the new request field cannot silently
|
||||
produce an acceptable integration-principal quote. Public provider DTOs and
|
||||
mappers stay inside the adapter. No Sendico modules, databases, gRPC clients or
|
||||
message bus are imported by SXCHNGE.
|
||||
|
||||
Amounts are parsed and formatted as exact decimal strings at the provider
|
||||
boundary and represented as integer minor units locally. Unknown scales, values
|
||||
beyond the browser's exact integer range, excess fractional precision, unknown
|
||||
payment states, unexecutable quotes and mismatched payment/quote references fail
|
||||
closed. Source principal, total debit and net destination credit come from the
|
||||
quote; rates account for different currency precisions. Only same-currency saved
|
||||
payout/funding methods are currently represented by the wallet review model.
|
||||
Exchange connects two dedicated currency ledgers belonging to the same customer.
|
||||
|
||||
## Provisioning and rollout
|
||||
|
||||
Merge/deploy the identity prerequisites first: SXCHNGE #47 → #49 and the Sendico
|
||||
identity-proof/limits-assignment work documented in `identity-verification.md`.
|
||||
Deploy the Sendico #2896 Product Edge and the Quotation/Orchestrator builds that
|
||||
consume the changed shared payment-storage Limits adapter together. Then deploy
|
||||
SXCHNGE #50. The Edge acknowledgement is a compatibility guard; it does not replace
|
||||
coordinated deployment of the platform's financial services.
|
||||
|
||||
Reuse `api.sendico` and its existing Vault-rendered token file. Do not put provider
|
||||
credentials or organization/source settings in Flutter. Provision the integration
|
||||
account's organization membership, payment-create/read, ledger-account/balance
|
||||
read and the permissions needed to resolve its stored payment methods. Verify
|
||||
those permissions in the intended environment before enabling real operations.
|
||||
|
||||
Through Sendico's existing operator APIs, provision one dedicated unowned
|
||||
organization ledger per customer currency, with active `asset` / `operating`
|
||||
semantics, no negative balance and metadata:
|
||||
|
||||
| Ledger metadata | Meaning |
|
||||
| --- | --- |
|
||||
| `sxchnge_subject_ref` | Confirmed independent subject reference from sign-up |
|
||||
| `sxchnge_payout_method_ref` | Optional prevalidated same-currency saved payout method |
|
||||
| `sxchnge_payout_label` | Safe customer-visible destination label, e.g. bank plus masked suffix |
|
||||
| `sxchnge_funding_method_ref` | Optional same-currency saved funding source supported by the platform route |
|
||||
|
||||
These are financial-resource bindings maintained by trusted provisioning, not
|
||||
client choices or new runtime configuration. Do not tag a pooled/integration-login
|
||||
ledger as a customer ledger. Do not place full bank/card details or secret values
|
||||
in labels or metadata. The BFF checks subject, organization, account type, role,
|
||||
status and owner on each account projection/quotation. It accepts only the bound
|
||||
saved payout method and replaces its opaque reference with the safe label in the
|
||||
reviewed quote. No arbitrary recipient identifier or free-text bank input can
|
||||
be executed from the live UI. Missing bindings have an explanatory disabled
|
||||
review action. Funding uses a quote for the provisioned stored source; it does
|
||||
not mint money or treat demo top-ups as real deposits. Additional destinations,
|
||||
recipient onboarding and other funding instructions remain separate features.
|
||||
|
||||
An empty set of provisioned ledgers is an empty wallet. Existing organization
|
||||
payments and pooled balances are never imported into customer history. This
|
||||
integration exposes the durable operations initiated through this BFF, with live
|
||||
provider reconciliation; independent inbound deposits/history ingestion and
|
||||
pagination are not added by this contribution. Ledger balances are posted ledger
|
||||
projections; execution-time solvency and holds remain the platform's authority.
|
||||
|
||||
## Authorization, idempotency and recovery
|
||||
|
||||
Local routes are under `/accounts/wallet`: snapshot GET, `/profile` GET,
|
||||
`/quote`, `/challenge`, `/resend`, `/verify`, `/submit` and `/submission` POST.
|
||||
All use the existing full authenticated-account wrapper, access token and browser
|
||||
CSRF guard. Pending-login credentials cannot access them. Local quote references
|
||||
are random BFF IDs; provider quote/payment references are not accepted as user
|
||||
ownership evidence.
|
||||
|
||||
`payment_quotes` stores customer, adapter namespace, confirmed subject, immutable
|
||||
quote, upstream quote reference, authorized execution key and eventual payment
|
||||
reference. A unique partial index enforces customer/namespace/key uniqueness.
|
||||
The existing second-factor service uses the dedicated `payment` purpose and
|
||||
exact local quote context. Successful code verification consumes its one-use
|
||||
proof in the same local Mongo transaction that records execution authorization.
|
||||
No external I/O runs in that transaction and raw factor proofs never leave the
|
||||
server. Verification response retries retain their key and recover committed
|
||||
intent. Resend uses a separate stable-per-retry issuance key.
|
||||
|
||||
Submit/reconciliation replays `sxchnge:payment:<local-quote-id>` at Sendico.
|
||||
A quote cannot be authorized under a second execution key. A network failure or
|
||||
lost provider response remains uncertain; it never permits execution with a new
|
||||
provider key. Once the provider payment reference is attached, refresh uses GET.
|
||||
Authorization is durable: reopening the wallet reconciles authorized intents even
|
||||
if the original client never received verification/submission success. Unknown
|
||||
outcomes remain pending and do not hide unrelated balances. `needs_attention`
|
||||
also stays pending; only `settled`/`failed` become terminal UI outcomes.
|
||||
|
||||
Generic upstream errors cannot prove that no payment exists. An unresolved
|
||||
pre-execution rejection (including an expired quote after durable authorization)
|
||||
therefore requires operational reconciliation rather than automatic release or a
|
||||
new payment. Inspect the local quote/provider reference and platform audit trail;
|
||||
never delete/rekey a record to bypass uncertainty. The UI's live copy does not
|
||||
promise a debit, refund or completion from mere acceptance. Only local drafts,
|
||||
not durable authorized intents, count as abandoned when a user leaves.
|
||||
|
||||
The adapter rejects changed-session HTTP responses, and the app clears wallet
|
||||
snapshots/journeys when the authenticated customer changes. Account-language
|
||||
reads use the authenticated customer. Backend language saving is deliberately
|
||||
unsupported and reports failure; the previous demo setting is not presented as
|
||||
persisted backend state.
|
||||
|
||||
## UX, observation and validation
|
||||
|
||||
The existing typed transfer journey recorder measures draft → quotation →
|
||||
second-factor challenge/verification → submission → authoritative settlement.
|
||||
Attempts/retries share their client journey; accepted operations finish once
|
||||
when the wallet observes a terminal platform status. An uncertain result is not
|
||||
success. Leaving an unresolved asynchronous operation is not draft abandonment.
|
||||
Use the existing per-attempt timestamps for durations and a 30-minute inactivity
|
||||
window for unfinished local drafts; server-authorized pending operations remain
|
||||
outside that abandonment denominator. Compare draft-to-quote and quote-to-submit
|
||||
drop-off, intent-to-terminal completion/failure and time to terminal outcome.
|
||||
The existing recorder provides in-memory client evidence only: deployment of a
|
||||
retained analytics sink and backend event delivery is needed for cross-session
|
||||
conversion/settlement measurement. No amounts, recipients, bank labels, codes or
|
||||
credentials enter analytics. Structured server logs use quote/payment references
|
||||
and bounded states; operational reconciliation uses the durable database record.
|
||||
|
||||
Tests cover public wire requests, exact money, source/destination isolation,
|
||||
blocked/unknown states, factor errors and retry keys, changed-session responses,
|
||||
durable authorization rollback/uniqueness, lost-response reconciliation and
|
||||
customer/provider-namespace isolation. Widget checks exercise saved destinations
|
||||
and the missing-destination state at 320px with 1.6× text, alongside the existing
|
||||
EN/RU workspace flows. Default Flutter keyboard/focus semantics are retained;
|
||||
full browser/screen-reader acceptance and live funded settlement are deployment
|
||||
checks, not claimed by synthetic tests. No live payments or deployment are run.
|
||||
|
||||
Validation passed after review: `make test` (backend race tests and all frontend
|
||||
suites), `make lint-backend`, a subsequent `make test-frontend` after the final
|
||||
long-label UI adjustment, changed-Dart format verification, and `flutter analyze`
|
||||
in `frontend/app` and `frontend/packages/sxchnge_wallet`. The final frontend run
|
||||
passed 21 wallet, 20 KYC, 7 analytics, 40 auth, 3 theme, 3 theme-example and 81 app
|
||||
tests. The provider prerequisite passed Sendico's full backend test/lint targets.
|
||||
Reference in new issue
Block a user