Concorbit HelpAll guides →

Operator Runbook

Billing operator runbook

This page documents the artisan commands an operator runs against the billing system: when to run each, what it does, and what to expect. None of these commands are scheduled, they are explicit operator actions for repair, backfill, or pre-flight checks.

All commands accept --help for their option list. The descriptions here cover the use cases.

concorbit:billing-orphan-payment-methods

php artisan concorbit:billing-orphan-payment-methods

Read-only. Lists payment methods whose stored stripe_account_id is NULL (legacy) or does not match the tenant's current stripe_connect_account_id (Stripe Connect rotation since the card was saved). The autopay path refuses to charge orphans; this command reports who is affected so operations can prompt customers to re-save their card before the next billing cycle.

Output is per-tenant, with a count and a sample of up to five orphan PM ids:

[acme] Acme MSP, 4 orphan PM(s); sample ids: 12, 14, 18, 22, 31
Total orphans across tenants: 4

A (tenant disconnected) note appears when the tenant has no current Stripe Connect account. Every active PM is orphaned in that case until a reconnect.

concorbit:billing-repair-orphan-blocks

Recovery for invoices that are orphan-blocked (autopay refused them because their stamped PM is on the wrong Stripe Connect account).

--contact mode

php artisan concorbit:billing-repair-orphan-blocks --contact=<contact-uuid>

Re-runs OrphanRecoveryService::recoverForContact, the same logic that fires automatically after a contact saves a new payment method. Use when the post-save sweep failed (you see autopay.save.orphan_recovery_failed in the logs) and the contact already has a current-account default PM on file.

The command refuses if the tenant has no current Stripe Connect account (cannot repair against an unscoped lookup) or if the contact has no active default PM stamped against the current account. Add a card first.

--invoice mode

php artisan concorbit:billing-repair-orphan-blocks --invoice=<invoice-uuid> --override

Bypass mode. Clears the orphan marker on a single invoice and dispatches its retry job, regardless of which PM was originally blocked. Use when:

  • The blocked PM was hard-deleted (the FK was nulled) so the lineage cannot be matched.

  • The blocked PM belonged to a different contact, for example a company-default-PM rotation, that the contact-scoped recovery cannot reach.

--override is required and writes a distinct audit action (override = true) so the bypass is visible in the audit log. Without --override the command refuses.

concorbit:backfill-payment-method-accounts

php artisan concorbit:backfill-payment-method-accounts

One-shot post-deploy backfill. Stamps payment_methods.stripe_account_id on legacy rows from the durable stripe_customers table. Idempotent (only touches NULL stamps), chunks by id at 500 per chunk, and explicitly scopes per tenant so the HKDF-derived encryption key resolves correctly for each PM.

This was the one-shot CB#5 backfill at rollout time. Run it after a deploy that adds the column; re-runnable safely if you suspect new NULL stamps have appeared.

Output:

Backfill complete: stamped=2, missed=0

A missed row is one where the durable stripe_customers row could not be located. The cb5.backfill.miss log line records the details.

concorbit:billing-cutover-backfill-modes

php artisan concorbit:billing-cutover-backfill-modes [--tenant=<uuid>] [--dry-run]

The pre-enable safety stamp for the Cutover flag. Stamps at_mirrored on AT-linked companies and contracts that are still defaulting concorbit_native because they were linked in the gap between the 7a seed migration and the 7c first-link stamp.

Run with --dry-run first. If it reports zero, you are clean. Then run live to commit any stamps.

Idempotent: re-running stamps nothing once clean. Scopes that were deliberately flipped to native (have a non-reverted cutover ledger row) are never re-stamped.

Run this immediately before flipping BILLING_CUTOVER_NATIVE_GENERATION_ENABLED=true.

concorbit:autotask-reconcile-charge-billing

php artisan concorbit:autotask-reconcile-charge-billing [--kind=product|contract|labour|all] [--tenant=<uuid>] [--queue=<concorbit-queue-uuid>] [--allow-unmigrate] [--dry-run]

Reconciles imported AT charge statuses against Autotask's BillingItems (the authoritative invoiced ledger). Billed BillingItems flip the matching concorbit Charge to migrated; not-billed flip it to pending.

--kind selects the family: product (TicketCharges), contract (ContractCharges), labour (TimeEntries), or all.

--allow-unmigrate allows billed, pending downgrades. Off by default for double-bill safety. Use only when you know a previous reconciliation falsely promoted a charge.

--dry-run prints the would-do counts without writing.

Scheduled to run nightly per tenant, but useful manually after a fresh AT pull when you want a quick consistency check.

When to reach for what

SymptomCommand
Customer's autopay refusing on a saved card; no orphan banner shown to the customerconcorbit:billing-orphan-payment-methods to confirm, then have them re-save.
Saved-card flow logged autopay.save.orphan_recovery_failedconcorbit:billing-repair-orphan-blocks --contact=<uuid>
Single invoice stuck orphan-blocked but the lineage is deadconcorbit:billing-repair-orphan-blocks --invoice=<uuid> --override
Just deployed CB#5; backfill the columnconcorbit:backfill-payment-method-accounts
About to enable native generation across the tenantconcorbit:billing-cutover-backfill-modes --dry-run, then --tenant= live, then set the env var.

For the user-facing operations (recording payments, voiding, refunds), see Payments. For dispute workflow, see Disputes.