Upgrading
The package is pre-1.0: the API and schema may change between minor versions. The full list is in the CHANGELOG.
-
billing.invoices.number_per_tenant(defaulttrue, unchanged behaviour).falsenumbers all tenants’ documents in one sequence. -
0.12.1: an invoice issued for an already paid payment no longer names the subscription’s (next) period.
-
0.12.2:
auto_invoice/auto_receiptissue nothing while the seller has no name. -
0.12.9:
billing:reconcile-pending-paymentsmeasures age fromupdated_atinstead ofcreated_at— any update to a pending payment postpones its reconciliation byreconcile_after_minutes. -
0.12.8:
Price::delete()andPlan::delete()throwBillingExceptionwhen the price (or one of the plan’s prices) has subscriptions. Before, the database cascaded and deleted them. The published migration now createsbilling_subscriptions.price_idwithrestrictOnDelete(); an existing database keeps the cascade, which still applies to deletes bypassing the models. To change it:Schema::table('billing_subscriptions', function (Blueprint $table) {$table->dropForeign(['price_id']);$table->foreign('price_id')->references('id')->on('billing_prices')->restrictOnDelete();}); -
0.12.7:
cancel()on a trial keeps it untiltrial_ends_atinstead of cancelling now;resume()restarts a period that ran out during the pause. -
0.12.6:
billing:healthwithout an argument skips gateways with no secret credentials. -
0.12.5: package-managed subscriptions on a gateway without off-session charges (Paddle, custom drivers) enter dunning when their period ends instead of staying
active. Check for such rows before upgrading — overdue ones go topast_dueon the next run. -
0.12.4: a failed payment against an
activesubscription before its period ends no longer starts dunning. -
0.12.3: with a morph map, drivers now store the billable’s alias instead of its class name in
billing_payment_methods.billable_type. Without a morph map nothing changes. With one, rows saved byattachPaymentMethod()before the upgrade still hold the class name and stay invisible to$billable->paymentMethodsand renewals — rewrite them once:use Fomvasss\Billing\Models\PaymentMethod;use Illuminate\Database\Eloquent\Relations\Relation;foreach (Relation::morphMap() as $alias => $class) {PaymentMethod::where('billable_type', $class)->update(['billable_type' => $alias]);}If a billable then has two rows for the same card (one from a checkout, one attached by hand), delete the duplicate. On Stripe, a billable may also have extra customers created before the fix; they are harmless, the package reuses the customer of the billable’s first saved card.
issueInvoice()accepts a paid payment (the invoice comes out paid) instead of throwing.- New
billing.invoices.auto_invoiceandInvoiceItemsContract.
0.10 — invoices
Section titled “0.10 — invoices”- New optional group
billing-migrations-invoices(billing_invoices,billing_document_sequences) andbilling.invoices.*config — publish both and setBILLING_INVOICES_ENABLED=trueto use them.
0.9 — Paddle, provider-managed subscriptions
Section titled “0.9 — Paddle, provider-managed subscriptions”- Stripe API version pinned to
2026-08-26.dahlia. Re-create an existing endpoint:php artisan billing:stripe-register-webhook --fresh, then store the new signing secret. The command warns when an endpoint’s version differs. - Re-run
billing:stripe-register-webhook(without--freshit now updates events in place) to subscribe tocharge.refunded,invoice.paidandcustomer.subscription.*. - New columns:
billing_subscriptions.provider_synced_at(nullable datetime) and an index on(gateway, external_id). SubscriptionGatewayContractis deprecated in favour ofStartsProviderSubscriptions+ManagesProviderSubscriptions.cancel()/pause()/resume()/swapPlan()on a row withexternal_idnow go to the driver and throwNotSupportedExceptionwhen it can’t — they no longer change only the local row. The payment-outcome listener now skips such rows.- Custom signature validators should resolve credentials by
$request->route('gateway')(andWebhookTenant::fromRequest()), not a hard-coded name. - Refund rows may be
pending(Paddle) — filter$payment->refundsby status.refundableRemainder()reserves pending ones;Payment::recordRefundOf()takes a status. - Paddle setup:
billing:paddle-register-webhook, the default payment link, domain approval.
- New status
SubscriptionStatus::Incomplete(no migration — plain string column). If your checkout createdtrialingrows with a pasttrial_ends_at, create them asincompleteinstead. - 0.8.1:
charge()resets a non-paid payment topendingwhen it issues a checkout.
TrialEndedevent;billing:expire-trialsexpires trials row by row.- Every path into
canceledgoes throughmarkCanceled(), which clearsnext_retry_atandgrace_ends_at.
- New column
billing_payments.initiation(nullable string(20)). - 0.6.1:
billing_payment_methodsunique index is now(gateway, billable_type, billable_id, external_customer_id, external_id), andexternal_customer_id/external_idarevarchar(191). - 0.6.2:
tenant_idis derived from the billable on create. Backfill old rows yourself if reports need them.
- New columns:
billing_prices.quota_interval,quota_interval_count;billing_subscriptions.quota_period_ends_at. - New command
billing:reset-usage-quotas(scheduled hourly). - 0.5.2:
ReissueChargeOptionsContract(default unchanged).
isActive()/active()derive access from the row’s dates (trial end,cancels_at, duepause_ends_at) instead of waiting for the scheduler.billing:expire-trialsand the newbilling:send-period-noticesrun hourly.- New columns:
billing_prices.period_ending_notices,billing_subscriptions.period_notices_sent. SubscriptionRenewedhaspreviousStatus.
- Hutko refunds via the API.
0.2 — retry ladder
Section titled “0.2 — retry ladder”billing.retry_interval_hours/BILLING_RETRY_INTERVAL_HOURSreplaced bybilling.retry_intervals(a list); new columnbilling_prices.retry_intervals.max_recurring_attemptsdefaults to4and counts attempts, not retries.grace_ends_at=next_retry_at+grace_period_days.- An unparsable or non-positive interval throws.
RenewalChargeOptionsContract,billing.renewal.receipt_itemsfor fiscal baskets on renewals.