vatly/vatly-laravel

Laravel integration for Vatly, inspired by Laravel Cashier

Maintainers

Package info

github.com/Vatly/vatly-laravel

Issues

pkg:composer/vatly/vatly-laravel

Statistics

Installs: 13

Dependents: 0

Suggesters: 0

Stars: 1


README

Latest Version on Packagist Tests Total Downloads

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

  1. Publish the config:

    php artisan vendor:publish --tag=vatly-config
  2. 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_ vs live_) — no extra config needed.

  3. 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_id column to your users table plus vatly_subscriptions, vatly_orders, and vatly_webhook_calls tables.

  4. Add the Billable trait 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\WebhookReceived
  • Vatly\Fluent\Events\OrderPaid — carries total, subtotal, taxSummary (full per-rate breakdown), currency, invoiceNumber, paymentMethod. Materialize local invoices without an extra API call.
  • Vatly\Fluent\Events\SubscriptionStarted
  • Vatly\Fluent\Events\SubscriptionCanceledImmediately
  • Vatly\Fluent\Events\SubscriptionCanceledWithGracePeriod
  • Vatly\Fluent\Events\LocalSubscriptionCreated
  • Vatly\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