Skip to content

Running Billing Day to Day ​

Once billing is set up and a gateway is connected, most of it runs itself. This guide covers what happens automatically, the screens you will use, and the handful of things you do by hand.

If you have not set billing up yet, start with Setting up the billing system.

What happens automatically ​

The interface runs a small set of scheduled jobs. You do not start them: they run as soon as billing is turned on, and they stop entirely when it is turned off.

JobWhenWhat it does
ReconcileEvery hourChecks that every customer is in the Octeth user group their subscription says they should be in, and moves any that have drifted.
Billing runEvery hourIssues invoices for subscriptions whose period has ended, and charges them.
DunningEvery hourChases failed payments, suspends customers past their grace period, and cancels those who never pay.
Reap stranded paymentsEvery hourCleans up payments that were interrupted midway, so none is left in limbo.
Drift reportDaily, 03:15Looks across every account for anything the hourly reconcile cannot see. Reports only, changes nothing.
Usage meteringDaily, 04:20Records yesterday's sending and subscriber usage for each account. Accounts that are not enabled in Octeth, such as signups that never verified their email address, are skipped.
Revenue snapshotDaily, 05:10Captures the day's revenue figures for the reporting screens.

INFO

The billing run deliberately waits about an hour after a subscription's period closes before invoicing it. That gives the usage metering time to write the final day of usage, so a bill that includes usage charges is calculated against complete figures rather than a partial day.

TIP

All of these are safe to run twice. If you ever need to run one by hand, for example after fixing a configuration problem, nothing is double-charged.

When a payment fails ​

The sequence is the same every time:

  1. The charge fails. The subscription is marked past due and the customer is emailed. They keep full access at this point, on purpose: someone who can still use the product is far more likely to go and fix their card than someone who has been cut off.
  2. The card is retried on each day in your retry schedule, 1,3,5 by default.
  3. If every retry fails, the account is suspended. The customer is moved to your suspended user group and emailed.
  4. If the grace period passes with no payment, the subscription is cancelled. The customer is moved to your cancelled group and emailed.

[[SCREENSHOT: The staff Worklists screen showing the past due and dunning list with customer names, amounts and next attempt dates]]

TIP

The customer can break this cycle themselves at any point. Their Billing overview has a Retry payment now button that runs the same charge immediately, so they do not have to wait for the next scheduled attempt.

You can change the retry schedule and the grace period in the staff sidebar under Billing, Settings, Billing policy. See Step 8 of the setup guide.

The screens you will use ​

Worklists ​

In the staff sidebar, Billing, then Worklists. This is the screen to open each morning. It shows four lists:

  • Past due and dunning, with the next retry date for each.
  • Suspended and in grace, the accounts about to be cancelled.
  • Stuck payments, anything that needs a human to look at it.
  • Upcoming renewals, so a large charge is never a surprise.

Transactions ​

In the staff sidebar, Billing, then Transactions. Every payment attempt across all customers, newest first, 50 to a page. The screen is read-only: open a customer to retry a payment or refund it.

Each row shows:

ColumnMeaning
Attempted atWhen the charge was attempted.
CustomerThe company name, the customer ID, the contact name and the email address. Click the name or the ID to open that customer. A customer that no longer exists shows Unknown customer.
PurposeInvoice, Plan change, Add-on change or Credit pack. An invoice payment shows the invoice number below, which downloads the invoice PDF. A purchase shows the product name.
AmountThe amount and currency.
StatusSucceeded, Failed, Pending or Unknown.
Gateway referenceThe payment processor's own ID for the charge.
Resolved atWhen the attempt reached its final status.

[[SCREENSHOT: The staff Transactions screen showing the filter bar, the per-currency summary line and a payment list with one Failed row]]

Filtering the list ​

Use the bar above the list:

  • All statuses and All purposes narrow the list to one status or purpose.
  • The date range starts at Last 30 days. Choose Last 7 days, Last 90 days, Last 12 months, All time or Custom range. A custom range shows a from date and a to date, and each one includes the whole day.
  • Customer ID shows one customer's payments. Enter the numeric ID.
  • Gateway reference finds payments whose reference contains the text you type.

Click Clear filters to go back to the default view. The filters are kept in the page address, so you can bookmark or share a filtered view.

