jetimob/asaas-sdk-php-laravel
Laravel integration for the Asaas payment gateway that wraps the Asaas PHP SDK with package configuration and service bindings, making it easier to manage customers, payments, subscriptions, and related billing operations in Laravel applications.
This release (v1.9.0.0) adds support for the externalReference field — Asaas' free-form correlation string that lets you attach your own identifier (order UUID, invoice ID, tenant key) to a customer, payment, or subscription and get it echoed back on the object and in webhook payloads. This is the single most useful field for reconciling Asaas records with your local models, so it's worth adopting early.
Minimal setup:
composer require jetimob/asaas-sdk-php-laravelphp artisan vendor:publish --tag=asaas-config (check vendor/jetimob/asaas-sdk-php-laravel/src/Providers for the exact tag if it differs)..env — typically ASAAS_API_KEY plus an environment/sandbox switch. Confirm the key names against the published config/asaas.php; do not assume.php artisan config:clear after editing env values.Where to look first:
config/asaas.php — environment, API key, base URI, and any HTTP/timeout options.vendor/jetimob/asaas-sdk-php/src — the underlying SDK. The Laravel package is a thin container/facade binding over it, so the entity classes (Customer, Payment, Subscription, Webhook, etc.) and their setters are documented there, not in the Laravel wrapper. This is where you'll confirm that externalReference is exposed as a setter on the entities you care about.First use case — correlate a charge with a local order:
$payment = Asaas::payments()->create([
'customer' => $asaasCustomerId,
'billingType' => 'PIX',
'value' => 149.90,
'dueDate' => now()->addDays(3)->toDateString(),
'externalReference' => $order->uuid, // new in v1.9.0.0
]);
Then persist $payment->id locally. You now have a bidirectional link: your UUID on their side, their ID on yours.
Always set externalReference on write, always resolve by it on read. Treat it as your join key. Set it to an immutable identifier (UUID, not an auto-increment ID you might recycle, and not something user-editable). On webhooks, resolve $payload['payment']['externalReference'] → your model rather than maintaining a separate asaas_id lookup table as the only path. Keep the asaas_id column too — you need it for API calls — but the external reference is what survives export/import and manual dashboard creation.
Wrap the SDK in a thin application service. Don't call the facade from controllers or models. A BillingService/AsaasGateway class that maps your domain objects to SDK payloads gives you one place to enforce externalReference, one place to normalise money, and one seam to fake in tests.
Push writes into queued jobs. Charge creation, customer creation, and subscription updates are network calls to a third party. Wrap them in jobs with $tries/backoff. Because externalReference is a natural idempotency hint, your job can check "does a payment with this externalReference already exist?" before creating a duplicate on retry.
Webhook flow. A dedicated route outside auth middleware → verify the asaas-access-token header against your configured secret → persist the raw payload → dispatch a job → respond 200 immediately. Do the actual state transition asynchronously. Asaas retries on non-2xx and can deliver out of order, so make the job idempotent keyed on the event/payment ID.
Reconciliation command. A scheduled asaas:reconcile artisan command that pulls recent payments (filtering or matching on externalReference) and diffs against local state catches the webhooks you missed during deploys. This is the pattern that makes the field pay for itself.
Testing. Bind a fake gateway implementation in the container for feature tests, and reserve real sandbox calls for a small number of integration tests. Don't let the suite depend on network availability.
externalReference is not unique or enforced. Asaas will happily store the same string on ten payments. Uniqueness is your invariant, enforced in your own code. Guard against double-submits in the job/controller, not in the API call.externalReference round-trips in the API response and inside the webhook payload for the specific event you handle — support was added incrementally and may not cover every entity/endpoint at v1.9.0.0.errors array, not as typed exceptions with useful messages. The SDK throws, but the human-readable reason (e.g. invalid CPF/CNPJ, duplicate charge) lives in the response body. Catch, log the body, and surface a domain error.sandbox => true in a cached config. Assert the environment at boot in non-local environments.config:cache freezes env values; reading env() directly anywhere in your own code alongside this package will silently return null in production.Y-m-d. Mixing cents and reais is the classic 100x billing bug.vendor/jetimob/asaas-sdk-php as well as the Laravel wrapper; most feature and breaking surface lives in the underlying SDK, and this package's version bumps can be very small relative to the behaviour change they pull in.How can I help you explore Laravel packages today?