ownerpro / asaas-php-sdk
Clean PHP SDK for the Asaas payment platform API
Requires
- php: ^8.3
- illuminate/http: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
Requires (Dev)
- driftingly/rector-laravel: ^2.2
- laravel/pint: ^1.29
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^4.4
- pestphp/pest-plugin-mutate: ^4.0
- pestphp/pest-plugin-type-coverage: ^4.0
- rector/rector: ^2.3
- tomasvotruba/cognitive-complexity: ^1.1
- vimeo/psalm: ^6.16
This package is auto-updated.
Last update: 2026-05-05 10:22:22 UTC
README
Clean PHP SDK for the Asaas payment platform API with typed request DTOs and result-based error handling.
Requirements
- PHP 8.3+
illuminate/http^11.0|^12.0
Works with Laravel 11 or 12 (auto-discovers ServiceProvider and Facade), and also works in any PHP project without Laravel.
Installation
composer require ownerpro/asaas-php-sdk
The package auto-discovers its ServiceProvider and Facade.
Configuration
Publish the config file:
php artisan vendor:publish --tag=asaas-config
Set the following environment variables:
ASAAS_API_KEY=your-api-key ASAAS_ENVIRONMENT=sandbox # or "production" ASAAS_TIMEOUT=30 # request timeout in seconds (default: 30) ASAAS_CONNECT_TIMEOUT=10 # TCP connect timeout in seconds (default: 10)
ASAAS_API_KEYis required. TheAsaasServiceProviderthrowsRuntimeExceptionthe first timeAsaasClientis resolved from the container if the key is missing or empty — keep this in mind when bootstrapping in CI or test environments where the env var may not be set.
Usage
Via Facade
use OwnerPro\Asaas\Asaas; $result = Asaas::payments()->create([ 'customer' => 'cus_abc123', 'billingType' => 'PIX', 'value' => 150.00, 'dueDate' => '2026-04-01', ]);
Via Dependency Injection
use OwnerPro\Asaas\AsaasClient; public function __construct(private AsaasClient $asaas) {} public function charge(): void { $result = $this->asaas->payments()->create([ 'customer' => 'cus_abc123', 'billingType' => 'PIX', 'value' => 150.00, 'dueDate' => '2026-04-01', ]); }
Standalone (without Laravel)
No Laravel framework needed — only illuminate/http as a Composer dependency:
use OwnerPro\Asaas\AsaasClient; use OwnerPro\Asaas\Support\Environment; $client = AsaasClient::for(apiKey: 'your-api-key'); $result = $client->payments()->find('pay_abc123'); // Override defaults $client = AsaasClient::for( apiKey: 'your-api-key', environment: Environment::Production, timeout: 60, // request timeout in seconds (default: 30) connectTimeout: 5, // TCP connect timeout in seconds (default: 10) );
Multi-Tenant
This SDK supports two multi-tenant patterns:
- Existing Asaas account (standalone) — the tenant already has an Asaas account and provides their own apiKey. The integrator has no administrative visibility; the tenant operates independently.
- White-label subaccount — the integrator creates a subaccount on behalf of the tenant via
accounts()->create()and drives the full onboarding (KYC, commercial info, document upload, bank account) without redirecting the tenant to the Asaas panel. The subaccount keeps its own balance and KYC status; the integrator only retains administrative visibility (listing, status checks). See My Account (myAccount()) for the onboarding endpoints.
In both patterns, instantiate one client per tenant using their apiKey:
// In Laravel — inherits environment/timeout from config use OwnerPro\Asaas\Asaas; use OwnerPro\Asaas\Support\Environment; foreach ($tenants as $tenant) { $client = Asaas::for(apiKey: $tenant->asaas_api_key); $result = $client->payments()->list(); } // Override per-tenant (any param omitted falls back to config/asaas.php) $client = Asaas::for( apiKey: $tenant->asaas_api_key, environment: Environment::Production, timeout: 60, connectTimeout: 5, ); // Standalone (without Laravel) use OwnerPro\Asaas\AsaasClient; foreach ($tenants as $tenant) { $client = AsaasClient::for(apiKey: $tenant->asaas_api_key); $result = $client->payments()->list(); }
Result Handling
All resource methods return AsaasResult or AsaasPaginatedResult.
$result = Asaas::payments()->create([...]); if ($result->success) { $payment = $result->data; // array<string, mixed> echo $payment['id']; echo $payment['status']; } else { $errors = $result->errors; // array of error details }
Throwing on Failure
// Throws AsaasRequestException on failure, returns self on success $result = Asaas::payments()->find('pay_abc123')->orFail(); $payment = $result->data;
Error Handling
use OwnerPro\Asaas\Support\AsaasRequestException; try { Asaas::payments()->find('pay_invalid')->orFail(); } catch (AsaasRequestException $e) { $e->getMessage(); // First error description $e->statusCode; // HTTP status code (0 for connection errors) $e->errors; // Full error array from API $e->response; // ?RawResponse — null for connection errors }
Resource ID Validation
Every method that takes a resource ID (find, update, delete, etc.) validates the input before making the HTTP call. IDs must be 1–255 chars and match [a-zA-Z0-9_-]+. Empty, oversized, or otherwise malformed IDs throw InvalidArgumentException synchronously — they never reach the API.
use InvalidArgumentException; try { Asaas::payments()->find(''); // empty Asaas::payments()->find('pay/abc'); // illegal char } catch (InvalidArgumentException $e) { // e.g. "Resource ID must be 1..255 chars; got 0." }
This guards against accidental URL-segment injection from unsanitized input. Validate or sanitize user-supplied IDs upstream if you need to surface a friendlier error.
Raw Response Access
Every AsaasResult and AsaasPaginatedResult carries the underlying HTTP response for debugging, rate limit tracking, and Asaas support tickets:
$result = Asaas::payments()->find('pay_abc123'); $result->response->status(); // HTTP status code $result->response->headers(); // All response headers $result->response->header('X-Request-Id'); // Single header (null if absent) $result->response->body(); // Raw response body // Connection errors have no HTTP response $result->response; // null when connection failed
The RawResponse wrapper keeps your code decoupled from the underlying HTTP client.
Enums
The SDK provides backed string enums for all domain values. Request DTOs accept both enum instances and plain strings (backward compatible). Responses return raw strings — use EnumType::from() when you need a typed enum.
use OwnerPro\Asaas\Payment\BillingType; use OwnerPro\Asaas\Payment\PaymentStatus; // Using enums in requests (IDE autocompletion + typo prevention) $result = Asaas::payments()->create(new CreatePaymentRequest( customer: 'cus_abc123', billingType: BillingType::Pix, value: 150.00, dueDate: '2026-04-01', )); // Plain strings still work $result = Asaas::payments()->create([ 'customer' => 'cus_abc123', 'billingType' => 'PIX', 'value' => 150.00, 'dueDate' => '2026-04-01', ]); // Responses return strings — hydrate to enums when needed $payment = $result->data; $payment['status']; // 'PENDING' PaymentStatus::from($payment['status']); // PaymentStatus::Pending
Available Enums
| Enum | Values |
|---|---|
Payment\BillingType |
Undefined, Boleto, CreditCard, DebitCard, Transfer, Deposit, Pix |
Payment\PaymentStatus |
Pending, Received, Confirmed, Overdue, Refunded, ReceivedInCash, RefundRequested, RefundInProgress, ChargebackRequested, ChargebackDispute, AwaitingChargebackReversal, DunningRequested, DunningReceived, AwaitingRiskAnalysis |
Pix\PixAddressKeyType |
Cpf, Cnpj, Email, Phone, Evp |
Pix\PixAddressKeyStatus |
AwaitingActivation, Active, AwaitingDeletion, AwaitingAccountDeletion, Deleted, Error |
Pix\QrCodeFormat |
All, Image, Payload |
PixTransaction\PixTransactionType |
Debit, Credit, CreditRefund, DebitRefund, DebitRefundCancellation |
PixTransaction\PixTransactionStatus |
AwaitingBalanceValidation, AwaitingInstantPaymentAccountBalance, AwaitingCriticalActionAuthorization, AwaitingCheckoutRiskAnalysisRequest, AwaitingCashInRiskAnalysisRequest, Scheduled, AwaitingRequest, Requested, Done, Refused, Cancelled |
PixTransaction\PixQrCodeType |
Static, Dynamic, DynamicWithAsaasAddressKey, Composite |
Transfer\TransferOperationType |
Pix, Ted, Internal |
Transfer\TransferStatus |
Pending, BankProcessing, Done, Cancelled, Failed |
Invoice\InvoiceStatus |
Scheduled, Authorized, ProcessingCancellation, Canceled, CancellationDenied, Error |
BillPayment\BillPaymentStatus |
Pending, BankProcessing, Paid, Failed, Cancelled, Refunded, AwaitingCheckoutRiskAnalysisRequest |
Account\CompanyType |
Mei, Limited, Individual, Association |
Account\PersonType |
Fisica, Juridica |
Account\DocumentType |
Identification, SocialContract, EntrepreneurRequirement, Minutes, Custom |
CreditCard\CreditCardBrand |
Visa, Mastercard, Elo, Diners, Discover, Amex, Cabal, Banescard, Credz, Sorocred, Credsystem, Jcb, Unknown |
Webhook\WebhookSendType |
Sequentially, NonSequentially |
Webhook\WebhookEvent |
111 event types (PaymentCreated, PaymentReceived, TransferDone, etc.) |
Statement\FinancialTransactionType |
129 transaction types (PaymentReceived, Transfer, BillPayment, etc.) |
Support\BankAccountType |
CheckingAccount, SavingsAccount |
Input: Arrays or Request Objects
Every create() and update() method accepts either a plain array or a typed request object:
// Array (validated at runtime via required fields) $result = Asaas::payments()->create([ 'customer' => 'cus_abc123', 'billingType' => 'PIX', 'value' => 100.00, 'dueDate' => '2026-04-01', ]); // Request object (validated at construction) use OwnerPro\Asaas\Payment\BillingType; use OwnerPro\Asaas\Payment\Request\CreatePaymentRequest; $result = Asaas::payments()->create(new CreatePaymentRequest( customer: 'cus_abc123', billingType: BillingType::Pix, value: 100.00, dueDate: '2026-04-01', ));
Request objects can also be created from arrays via fromArray():
$data = CreatePaymentRequest::fromArray($request->validated());
Nested Value Objects
Fields like creditCard, creditCardHolderInfo, bankAccount, taxes, split, callback, and qrCode accept either a plain array or a typed DTO from OwnerPro\Asaas\Support\DTO. Using typed DTOs gives you IDE autocompletion and construction-time validation.
use OwnerPro\Asaas\Support\DTO\CreditCard; use OwnerPro\Asaas\Support\DTO\CreditCardHolderInfo; // Raw array (still works) $result = Asaas::payments()->create([ 'customer' => 'cus_abc123', 'billingType' => 'CREDIT_CARD', 'value' => 200.00, 'dueDate' => '2026-04-01', 'creditCard' => [ 'holderName' => 'John Doe', 'number' => '4111111111111111', 'expiryMonth' => '06', 'expiryYear' => '2028', 'ccv' => '123', ], 'creditCardHolderInfo' => [ 'name' => 'John Doe', 'email' => 'john@example.com', 'cpfCnpj' => '12345678901', 'postalCode' => '01001000', 'addressNumber' => '100', 'phone' => '11999999999', ], ]); // Typed DTOs (IDE autocompletion + construction-time validation) $result = Asaas::payments()->create(new CreatePaymentRequest( customer: 'cus_abc123', billingType: 'CREDIT_CARD', value: 200.00, dueDate: '2026-04-01', creditCard: new CreditCard( holderName: 'John Doe', number: '4111111111111111', expiryMonth: '06', expiryYear: '2028', ccv: '123', ), creditCardHolderInfo: new CreditCardHolderInfo( name: 'John Doe', email: 'john@example.com', cpfCnpj: '12345678901', postalCode: '01001000', addressNumber: '100', phone: '11999999999', ), ));
Available nested DTOs (OwnerPro\Asaas\Support\DTO\*):
| DTO | Used in |
|---|---|
CreditCard |
CreatePaymentRequest, CreditCardRequest, PayWithCreditCardRequest |
CreditCardHolderInfo |
CreatePaymentRequest, CreditCardRequest, PayWithCreditCardRequest |
BankAccount |
TransferRequest |
Bank |
Nested inside BankAccount |
Taxes |
CreateInvoiceRequest, UpdateInvoiceRequest |
Split |
CreatePaymentRequest, UpdatePaymentRequest |
SplitRefund |
RefundPaymentRequest |
Callback |
CreatePaymentRequest |
QrCodePayload |
PayQrCodeRequest |
Example with Split (marketplace splits) and Callback (post-payment redirect):
use OwnerPro\Asaas\Payment\BillingType; use OwnerPro\Asaas\Payment\Request\CreatePaymentRequest; use OwnerPro\Asaas\Support\DTO\Callback; use OwnerPro\Asaas\Support\DTO\Split; Asaas::payments()->create(new CreatePaymentRequest( customer: 'cus_abc123', billingType: BillingType::Pix, value: 200.00, dueDate: '2026-04-01', split: [ new Split(walletId: 'wallet_partner_a', percentualValue: 70.0), new Split(walletId: 'wallet_partner_b', fixedValue: 30.00), ], callback: new Callback( successUrl: 'https://example.com/return', autoRedirect: true, ), ));
New Request DTOs
Beyond create() and update(), several action methods now accept typed request objects:
Payment actions:
use OwnerPro\Asaas\Payment\Request\SimulatePaymentRequest; use OwnerPro\Asaas\Payment\Request\RefundPaymentRequest; use OwnerPro\Asaas\Payment\Request\PayWithCreditCardRequest; use OwnerPro\Asaas\Payment\Request\ReceivePaymentInCashRequest; use OwnerPro\Asaas\Support\DTO\CreditCard; use OwnerPro\Asaas\Support\DTO\CreditCardHolderInfo; use OwnerPro\Asaas\Support\DTO\SplitRefund; // Simulate payment use OwnerPro\Asaas\Payment\BillingType; Asaas::payments()->simulate(new SimulatePaymentRequest( value: 500.00, billingTypes: [BillingType::CreditCard, BillingType::Pix], installmentCount: 3, )); // Refund with split refunds Asaas::payments()->refund('pay_abc123', new RefundPaymentRequest( value: 50.00, description: 'Partial refund', splitRefunds: [ new SplitRefund(id: 'split_abc', value: 25.00), new SplitRefund(id: 'split_def', value: 25.00), ], )); // Pay with credit card Asaas::payments()->payWithCreditCard('pay_abc123', new PayWithCreditCardRequest( creditCard: new CreditCard( holderName: 'John Doe', number: '4111111111111111', expiryMonth: '06', expiryYear: '2028', ccv: '123', ), creditCardHolderInfo: new CreditCardHolderInfo( name: 'John Doe', email: 'john@example.com', cpfCnpj: '12345678901', postalCode: '01001000', addressNumber: '100', phone: '11999999999', ), remoteIp: '203.0.113.42', // payer's IP — required for Asaas antifraud analysis )); // Receive payment in cash Asaas::payments()->receiveInCash('pay_abc123', new ReceivePaymentInCashRequest( paymentDate: '2026-03-26', value: 100.00, notifyCustomer: true, ));
Pix Transactions:
use OwnerPro\Asaas\PixTransaction\Request\DecodeQrCodeRequest; use OwnerPro\Asaas\PixTransaction\Request\PayQrCodeRequest; use OwnerPro\Asaas\Support\DTO\QrCodePayload; // Decode a QR code Asaas::pixTransactions()->decodeQrCode(new DecodeQrCodeRequest( payload: '00020126580014br.gov.bcb.pix...', )); // Pay a QR code with typed payload Asaas::pixTransactions()->payQrCode(new PayQrCodeRequest( qrCode: new QrCodePayload( payload: '00020126580014br.gov.bcb.pix...', changeValue: 5.00, ), value: 150.00, description: 'QR Code payment', ));
Pix:
use OwnerPro\Asaas\Pix\Request\StaticQrCodeRequest; Asaas::pix()->createStaticQrCode(new StaticQrCodeRequest( addressKey: 'abc-uuid-key', description: 'Store payment', value: 49.90, allowsMultiplePayments: true, ));
Bill Payments:
use OwnerPro\Asaas\BillPayment\Request\SimulateBillPaymentRequest; Asaas::billPayments()->simulate(new SimulateBillPaymentRequest( identificationField: '23793.38128 60000.000003 00000.000400 1 84340000012345', ));
Clearing Fields on Updates
Update DTOs (UpdatePaymentRequest, UpdateInvoiceRequest, UpdateWebhookRequest) support three states for each field:
use OwnerPro\Asaas\Payment\Request\UpdatePaymentRequest; // 1. Don't change — omit the field (default): Asaas::payments()->update('pay_123', new UpdatePaymentRequest( value: 200.00, // description not passed → field not sent → API keeps current value )); // 2. Clear the field — pass null explicitly: Asaas::payments()->update('pay_123', new UpdatePaymentRequest( description: null, // → sends {"description": null} → API clears it )); // 3. Set a new value: Asaas::payments()->update('pay_123', new UpdatePaymentRequest( description: 'New description', ));
This also works with fromArray() — missing keys are omitted, explicit null values are sent:
// Only updates value, description untouched: Asaas::payments()->update('pay_123', ['value' => 200.00]); // Clears description: Asaas::payments()->update('pay_123', ['value' => 200.00, 'description' => null]);
Updated Request DTOs with Nested DTO Support
CreatePaymentRequest, CreditCardRequest, CreateInvoiceRequest, and TransferRequest now accept typed nested DTOs alongside plain arrays:
use OwnerPro\Asaas\CreditCard\Request\CreditCardRequest; use OwnerPro\Asaas\Support\DTO\CreditCard; use OwnerPro\Asaas\Support\DTO\CreditCardHolderInfo; // Tokenize with typed DTOs Asaas::creditCards()->tokenize(new CreditCardRequest( customer: 'cus_abc123', creditCard: new CreditCard( holderName: 'John Doe', number: '4111111111111111', expiryMonth: '06', expiryYear: '2028', ccv: '123', ), creditCardHolderInfo: new CreditCardHolderInfo( name: 'John Doe', email: 'john@example.com', cpfCnpj: '12345678901', postalCode: '01001000', addressNumber: '100', phone: '11999999999', ), remoteIp: '127.0.0.1', ));
use OwnerPro\Asaas\Invoice\Request\CreateInvoiceRequest; use OwnerPro\Asaas\Support\DTO\Taxes; // Create invoice with typed Taxes Asaas::invoices()->create(new CreateInvoiceRequest( serviceDescription: 'Consulting services', observations: 'March 2026', value: 5000.00, deductions: 0.00, effectiveDate: '2026-03-26', municipalServiceName: 'Consultoria em TI', taxes: new Taxes( retainIss: false, iss: 2.0, pis: 0.65, cofins: 3.0, csll: 1.0, inss: 0.0, ir: 1.5, ), ));
use OwnerPro\Asaas\Transfer\Request\TransferRequest; use OwnerPro\Asaas\Transfer\TransferOperationType; use OwnerPro\Asaas\Support\DTO\BankAccount; use OwnerPro\Asaas\Support\DTO\Bank; use OwnerPro\Asaas\Support\BankAccountType; // Transfer with typed BankAccount Asaas::transfers()->create(new TransferRequest( value: 1000.00, bankAccount: new BankAccount( ownerName: 'Jane Doe', cpfCnpj: '12345678901', agency: '0001', account: '123456', accountDigit: '1', bank: new Bank(code: '001'), bankAccountType: BankAccountType::CheckingAccount, ), operationType: TransferOperationType::Ted, ));
Pagination
List methods return AsaasPaginatedResult:
$result = Asaas::payments()->list(['limit' => 10]); $result->data; // list of arrays (each array is a raw API response) $result->totalCount; // total items available $result->hasMore; // more pages available? $result->limit; $result->offset; // Fetch next page $nextPage = $result->next();
Lazy Iteration
The all() method returns a Generator that auto-paginates:
foreach (Asaas::payments()->all(['limit' => 100]) as $payment) { if ($payment instanceof \OwnerPro\Asaas\Support\AsaasPaginatedError) { // Handle error — iteration stops after this Log::error('Pagination failed at offset '.$payment->offset, $payment->errors); break; } echo $payment['id']; } // Or collect all at once $allPayments = iterator_to_array(Asaas::payments()->all());
If an API error occurs during pagination, the Generator yields an AsaasPaginatedError instead of throwing. This object carries:
errors— the error list from the APIresponse— the raw HTTP response (null for connection errors)offset— the page offset that failedlimit— the page size
You can opt-in to exceptions by calling orFail() on the error object:
foreach (Asaas::payments()->all() as $payment) { if ($payment instanceof \OwnerPro\Asaas\Support\AsaasPaginatedError) { $payment->orFail(); // throws AsaasRequestException } processPayment($payment); }
Resources
Payments (payments())
Asaas::payments()->create(array|CreatePaymentRequest $data): AsaasResult Asaas::payments()->find(string $id): AsaasResult Asaas::payments()->list(array $query = []): AsaasPaginatedResult Asaas::payments()->update(string $id, array|UpdatePaymentRequest $data): AsaasResult Asaas::payments()->delete(string $id): AsaasResult Asaas::payments()->refund(string $id, array|RefundPaymentRequest $data = []): AsaasResult Asaas::payments()->restore(string $id): AsaasResult Asaas::payments()->captureAuthorized(string $id): AsaasResult Asaas::payments()->payWithCreditCard(string $id, array|PayWithCreditCardRequest $data): AsaasResult Asaas::payments()->receiveInCash(string $id, array|ReceivePaymentInCashRequest $data = []): AsaasResult Asaas::payments()->undoReceivedInCash(string $id): AsaasResult Asaas::payments()->status(string $id): AsaasResult Asaas::payments()->billingInfo(string $id): AsaasResult Asaas::payments()->pixQrCode(string $id): AsaasResult Asaas::payments()->identificationField(string $id): AsaasResult Asaas::payments()->viewingInfo(string $id): AsaasResult Asaas::payments()->simulate(array|SimulatePaymentRequest $data): AsaasResult Asaas::payments()->limits(): AsaasResult Asaas::payments()->all(array $filters = []): Generator (yields array|AsaasPaginatedError)
Pix Keys (pix())
Asaas::pix()->createKey(array|PixKeyRequest $data): AsaasResult Asaas::pix()->findKey(string $id): AsaasResult Asaas::pix()->listKeys(array $query = []): AsaasPaginatedResult Asaas::pix()->deleteKey(string $id): AsaasResult Asaas::pix()->createStaticQrCode(array|StaticQrCodeRequest $data = []): AsaasResult Asaas::pix()->deleteStaticQrCode(string $id): AsaasResult Asaas::pix()->tokenBucket(): AsaasResult Asaas::pix()->all(array $filters = []): Generator (yields array|AsaasPaginatedError)
Pix Transactions (pixTransactions())
Asaas::pixTransactions()->decodeQrCode(array|DecodeQrCodeRequest $data): AsaasResult Asaas::pixTransactions()->payQrCode(array|PayQrCodeRequest $data): AsaasResult Asaas::pixTransactions()->find(string $id): AsaasResult Asaas::pixTransactions()->list(array $query = []): AsaasPaginatedResult Asaas::pixTransactions()->cancel(string $id): AsaasResult Asaas::pixTransactions()->all(array $filters = []): Generator (yields array|AsaasPaginatedError)
Transfers (transfers())
Asaas::transfers()->create(array|TransferRequest $data): AsaasResult Asaas::transfers()->find(string $id): AsaasResult Asaas::transfers()->list(array $query = []): AsaasPaginatedResult Asaas::transfers()->cancel(string $id): AsaasResult Asaas::transfers()->all(array $filters = []): Generator (yields array|AsaasPaginatedError)
Webhooks (webhooks())
Asaas::webhooks()->create(array|CreateWebhookRequest $data): AsaasResult Asaas::webhooks()->find(string $id): AsaasResult Asaas::webhooks()->list(array $query = []): AsaasPaginatedResult Asaas::webhooks()->update(string $id, array|UpdateWebhookRequest $data): AsaasResult Asaas::webhooks()->delete(string $id): AsaasResult Asaas::webhooks()->removeBackoff(string $id): AsaasResult Asaas::webhooks()->all(array $filters = []): Generator (yields array|AsaasPaginatedError)
Webhook Verification
When you create a webhook with an authToken, Asaas sends that token in the asaas-access-token header on every delivery. Use WebhookVerifier to validate incoming requests with a timing-safe comparison:
use OwnerPro\Asaas\Webhook\WebhookVerifier; $verifier = new WebhookVerifier(authToken: 'your-webhook-auth-token'); // Verify the token (timing-safe via hash_equals) if (! $verifier->verify($request->header('asaas-access-token', ''))) { abort(401); } // Optional: verify the request came from a known Asaas IP if (! $verifier->isFromAsaas($request->ip())) { abort(403); }
The default known Asaas IPs are 52.67.12.206, 18.230.8.159, 54.94.136.112, and 54.94.183.101. You can override them if Asaas updates their IP list:
$verifier = new WebhookVerifier( authToken: 'your-webhook-auth-token', trustedIps: [...WebhookVerifier::DEFAULT_IPS, '10.0.0.1'], );
Invoices (invoices())
Asaas::invoices()->create(array|CreateInvoiceRequest $data): AsaasResult Asaas::invoices()->find(string $id): AsaasResult Asaas::invoices()->list(array $query = []): AsaasPaginatedResult Asaas::invoices()->update(string $id, array|UpdateInvoiceRequest $data): AsaasResult Asaas::invoices()->authorize(string $id): AsaasResult Asaas::invoices()->cancel(string $id): AsaasResult Asaas::invoices()->all(array $filters = []): Generator (yields array|AsaasPaginatedError)
Accounts (accounts())
Asaas::accounts()->create(array|AccountRequest $data): AsaasResult Asaas::accounts()->find(string $id): AsaasResult Asaas::accounts()->list(array $query = []): AsaasPaginatedResult Asaas::accounts()->listAccessTokens(string $accountId): AsaasResult Asaas::accounts()->createAccessToken(string $accountId): AsaasResult Asaas::accounts()->updateAccessToken(string $accountId, string $tokenId, array|AccessTokenRequest $data): AsaasResult Asaas::accounts()->deleteAccessToken(string $accountId, string $tokenId): AsaasResult Asaas::accounts()->all(array $filters = []): Generator (yields array|AsaasPaginatedError)
My Account (myAccount())
Operates on the current account behind the apiKey — used for tenant onboarding (KYC, commercial info, document upload, bank account). Must be called with the subaccount's apiKey, not the master.
Asaas::myAccount()->status(): AsaasResult Asaas::myAccount()->commercialInfo(): AsaasResult Asaas::myAccount()->updateCommercialInfo(array|CommercialInfoRequest $data): AsaasResult Asaas::myAccount()->documents(): AsaasResult Asaas::myAccount()->uploadDocumentFile(string $documentId, string|resource $file, DocumentType|string $type, string $filename): AsaasResult Asaas::myAccount()->deleteDocumentFile(string $fileId): AsaasResult Asaas::myAccount()->bankAccount(): AsaasResult Asaas::myAccount()->updateBankAccount(array|AccountBankAccountRequest $data): AsaasResult Asaas::myAccount()->delete(array|DeleteAccountRequest $data): AsaasResult
Subaccount onboarding (white label)
After accounts()->create() returns a new subaccount, the tenant must complete KYC, send commercial info, and register a bank account before they can transact. Drive the full flow without redirecting the tenant to the Asaas panel by instantiating a tenant-scoped client with the subaccount's apiKey and calling myAccount().
use OwnerPro\Asaas\Account\DocumentType; use OwnerPro\Asaas\Account\Request\AccountBankAccountRequest; use OwnerPro\Asaas\Account\Request\CommercialInfoRequest; use OwnerPro\Asaas\AsaasClient; use OwnerPro\Asaas\Account\CompanyType; use OwnerPro\Asaas\Support\BankAccountType; use OwnerPro\Asaas\Support\Environment; $tenantClient = AsaasClient::for( apiKey: $tenant->asaas_api_key, environment: Environment::Production, ); // 1. Pull current onboarding status $status = $tenantClient->myAccount()->status(); // $status->data: ['general' => ..., 'commercialInfo' => ..., 'documentation' => ..., 'bankAccountInfo' => ...] // 2. Update commercial info (partial — only changed fields) $tenantClient->myAccount()->updateCommercialInfo(new CommercialInfoRequest( incomeValue: 12000.0, companyType: CompanyType::Limited, )); // 3. Upload required KYC documents $documents = $tenantClient->myAccount()->documents(); $documentId = $documents->data['data'][0]['id']; $tenantClient->myAccount()->uploadDocumentFile( documentId: $documentId, file: fopen('/tmp/rg.png', 'rb'), type: DocumentType::Identification, filename: 'rg.png', ); // 4. Set the bank account where withdrawals will land $tenantClient->myAccount()->updateBankAccount(new AccountBankAccountRequest( bankCode: '341', agency: '1234', account: '56789', accountDigit: '0', accountType: BankAccountType::CheckingAccount, ));
Listen to the ACCOUNT_STATUS_* webhook events (already in WebhookEvent) to react to approvals and rejections asynchronously.
Credit Cards (creditCards())
Asaas::creditCards()->tokenize(array|CreditCardRequest $data): AsaasResult Asaas::creditCards()->getPreAuthorizationConfig(): AsaasResult Asaas::creditCards()->setPreAuthorizationConfig(array|PreAuthConfigRequest $data): AsaasResult
Bill Payments (billPayments())
Asaas::billPayments()->create(array|CreateBillPaymentRequest $data): AsaasResult Asaas::billPayments()->find(string $id): AsaasResult Asaas::billPayments()->list(array $query = []): AsaasPaginatedResult Asaas::billPayments()->simulate(array|SimulateBillPaymentRequest $data = []): AsaasResult Asaas::billPayments()->cancel(string $id): AsaasResult Asaas::billPayments()->all(array $filters = []): Generator (yields array|AsaasPaginatedError)
Statements (statements())
Asaas::statements()->list(array $query = []): AsaasPaginatedResult Asaas::statements()->all(array $filters = []): Generator (yields array|AsaasPaginatedError)
Custom Connector
All resources depend on the Connector interface (OwnerPro\Asaas\Support\Connector) rather than the concrete AsaasConnector. You can provide your own implementation for testing, logging, caching, or any custom behavior.
The Connector interface defines seven methods: the four HTTP verbs (get, post, put, delete), postMultipart for file uploads, plus paginate and all. The PaginatesResults trait provides default implementations of paginate() and all() built on top of get(), so you only need to implement the five HTTP methods:
use OwnerPro\Asaas\AsaasClient; use OwnerPro\Asaas\Support\Connector; use OwnerPro\Asaas\Support\PaginatesResults; class MyLoggingConnector implements Connector { use PaginatesResults; public function get(string $path, array $query = []): AsaasResult { /* ... */ } public function post(string $path, array $data = []): AsaasResult { /* ... */ } public function put(string $path, array $data = []): AsaasResult { /* ... */ } public function delete(string $path): AsaasResult { /* ... */ } public function postMultipart(string $path, array $data, array $files): AsaasResult { /* ... */ } // paginate() and all() are provided by the trait } $client = new AsaasClient(new MyLoggingConnector());
Testing
For app-level tests, swap the real connector for a fake one. The Connector interface plus RawResponse::fake() and AsaasResult::success()/failure() factories give you everything needed without hitting the network.
use OwnerPro\Asaas\AsaasClient; use OwnerPro\Asaas\Support\AsaasResult; use OwnerPro\Asaas\Support\Connector; use OwnerPro\Asaas\Support\PaginatesResults; use OwnerPro\Asaas\Support\RawResponse; final class FakeConnector implements Connector { use PaginatesResults; /** @param array<string, AsaasResult> $responses keyed by "VERB path" */ public function __construct(private array $responses) {} public function get(string $path, array $query = []): AsaasResult { return $this->responses["GET {$path}"]; } public function post(string $path, array $data = []): AsaasResult { return $this->responses["POST {$path}"]; } public function put(string $path, array $data = []): AsaasResult { /* ... */ } public function delete(string $path): AsaasResult { /* ... */ } } // In your test $client = new AsaasClient(new FakeConnector([ 'GET /payments/pay_abc123' => AsaasResult::success( data: ['id' => 'pay_abc123', 'status' => 'CONFIRMED'], rawResponse: RawResponse::fake(200, [], '{"id":"pay_abc123"}'), ), ])); $result = $client->payments()->find('pay_abc123'); expect($result->success)->toBeTrue(); expect($result->data['status'])->toBe('CONFIRMED');
In Laravel, bind the fake to the container:
$this->app->instance(AsaasClient::class, new AsaasClient(new FakeConnector([...])));
This keeps tests fast and deterministic — no HTTP calls, no fixtures to maintain beyond the responses your test cares about.
License
MIT