Totals by currency ​

The line above the list counts the payments that match your filters. For succeeded payments, it adds a count and a total for each currency, for example Succeeded USD: 42 totalling $4,180.00. Amounts in different currencies are never added together.

Why a payment failed ​

A Failed status with a small information icon carries the payment processor's error message. Hover over the status to read it.

INFO

The processor's error text is shown to staff only. Customers never see it.

Revenue ​

In the staff sidebar, Billing, then Revenue. Monthly and annual recurring revenue, average revenue per account, your plan mix, revenue over time, revenue at risk, outstanding credit liability, why customers cancelled, and how your recurring revenue moved over the last 30 days.

Revenue over time is net of refunds. A refund is deducted on the day it completed, so a day with a large refund can show negative revenue.

[[SCREENSHOT: The staff Revenue dashboard showing the MRR and ARR cards, the revenue over time chart and the plan mix breakdown]]

INFO

The revenue figures are built from the daily snapshot, so a brand new install shows an empty chart until the snapshot job has run a few times. That is expected, not a fault.

Health ​

In the staff sidebar, Billing, then Health. A read-only check of your gateway wiring, tax setup, incoming payment notifications, scheduled job timing and outstanding readiness problems.

TIP

Check this screen after any change to plans, groups or gateway settings. It is the fastest way to find a configuration gap, and it never changes anything itself.

One customer ​

In the staff sidebar, Users, then pick a customer. This shows their subscription, their invoices, their payments and their usage, and lets you download any of their invoices as a PDF.

[[SCREENSHOT: The staff customer detail screen showing the subscription summary, invoice list and usage figures]]

The Lifetime paid figure is net of refunds.

Refunding a payment ​

In the customer's payments list, press Refund next to a succeeded payment. Leave the amount blank to refund everything still left on that payment, or enter a smaller amount in cents for a partial refund. The reason field is optional and only staff see it. The dialog shows how much has already been refunded and how much can still be refunded, and it refuses an amount above that.

Every refund is recorded against the payment, which then shows Partially refunded or Refunded with the amount. Once a payment is fully refunded, its Refund button no longer appears. The payment's own status stays Succeeded, because the charge did succeed.

[[SCREENSHOT: The staff customer payments list showing a payment with a Partially refunded pill and the Refund dialog open with the remaining refundable amount]]

INFO

A refund is shown as Refund pending confirmation in two cases. If the processor accepts the refund but reports it as still pending, the amount counts against what can still be refunded, but it is not deducted from lifetime paid or revenue until the processor confirms it. If the processor does not answer in time, the Refund button is also hidden for that payment, because the money may already have been returned. In both cases the processor's webhook settles the refund. For Stripe, check that the refund events are enabled on your webhook endpoint (see Step 4 of Connecting a Payment Gateway).

Clearing a refund that never reached Stripe ​

If your request timed out before Stripe answered, the refund stays at Refund pending confirmation and the Refund button stays hidden. When Stripe never received the request, no webhook arrives to settle it. Mark as failed clears it.

The button appears next to that payment 15 minutes after you requested the refund. Before then, the row shows Can be marked as failed after with the time.

  1. Click Mark as failed.
  2. Confirm the message Check this refund with the payment processor? If the processor holds no such refund, it is marked as failed and the payment can be refunded again.

Octeth asks Stripe for every refund on that payment, then:

  • If Stripe holds no such refund, the refund is marked as failed. The Refund button returns, so you can refund the payment again.
  • If Stripe did receive it, Octeth records Stripe's result instead and does not mark it failed. The message tells you whether the refund succeeded, is still processing or failed.
  • If Stripe cannot be reached, nothing changes. Try again shortly.

Both outcomes that change the refund are recorded in the staff audit log with your name against it.

INFO

Mark as failed is available for Stripe only, and only for refunds issued from this screen. For an accept.blue payment, check the accept.blue dashboard instead. A payment with no processor reference shows No processor reference to check this refund against.

Refunds you issue in the Stripe dashboard are recorded the same way, from Stripe's webhook, so they also reduce the refundable amount, lifetime paid and revenue. Refunds issued in the accept.blue dashboard are not recorded yet: issue accept.blue refunds from this screen.

Discount codes ​

