From 0ba05ef1226357846ac6783045d64c94aff978f8 Mon Sep 17 00:00:00 2001 From: Raul Date: Mon, 9 Nov 2026 17:14:51 +0200 Subject: [PATCH 1/2] fix: retrive transactions only for provider --- go/backend/reports/provider_users.go | 8 +++++--- typescript/botanist/app/routes/_index.tsx | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/go/backend/reports/provider_users.go b/go/backend/reports/provider_users.go index c06a5c672..178051f22 100644 --- a/go/backend/reports/provider_users.go +++ b/go/backend/reports/provider_users.go @@ -60,6 +60,7 @@ type providerReport struct { // title is the human-readable report name, used in the PDF. title string // linkedAccountProvider and linkedAccountType identify the balance linked account. + // linkedAccountProvider also filters the transactions to the provider's own. linkedAccountProvider string linkedAccountType string // selectUsers picks the report's users and wallets. alwaysInclude lists users that get @@ -301,7 +302,7 @@ func (p providerReport) listUsers(ctx context.Context, b Backends) ([]reportUser } // fillWallet adds a wallet's KYC status, Persona inquiry, provider details, balance -// and full transaction history to w. +// and the provider's transaction history to w. func (p providerReport) fillWallet(ctx context.Context, b Backends, w *reportWallet) error { linkedAccounts, err := b.LinkedAccounts().ListByWalletId(ctx, w.WalletID) if err != nil { @@ -368,12 +369,13 @@ func (p providerReport) fillWallet(ctx context.Context, b Backends, w *reportWal UpdatedAt time.Time `db:"updated_at"` } // Queried directly because the transactions client doesn't expose created_at. + // Only the report's provider's transactions are included. err = b.DB().SelectContext(ctx, &txs, ` SELECT id, type, state, provider, title, amount, provider_fee, asset_scale, asset_code, created_at, updated_at FROM transactions - WHERE wallet_id = $1 + WHERE wallet_id = $1 AND provider = $2 ORDER BY created_at DESC, id`, - w.WalletID) + w.WalletID, p.linkedAccountProvider) if err != nil { return fmt.Errorf("listing transactions: %w", err) } diff --git a/typescript/botanist/app/routes/_index.tsx b/typescript/botanist/app/routes/_index.tsx index 66fb53cbc..44bba0f46 100644 --- a/typescript/botanist/app/routes/_index.tsx +++ b/typescript/botanist/app/routes/_index.tsx @@ -147,8 +147,8 @@ export default function Page() {

Reports

Users with their wallets, KYC status, Persona inquiry, balance and - full transaction history. Contains personal data; store it - somewhere restricted. + transaction history with that provider. Contains personal data; + store it somewhere restricted.

{REPORTS.map((report) => ( From 6c82b9bd78d6b917fa42b1a9fa665118f8ea9d38 Mon Sep 17 00:00:00 2001 From: Raul Date: Mon, 9 Nov 2026 17:30:13 +0200 Subject: [PATCH 2/2] fix: filter out transactions by curency --- go/backend/reports/gatehub_users.go | 1 + go/backend/reports/provider_users.go | 32 +++++++++++++------ go/backend/reports/xago_users.go | 3 +- typescript/botanist/app/routes/_index.tsx | 2 +- .../botanist/app/routes/reports.xago-users.ts | 4 +-- 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/go/backend/reports/gatehub_users.go b/go/backend/reports/gatehub_users.go index 31c273535..bf303bbc5 100644 --- a/go/backend/reports/gatehub_users.go +++ b/go/backend/reports/gatehub_users.go @@ -18,6 +18,7 @@ var gatehubUsersReport = providerReport{ title: "GateHub users report", linkedAccountProvider: gatehub.ProviderName, linkedAccountType: gatehub.AccTypeBalance, + currency: currency.EUR, selectUsers: selectGatehubUsers, balance: func(ctx context.Context, b Backends, linkedAccountID string) (currency.Amount, currency.Amount, error) { bal, err := b.Gatehub().GetBalance(ctx, linkedAccountID) diff --git a/go/backend/reports/provider_users.go b/go/backend/reports/provider_users.go index 178051f22..c2cb23f6e 100644 --- a/go/backend/reports/provider_users.go +++ b/go/backend/reports/provider_users.go @@ -60,9 +60,11 @@ type providerReport struct { // title is the human-readable report name, used in the PDF. title string // linkedAccountProvider and linkedAccountType identify the balance linked account. - // linkedAccountProvider also filters the transactions to the provider's own. linkedAccountProvider string linkedAccountType string + // currency picks the transactions: in production each provider holds a single + // currency, and no other provider holds it (Xago ZAR, GateHub EUR). + currency currency.Currency // selectUsers picks the report's users and wallets. alwaysInclude lists users that get // a row even without a wallet. selectUsers func(ctx context.Context, b Backends, identities []user.User) (alwaysInclude []string, wallets []walletRow, err error) @@ -355,6 +357,19 @@ func (p providerReport) fillWallet(ctx context.Context, b Backends, w *reportWal w.BalanceCurrency = total.Currency.String() } + w.Transactions, err = listTransactions(ctx, b.DB(), w.WalletID, p.currency) + if err != nil { + return fmt.Errorf("listing transactions: %w", err) + } + return nil +} + +// listTransactions returns the wallet's transactions in the given currency, newest first. +// A transaction is stored in its own wallet's currency (a cross-currency payment is +// saved once per side), so this picks the transactions of the provider that holds it. +// transactions.provider isn't used because Rafiki labels payments "gatehub" and wallet +// to wallet payments "payments_engine" whatever account paid. +func listTransactions(ctx context.Context, dbc *sqlx.DB, walletID string, cc currency.Currency) ([]reportTransaction, error) { var txs []struct { ID string `db:"id"` Type string `db:"type"` @@ -369,22 +384,21 @@ func (p providerReport) fillWallet(ctx context.Context, b Backends, w *reportWal UpdatedAt time.Time `db:"updated_at"` } // Queried directly because the transactions client doesn't expose created_at. - // Only the report's provider's transactions are included. - err = b.DB().SelectContext(ctx, &txs, ` + err := dbc.SelectContext(ctx, &txs, ` SELECT id, type, state, provider, title, amount, provider_fee, asset_scale, asset_code, created_at, updated_at FROM transactions - WHERE wallet_id = $1 AND provider = $2 + WHERE wallet_id = $1 AND asset_code = $2 ORDER BY created_at DESC, id`, - w.WalletID, p.linkedAccountProvider) + walletID, cc) if err != nil { - return fmt.Errorf("listing transactions: %w", err) + return nil, err } - w.Transactions = make([]reportTransaction, 0, len(txs)) + ret := make([]reportTransaction, 0, len(txs)) for _, t := range txs { amount := currency.Amount{Value: t.Amount, Currency: currency.ParseCurrency(t.Asset), Scale: t.Scale} fee := currency.Amount{Value: t.ProviderFee, Currency: amount.Currency, Scale: t.Scale} - w.Transactions = append(w.Transactions, reportTransaction{ + ret = append(ret, reportTransaction{ ID: t.ID, CreatedAt: t.CreatedAt, UpdatedAt: t.UpdatedAt, @@ -397,5 +411,5 @@ func (p providerReport) fillWallet(ctx context.Context, b Backends, w *reportWal Title: t.Title.String, }) } - return nil + return ret, nil } diff --git a/go/backend/reports/xago_users.go b/go/backend/reports/xago_users.go index 7ff68c6e7..4f746b6a5 100644 --- a/go/backend/reports/xago_users.go +++ b/go/backend/reports/xago_users.go @@ -15,9 +15,10 @@ import ( var xagoUsersReport = providerReport{ name: "xago", displayName: "Xago", - title: "Xago users report", + title: "South Africa users report", linkedAccountProvider: xago.ProviderName, linkedAccountType: xago.AccTypeBalance, + currency: currency.ZAR, selectUsers: selectXagoUsers, balance: func(ctx context.Context, b Backends, linkedAccountID string) (currency.Amount, currency.Amount, error) { bal, err := b.Xago().GetBalance(ctx, linkedAccountID) diff --git a/typescript/botanist/app/routes/_index.tsx b/typescript/botanist/app/routes/_index.tsx index 44bba0f46..185183d15 100644 --- a/typescript/botanist/app/routes/_index.tsx +++ b/typescript/botanist/app/routes/_index.tsx @@ -52,7 +52,7 @@ export async function loader({ request }: LoaderFunctionArgs) { } const REPORTS = [ - { label: 'Xago users report', path: '/reports/xago-users' }, + { label: 'South Africa users report', path: '/reports/xago-users' }, { label: 'GateHub users report', path: '/reports/gatehub-users' } ] diff --git a/typescript/botanist/app/routes/reports.xago-users.ts b/typescript/botanist/app/routes/reports.xago-users.ts index 72ef681b0..b394ca154 100644 --- a/typescript/botanist/app/routes/reports.xago-users.ts +++ b/typescript/botanist/app/routes/reports.xago-users.ts @@ -2,11 +2,11 @@ import type { LoaderFunctionArgs } from 'react-router' import { grpcClient } from '~/lib/proto.server' import { reportResponse } from '~/lib/reports.server' -// Resource route: downloads the Xago users report as CSV, or PDF with ?format=pdf. +// Resource route: downloads the South Africa (Xago) users report as CSV, or PDF with ?format=pdf. export async function loader({ request }: LoaderFunctionArgs) { return reportResponse( request, grpcClient.getXagoUsersReport.bind(grpcClient), - 'Xago users report' + 'South Africa users report' ) }