BillKit/Docs Console
SDK

Laravel

Cashier-shaped billing layer with a Billable trait, an Eloquent Subscription model and a verified webhook route.

v0.4.0 Laravel 11 or 12, PHP 8.2+ Beta Source

Install

composer require billkit-eu/billkit-laravel

Then run the migration. It creates billkit_subscriptions and adds billkit_customer_id to your billable table.

php artisan migrate

The service provider is auto-discovered. Publish the config with php artisan vendor:publish --tag=billkit-config if you want to change it, and set your credentials.

ini
BILLKIT_API_KEY=bk_test_...
BILLKIT_WEBHOOK_SECRET=bkwhsec_...

The package wraps billkit-eu/billkit-php, which it pulls in for you. Reach the raw client any time with $user->billkitClient().

Make a model billable

php
<?php

use BillKit\Laravel\Billable;

class User extends Authenticatable
{
  use Billable;
}

Start a subscription

BillKit provisions subscriptions from the inbound payment webhook. There is no synchronous “create subscription” call, so the flow is checkout, then redirect, then your webhook creates the local Subscription. That is the Cashier model exactly.

checkout() returns a Checkout, which is Responsable, so a controller can return it and Laravel does the redirect.

php
return $request->user()->checkout('price_9XKp2vQ1', [
  'success_url' => route('billing.done'),
  'cancel_url'  => route('pricing'),
  'trial_days'  => 14,
  'coupon_code' => 'LAUNCH',
]);

One-off charges

charge() is the mandate-less single payment, Cashier’s charge() in shape. It is redirect-based too, and settles through the one_shot_payment.succeeded and one_shot_payment.failed webhooks.

php
return $request->user()->charge(1999, 'EUR', 'creditcard', [
  'success_url'        => route('thanks'),
  'cancel_url'         => route('cart'),
  'description'        => 'One premium widget',
  'refund_window_days' => 30, // 0 disables refunds; default 30; max 365
]);

// Later, within the window:
$user->refundOneShot('osp_9XKp2vQ1');                        // full
$user->refundOneShot('osp_9XKp2vQ1', ['amount_cents' => 500]); // partial

Gate access

php
if ($user->subscribed()) {
  // active, trialing, past_due, or inside a cancellation grace period
}

$user->onTrial();
$user->onGracePeriod();

Manage a subscription

php
$sub = $user->subscription();      // BillKit\Laravel\Subscription

$sub->previewSwap('price_pro_yearly'); // quote the proration first
$sub->swap('price_pro_yearly');        // change plan
$sub->cancel();                        // at period end
$sub->reactivate();                    // undo a scheduled cancellation
$sub->pause();
$sub->resume();

// Both return a URL to redirect to:
return redirect($sub->updatePaymentMethod(route('billing')));
return $sub->redirectToBillingPortal(route('billing'));

Webhooks

The package registers POST /billkit/webhook, verifies the signature and keeps the local Subscription rows in sync. Point a BillKit webhook endpoint at that URL and set BILLKIT_WEBHOOK_SECRET.

For anything the package does not handle itself, listen for the dispatched event.

php
use BillKit\Laravel\Events\WebhookReceived;

Event::listen(function (WebhookReceived $event) {
  if ($event->payload['type'] === 'invoice.paid') {
      // $event->payload['data'] is the invoice
  }
});

Logging

Off unless you name a channel from config/logging.php. A dedicated channel keeps billing traffic out of your main log.

ini
BILLKIT_LOG_CHANNEL=billkit

You then get one debug record per HTTP attempt and per response, and one warning per retry. The channel’s own level still applies. API keys, bodies and query strings are never written.