In the staff sidebar, Billing, then Discounts.

Create a code and set:

FieldMeaning
CodeWhat the customer types.
TypeA percentage off, or a fixed amount off.
ValueThe percentage or the amount.
DurationHow many billing periods it applies to. Leave it empty for forever.
Maximum redemptionsA cap on how many customers can use it. Leave it empty for no cap.
Valid from and untilAn optional window, for a scheduled promotion. Leave both empty and the code always works.

[[SCREENSHOT: The staff Discounts screen showing the code list with type, value, redemption count and status]]

You can expire a code, reactivate it, change its redemption cap, and see exactly who redeemed it.

TIP

Every one of these actions is recorded in the staff audit log with your name against it. That is worth knowing before you change a live code.

Add-ons and credit packs ​

Alongside plans, your catalog can hold:

  • Add-ons, which a customer keeps alongside their plan and which can have a quantity, for example dedicated IP addresses.
  • Credit packs, a one-off purchase of a credit balance the customer draws down as they use it.

You create both on the Catalog screen, the same way you create a plan. Customers buy them from the Add-ons tab of their own billing section.

WARNING

Add-ons and credit packs are not created by the starter catalog. If you want to sell them, you create them yourself.

Moving an existing customer base onto billing ​

If you already have customers, each with their own Octeth user group, you probably want to put them all on a single grandfathered plan without moving anyone or changing anyone's sending limits.

There is a command for exactly this. It links every existing Octeth user group to a plan you name, using the Member role, so the hourly reconcile sees each account as already in a valid group and leaves it alone.

Preview what it would do:

bash
docker exec oempro_ui bash -c 'cd /var/www/html/ui && php artisan billing:link-groups --plan=plan.managed --dry-run'

Apply it:

bash
docker exec oempro_ui bash -c 'cd /var/www/html/ui && php artisan billing:link-groups --plan=plan.managed --force'

Use --exclude= with a comma-separated list of group ids to skip any group that should not be linked, such as a shared group or your suspended group.

INFO

A dry run is the default, so the command never writes anything unless you pass --force. It also never creates, changes or deletes an Octeth user group, and never moves an account.

DANGER

This command does not set the plan's Default link, on purpose. Deciding where new customers land is a choice you should make deliberately on the Catalog screen, not something to infer from a sweep of your existing groups.

Running a job by hand ​

Occasionally you will want to run one of the scheduled jobs immediately, usually just after fixing a configuration problem. Each of these is safe to run at any time:

bash
# Put every account back in the group its subscription says it should be in
docker exec oempro_ui bash -c 'cd /var/www/html/ui && php artisan billing:reconcile'

# Issue and charge any invoices that are due
docker exec oempro_ui bash -c 'cd /var/www/html/ui && php artisan billing:run'

# Chase failed payments, suspend and cancel as the policy dictates
docker exec oempro_ui bash -c 'cd /var/www/html/ui && php artisan billing:dunning'

# Record yesterday's usage for every account
docker exec oempro_ui bash -c 'cd /var/www/html/ui && php artisan billing:meter'

# Report on accounts that have drifted, without changing anything
docker exec oempro_ui bash -c 'cd /var/www/html/ui && php artisan billing:backfill --dry-run'

WARNING

Each of these commands does nothing at all when billing is turned off, and says so. If a command appears to run and produce no output, check BRAND_FEATURE_BILLING first.

Troubleshooting ​

A customer is in the wrong Octeth user group ​

Run the reconcile command above and check the output. If it reports an error for that account, the usual cause is a plan with no Default group link. Fix it on the Catalog screen and run reconcile again.

Nobody is ever suspended for not paying ​

In the staff sidebar, check Billing, Settings, User groups, and confirm the Suspended group is set. If it is empty and the customer's plan has no suspended link of its own, there is nowhere to move them to. The Billing health screen reports this.

The revenue charts are empty ​

The daily snapshot has not run enough times yet. Wait a day, or run the snapshot by hand:

bash
docker exec oempro_ui bash -c 'cd /var/www/html/ui && php artisan billing:mrr-snapshot'

Invoices show no tax ​

That is the current behaviour: no tax vendor is connected and every invoice is calculated with zero tax. See the tax note in Setting up the billing system.

Any questions? Contact us.