Aller au contenu

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 WalletContainerBalanceBlock + 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=true search param to auto-open top-up
  • BalanceDetailsModal: 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 modal
  • TransactionItem / TransactionCategoryIcon: Per-kind icon variant via TRANSACTION_ICON_VARIANT_BY_KIND
  • OperationsFilterModal: Credit/debit operation type filters
  • ProjectFilterModal: 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):

amount_input → payment_method_selection → [checkout_flow | wire_details] → result
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):

amount_input → contract → payment_method → [card_mercanet] → result
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):

amount → account_selection → 2fa → result
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 from checkoutFlowBranding.ts backed by AppConfig

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 CheckoutFlowCardPayment native implementation includes an AppState workaround: 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 touching index.html or ModalWindow
  • useWalletTransactions monthly sums exclude declined and canceled transactions from the totals but still display them in the list
  • The moneyin search param auto-opens top-up and is cleared immediately — if the screen remounts, it won't re-trigger