Tools
Tools: Building the CREEM Laravel Package: Accept Global Payments in Minutes
2026-02-20
0 views
admin
Why CREEM? ## What I Built ## 26 API Methods via Facade ## Billable Trait (Laravel Cashier-style) ## 15 Webhook Events ## Auto Sandbox Detection ## Full Test Suite ## 5-Minute Quick Start ## Live Demo ## Package Highlights I recently built a full-featured Laravel package for CREEM, the developer-first Merchant of Record platform. Here's why I built it, what it does, and how you can start accepting payments in your Laravel app in under 5 minutes. If you're selling SaaS, digital products, or software licenses, handling global payments is painful. You need to deal with: CREEM handles all of this as a Merchant of Record at 3.9% + 30 cents with zero monthly fees. They take legal responsibility for tax compliance so you can focus on building your product. The only thing missing? A proper Laravel package. creem/laravel is a comprehensive Laravel package that wraps the entire CREEM API with Laravel-native patterns: Every CREEM API endpoint is covered: Products, Checkouts, Subscriptions, Customers, Transactions, Licenses, and Discounts. Instead of calling the Facade directly, attach the Billable trait to your User model: Now billing methods live directly on your model: The package auto-registers a webhook endpoint at POST /creem/webhook with HMAC-SHA256 signature verification. Every webhook type maps to a typed Laravel event: I also added AccessGranted and AccessRevoked convenience events, inspired by CREEM's TypeScript SDK pattern. These fire automatically on the right webhook combinations, so you don't need to remember which events mean "give access" vs "revoke access": Use a test API key (creem_test_*) and the package automatically routes to the sandbox API. Switch to production (creem_*) and it hits the live API. Zero config changes needed. 73 tests with 137 assertions covering every API method, webhook signature verification, event dispatching, and edge cases. The CI matrix runs across PHP 8.1-8.4 and Laravel 10, 11, 12. 3. Add Billable to User That's it. CREEM handles the payment page, tax calculation, and receipt. Your webhook listener grants access when payment succeeds. Check it out live: https://creem.h90.space The demo runs the included Docker app connected to the CREEM sandbox API. It features a live dashboard with webhook event tracking, interactive detail drawers, product listing pulled from the CREEM API, and a complete checkout flow — all with a premium dark glassmorphism UI. Want to run it yourself? The repo includes everything: Built by Hani Amin (Discord: xh90) for the CREEM Scoops Laravel integration bounty. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse COMMAND_BLOCK:
use Creem\Laravel\Facades\Creem; // Create a checkout in one line
$checkout = Creem::createCheckout('prod_abc123', [ 'success_url' => route('checkout.success'), 'customer' => ['email' => $user->email],
]); return redirect($checkout['checkout_url']); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
use Creem\Laravel\Facades\Creem; // Create a checkout in one line
$checkout = Creem::createCheckout('prod_abc123', [ 'success_url' => route('checkout.success'), 'customer' => ['email' => $user->email],
]); return redirect($checkout['checkout_url']); COMMAND_BLOCK:
use Creem\Laravel\Facades\Creem; // Create a checkout in one line
$checkout = Creem::createCheckout('prod_abc123', [ 'success_url' => route('checkout.success'), 'customer' => ['email' => $user->email],
]); return redirect($checkout['checkout_url']); CODE_BLOCK:
use Creem\Laravel\Traits\Billable; class User extends Authenticatable
{ use Billable;
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
use Creem\Laravel\Traits\Billable; class User extends Authenticatable
{ use Billable;
} CODE_BLOCK:
use Creem\Laravel\Traits\Billable; class User extends Authenticatable
{ use Billable;
} COMMAND_BLOCK:
$user->checkout('prod_abc123', ['success_url' => '/thanks']);
$user->creemSubscriptions();
$user->cancelSubscription('sub_xyz', 'scheduled');
$user->billingPortalUrl(); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
$user->checkout('prod_abc123', ['success_url' => '/thanks']);
$user->creemSubscriptions();
$user->cancelSubscription('sub_xyz', 'scheduled');
$user->billingPortalUrl(); COMMAND_BLOCK:
$user->checkout('prod_abc123', ['success_url' => '/thanks']);
$user->creemSubscriptions();
$user->cancelSubscription('sub_xyz', 'scheduled');
$user->billingPortalUrl(); COMMAND_BLOCK:
// In your EventServiceProvider
CheckoutCompleted::class => [GrantAccessListener::class],
SubscriptionCanceled::class => [RevokeAccessListener::class], Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
// In your EventServiceProvider
CheckoutCompleted::class => [GrantAccessListener::class],
SubscriptionCanceled::class => [RevokeAccessListener::class], COMMAND_BLOCK:
// In your EventServiceProvider
CheckoutCompleted::class => [GrantAccessListener::class],
SubscriptionCanceled::class => [RevokeAccessListener::class], COMMAND_BLOCK:
Event::listen(AccessGranted::class, function ($event) { // Fires on: checkout.completed, subscription.active, subscription.paid $user = User::where('email', $event->payload['customer']['email'])->first(); $user->update(['has_access' => true]);
}); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
Event::listen(AccessGranted::class, function ($event) { // Fires on: checkout.completed, subscription.active, subscription.paid $user = User::where('email', $event->payload['customer']['email'])->first(); $user->update(['has_access' => true]);
}); COMMAND_BLOCK:
Event::listen(AccessGranted::class, function ($event) { // Fires on: checkout.completed, subscription.active, subscription.paid $user = User::where('email', $event->payload['customer']['email'])->first(); $user->update(['has_access' => true]);
}); CODE_BLOCK:
composer require creem/laravel
php artisan vendor:publish --tag=creem-config
php artisan vendor:publish --tag=creem-migrations
php artisan migrate Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
composer require creem/laravel
php artisan vendor:publish --tag=creem-config
php artisan vendor:publish --tag=creem-migrations
php artisan migrate CODE_BLOCK:
composer require creem/laravel
php artisan vendor:publish --tag=creem-config
php artisan vendor:publish --tag=creem-migrations
php artisan migrate CODE_BLOCK:
CREEM_API_KEY=creem_test_your_key
CREEM_WEBHOOK_SECRET=whsec_your_secret Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
CREEM_API_KEY=creem_test_your_key
CREEM_WEBHOOK_SECRET=whsec_your_secret CODE_BLOCK:
CREEM_API_KEY=creem_test_your_key
CREEM_WEBHOOK_SECRET=whsec_your_secret CODE_BLOCK:
use Creem\Laravel\Traits\Billable; class User extends Authenticatable
{ use Billable;
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
use Creem\Laravel\Traits\Billable; class User extends Authenticatable
{ use Billable;
} CODE_BLOCK:
use Creem\Laravel\Traits\Billable; class User extends Authenticatable
{ use Billable;
} COMMAND_BLOCK:
Route::post('/buy', function (Request $request) { $checkout = $request->user()->checkout('prod_your_product', [ 'success_url' => url('/thanks'), ]); return redirect($checkout['checkout_url']);
}); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
Route::post('/buy', function (Request $request) { $checkout = $request->user()->checkout('prod_your_product', [ 'success_url' => url('/thanks'), ]); return redirect($checkout['checkout_url']);
}); COMMAND_BLOCK:
Route::post('/buy', function (Request $request) { $checkout = $request->user()->checkout('prod_your_product', [ 'success_url' => url('/thanks'), ]); return redirect($checkout['checkout_url']);
}); COMMAND_BLOCK:
// app/Listeners/GrantAccess.php
use Creem\Laravel\Events\AccessGranted; class GrantAccess
{ public function handle(AccessGranted $event): void { $email = $event->payload['customer']['email'] ?? null; User::where('email', $email)->update(['is_premium' => true]); }
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
// app/Listeners/GrantAccess.php
use Creem\Laravel\Events\AccessGranted; class GrantAccess
{ public function handle(AccessGranted $event): void { $email = $event->payload['customer']['email'] ?? null; User::where('email', $email)->update(['is_premium' => true]); }
} COMMAND_BLOCK:
// app/Listeners/GrantAccess.php
use Creem\Laravel\Events\AccessGranted; class GrantAccess
{ public function handle(AccessGranted $event): void { $email = $event->payload['customer']['email'] ?? null; User::where('email', $email)->update(['is_premium' => true]); }
} COMMAND_BLOCK:
cd examples/demo
cp .env.example .env
# Add your CREEM API key
docker compose up -d --build Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
cd examples/demo
cp .env.example .env
# Add your CREEM API key
docker compose up -d --build COMMAND_BLOCK:
cd examples/demo
cp .env.example .env
# Add your CREEM API key
docker compose up -d --build - VAT/GST/sales tax across 190+ countries
- Payment processor integration
- Subscription lifecycle management
- License key distribution
- Refunds and disputes - Live Demo: creem.h90.space
- Packagist: packagist.org/packages/creem/laravel
- GitHub: github.com/Haniamin90/creem-laravel
- CREEM: creem.io
- CREEM Docs: docs.creem.io
how-totutorialguidedev.toaiswitchdockersslgitgithub