Billing & Invoices
The Billing page is the cashier’s desk of ProxPanel. Every charge a subscriber incurs — first month, renewal, plan change, top-up, refill, prepaid card redemption — becomes a row in the invoices table. From here you create invoices manually, post payments (full or partial), regenerate PDFs in any of 17 paper sizes, send the receipt by WhatsApp, consolidate multiple unpaid invoices into one, and reconcile reseller commissions.
Invoices are first-class records: each carries a public token that lets the subscriber view or print their receipt without logging in (used by the QR code on the PDF), and a JSONB notification_log so you can see exactly which channels — email, SMS, WhatsApp — were used to deliver each copy.
How to get here
Section titled “How to get here”- Sidebar → Invoices (receipt icon).
- Direct URL:
/invoices. - From any subscriber’s detail page → Billing tab.
- From the Dashboard’s Unpaid Invoices card.
A subscriber’s reseller can view and act on their own subscribers’ invoices; admins see everything. The invoices.view / invoices.create / invoices.edit permissions gate access for resellers without those flags set.
Layout
Section titled “Layout”| Column | Description |
|---|---|
| Number | Auto-generated invoice number (configurable prefix + zero-padded sequence). |
| Subscriber | Name + username. Click to open the subscriber. |
| Issued / Due | created_at and due_date. Overdue rows are red. |
| Total / Paid / Balance | Three currency columns. Balance > 0 → still owed. |
| Status | pending, partial, paid, cancelled, refunded. |
| Channels | Pills showing which notification channels delivered the invoice. |
| Actions | View, PDF, Send WhatsApp, Add Payment, Delete. |
Filters at the top let you narrow by status, date range, reseller, subscriber, and amount range. The top toolbar surfaces Create Invoice, Consolidate Pending, and Export CSV.
Creating an invoice
Section titled “Creating an invoice”The POST /api/invoices handler accepts a subscriber ID, due date, optional notes, and a list of line items. Each item carries a description, quantity, unit price, and an optional tax percentage; the handler computes subtotal, tax, and grand total server-side so the totals shown in the UI always match what is stored. Currency, tax label, and decimal precision come from the Billing tab in Settings — they apply to every invoice the system produces.
Two flows generate invoices automatically — you rarely create them by hand:
- A subscriber renews → the renewal handler creates a
neworrenewaltransaction and an invoice withstatus=pendingfor the renewed amount. - A subscriber changes service plan → the prorated charge or credit becomes its own line item on a new invoice.
When you do create one manually, the form pre-fills the subscriber’s last plan price and lets you add ad-hoc items (installation, equipment sale, late fee).
PDF generation — 17 paper sizes
Section titled “PDF generation — 17 paper sizes”GeneratePDF does not render the PDF from scratch. Instead it points headless Chromium at the same web invoice view the operator sees (http://proxpanel-frontend/invoices/:id/print?token=...), waits for the page to settle, and prints to PDF. This guarantees the PDF is pixel-identical to the web version and updates automatically when you change the invoice template.
Selectable paper sizes in Settings → Billing → Invoice PDF:
| Region | Sizes available |
|---|---|
| ISO A | A3, A4, A5, A6 |
| ISO B | B4, B5 |
| US | Letter, Legal, Tabloid, Executive |
| Thermal / receipt | 58 mm, 80 mm |
| Custom | Width × height in mm |
Chromium runs with --no-sandbox --disable-gpu --hide-scrollbars --print-to-pdf-no-header --virtual-time-budget=5000. The virtual-time budget gives JavaScript (charts, QR rendering) up to 5 seconds to finish before the snapshot. For the consolidated-invoice page, which embeds multiple sub-invoices in iframes, the budget is raised so child frames finish loading.
QR codes and the public receipt URL
Section titled “QR codes and the public receipt URL”Each invoice has a public_token — a random 32-byte URL-safe string generated at creation time. The QR printed on the PDF encodes:
https://your-panel-host/p/invoice/<id>?token=<public_token>The corresponding public route (GET /invoices/public/:id) accepts the token, validates it with a constant-time compare to defeat timing attacks (audit 2026-05-11), and serves the invoice without requiring authentication. Subscribers scan the QR and see the receipt instantly — convenient for cash collectors who hand the PDF over after collecting payment.
If you suspect a leaked token, click Regenerate Token on the invoice — the old URL stops working and a new QR is printed.
Adding payments (full or partial)
Section titled “Adding payments (full or partial)”POST /api/invoices/:id/payments records a payment row. Partial payments are first-class:
| Field | Effect |
|---|---|
amount | Decremented from invoice.balance. |
method | cash, card, bank_transfer, prepaid_card, collector, online. |
reference | Free-text (cheque #, transaction ref). |
paid_at | Defaults to now; can backdate. |
After the payment is saved:
- If
balancereaches zero →statusis set topaid. - If still > 0 →
statusbecomespartial. - A matching
transactionsrow is inserted (income side) so revenue reports stay in sync. - If the invoice is for a renewal and is now fully paid →
autoRenewSubscriber()extends the subscriber’s expiry by the service period.
The QuickPay action on the subscriber detail page does the same in one click — useful when a customer walks in to pay an exact balance.
Prorated plan changes — auto-credit / auto-charge
Section titled “Prorated plan changes — auto-credit / auto-charge”When a subscriber changes service mid-cycle, the CalculateProrata endpoint determines whether to credit them for unused days on the old plan, charge them for the remaining days on the new plan, or both.
Formula:
unused_days = (expiry_date - today) clamped to [0, service_period]old_credit = (old_price / service_period) × unused_daysnew_charge = (new_price / service_period) × unused_daysprorate = new_charge - old_creditIf prorate > 0 → an invoice is created for the difference. If prorate < 0 → a credit-note transaction is posted to the subscriber’s account ledger. The UI shows the breakdown before the operator confirms, so the customer sees exactly why they owe (or are owed) what they are.
Consolidating pending invoices
Section titled “Consolidating pending invoices”Customers often accumulate a handful of small unpaid invoices over a few months. ConsolidatePending rolls them into one:
- All
pendingandpartialinvoices for the subscriber are gathered. - A new parent invoice is created with their original line items copied in.
- Each source invoice is marked
cancelledand linked to the parent viaconsolidated_into_id. - The new invoice carries one combined balance; the audit trail to the source invoices is preserved for accounting.
The collector can now hand the customer a single PDF instead of five.
Sending via WhatsApp
Section titled “Sending via WhatsApp”SendInvoiceWhatsApp posts to the Ultramsg-compatible gateway configured in Communication Rules. The handler:
- Calls
GeneratePDFToFileto render the PDF to a temp path. - Uploads the file to the gateway as a media attachment.
- Sends a template message (configurable per panel) that includes the public receipt URL.
- Appends to the invoice’s
notification_logJSONB: timestamp, channel=whatsapp, status=sentorfailed, gateway response, and the operator who triggered it.
If WhatsApp is unconfigured or the gateway returns an error, the failure is logged but the invoice itself is unchanged. The operator sees the error toast immediately.
SendCombinedInvoice does the same for a consolidated parent invoice — one message, one PDF, all sub-invoices included.
Reseller commissions
Section titled “Reseller commissions”GetCommissions returns a per-reseller breakdown for a chosen date range:
| Column | Source |
|---|---|
| Reseller | resellers table |
| Invoices issued | count of invoices where reseller_id = ? and created_at in range |
| Total billed | sum of total |
| Total collected | sum of paid_amount |
| Commission rate | reseller.commission_percent |
| Commission earned | total_collected × commission_percent / 100 |
| Already paid out | sum of transactions.type='commission_payout' |
| Outstanding | earned − paid out |
Click Mark Paid on a row to post a commission_payout transaction; the reseller’s balance is reduced and the outstanding amount drops to zero. The audit log records the payout under your username.
Common workflows
Section titled “Common workflows”Collect a partial payment and finish billing tomorrow
Section titled “Collect a partial payment and finish billing tomorrow”- Open the invoice, click Add Payment.
- Enter the amount the customer is paying today, method =
cash, reference = receipt book number. - Save. Invoice status flips to
partialand the balance updates. - The next day, repeat with the remaining amount. Status flips to
paidand the subscriber’s expiry is extended.
Mid-cycle plan upgrade
Section titled “Mid-cycle plan upgrade”- Open the subscriber → click Change Service → pick the new plan.
- Tick Calculate proration. The dialog shows old credit, new charge, and net amount.
- Confirm. The plan is changed, the prorated invoice is generated, and (if the subscriber is online) a CoA disconnect forces them to reconnect on the new pool.
- Add the payment when the customer pays.
Roll five unpaid invoices into one for collection
Section titled “Roll five unpaid invoices into one for collection”- Open the subscriber → Billing tab.
- Click Consolidate Pending. Confirm the prompt.
- A single new invoice replaces the five originals (which are cancelled and linked).
- Click Send WhatsApp to deliver the consolidated PDF + receipt URL.
Permissions
Section titled “Permissions”| Permission | What it gates |
|---|---|
invoices.view | See the Invoices list and detail pages. |
invoices.create | Create manual invoices and consolidate pending ones. |
invoices.edit | Edit due dates, line items, and post payments. |
invoices.delete | Soft-delete an invoice. Refunded invoices remain in the database. |
transactions.view_all | See all invoices system-wide, not only the reseller’s own. |
Resellers without invoices.view_all see only invoices for their own subscribers. Sub-resellers see only their parent’s subscribers when permitted.
Related pages
Section titled “Related pages”- Cash Collection — collectors mark invoices paid in the field and trigger auto-renewal.
- Reports — Revenue, Transaction, and Reseller Performance reports source their numbers from the
invoicesandtransactionstables. - Communication Rules — configures the WhatsApp gateway and templates used by
SendInvoiceWhatsApp. - Settings → Billing — currency, tax rate, invoice number format, paper size, bank details printed on PDFs.
- Subscriber detail — per-subscriber billing tab and QuickPay.