Balance & Transactions
Every reseller has a wallet (balance) and a credit limit (credit). Every operational action — renew, top-up, plan change, refund — debits or credits the wallet and writes a row to the transactions table. This page explains the wallet mechanics, the 14 transaction types, and which ones count as revenue vs which are pure accounting movements.
How to get here
Section titled “How to get here”| Page | URL | What it shows |
|---|---|---|
| My balance (reseller’s own wallet) | /profile or sidebar header chip | Your live balance, last 10 transactions. |
| Reseller detail (admin or parent) | /resellers/{id} → Transactions tab | Full transaction history for one reseller. |
| Transactions (global) | /transactions | All transactions across your scope (filterable by type, date, reseller, subscriber). |
The current balance is also shown in the top-right header next to the clock — a reseller sees their own balance auto-refreshing every 60 seconds (v1.0.258).
Wallet vs credit — two numbers, one rule
Section titled “Wallet vs credit — two numbers, one rule”| Field | Meaning |
|---|---|
| balance | Live money in the wallet. Goes up on Transfer-in, top-up, refund. Goes down on renewal, plan change, withdraw. |
| credit | How far below zero balance is allowed to go. Default 0 means renewals fail the moment the wallet hits zero. A credit of 500 means you can stay at -500 before renewals start failing. |
The rule the API enforces:
if (action.cost > balance + credit) → reject with "Insufficient balance"Only an admin can change a reseller’s credit — see Onboarding.
The 14 transaction types
Section titled “The 14 transaction types”Transactions live in the transactions table with a type column. They split into three groups:
Revenue (11 types)
These count toward “Total Income” on the Dashboard and Reports → Revenue. They’re real money the ISP collected.
| Type | When it’s written | Sign |
|---|---|---|
new | First charge when a subscriber is created. | Positive |
renewal | Recurring charge when a subscriber renews their plan. | Positive |
change_service | Prorated charge / refund when a subscriber changes plan. | Positive or negative (refund is on downgrade). |
static_ip | One-time fee for assigning a static IP. | Positive |
addon | Optional add-on fee (configured per service). | Positive |
refill | Operator manually tops up a subscriber’s quota outside a renewal. | Positive |
data_topup | Customer or operator buys extra GB. | Positive |
prepaid_card | Subscriber redeems a prepaid voucher. | Positive |
subscriber_topup | Subscriber adds money to their own wallet (cash receipt). | Positive |
subscriber_purchase | Subscriber spends from their wallet on the portal. | Positive (revenue to ISP, debit on subscriber wallet). |
reset_fup | Optional charge when an operator forces a FUP reset (configured per reseller). | Positive |
rename | Optional fee for renaming a subscriber’s PPPoE username. | Positive |
Neutral (3 types)
These are accounting movements between two wallets. They do not count as revenue. The Dashboard’s Total Income card excludes them.
| Type | What moves | Sign |
|---|---|---|
transfer | Money moves parent → sub-reseller. One row on each side. | + on receiver, − on sender |
withdraw | Money moves sub-reseller → parent. One row on each side. | + on parent, − on sub |
refund | Reverses a prior charge. One row debiting wallet, optionally a matching refund on the subscriber side. | Negative |
Each row, what it contains
Section titled “Each row, what it contains”Every transaction row has:
| Column | Use |
|---|---|
type | One of the 14 above. |
amount | Signed — positive for credit, negative for debit on this wallet. |
balance_before / balance_after | The wallet’s balance immediately before and after the row was written. Used for audit reconciliation. |
reseller_id | Whose wallet this row affects. |
subscriber_id | Optional — set when the transaction relates to a specific subscriber. |
target_reseller_id | Optional — set on transfer / withdraw to point to the other party. |
description | Human-readable note shown in the UI. |
created_by | The user (admin or reseller) who triggered the action. |
ip_address | The originating IP — useful for audit. |
created_at | Timestamp. |
Topping up a reseller — how it works end-to-end
Section titled “Topping up a reseller — how it works end-to-end”When an admin or parent reseller hits the Transfer button on a reseller’s row:
- The API checks the caller is either admin or the direct parent of the target.
- If the caller is a reseller, it checks the caller’s wallet has enough (
balance + credit >= amount). - In one DB transaction: subtract amount from source wallet, add to target wallet.
- Insert one
transferrow on the source (amount=-X, target=target_id), one on the target (amount=+X). - Insert an audit log entry:
Transferred $X.XX to <target_name>. - Return success. Both balances update live in the UI on next refresh.
If step 1 fails → 403. If step 2 fails → 400. No DB writes happen.
Withdrawing from a reseller (parent or admin)
Section titled “Withdrawing from a reseller (parent or admin)”Same flow as Transfer, but reversed direction. Most operators use Withdraw at month-end as a settlement — pull back money the sub didn’t burn, before the next month’s grant.
Refunds
Section titled “Refunds”A refund is created by:
- A subscriber-initiated downgrade (
change_serviceflagged as a downgrade). - An admin manually creating a refund from the subscriber’s billing tab.
Refunds always credit the subscriber’s wallet, never the reseller’s. The reseller side of a refund is recorded as a change_service or manual adjustment with a negative amount.
Common workflows
Section titled “Common workflows”Funding a sub-reseller mid-month
Section titled “Funding a sub-reseller mid-month”- Resellers → click the sub-reseller → Transfer.
- Enter amount (e.g.
300) and a note ("Mid-month top-up"). - Confirm. Two rows appear: one on your wallet (
transfer, −300), one on theirs (transfer, +300).
Reconciling a month — “where did my balance go?”
Section titled “Reconciling a month — “where did my balance go?””- Transactions → filter
date >= 1st of last month, date <= last day of last month. - Group mentally by type:
new + renewal + change_service + static_ip + addon + refill + data_topup + prepaid_card + subscriber_purchase + reset_fup + rename= revenue earnedtransfer (negative)= money you sent to substransfer (positive)= top-ups received from adminwithdraw (positive)= money you pulled back from subs
- Net change in balance should equal
start_balance + sum(amount) = end_balance. If it doesn’t, look atbalance_before/balance_aftercolumns to find the offending row.
Spotting suspicious activity
Section titled “Spotting suspicious activity”- Transactions → filter
type = transfer OR type = withdraw. - Sort by
amount DESC. - Look for unusual large movements between resellers — particularly ones that happen at odd hours.
- The
ip_addresscolumn tells you where the action was triggered from. If a Transfer happened from an IP that isn’t your normal office IP, audit the reseller account. - To lock the reseller down going forward, set their
allowed_ipsto your office’s public IP — see IP Restriction.
Permissions
Section titled “Permissions”| Permission | Effect |
|---|---|
transactions.view | See the Transactions page (your own scope). |
transactions.view_all | See all transactions across the system (system-wide audit / partner role). |
transactions.transfer | Use the Transfer button. |
transactions.withdraw | Use the Withdraw button. |
transactions.refund | Issue a manual refund to a subscriber. |
Caveats
Section titled “Caveats”- Float precision. All amounts are stored as
NUMERIC(12,2)and rounded to two decimals on every write. The frontend never does float math. - No undo. A wrong Transfer cannot be undone with a single click — you must Withdraw the same amount back. Both rows stay in the history for audit.
- Cross-customer transfers. Not possible — you can only Transfer to a direct child. The original M6 audit fix removed an exploit where a reseller could move balance between any two accounts.
Related pages
Section titled “Related pages”- Sub-Resellers — direct-child ownership and the security checks behind Transfer / Withdraw.
- Reseller Onboarding — setting initial balance and credit limit.
- Customer → Buy Extra Data — the subscriber side of
subscriber_purchase. - Reports → Revenue — admin-side aggregation of the 11 revenue types.
- IP Restriction — locking a reseller account to specific source IPs.