Balance Management
How to query, manage, and monitor prepaid wallet balances.
Balance Types
| Type | Status Included | Description |
|---|---|---|
| Current | OPEN + RESERVED + TREATED | Total committed balance |
| Reserved | RESERVED | Held for pending transactions |
| Open | OPEN + TREATED | Available balance |
Available Balance = Open Balance - Reserved Balance
Query Endpoints
Current Balance
Total balance including reserved amounts.
POST /api/rest/billing/wallet/balance/current
Content-Type: application/json
{
"userAccountCode": "UA_001",
"walletCode": "API_CREDITS",
"startDate": "2024-01-01",
"endDate": "2024-01-31"
}
Response:
{
"status": "SUCCESS",
"amountWithoutTax": 85.00,
"amountWithTax": 102.00
}
Reserved Balance
Amount held for pending reservations.
POST /api/rest/billing/wallet/balance/reserved
Content-Type: application/json
{
"userAccountCode": "UA_001",
"walletCode": "API_CREDITS"
}
Response:
{
"status": "SUCCESS",
"amountWithoutTax": 10.00,
"amountWithTax": 12.00
}
Open Balance
Available balance (can be used).
POST /api/rest/billing/wallet/balance/open
Content-Type: application/json
{
"userAccountCode": "UA_001",
"walletCode": "API_CREDITS"
}
Response:
{
"status": "SUCCESS",
"amountWithoutTax": 75.00,
"amountWithTax": 90.00
}
Balance Query Levels
Query balance at different hierarchy levels:
| Parameter | Level |
|---|---|
sellerCode | All customers under seller |
customerCode | All accounts under customer |
customerAccountCode | All billing accounts |
billingAccountCode | All user accounts |
userAccountCode | Specific user account |
Example: Seller-Level Balance
POST /api/rest/billing/wallet/balance/current
Content-Type: application/json
{
"sellerCode": "MAIN_SELLER"
}
Balance Operations
Top-Up (Credit)
POST /api/rest/billing/wallet/operation
Content-Type: application/json
{
"userAccount": "UA_001",
"walletTemplate": "API_CREDITS",
"chargeInstance": "CREDIT_CHARGE",
"currency": "USD",
"amountWithTax": 100.00,
"description": "Top-up via payment"
}
Manual Deduction
POST /api/rest/billing/wallet/operation
Content-Type: application/json
{
"userAccount": "UA_001",
"walletTemplate": "API_CREDITS",
"chargeInstance": "ADJUSTMENT_CHARGE",
"currency": "USD",
"amountWithTax": -25.00,
"description": "Fraud adjustment"
}
Transfer Between Wallets
# 1. Debit source wallet
POST /api/rest/billing/wallet/operation
{
"userAccount": "UA_001",
"walletTemplate": "WALLET_A",
"amountWithTax": -50.00,
"description": "Transfer to WALLET_B"
}
# 2. Credit destination wallet
POST /api/rest/billing/wallet/operation
{
"userAccount": "UA_001",
"walletTemplate": "WALLET_B",
"amountWithTax": 50.00,
"description": "Transfer from WALLET_A"
}
List Wallet Operations
GET Request
GET /api/rest/billing/wallet/operation/list?query=wallet.code:API_CREDITS&limit=50&sortBy=operationDate&sortOrder=DESCENDING
POST Request with Filters
POST /api/rest/billing/wallet/operation/list
Content-Type: application/json
{
"filters": {
"wallet.userAccount.code": "UA_001",
"status": "OPEN",
"fromRange operationDate": "2024-01-01",
"toRange operationDate": "2024-01-31"
},
"offset": 0,
"limit": 100,
"sortBy": "operationDate",
"sortOrder": "DESCENDING"
}
Insufficient Balance Handling
Check Before Operation
async function checkAndCharge(userAccount, walletCode, amount) {
// 1. Get current balance
const balance = await getOpenBalance(userAccount, walletCode);
// 2. Check reject level
const wallet = await getWallet(userAccount, walletCode);
const availableCredit = balance.amountWithTax - wallet.rejectLevel;
if (amount > availableCredit) {
throw new InsufficientBalanceError(
`Insufficient balance: need ${amount}, available ${availableCredit}`
);
}
// 3. Proceed with reservation
return await reserve(userAccount, walletCode, amount);
}
Handle Rejection
{
"status": "FAIL",
"errorCode": "INSUFFICIENT_BALANCE",
"message": "Insufficient balance when charging 15.00 for wallet operation 12345"
}
Balance Caching
Cache Configuration
# Enable prepaid balance caching
cache.cachePrepaidBalance=true
Cache Structure
// Infinispan cache
Key: walletInstanceId (Long)
Value: [openBalance, reservedBalance] (BigDecimal[])
Cache Operations
// Update on operation status change
walletCacheContainerProvider.updateBalance(walletOperation);
// Force cache refresh
walletCacheContainerProvider.initializeBalanceCachesForWallet(walletId);
Low Balance Notifications
Trigger Condition
if (currentBalance <= wallet.getLowBalanceLevel()) {
lowBalanceEvent.fire(new LowBalanceEvent(wallet, currentBalance));
}
Notification Configuration
{
"code": "LOW_BALANCE_EMAIL",
"eventType": "LOW_BALANCE",
"classNameFilter": "WalletInstance",
"notificationType": "EMAIL",
"emailTemplate": {
"code": "LOW_BALANCE_ALERT",
"subject": "Low Balance Alert - #{entity.code}",
"htmlContent": "<p>Balance: #{entity.balance}</p>"
},
"emailToEl": "#{entity.userAccount.billingAccount.email}"
}
Monitoring Queries
Wallets Below Threshold
SELECT wi.code, wi.user_account_id,
(SELECT SUM(wo.amount_with_tax)
FROM billing_wallet_operation wo
WHERE wo.wallet_id = wi.id
AND wo.status IN ('OPEN', 'TREATED')) as balance
FROM billing_wallet wi
JOIN cat_wallet_template wt ON wi.wallet_template_id = wt.id
WHERE wt.wallet_type = 'PREPAID'
HAVING balance < wi.low_balance_level;
Reserved Balance Report
SELECT ua.code as user_account,
wi.code as wallet,
SUM(CASE WHEN wo.status = 'RESERVED' THEN wo.amount_with_tax ELSE 0 END) as reserved,
SUM(CASE WHEN wo.status = 'OPEN' THEN wo.amount_with_tax ELSE 0 END) as open
FROM billing_wallet_operation wo
JOIN billing_wallet wi ON wo.wallet_id = wi.id
JOIN billing_user_account ua ON wi.user_account_id = ua.id
GROUP BY ua.code, wi.code;
Best Practices
1. Use Caching for High-Volume
cache.cachePrepaidBalance=true
2. Set Appropriate Low Balance Levels
API Credits: lowBalanceLevel = 10% of average monthly usage
Telecom: lowBalanceLevel = 1 day of average usage
Gaming: lowBalanceLevel = 100 coins (minimum purchase)
3. Monitor Expired Reservations
SELECT COUNT(*) as expired_reservations
FROM billing_reservation
WHERE status = 'OPEN'
AND expiry_date < NOW();