fevinta / cashier-asaas
Laravel Cashier integration for Asaas payment gateway (Brazil)
Installs: 2
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/fevinta/cashier-asaas
Requires
- php: ^8.2
- illuminate/contracts: ^11.0|^12.0
- illuminate/database: ^11.0|^12.0
- illuminate/http: ^11.0|^12.0
- illuminate/routing: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
Requires (Dev)
- laravel/pint: ^1.0
- moneyphp/money: ^4.8
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
README
Laravel Cashier-style subscription billing for Asaas payment gateway (Brazil).
Features
- π§π· Brazilian Payment Methods: PIX, Boleto, Credit Card
- π³ Subscription Management: Create, update, cancel, resume subscriptions
- π Plan Swapping: Change plans with automatic proration
- β° Trial Periods: Support for trial days
- πͺ Webhook Handling: Automatic payment status updates
- π― Laravel-like API: Familiar Cashier-style fluent interface
Installation
composer require fevinta/cashier-asaas
Publish the configuration and migrations:
php artisan vendor:publish --tag=cashier-asaas-config php artisan vendor:publish --tag=cashier-asaas-migrations php artisan migrate
Configuration
Add your Asaas credentials to .env:
ASAAS_API_KEY=your-api-key ASAAS_SANDBOX=true ASAAS_WEBHOOK_TOKEN=optional-webhook-token
Define your subscription plans in config/cashier-asaas.php:
'plans' => [ 'basic' => [ 'price' => 29.90, 'name' => 'Plano BΓ‘sico', ], 'pro' => [ 'price' => 99.90, 'name' => 'Plano Pro', ], 'enterprise' => [ 'price' => 299.90, 'name' => 'Plano Enterprise', ], ],
Setup
Add the Billable trait to your User model:
use Fevinta\CashierAsaas\Billable; class User extends Authenticatable { use Billable; // Optional: customize customer data for Asaas public function asaasCpfCnpj(): ?string { return $this->document; } }
Usage
Creating Subscriptions
// Basic subscription with credit card $user->newSubscription('default', 'pro') ->withCreditCard([ 'holderName' => 'John Doe', 'number' => '4111111111111111', 'expiryMonth' => '12', 'expiryYear' => '2025', 'ccv' => '123', ], [ 'name' => 'John Doe', 'email' => 'john@example.com', 'cpfCnpj' => '12345678901', 'postalCode' => '01310100', 'addressNumber' => '123', ]) ->create(); // Subscription with boleto $user->newSubscription('default', 'basic') ->withBoleto() ->create(); // Subscription with PIX $user->newSubscription('default', 'basic') ->withPix() ->create(); // Let customer choose payment method $user->newSubscription('default', 'pro') ->askCustomer() ->create(); // With trial period $user->newSubscription('default', 'pro') ->trialDays(14) ->withCreditCardToken($token) ->create(); // Yearly subscription $user->newSubscription('default', 'pro') ->yearly() ->withCreditCardToken($token) ->create(); // Custom price (override plan config) $user->newSubscription('default', 'custom') ->price(149.90) ->monthly() ->withBoleto() ->create();
Checking Subscription Status
// Check if subscribed if ($user->subscribed('default')) { // Has active subscription } // Check specific plan if ($user->subscribedToPlan('pro', 'default')) { // Subscribed to Pro plan } // Check trial if ($user->onTrial('default')) { // On trial period } // Get subscription $subscription = $user->subscription('default'); // Check subscription state $subscription->active(); // Is active $subscription->onTrial(); // On trial $subscription->cancelled(); // Has been cancelled $subscription->onGracePeriod(); // Cancelled but still active $subscription->ended(); // Completely ended
Managing Subscriptions
$subscription = $user->subscription('default'); // Cancel (at period end) $subscription->cancel(); // Cancel immediately $subscription->cancelNow(); // Resume cancelled subscription (if on grace period) $subscription->resume(); // Swap to different plan $subscription->swap('enterprise'); // Update price $subscription->updateValue(149.90); // Change billing type $subscription->changeBillingType(BillingType::PIX); // Update credit card $subscription->updateCreditCard($cardData, $holderInfo); // Or with token $subscription->updateCreditCardToken($newToken);
Plan Swapping & Proration
When you swap plans, automatic proration is applied via the Asaas API. Here's how it works:
Example: Upgrading from R$10/month to R$20/month
Starting point:
- Current plan: R$10/month
- Billing cycle: 30 days
- Days already used: 15 days
- Days remaining: 15 days
When you upgrade to R$20/month:
1. Credit for unused time at old rate:
R$10 x (15/30) = R$5.00
2. Charge for remaining time at new rate:
R$20 x (15/30) = R$10.00
3. Prorated upgrade charge:
R$10.00 - R$5.00 = R$5.00 (added to next payment)
4. Next full payment: R$20.00
The swap() method sends updatePendingPayments: true to Asaas, which automatically:
- Calculates the prorated difference based on days remaining
- Adjusts pending invoices to include the prorated amount
- Sets all future payments to the new price
// Swap using config price $subscription->swap('premium'); // Swap with custom price $subscription->swap('custom', 99.90); // Just update price without changing plan name $subscription->updateValue(99.90);
Single Charges
use Fevinta\CashierAsaas\Enums\BillingType; // Charge with PIX $payment = $user->charge(100.00, BillingType::PIX, [ 'description' => 'Product purchase', 'dueDate' => now()->addDays(3), ]); // Charge with boleto $payment = $user->charge(100.00, BillingType::BOLETO, [ 'description' => 'Service fee', 'dueDate' => now()->addDays(5), ]); // Charge with credit card $payment = $user->charge(100.00, BillingType::CREDIT_CARD, [ 'description' => 'Premium feature', 'creditCardToken' => $token, ]); // Installment payment (credit card only) $payment = $user->chargeInstallments(600.00, 6, [ 'description' => 'Annual plan', 'creditCardToken' => $token, ]);
Webhooks
The package automatically handles Asaas webhooks. Configure the webhook URL in your Asaas dashboard:
https://your-app.com/asaas/webhook
Available events you can listen to:
// In EventServiceProvider protected $listen = [ \Fevinta\CashierAsaas\Events\PaymentReceived::class => [ \App\Listeners\HandlePaymentReceived::class, ], \Fevinta\CashierAsaas\Events\PaymentOverdue::class => [ \App\Listeners\HandlePaymentOverdue::class, ], \Fevinta\CashierAsaas\Events\PaymentRefunded::class => [ \App\Listeners\HandlePaymentRefunded::class, ], ];
Middleware
Protect routes requiring subscription:
Route::middleware(['auth', 'subscribed'])->group(function () { Route::get('/premium', PremiumController::class); });
Register the middleware in your Kernel:
protected $middlewareAliases = [ 'subscribed' => \Fevinta\CashierAsaas\Http\Middleware\EnsureUserIsSubscribed::class, ];
Payment Split
Share revenue with partners:
$user->newSubscription('default', 'pro') ->split('wallet_partner_id', fixedValue: 10.00) // R$ 10 fixed ->split('wallet_affiliate_id', percentualValue: 10) // 10% ->withCreditCardToken($token) ->create();
API Reference
Billable Trait Methods
| Method | Description |
|---|---|
createAsAsaasCustomer() |
Create customer in Asaas |
updateAsaasCustomer() |
Update customer data |
asAsaasCustomer() |
Get Asaas customer data |
newSubscription($type, $plan) |
Start subscription builder |
subscription($type) |
Get subscription by type |
subscribed($type) |
Check if subscribed |
onTrial($type) |
Check if on trial |
charge($amount, $type, $options) |
Single charge |
Subscription Methods
| Method | Description |
|---|---|
active() |
Is subscription active |
valid() |
Is subscription valid (active/trial/grace) |
cancel() |
Cancel at period end |
cancelNow() |
Cancel immediately |
resume() |
Resume cancelled subscription |
swap($plan) |
Change plan |
updateValue($value) |
Update subscription price |
updateCreditCard() |
Update payment card |
Testing
The package uses PEST for testing with a dual approach: mocked HTTP for fast unit/feature tests, and real Asaas Sandbox API for integration tests.
Run All Tests (Mocked)
# Using composer script composer test # Or directly with PEST ./vendor/bin/pest # With coverage report ./vendor/bin/pest --coverage
Run Specific Test Suites
# Unit tests only ./vendor/bin/pest --testsuite=Unit # Feature tests only ./vendor/bin/pest --testsuite=Feature # Integration tests (requires Asaas credentials) ./vendor/bin/pest --testsuite=Integration
Integration Tests (Real Asaas Sandbox)
Integration tests hit the real Asaas Sandbox API. They are skipped by default when no credentials are configured.
# Set your Asaas Sandbox API key export ASAAS_API_KEY=your_sandbox_api_key_here # Run integration tests ./vendor/bin/pest --testsuite=Integration
Static Analysis
# Run PHPStan
./vendor/bin/phpstan analyse
Test Configuration
Environment variables for testing:
| Variable | Description | Default |
|---|---|---|
ASAAS_API_KEY |
Asaas API key (required for integration tests) | - |
ASAAS_SANDBOX |
Enable sandbox mode | true |
ASAAS_WEBHOOK_TOKEN |
Webhook verification token | - |
License
MIT License. See LICENSE for details.