Skip to main content

ArtinTech Solution

MikroTik billing automation in Linkhive: routers page with online status, customer count, RouterOS version and CPU load

An internet provider has two sources of truth: the billing system and the router. MikroTik billing automation is the work of keeping them in agreement, and the expensive bugs live in the gap between them: a customer who paid but is still cut off, a customer who stopped paying but stays online, or a renewal applied twice because a payment gateway retried its webhook. When we built Linkhive, our self-hosted Laravel billing platform for broadband and Wi-Fi providers, these three failures shaped the design more than any feature did.

MikroTik billing automation starts with steps that are safe to run twice

Linkhive’s billing cycle is an ordered list of steps: create renewal invoices, auto-renew from prepaid balance, fulfil paid invoices, send reminders, expire and suspend, then handle vouchers. The scheduler runs it every 15 minutes, and on hosting without cron the same cycle runs from web traffic at most every 15 minutes.

Frequent runs are only safe if a second run changes nothing. So each step asks whether its work is already done. Before invoicing a renewal, it looks for an invoice for that customer, package and period. Before sending a reminder, it checks the message log for one sent today. Each step also runs inside its own try/catch: a failure is logged and the remaining steps still run, so one bad record cannot stop every suspension on the network.

The timing is configurable. By default an invoice is raised three days before expiry, reminders go out three days and one day before, and service stops after two grace days.

Linkhive customer account showing package, expiry date, prepaid balance with auto-renew on, and invoice history
A demo customer two days from expiry, with auto-renew on and the next invoice already raised.

One atomic update stops a double renewal

A single payment can reach the system by three routes at once: the customer’s browser returning from the gateway, the gateway’s webhook, and the billing cycle picking up a paid invoice. If each route checks “has this been renewed?” and then renews, two of them can pass the check together and the customer gets two months for the price of one.

A check followed by a write leaves that gap, so we made the check and the write one statement:

$claimed = Invoice::whereKey($invoice->id)
    ->whereNull('fulfilled_at')
    ->update(['fulfilled_at' => now()]);

if ($claimed === 0) {
    return false; // another request got here first
}

The database lets only one UPDATE match that row, whichever request arrives first. The others see zero affected rows and stop. Payments get similar treatment: approvePayment() locks the row with lockForUpdate() inside a transaction, so calling it twice applies the money once.

Ask the gateway, never the browser

Anything arriving on a return URL can be forged, so it is treated as a hint and nothing more. For Stripe, Linkhive stores the Checkout session ID when the payment starts, then fetches that session from Stripe’s API on return. The money is booked only if the session matches and Stripe reports it as paid, with the right reference, the exact amount and the right currency. PayPal follows the same pattern, and the SSLCommerz, bKash and Nagad drivers confirm the amount and reference with their own APIs.

Linkhive invoice list with invoice numbers, customers, due dates, balances and unpaid status
Invoices stay unpaid until a staff approval or the gateway's own API says otherwise.

An offline router must not roll back a payment

The tempting design talks to the router inside the payment transaction. Then a router reboot turns a successful card payment into an error page. In Linkhive, every router change goes through one class, Provisioner, and it never throws. The money is recorded first. If the router cannot be reached, the failure is written to a provisioning log and the subscription is flagged for retry.

An hourly job retries flagged subscriptions for up to three days. It reads the subscription’s current state instead of replaying the old command: an active subscription is activated and any other is suspended. If a customer paid while the router was down, the retry reconnects them and does not apply the stale suspension.

For MikroTik billing automation, the driver works through the RouterOS API, on port 8728 or over TLS on 8729, with a client we wrote ourselves. It updates /ppp/secret for PPPoE or /ip/hotspot/user for hotspot users, then drops the active session so the customer reconnects on the right profile. FreeRADIUS sits behind the same driver interface, so the billing code does not care which one a provider runs.

MikroTik billing automation in Linkhive: routers page with online status, customer count, RouterOS version and CPU load
Router health is checked every five minutes. The routers in the demo are simulated.

Prove it with tests that attack the rules

Rules like these fail silently, so MikroTik billing automation needs tests that try to break them. Linkhive ships with 147 automated tests that pass on SQLite and on MySQL 8.4. They run the billing cycle repeatedly to check idempotency, send forged gateway returns, and try amount and currency mismatches. Static analysis with Larastan at level 5 reports no errors.

Linkhive admin dashboard in dark theme with active customers, collections, recurring revenue and outstanding balance
The operator's view of the demo network, in the dark theme.

Key takeaways

  • Write billing steps so that a second run changes nothing, then run them often.
  • Claim work with a single atomic UPDATE, not a read followed by a write.
  • Confirm every payment with the gateway’s API, including the amount and currency.
  • Keep router calls out of payment transactions, and record failures for retry.
  • Retry from current state, never by replaying an old command.

You can see all of this at work in the Linkhive live demo, read the feature overview on Charming Theme, or get Linkhive on Vibe96.

Running a broadband or Wi-Fi network and outgrowing spreadsheets? Let’s Talk. ArtinTech Solution builds this kind of billing and integration work through our Laravel development service.

Share this article