okeke-dev / laravel-bachs
An independent, community-maintained Laravel integration for Bachs.io: Billable, subscriptions, checkout, webhooks, and more.
Fund package maintenance!
Requires
- php: ^8.2
- illuminate/config: ^12.0|^13.0
- illuminate/console: ^12.0|^13.0
- illuminate/contracts: ^12.0|^13.0
- illuminate/events: ^12.0|^13.0
- illuminate/http: ^12.0|^13.0
- illuminate/routing: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.9
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^3.8|^5.0
- pestphp/pest-plugin-laravel: ^3.2|^5.0
README
An independent, community-maintained Laravel integration for Bachs.io.
Introduction
Laravel Bachs brings Bachs.io billing to Laravel: products,
customers, subscriptions, checkout sessions, payments, refunds, payment methods,
webhooks, and more — with a Cashier-style API that fits naturally into your
application.
Requires PHP ^8.2 and Laravel ^12.0|^13.0. See
CHANGELOG.mdfor the latest changes.
Features
- Products & product groups — create, list, update, archive/unarchive.
- Customers — create, list, update, and portal sessions.
- Billable trait — attach billing to any Eloquent model with
createAsBachsCustomer(),checkout(),subscribeTo(),subscribed(),cancel(),resume(), andbillingPortalUrl(). - Checkout sessions — hosted redirects and inline overlay modals.
- Subscriptions — list, get, update, cancel with status helpers.
- Payments & refunds — list, get, refund with status helpers.
- Payment methods — list and payment rail lookup.
- Currencies & balances — supported currencies and balance queries.
- Media uploads — multipart upload support with validation.
- Webhooks — signature verification, typed events, queue-safe processing, idempotency, optional persistence, and automatic local model sync.
- Blade components —
<x-bachs::checkout>,<x-bachs::checkout-overlay>, and<x-bachs::subscribe>. - Artisan commands —
bachs:install,bachs:health, and webhook management. - Local models — opt-in database mirrors for customers, products, payments, and subscriptions synced via webhooks.
- Multi-connection — talk to multiple Bachs accounts from one application.
- Typed exceptions — every error maps to a specific PHP exception class.
Requirements
- PHP
^8.2 - Laravel
^12.0|^13.0
Installation
composer require okeke-dev/laravel-bachs
The package registers its own service provider automatically. Run the install command to publish config, migrations, and views:
php artisan bachs:install
Or publish individually:
php artisan vendor:publish --tag=bachs-config php artisan vendor:publish --tag=bachs-migrations php artisan vendor:publish --tag=bachs-views
Configuration
Set your credentials in .env:
BACHS_SECRET_KEY=sk_sandbox_xxxxxxxxxxxxxx BACHS_ENV=sandbox BACHS_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxx
BACHS_ENVselects the sandbox (https://sandbox-api.bachs.io/v1) or live (https://api.bachs.io/v1) base URL.BACHS_BASE_URLoverrides the base URL entirely when set.
The published config/bachs.php also supports multiple named connections:
'connections' => [ 'default' => [/* ... */], 'partner' => [/* ... */], ],
Usage
use OkekeDev\Bachs\Facades\Bachs; $client = Bachs::connection(); // default connection $client = Bachs::connection('partner'); // named connection // Or use the global helper: $client = bachs();
Products
use OkekeDev\Bachs\Resources\Products; $products = Products::list(['limit' => 10]); $product = Products::create([ 'name' => 'Pro Plan', 'description' => 'Access to all features', 'price' => ['amount' => '29.99', 'currency' => 'USD'], ]); Products::update($product->id(), ['name' => 'Pro Plan (Annual)']); Products::archive($product->id()); Products::unarchive($product->id());
Customers
use OkekeDev\Bachs\Resources\Customers; $customer = Customers::create([ 'email' => ' customer@example.com', 'name' => 'Jane Doe', ]); Customers::update($customer->id(), ['name' => 'Jane Smith']); $portalSession = Customers::createPortalSession($customer->id()); $url = $portalSession->url();
Billable trait
Add the Billable trait to any Eloquent model (e.g. User) to get
billing helpers:
use OkekeDev\Bachs\Concerns\Billsable; class User extends Authenticatable { use Billsable; }
// Create a Bachs customer from the model $user->createAsBachsCustomer(); // Checkout $session = $user->checkout([ 'product_cart' => [['product_id' => 'prod_xxx', 'quantity' => 1]], ]); return redirect($session->url()); // Subscriptions $session = $user->subscribeTo('prod_xxx'); $user->subscribed(); // bool $user->subscription(); // ?Subscription $user->cancel(); $user->resume(); // Billing portal return redirect($user->billingPortalUrl());
Checkout
use OkekeDev\Bachs\Resources\CheckoutSessions; $session = CheckoutSessions::create([ 'product_cart' => [['product_id' => 'prod_xxx', 'quantity' => 1]], 'customer' => ['customer_id' => 'cus_xxx'], 'success_url' => 'https://example.com/success', 'cancel_url' => 'https://example.com/cancel', ]); $session->url(); // redirect URL $session->status(); // checkout status
Subscriptions
use OkekeDev\Bachs\Resources\Subscriptions; $subscription = Subscriptions::get('sub_xxx'); $subscription->isActive(); $subscription->isTrialing(); $subscription->isPastDue(); $subscription->isCanceled(); Subscriptions::cancel('sub_xxx'); Subscriptions::update('sub_xxx', ['cancel_at_period_end' => true]);
Payments & refunds
use OkekeDev\Bachs\Resources\Payments; use OkekeDev\Bachs\Resources\Refunds; $payments = Payments::list(['limit' => 10]); $payment = Payments::get('pay_xxx'); $payment->isSucceeded(); $payment->isRefundable(); // Full refund $refund = $payment->refund(); // Partial refund via the Refunds resource $refund = Refunds::create([ 'payment_id' => 'pay_xxx', 'amount' => '10.00', ]);
Webhooks
Register the webhook route in your routes/web.php:
Route::post('bachs/webhook', [\OkekeDev\Bachs\Http\Controllers\WebhookController::class, '__invoke']);
Set the signing secret in .env:
BACHS_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxx
Listen for typed events in your EventServiceProvider:
use OkekeDev\Bachs\Events\PaymentSucceeded; use OkekeDev\Bachs\Events\SubscriptionCanceled; protected $listen = [ PaymentSucceeded::class => [/* ... */], SubscriptionCanceled::class => [/* ... */], ];
Enable local model sync in config/bachs.php:
'database' => [ 'sync' => true, ],
Blade components
{{-- Hosted checkout redirect --}} <x-bachs::checkout product="prod_xxx" email="customer@example.com" success-url="https://example.com/success" cancel-url="https://example.com/cancel" class="btn btn-primary" > Subscribe Now </x-bachs::checkout> {{-- Inline overlay modal --}} <x-bachs::checkout-overlay product="prod_xxx" email="customer@example.com" /> {{-- Subscription checkout --}} <x-bachs::subscribe product="prod_xxx" email="customer@example.com" />
Artisan commands
# Publish config, migrations, and views php artisan bachs:install # Verify API connectivity php artisan bachs:health # Webhook management php artisan bachs:webhook:test https://example.com/webhook php artisan bachs:webhook:list php artisan bachs:webhook:inspect evt_xxx php artisan bachs:webhook:replay evt_xxx
Low-level transport
BachsClient is the HTTP transport used by the resource layer. It handles
authentication, JSON, timeouts, idempotency keys, custom headers, retries with
exponential backoff, and typed exceptions:
$response = $client->get('products', ['limit' => 20]); $response->status(); // 200 $response->json(); // decoded payload $response->json('price.amount', '0.00'); // dot-notated access $response->toArray(); // decoded payload as an array $response->requestId(); // x-request-id, for support $client->post('customers', ['email' => 'a@b.com'], 'idem_123');
- Retries: safe methods (
GET/HEAD/OPTIONS) and requests carrying anIdempotency-Keyretry on 429/5xx and network failures. The delay grows exponentially and a 429'sRetry-Afteris honored. - Headers: connection-level defaults plus per-request headers.
Authorization,Accept,Content-Type, andIdempotency-Keyare reserved and cannot be overridden. - Exceptions: non-2xx responses throw typed exceptions —
BachsAuthenticationException(401),BachsValidationException(422),BachsNotFoundException(404),BachsRateLimitException(429),BachsConflictException(409),BachsNetworkException, and the baseBachsApiException.
Testing
composer check # tests + style + static analysis composer test # Pest composer test:style # Pint composer test:types # PHPStan (level 6)
Changelog
Please see CHANGELOG.md for what changed recently.
Contributing
Please see CONTRIBUTING.md for details.
Security
Please see SECURITY.md for reporting vulnerabilities.
Credits
- Okeke Chimezie Glory
- Bachs.io — the API provider this package wraps.
- Laravel Cashier — whose API design this package is inspired by.
License
The MIT License (MIT). Please see LICENSE for more
information.