veekthoven/laravel-cashier-bachs
Composer 安装命令:
composer require veekthoven/laravel-cashier-bachs
包简介
Laravel Cashier Bachs provides an expressive, fluent interface to Bachs' subscription billing services.
关键字:
README 文档
README
Laravel Cashier Bachs provides an expressive, fluent interface to Bachs' subscription billing services — modeled after Laravel Cashier for Stripe. It handles Bachs customers, hosted checkouts, one-time payments, subscriptions with trials, plan swaps with proration, cancellation grace periods, and webhook-driven state syncing.
Note
In Bachs, subscriptions are always started by the customer completing a hosted checkout for a recurring product. This package creates the checkout session and then keeps your local subscription records in sync via webhooks.
Installation
Install the package via composer:
composer require veekthoven/laravel-cashier-bachs
Run Cashier's migrations, which add a bachs_id and trial_ends_at column to your users table and create the subscriptions table:
php artisan migrate
You may publish the migrations to customize them:
php artisan vendor:publish --tag="cashier-migrations"
And optionally the config file:
php artisan vendor:publish --tag="cashier-config"
Configuration
Add your Bachs credentials to your .env file:
BACHS_API_KEY=sk_sandbox_... BACHS_WEBHOOK_SECRET=whsec_...
Keys prefixed with sk_sandbox_ are automatically routed to the Bachs sandbox API and sk_live_ keys to production — no extra configuration needed.
Billable model
Add the Billable trait to your model:
use Veekthoven\CashierBachs\Billable; class User extends Authenticatable { use Billable; }
If your billable model is not App\Models\User, register it in a service provider:
use Veekthoven\CashierBachs\Cashier; Cashier::useCustomerModel(Team::class);
Customers
// Create the customer in Bachs (uses the model's email, name and phone)... $user->createAsBachsCustomer(); // Or with overrides... $user->createAsBachsCustomer(['name' => 'Jane Doe']); $user->hasBachsId(); $user->bachsId(); // Retrieve the raw Bachs customer payload... $customer = $user->asBachsCustomer(); // Push local changes to Bachs... $user->syncBachsCustomerDetails();
The trait reads email, name and phone from your model by default. Override bachsEmail(), bachsName() or bachsPhone() to customize.
Subscriptions
Creating subscriptions
Subscriptions start with a hosted checkout for a recurring Bachs product:
use Illuminate\Http\Request; Route::get('/subscribe', function (Request $request) { return $request->user() ->newSubscription('default', 'prod_abc123') ->checkout([ 'success_url' => route('billing.success'), 'cancel_url' => route('billing.cancel'), ]); });
The Checkout object is responsable — returning it redirects the customer to the Bachs-hosted checkout page. You can also grab the URL yourself with $checkout->url().
Once the customer pays, Bachs sends a customer.subscription.created webhook and Cashier creates the local subscription record automatically. The subscription "type" (default above) travels through the checkout session's metadata.
Checking subscription status
$user->subscribed(); // has a valid "default" subscription $user->subscribed('default', 'prod_abc123'); // ...to a specific product $user->subscribedToProduct('prod_abc123'); $subscription = $user->subscription(); $subscription->valid(); // active, trialing, past_due, or on grace period $subscription->active(); $subscription->onTrial(); $subscription->pastDue(); $subscription->paused(); $subscription->canceled(); $subscription->onGracePeriod(); // canceled but still within the paid period $subscription->ended();
Trials
Recurring products configured with a trial in Bachs start subscriptions in the trialing state — no extra code needed. You can also manage trials directly:
$subscription->extendTrial(now()->addDays(30)); $subscription->endTrial(); // ends the trial and bills immediately
For trials without requiring a checkout ("generic" trials), set trial_ends_at on the billable model when creating it:
$user = User::create([ // ... 'trial_ends_at' => now()->addDays(14), ]); $user->onTrial(); // true (generic or subscription trial) $user->onGenericTrial(); // true (generic trial only)
Swapping plans
$subscription->swap('prod_premium'); // prorated, invoiced now (Bachs default) $subscription->swapWithoutProration('prod_premium'); // no proration $subscription->swap('prod_premium', [ 'proration_behavior' => 'next_cycle', // settle the difference next cycle ]);
Cancelling
$subscription->cancel(); // at period end; enters a grace period $subscription->cancelNow(); // immediately $subscription->cancel('Too expensive'); // with a reason
While on the grace period, $subscription->valid() remains true until the paid period runs out.
Payment method
$subscription->updatePaymentMethod('pm_abc123');
One-time payments
Sell one-time products through a hosted checkout:
// Single product... return $user->checkout('prod_ebook', [ 'success_url' => route('shop.success'), ]); // Multiple products with quantities... return $user->checkout([ ['product_id' => 'prod_course', 'quantity' => 2], 'prod_ebook', ]);
Refunds
$user->refund('chr_1a2b3c4d5e6f'); $user->refund('chr_1a2b3c4d5e6f', ['amount' => '10.00', 'reason' => 'Requested by customer']);
Webhooks
Cashier registers a webhook route at /bachs/webhook that keeps subscriptions in sync (customer.subscription.created, .updated, .deleted). Create the Bachs webhook endpoint with:
php artisan cashier:webhook
The command prints the signing secret — add it to your .env as BACHS_WEBHOOK_SECRET. Incoming webhooks are verified against the X-Bachs-Signature header (HMAC-SHA256) and stale deliveries are rejected.
Important
Make sure the webhook route is excluded from CSRF verification. In bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) { $middleware->validateCsrfTokens(except: ['bachs/*']); })
Handling other events
Listen for any Bachs event via the WebhookReceived event:
use Veekthoven\CashierBachs\Events\WebhookReceived; Event::listen(function (WebhookReceived $event) { if ($event->payload['type'] === 'invoice.payment_failed') { // ... } });
A WebhookHandled event fires after Cashier has processed one of the events it manages itself.
Low-level API access
Anything not covered by the fluent interface is available through the API client:
use Veekthoven\CashierBachs\Cashier; $products = Cashier::api()->listProducts(); $payment = Cashier::api()->getPayment('pay_1a2b3c4d5e'); $response = Cashier::api()->get('/accounts/balances');
Failed requests throw Veekthoven\CashierBachs\Exceptions\BachsApiError, exposing the stable errorCode, validation errors, and the requestId for support.
Customization
use Veekthoven\CashierBachs\Cashier; Cashier::ignoreRoutes(); // don't register the webhook route Cashier::ignoreMigrations(); // don't load the package migrations Cashier::useSubscriptionModel(CustomSubscription::class); Cashier::formatCurrencyUsing(fn ($amount, $currency) => /* ... */);
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Credits
License
The MIT License (MIT). Please see License File for more information.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 2
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-07-14