Cbox Billing
beta
Policy & thresholds
Policy & thresholds
The whole engine is driven by config('billing.approvals'). Every action ships disabled, so
an unconfigured deployment behaves exactly as before — no held requests, no regression. An
operator opts each sensitive action in.
'approvals' => [
'permission' => env('CBOX_BILLING_APPROVALS_PERMISSION', 'approvals:decide'),
'expire_after_days' => $intOrNull(env('CBOX_BILLING_APPROVALS_EXPIRE_DAYS')),
'actions' => [
'invoice.refund' => ['enabled' => false, 'threshold_minor' => null, 'required' => 1],
'wallet.adjust' => ['enabled' => false, 'threshold_minor' => null, 'required' => 1],
'customer.suspend' => ['enabled' => false, 'threshold_minor' => null, 'required' => 1],
'plan.archive' => ['enabled' => false, 'threshold_minor' => null, 'required' => 1],
],
],
Per-action keys
enabled— (defaultfalse). When false, the action executes directly, exactly as before. Turn it on to route the action through the gate.threshold_minor— when enabled, an amount at or above this floor (minor units) requires approval; an amount below runs directly.nullmeans no amount gate → every invocation of an enabled action requires approval. Actions with no money dimension (customer.suspend,plan.archive) ignore the floor and always hold when enabled.required— how many distinct operators must approve before the action runs (the M in an M-of-N maker-checker). Default1. The maker is never counted.
Global keys
permission— thefeature:actionslug a checker needs to decide (approve/reject). Enforced by the same flag-heldbilling.permissionmiddleware as the rest of the console — inert until Cbox ID emits apermissionsclaim andCBOX_ID_RBAC_ENFORCEis on. The two-person rule itself is always enforced in the service regardless.expire_after_days— a pending request auto-expires this many days after creation (null= never). An expired request never executes; sweep them with the scheduled sweep (see Held actions).
Example: refunds over kr 5.000 need one approval
CBOX_BILLING_APPROVE_REFUND=true
CBOX_BILLING_APPROVE_REFUND_THRESHOLD_MINOR=500000
A refund below kr 5.000,00 issues its credit note immediately; at or above it, the refund is held for a second operator and issues no credit note until approved.
The action-type keys (
invoice.refund, …) are literal dotted keys in theactionsarray — not a nestedinvoice → refundpath. Set them as whole keys when overriding config in a test:config()->set('billing.approvals.actions', ['invoice.refund' => [...]]).