vatly / vatly-laravel
Laravel integration for Vatly, inspired by Laravel Cashier
Requires
- php: ^8.3
- illuminate/contracts: ^12.0|^13.0
- illuminate/database: ^12.0|^13.0
- illuminate/http: ^12.0|^13.0
- illuminate/routing: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- vatly/vatly-fluent-php: 0.6.0-alpha.1
Requires (Dev)
- larastan/larastan: ^3.9
- mockery/mockery: ^1.6
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.0|^12.0
- dev-main
- v0.5.0-alpha.1
- v0.4.0-alpha.2
- v0.4.0-alpha.1
- v0.3.0-alpha.3
- v0.3.0-alpha.2
- v0.3.0-alpha.1
- v0.2.0-alpha.1
- v0.1.0-alpha.2
- v0.1.0-alpha.1
- dev-feat/dev-xp-log-3
- dev-chore/sync-openapi-self-service-link-renames
- dev-chore/laravel-13-support
- dev-chore/laravel-only-messaging
- dev-feat/billable-orchestrator
- dev-chore/reposition-as-driver-internals
- dev-chore/rename-create-billing-update-link
- dev-docs/readme-refresh
- dev-larafast_dev_sprint
- dev-feature/order-model
This package is auto-updated.
Last update: 2026-05-21 21:40:25 UTC
README
Alpha release -- under active development. Expect breaking changes.
A Cashier-style integration for Vatly in your Laravel application. Drop a Billable trait on your User model and you get subscriptions, checkouts, customer management, hosted billing update links, and a fully wired webhook endpoint — built around Eloquent and Laravel's IoC, events, and routing.
If you've used Laravel Cashier for Stripe, this will feel familiar. Vatly handles Merchant of Record billing for EU SaaS, so you get a similar developer experience without managing VAT, invoicing, or payment compliance yourself.
Documentation
Full docs at docs.vatly.com. In this repo:
Requirements
- PHP 8.3+
- Laravel 12 or 13
- A Vatly API key (vatly.com)
Installation
composer require vatly/vatly-laravel:v0.3.0-alpha.1
Pin to an exact version during alpha — the API will change.
Setup
-
Publish the config:
php artisan vendor:publish --tag=vatly-config
-
Add credentials to
.env:VATLY_KEY=test_xxxxxxxxxxxx VATLY_WEBHOOK_SECRET=your-webhook-secret VATLY_REDIRECT_URL_SUCCESS=https://your-app.test/checkout/success VATLY_REDIRECT_URL_CANCELED=https://your-app.test/checkout/canceled
See docs/configuration.md for the full list. Testmode is inferred from the key prefix (
test_vslive_) — no extra config needed. -
Publish and run migrations:
php artisan vendor:publish --tag=vatly-migrations php artisan vendor:publish --tag=vatly-billable-migrations php artisan migrate
This adds a
vatly_idcolumn to your users table plusvatly_subscriptions,vatly_orders, andvatly_webhook_callstables. -
Add the
Billabletrait to your User model:use Vatly\Fluent\Contracts\BillableInterface; use Vatly\Laravel\Billable; class User extends Authenticatable implements BillableInterface { use Billable; }
Usage
// Start a subscription checkout $checkout = $user->subscribe() ->toPlan('plan_premium') ->create(); return redirect($checkout->links->checkoutUrl->href); // Or one-off checkouts with explicit items $checkout = $user->checkout()->create( items: [['id' => 'plan_premium', 'quantity' => 1]], redirectUrlSuccess: 'https://example.com/success', redirectUrlCanceled: 'https://example.com/canceled', ); // Subscription state $user->subscribed(); // bool, default type $user->subscribed('team'); // bool, custom type $user->subscription()->active(); $user->subscription()->onGracePeriod(); $user->subscription()->cancelled(); // Swap plan $user->subscription()->swap('plan_premium'); // Cancel at period end (Vatly decides immediate vs grace period) $user->subscription()->cancel();
$user->subscription() returns a Vatly\Fluent\SubscriptionHandle — a thin wrapper around the local Subscription Eloquent model with the API-driven operations on it. Reach the underlying model via $user->subscription()->model() or query directly with $user->subscriptions()->where(...).
For more explicit/namespaced access, $user->vatlyBillable() returns the framework-agnostic orchestrator: $user->vatlyBillable()->subscribed('default'), $user->vatlyBillable()->createAsVatlyCustomer(), etc.
See docs/Subscriptions.md and docs/Checkouts.md for the full surface.
Webhooks
The package registers POST /webhooks/vatly automatically. Set this URL and your VATLY_WEBHOOK_SECRET in the Vatly dashboard, and subscriptions/orders sync to your database automatically.
Vatly events are dispatched on Laravel's event bus — register listeners the usual way:
use Vatly\Fluent\Events\OrderPaid; Event::listen(OrderPaid::class, function (OrderPaid $event) { // send receipt, etc. });
Events available:
Vatly\Fluent\Events\WebhookReceivedVatly\Fluent\Events\OrderPaid— carriestotal,subtotal,taxSummary(full per-rate breakdown),currency,invoiceNumber,paymentMethod. Materialize local invoices without an extra API call.Vatly\Fluent\Events\SubscriptionStartedVatly\Fluent\Events\SubscriptionCanceledImmediatelyVatly\Fluent\Events\SubscriptionCanceledWithGracePeriodVatly\Fluent\Events\LocalSubscriptionCreatedVatly\Fluent\Events\UnsupportedWebhookReceived
The webhook route is named vatly.webhook — reach it with route('vatly.webhook').
See docs/Webhooks.md for signature verification, retries, and customising reactions.
Testing
composer test
When testing code that calls the Billable trait shortcuts, your test models must implement BillableInterface (applying the trait does this for you). The contract is enforced at runtime — there's no "loose" mode.
For the order.paid webhook flow, the package fetches the full Order from the Vatly API to populate the tax breakdown. In integration tests, swap the GetOrder action with a Mockery mock:
use Mockery; use Vatly\Fluent\Actions\GetOrder; $action = Mockery::mock(GetOrder::class); $action->shouldReceive('execute')->andReturn($yourFakeApiOrder); $this->app->instance(GetOrder::class, $action);
Under the hood
This package builds on vatly/vatly-fluent-php, which holds the webhook pipeline, contracts, events, DTOs, and the Vatly\Fluent\Billable orchestrator. You don't need to interact with fluent directly — it's an implementation detail of vatly-laravel.
License
MIT