The billing engine is the core of any ISP management system. But billing for a Bangladeshi ISP has unique requirements that off-the-shelf tools don't handle well.

What the billing engine needs to do:

  • Auto-generate invoices for hundreds of customers on their billing date (monthly, anniversary, or custom cycle)
  • Support proration when a customer changes package mid-cycle
  • Apply late fees automatically after a configurable grace period
  • Accept payments via bKash, Nagad, SSLCommerz, AmarPay, cash, and bank transfer
  • Mark the invoice paid, trigger MikroTik reactivation, and send an SMS confirmation — atomically
  • Generate branded PDF invoices and receipts
  • Produce aging reports (current, 30, 60, 90+ days overdue)

The scheduler

A GenerateDueInvoices command runs via Laravel Scheduler at midnight every day. It checks the customers table for every active subscriber whose next_billing_date is today, calculates the invoice amount (base package + add-ons + VAT + any outstanding late fee), generates an Invoice record and InvoiceItem rows, and dispatches an InvoiceGenerated event that triggers an SMS notification to the customer.

Proration

When a customer upgrades from a ৳500 to a ৳800 package on day 15 of a 30-day cycle, they've already paid for 15 days at ৳500 and owe for 15 days at ৳800. The proration formula:

Credit = (days_remaining_at_old_rate / days_in_cycle) × old_package_price
Charge = (days_remaining / days_in_cycle) × new_package_price
Prorated amount = Charge - Credit

This generates a one-time invoice for the difference, then resets the next billing date.

Payment gateway integration

For online payment, the customer portal redirects to SSLCommerz or AmarPay with the invoice total. On payment success, the gateway fires a webhook to our endpoint. We validate the signature, mark the invoice paid, fire InvoicePaid, and let the event listener handle MikroTik reactivation and SMS confirmation.

For bKash and Nagad, we use their payment gateway APIs — customers enter their mobile wallet number, receive an OTP on their phone, and confirm. The callback triggers the same event pipeline.

The atomic guarantee: the MikroTik reactivation job is dispatched inside a database transaction with the payment record. If the database write fails, the job doesn't fire. If the job fails, the failure is logged and retried — the payment record is already committed.

---

Mustafa Kamal Hossain

Mustafa Kamal Hossain

Founder & Principal Engineer at Manfi. Passionate about Laravel, SaaS architecture, and high-performance engineering.