Merge pull request 'Set up RUB, USDT and USDC accounts automatically' (#73) from wallet-72 into main

This commit was merged in pull request #73.
This commit is contained in:
tech committed 2026-10-23 11:10:45 +00:00
commit 3b551f1565
21 files changed
+687 -62

No files matched your search

+4 -2
View File
@@ -130,8 +130,10 @@ This recovery retains the configured provider URL, account and replay namespace.
layout and least-privilege identities, Mongo transactions/lease rotation, and
authenticated Sendico currency and ledger reads through the same loopback
connection and token used by BFF. It sends no email and executes no payment.
An empty wallet is expected until dedicated subject-bound currency ledgers and
saved methods are provisioned as described in [payment provisioning](payments.md).
Signup and wallet access automatically ensure subject-bound RUB, USDT and USDC
ledgers as described in [account setup](payments.md#automatic-currency-accounts-72).
Deploy the provider prerequisite and grant ledger-create permission first.
Saved funding and payout methods remain separate from currency-account setup.
## Recovery
+36 -7
View File
@@ -52,22 +52,50 @@ 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
account's organization membership, payment-create/read, ledger-account create/read,
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:
### Automatic currency accounts (#72)
The backend ensures **RUB, USDT and USDC** accounts after confirmed identity intake
during signup, before sending the activation email. Resending activation retries
the same setup. Every authenticated wallet load also ensures the defaults, so
existing customers receive missing accounts automatically when they next open
their wallet. This is lazy backfill, not an eager scan of dormant customers.
Setup creates dedicated unowned organization ledgers through the public
`POST /ledger_accounts/{organization}` API, with active `asset` / `operating`
semantics, zero initial funds, no negative balance and metadata:
| Ledger metadata | Meaning |
| --- | --- |
| `sxchnge_subject_ref` | Confirmed independent subject reference from sign-up |
| `external_subject_ref` | Same confirmed subject; provider-enforced organization/subject/currency uniqueness |
| `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
Existing customer ledgers and their balances are preserved, including manually
provisioned ledgers with the original `sxchnge_subject_ref` tag. Only missing
defaults are created; existing additional currencies remain visible. Repeated
bound creates reuse the ledger and resume Sendico's durable wallet/route setup
after an interrupted response. Frozen, duplicate or incorrectly owned bindings
fail instead of creating a replacement. All required currency precision entries
are checked against the provider catalog before issuing creates. Saved funding
and payout bindings remain separate from creation of the currency accounts.
Deploy [Sendico #2968](https://git.sendico.io/tech/sendico/issues/2968) first:
Ledger (including its unique index), then Product Edge, then SXCHNGE backend and
frontend from [PR #73](https://git.sendico.io/tech/sxchnge/pulls/73). The public
ledger list must advertise `externalSubjectAccounts: true` before SXCHNGE sends
any create requests. Older providers fail safely without creating accounts.
The acknowledgement assumes Ledger and Edge are deployed in that order; it
does not discover the version of a separately running Ledger. No new runtime
configuration or credentials are introduced. Existing deployments must give the
integration principal the ledger-create permission in addition to its reads.
These are financial-resource bindings maintained by server-side setup, 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,
@@ -79,7 +107,8 @@ 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
If setup reports a failure, the API returns an unavailable wallet rather than an
empty snapshot or demo balances. Retrying the app load retries setup. 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
+45 -23
View File
@@ -302,32 +302,54 @@ that contradicts displayed balances or usage without explaining the fixtures.
- Keep unrelated accounts/history accessible during a pending operation. Do not
block the whole product behind an exchange spinner.
## Empty currency accounts (task #66)
## Automatic currency accounts (task #72)
The shared empty-account message used on Accounts and transfer screens explains
that no currency accounts have been set up and directs the customer to support
to request setup. English and Russian no longer suggest that refreshing creates
accounts or imply that automatic provisioning is underway. This follows the
existing support guidance for missing funding and payout setup. The application
has no configured support contact channel; the message therefore provides text
guidance without a nonfunctional contact button or invented contact details.
New customers receive RUB, USDT and USDC accounts through backend-owned setup
during signup. Existing customers receive missing defaults on their next wallet
load. The UI requires no account-creation form or support contact. During initial
loading it announces that the accounts are being prepared. Failure shows one
clear load error and a **Try again** action that reruns the same idempotent setup.
An unexpected empty snapshot has its own setup-failure fallback, rather than
being shown simultaneously with an error. A failed refresh retains existing
cards and the stale-data warning. Account-not-found copy requires loaded data.
UX review: the copy names the missing resource and the next step, retains the
existing wrapping text and accessible reading order, and does not alter loading,
error or populated-state behavior. Existing manual refresh remains available for
accounts provisioned externally. This copy-only change adds no controls or
journey outcomes and requires no new analytics events. Existing transaction
journey signals and their production measurement limitations below still apply;
support resolution and external account provisioning are not measured in-app.
The existing DTO → mapper → service → model → provider → UI boundaries remain
intact. Account creation belongs to the backend/provider, and widgets invoke the
existing provider refresh action. English and Russian use wrapping text and the
existing accessible button pattern; status text uses a live region. Dedicated
widget regressions cover loading, failure, retry and successful default accounts
on Accounts, Top up, Exchange and Payout at 320px with 1.6× text. Native device and
screen-reader QA remain a release check; tests do not claim physical-device QA.
Validation: `flutter gen-l10n`, `flutter analyze` in `frontend/app`,
`make test-frontend` (217 tests), and `git diff --check` passed. No Dart source
changed, so Dart formatting was not applicable. Existing responsive workspace
tests cover Russian at 320px with enlarged text; this copy-only change does not
add a dedicated empty-state rendering or native screen-reader test. No security
scan was requested. Delivery: [task #66](https://git.sendico.io/tech/sxchnge/issues/66),
branch `wallet-66`, [PR #68](https://git.sendico.io/tech/sxchnge/pulls/68).
Merge and deployment remain separate.
Measurement: `sxchnge_currency_accounts_setup_total{outcome="ready|failed"}` and
`sxchnge_currency_accounts_setup_duration_seconds` cover the server setup attempt.
`ready` is emitted only after all default bindings are confirmed, including
idempotent completion of provider wallet/route setup for newly bound ledgers.
Each signup/resend/wallet load is a separate attempt; a retry is not a new unique
customer conversion. The failure fraction uses all attempts as its denominator;
the histogram measures one attempt, not time across multiple browser sessions.
No customer, ledger, currency, balance, credentials or form input is a metric
label. These server metrics do not measure client abandonment or prove that a
balance card was viewed. Existing transaction journey outcomes below are reused;
no speculative button-click events are added.
Scope and rollout: [task #72](https://git.sendico.io/tech/sxchnge/issues/72), branch
`wallet-72`, [PR #73](https://git.sendico.io/tech/sxchnge/pulls/73). The provider
prerequisite and deployment order are documented in [payments](../payments.md).
Merge and deployment remain separate. Final validation is recorded in the task
and PR, including any transient failures and environmental blockers.
Final validation (2026-10-23): `flutter gen-l10n`, `flutter analyze` in
`frontend/app`, the changed-Dart formatting check, `make lint-backend`, and
`git diff --check` passed. `make test-frontend` passed all 242 tests on the current
app base, including the eight setup recovery scenarios. The full `make test`
passed with `TESTCONTAINERS_RYUK_DISABLED=true GOFLAGS='-p=2'`: earlier attempts
hit dependency DNS and Mongo/cleanup-helper startup timeouts. Disabling only the
failing helper retained every integration assertion, test timeout and race check;
each test's existing container cleanup still ran. No security scan was requested.
The live local provider catalog confirms RUB scale 2 and USDT/USDC scale 6.
Provider-side concurrency and frozen-binding checks passed against a disposable
Mongo replica set; see [Sendico PR #2970](https://git.sendico.io/tech/sendico/pulls/2970).
## Journey measurement plan