Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go/backend/reports/gatehub_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
32 changes: 24 additions & 8 deletions go/backend/reports/provider_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ type providerReport struct {
// linkedAccountProvider and linkedAccountType identify the balance linked account.
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)
Expand Down Expand Up @@ -301,7 +304,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 {
Expand Down Expand Up @@ -354,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"`
Expand All @@ -368,21 +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.
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
WHERE wallet_id = $1 AND asset_code = $2
ORDER BY created_at DESC, id`,
w.WalletID)
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,
Expand All @@ -395,5 +411,5 @@ func (p providerReport) fillWallet(ctx context.Context, b Backends, w *reportWal
Title: t.Title.String,
})
}
return nil
return ret, nil
}
3 changes: 2 additions & 1 deletion go/backend/reports/xago_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions typescript/botanist/app/routes/_index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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' }
]

Expand Down Expand Up @@ -147,8 +147,8 @@ export default function Page() {
<h2 className='font-display text-lg font-medium'>Reports</h2>
<p className='mt-2 text-sm text-gray-700'>
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.
</p>
<div className='mt-4 flex flex-col gap-3'>
{REPORTS.map((report) => (
Expand Down
4 changes: 2 additions & 2 deletions typescript/botanist/app/routes/reports.xago-users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
)
}
Loading