Wallet Module¶
Investor wallet: balance display, top-up (credit), withdraw (debit), and transaction history.
Route¶
/(authenticated)/wallet — single screen (WalletScreen) composing BalanceBlock + TransactionsBlock with pull-to-refresh.
Architecture¶
The wallet is modal-heavy: the main screen displays balance and transactions, while top-up and withdraw flows run entirely inside modals opened via useModals().openModal(). There is no Zustand store — modal step state is managed via local useState discriminated unions; server state is React Query.
Screens¶
| Screen | Role |
|---|---|
WalletScreen |
Composes WalletContainer → BalanceBlock + TransactionsBlock. useGoBack('/(authenticated)/(tabs)') for navigation. Pull-to-refresh via useWalletRefresh |
Key components¶
Balance¶
BalanceBlock(smart + UI + loading): Shows withdrawable balance + gift balance + boosted balance link. KYC wall on top-up/withdraw buttons. Reads?moneyin=truesearch param to auto-open top-upBalanceDetailsModal: Balance breakdown on tap
Transactions¶
TransactionsBlock(smart + UI + loading + empty): Infinite-scroll transaction list with URL-serialized filters, XLSX export, filter modals, and transaction detail modalTransactionItem/TransactionCategoryIcon: Per-kind icon variant viaTRANSACTION_ICON_VARIANT_BY_KINDOperationsFilterModal: Credit/debit operation type filtersProjectFilterModal: Filter by bought properties (useBoughtProperties)TransactionDetailModal: Detail view by kind — withholding tax, primary purchase (with cancel flow), recurring revenue, or default
Top-up flow¶
Multi-step modal (TopUpModal):
| Step | Component | Details |
|---|---|---|
| Amount | TopUpAmountStep |
Amount entry with current balance hint |
| Method | TopUpMethodStep |
Wire + Checkout.com card payment |
| Card (Checkout) | CheckoutFlowCardPayment |
Shared under wallet/components/ — native SDK or web components |
| Wire | TopUpWireStep |
Lemonway VIBAN display (informational only) |
| Result | TopUpResultStep |
success_instant / success_deferred / pending / error |
Payment method: Checkout.com Flow SDK (native or web components) for card + wire transfer. Amount above businessRules.moneyIn.card.maxAmountInCents → wire only.
Web redirect handling: TopUpCheckoutReturnProvider (in authenticated layout) watches ?payment_status=success|error&amount= after Checkout.com 3DS redirect, clears params, and reopens the right modal:
- Top-up → invalidate wallet queries + TopUpModal result (success_instant / error)
- Bricks purchase (checkout_flow=bricks_purchase + investment_amount) → PurchaseResultModal (same PurchaseResultStep UI as Lemonway / in-flow Checkout; owns its cache invalidation)
Purchase bricks flow¶
Multi-step modal (PurchaseModal):
| Path | Behaviour |
|---|---|
| Wallet/gift covers full amount | Contract succeeds without reservation → result |
Money-in needed + ENABLE_CHECKOUT_CARD_BRICKS_PURCHASE |
CheckoutFlowCardPayment embedded in payment_method (no separate card step) |
| Money-in needed + FF off (Mercanet) | CTA on payment_method → dedicated card_mercanet WebView step |
Withdraw flow¶
Multi-step modal (WithdrawModal):
| Step | Component | Details |
|---|---|---|
| Amount | WithdrawAmountStep |
Amount input |
| Account | WithdrawAccountStep |
Bank account list, fees display, add account flow, pre-generates MFA code |
| 2FA | WithdrawTwoFAStep |
Code entry → POST customers/lemonway/payment-intent/withdrawal with TFA header |
| Result | WithdrawResultStep |
Success or error with buildApiErrorMessage |
Sub-modal: AddBankAccountModal (form → 2FA → success/error) accessible from the account step.
Services¶
| File | Kind | Key |
|---|---|---|
queryKeys.ts |
Keys | customerBalances, boughtProperties, transactions(filtersKey?) |
useCustomerBalances.ts |
Query | Investor balances (withdrawable + gift) |
useBoughtProperties.ts |
Query | Properties for transaction filter |
useWalletTransactions.ts |
Infinite query | Cursor-paginated, grouped by month, sums excluding declined/canceled |
useCreatePaymentSessionMutation.ts |
Mutation | Checkout.com payment session creation |
useExportTransactions.ts |
Mutation | XLSX export download |
useWalletRefresh.ts |
Utility | Invalidates getMe + customerBalances + transactions() + boostedBalanceView |
External hooks used: useWalletWithdraw (@api/wallet/), useBankAccountsList, useLemonwayViban, useCancelPurchaseBricks, MFA hooks.
Hooks¶
useCheckoutConfiguration(native +.web.ts): Builds Checkout.com public key, environment, locale, theme-mapped styles, custom translations fromcheckoutFlowBranding.tsbacked byAppConfig
Utils¶
| File | Purpose |
|---|---|
areSetsEqual.ts |
Set equality for filter dirty detection |
transactionStatus.ts |
BADGE_VARIANT_BY_STATUS mapping |
transactionKinds.ts |
TRANSACTION_ICON_VARIANT_BY_KIND mapping |
mockTransactions.ts |
Storybook/test fixtures |
i18n¶
- Top-up:
modules.wallet.topup.*(+methodSheet,wire,card.checkoutFlow) - Container:
modules.wallet.components.container - Balance:
modules.wallet.components.balanceBlock
Pitfalls¶
- The
CheckoutFlowCardPaymentnative implementation includes anAppStateworkaround: it delays the success callback until the app returns to active state after Apple/Google Pay overlays dismiss - On desktop web, Apple Pay portals its QR sheet to
<body>as<apple-pay-modal>, outside the modal hosting the Checkout Flow — see the third-party sheets note in the uimmo README before touchingindex.htmlorModalWindow useWalletTransactionsmonthly sums excludedeclinedandcanceledtransactions from the totals but still display them in the list- The
moneyinsearch param auto-opens top-up and is cleared immediately — if the screen remounts, it won't re-trigger