Skip to content

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 (default true, unchanged behaviour). false numbers 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_receipt issue nothing while the seller has no name.

  • 0.12.9: billing:reconcile-pending-payments measures age from updated_at instead of created_at — any update to a pending payment postpones its reconciliation by reconcile_after_minutes.

  • 0.12.8: Price::delete() and Plan::delete() throw BillingException when the price (or one of the plan’s prices) has subscriptions. Before, the database cascaded and deleted them. The published migration now creates billing_subscriptions.price_id with restrictOnDelete(); 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 until trial_ends_at instead of cancelling now; resume() restarts a period that ran out during the pause.

  • 0.12.6: billing:health without 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 to past_due on the next run.

  • 0.12.4: a failed payment against an active subscription 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 by attachPaymentMethod() before the upgrade still hold the class name and stay invisible to $billable->paymentMethods and 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_invoice and InvoiceItemsContract.
  • New optional group billing-migrations-invoices (billing_invoices, billing_document_sequences) and billing.invoices.* config — publish both and set BILLING_INVOICES_ENABLED=true to 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 --fresh it now updates events in place) to subscribe to charge.refunded, invoice.paid and customer.subscription.*.
  • New columns: billing_subscriptions.provider_synced_at (nullable datetime) and an index on (gateway, external_id).
  • SubscriptionGatewayContract is deprecated in favour of StartsProviderSubscriptions + ManagesProviderSubscriptions.
  • cancel()/pause()/resume()/swapPlan() on a row with external_id now go to the driver and throw NotSupportedException when 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') (and WebhookTenant::fromRequest()), not a hard-coded name.
  • Refund rows may be pending (Paddle) — filter $payment->refunds by 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 created trialing rows with a past trial_ends_at, create them as incomplete instead.
  • 0.8.1: charge() resets a non-paid payment to pending when it issues a checkout.
  • TrialEnded event; billing:expire-trials expires trials row by row.
  • Every path into canceled goes through markCanceled(), which clears next_retry_at and grace_ends_at.
  • New column billing_payments.initiation (nullable string(20)).
  • 0.6.1: billing_payment_methods unique index is now (gateway, billable_type, billable_id, external_customer_id, external_id), and external_customer_id/external_id are varchar(191).
  • 0.6.2: tenant_id is 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, due pause_ends_at) instead of waiting for the scheduler.
  • billing:expire-trials and the new billing:send-period-notices run hourly.
  • New columns: billing_prices.period_ending_notices, billing_subscriptions.period_notices_sent.
  • SubscriptionRenewed has previousStatus.
  • Hutko refunds via the API.
  • billing.retry_interval_hours / BILLING_RETRY_INTERVAL_HOURS replaced by billing.retry_intervals (a list); new column billing_prices.retry_intervals.
  • max_recurring_attempts defaults to 4 and counts attempts, not retries.
  • grace_ends_at = next_retry_at + grace_period_days.
  • An unparsable or non-positive interval throws.
  • RenewalChargeOptionsContract, billing.renewal.receipt_items for fiscal baskets on renewals.