Get Wallet Balance
Read the calling user's FAN wallet balance and frozen flag. Returns 0.00 + frozen=false if no wallet row exists yet (no auto-create on read).
GET /api/v1/wallet/balance — 🔑 Bearer
Returns the calling user's FAN-type wallet balance (decimal as string, fixed 2 places) and the frozen flag. If no wallet row exists yet, returns { balance: '0.00', frozen: false } without creating one — the wallet is auto-created lazily on first load (POST /wallet/load).
frozen: true blocks debits. When admin / fraud detection freezes a wallet, debits (e.g. paying for a DM) are rejected, but the balance is still readable. Surface a banner on frozen: true so the user understands why their next payment fails.
Decimal as string. Balance is returned as a string ("25.00", not 25.00) to preserve precision across JS clients. Parse with Number(s) or a decimal library — never compare two balance strings with === after arithmetic.
Request
No body, no params.
| Header | Required | Notes |
|---|---|---|
Authorization: Bearer <accessToken> | ✓ | JWT from POST /auth/login |
Response
200 OK — ApiResponseOf<WalletBalanceResponseDto>
{
"success": true,
"data": {
"balance": "25.00",
"frozen": false
}
}| Field | Type | Notes |
|---|---|---|
balance | string (decimal) | Always 2 decimal places. "0.00" when no wallet row exists yet. |
frozen | boolean | true when admin / fraud detection has frozen the wallet (blocks debits, not reads). |
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
401 | (guard) | Missing / invalid bearer token |
503 | features.payment_disabled | Admin kill switch PAYMENT is active |
Side effects
prisma.wallet.findUnique({ where: { userId_type: { userId, type: 'FAN' } } }).- Wallet missing — short-circuit return
{ balance: '0.00', frozen: false }. No row creation, no mutations. - Wallet exists — return
{ balance: <Prisma.Decimal>.toFixed(2), frozen: wallet.frozen }.
Code samples
curl https://api.bio.re/api/v1/wallet/balance \
-H "Authorization: Bearer $ACCESS_TOKEN"type WalletBalance = {
balance: string; // decimal as string, e.g. "25.00"
frozen: boolean;
};
async function getWalletBalance(accessToken: string): Promise<WalletBalance> {
const res = await fetch('https://api.bio.re/api/v1/wallet/balance', {
headers: { Authorization: `Bearer ${accessToken}` },
});
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Wallet balance fetch failed'), {
code: json?.error?.code,
});
}
return json.data;
}import { useQuery } from '@tanstack/react-query';
export const walletKeys = {
balance: () => ['wallet', 'balance'] as const,
};
export function useWalletBalance() {
return useQuery({
queryKey: walletKeys.balance(),
queryFn: async () => {
const res = await fetch('/api/v1/wallet/balance');
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Wallet balance fetch failed'), {
code: json?.error?.code,
i18nKey: json?.error?.i18nKey,
});
}
return json.data as WalletBalance;
},
// Balance changes via /load (user action) and via webhook (server-side). Refresh on focus.
staleTime: 30_000,
});
}Try it
Authorization
bearer In: header
Response Body
application/json
application/json
curl -X GET "https://loading/api/v1/wallet/balance"{
"success": true,
"data": {
"balance": "25.00",
"frozen": false
}
}{
"success": false,
"error": {
"code": "AUTH_UNAUTHORIZED",
"message": "Invalid credentials",
"i18nKey": "auth.login.invalid_credentials",
"i18nVars": {
"field": "email"
},
"details": [
{
"message": "email must be an email"
}
],
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}Source
| Source | Path | Lines |
|---|---|---|
| Controller | apps/api-core/src/modules/payment/wallet.controller.ts | 30–36 (getBalance) |
| DTO (response) | apps/api-core/src/modules/payment/dto/wallet-response.dto.ts | 30–37 (WalletBalanceResponseDto) |
| Service | apps/api-core/src/modules/payment/wallet.service.ts | 59–65 (getBalance) |
| Prisma model | packages/prisma/prisma/schema.prisma | Wallet (compound unique userId_type), enum WalletType.FAN |
Get Load Packages
Public list of suggested wallet top-up amounts plus the absolute min/max limits and active currency. Drives the load-amount picker in the wallet UI.
Create Wallet Load Session
Create a Stripe Checkout session for a wallet top-up. Validates min/max/balance-cap via admin config. Idempotency-Key header recommended for retry safety. Actual credit happens server-side via webhook.