zenphp / obsidian
Obsidian provides expressive, fluent subscription billing for Laravel applications using CCBill and SegPay payment processors.
Installs: 9
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/zenphp/obsidian
Requires
- php: ^8.4
- ext-intl: *
- ext-json: *
- laravel/framework: ^12.41.1
- moneyphp/money: ^4.8.0
- nesbot/carbon: ^3.11.0
- symfony/console: ^7.4.0
- symfony/http-kernel: ^7.4.0
- symfony/polyfill-intl-icu: ^1.33.0
- symfony/polyfill-php84: ^1.33.0
Requires (Dev)
- driftingly/rector-laravel: ^2.1.6
- larastan/larastan: ^3.8.0
- orchestra/testbench: ^10.8.0
- pestphp/pest: ^4.1.6
- pestphp/pest-plugin-type-coverage: ^4.0.3
- rector/rector: ^2.2.11
- zenphp/fixr: ^1.0.4
README
About Obsidian
Obsidian provides expressive, fluent subscription billing for Laravel applications using CCBill and SegPay payment processors. Built specifically for adult content platforms and high-risk merchants who need reliable, compliant payment processing.
Gateway Support Status
| Gateway | Status | Subscriptions | One-Time Charges | Webhooks | Cancellation |
|---|---|---|---|---|---|
| CCBill | ✅ Implemented | ✅ | ✅ | ✅ | ✅ (via DataLink) |
| SegPay | 🚧 Planned | ❌ | ❌ | ❌ | ❌ |
| Fake | ✅ Implemented | ✅ | ✅ | ✅ | ✅ |
Note: SegPay integration is planned for a future release. Currently, all SegPay gateway methods will throw a
GatewayException.
Features
- Multiple Payment Gateways - Support for CCBill with SegPay planned
- Subscription Management - Create, cancel, and manage recurring subscriptions
- Trial Periods - Built-in support for trial subscriptions
- Webhook Handling - Automatic webhook processing with signature validation
- One-Time Charges - Process single payments alongside subscriptions
- Fake Gateway - Test your billing logic without hitting real APIs
- 100% Test Coverage - Fully tested with comprehensive mocked responses
- Type Safe - Full PHP 8.4 type coverage with PHPStan level max
Requirements
- PHP 8.4 or higher
- Laravel 12.0 or higher
- A CCBill merchant account (SegPay support coming soon)
Installation
Install the package via Composer:
composer require zenphp/obsidian
Publish Configuration
Publish the configuration file and migrations:
php artisan vendor:publish --tag=obsidian-config php artisan vendor:publish --tag=obsidian-migrations
Run the migrations:
php artisan migrate
Environment Configuration
Add your payment gateway credentials to your .env file:
# Default Gateway OBSIDIAN_GATEWAY=ccbill # CCBill Configuration CCBILL_MERCHANT_ID=your_merchant_id CCBILL_SUBACCOUNT_ID=your_subaccount_id CCBILL_MERCHANT_APP_ID=your_merchant_application_id CCBILL_SECRET_KEY=your_secret_key CCBILL_DATALINK_USERNAME=your_datalink_username CCBILL_DATALINK_PASSWORD=your_datalink_password CCBILL_WEBHOOK_SECRET=your_webhook_secret # Currency Settings OBSIDIAN_CURRENCY=usd OBSIDIAN_CURRENCY_LOCALE=en
CCBill Requirements
To use the CCBill gateway, you'll need:
- Merchant Application ID & Secret Key - For OAuth 2.0 authentication with the CCBill REST API
- DataLink Credentials - Username and password for subscription cancellation via the legacy DataLink system
- Webhook Secret - For validating incoming webhook signatures (HMAC SHA256)
- FlexForms - A configured FlexForm for payment page generation
Important: CCBill uses OAuth 2.0 for API authentication. The access token is automatically cached and refreshed as needed.
Setup
Add the Billable Trait
Add the Billable trait to your User model (or any model that should have subscriptions):
<?php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Zen\Obsidian\Billable; class User extends Authenticatable { use Billable; // ... rest of your model }
Database Columns
The migrations will add the following columns to your users table:
trial_ends_at- For generic trial periods (optional)
And create the following tables:
subscriptions- Stores subscription recordssubscription_items- Stores subscription line items (for future use)
Usage
Creating Subscriptions
Create a new subscription for a user:
use Illuminate\Http\Request; Route::post('/subscribe', function (Request $request) { $user = $request->user(); $subscription = $user->newSubscription('default', 'plan_monthly') ->create($request->payment_token); return redirect('/dashboard'); });
With Trial Period
Add a trial period to a subscription:
$subscription = $user->newSubscription('default', 'plan_monthly') ->trialDays(14) ->create($request->payment_token);
Specify Gateway
Choose a specific payment gateway:
$subscription = $user->newSubscription('default', 'plan_monthly') ->gateway('segpay') ->create($request->payment_token);
With Metadata
Attach custom metadata to a subscription:
$subscription = $user->newSubscription('default', 'plan_monthly') ->withMetadata([ 'user_ip' => $request->ip(), 'referral_code' => 'SUMMER2024', ]) ->create($request->payment_token);
Checking Subscription Status
Check if a user has an active subscription:
if ($user->subscribed('default')) { // User has an active subscription } // Check for a specific subscription name if ($user->subscribed('premium')) { // User has an active premium subscription }
Check Trial Status
if ($user->onTrial('default')) { // User is on trial }
Get Subscription
Retrieve a user's subscription:
$subscription = $user->subscription('default'); if ($subscription && $subscription->active()) { // Subscription is active }
Cancelling Subscriptions
Cancel a subscription at the end of the billing period:
$subscription = $user->subscription('default'); $subscription->cancel();
Cancel immediately:
$subscription->cancelNow();
One-Time Charges
Process a one-time payment:
$result = $user->charge(2999, $paymentToken, [ 'description' => 'Premium content purchase', ]); // Result contains: // - transaction_id // - amount // - status
Webhooks
Obsidian automatically handles webhooks from CCBill and SegPay to keep your subscription status in sync.
Webhook URLs
Configure these webhook URLs in your payment processor dashboards:
- CCBill:
https://yourdomain.com/webhooks/ccbill - SegPay:
https://yourdomain.com/webhooks/segpay
Webhook Events
Obsidian dispatches the following events that you can listen to:
Zen\Obsidian\Events\SubscriptionCreated- New subscription activatedZen\Obsidian\Events\PaymentSucceeded- Successful payment processedZen\Obsidian\Events\PaymentFailed- Payment failedZen\Obsidian\Events\SubscriptionCancelled- Subscription cancelled
Listening to Events
Create an event listener:
<?php namespace App\Listeners; use Zen\Obsidian\Events\PaymentSucceeded; class SendPaymentReceipt { public function handle(PaymentSucceeded $event): void { $subscription = $event->subscription; $amount = $event->amount; // Send receipt email to user $subscription->user->notify(new PaymentReceiptNotification($amount)); } }
Register in EventServiceProvider:
protected $listen = [ \Zen\Obsidian\Events\PaymentSucceeded::class => [ \App\Listeners\SendPaymentReceipt::class, ], ];
Testing
Obsidian includes a FakeGateway for testing your billing logic without hitting real payment APIs.
Using the Fake Gateway
In your tests or local environment:
// In your .env or test configuration OBSIDIAN_GATEWAY=fake // In your test $subscription = $user->newSubscription('default', 'plan_monthly') ->gateway('fake') ->create('fake_token_123'); expect($subscription->active())->toBeTrue();
Mocking HTTP Responses
For testing with real gateways, use Laravel's HTTP fake:
use Illuminate\Support\Facades\Http; Http::fake([ 'api.ccbill.com/*' => Http::response([ 'subscriptionId' => 'sub_123', 'status' => 'active', ], 200), ]); $subscription = $user->newSubscription('default', 'plan_monthly') ->gateway('ccbill') ->create('test_token');
Running Tests
# Run all tests composer test # Run tests with coverage composer test:coverage # Run type coverage composer test:types # Run static analysis composer test:static
API Reference
Billable Trait Methods
The Billable trait provides the following methods:
subscriptions()
Get all subscriptions for the user.
$subscriptions = $user->subscriptions;
subscription(string $name = 'default')
Get a specific subscription by name.
$subscription = $user->subscription('premium');
subscribed(string $name = 'default')
Check if the user has an active subscription.
if ($user->subscribed()) { // User is subscribed }
onTrial(string $name = 'default')
Check if the user is on a trial period.
if ($user->onTrial()) { // User is on trial }
onGenericTrial()
Check if the user is on a generic trial (not tied to a subscription).
if ($user->onGenericTrial()) { // User has a generic trial }
newSubscription(string $name, string $plan)
Start building a new subscription.
$builder = $user->newSubscription('default', 'plan_monthly');
charge(int $amount, string $token, array $options = [])
Process a one-time charge.
$result = $user->charge(2999, 'payment_token');
Subscription Methods
active()
Check if the subscription is active.
if ($subscription->active()) { // Subscription is active }
cancelled()
Check if the subscription has been cancelled.
if ($subscription->cancelled()) { // Subscription is cancelled }
expired()
Check if the subscription has expired.
if ($subscription->expired()) { // Subscription has expired }
onTrial()
Check if the subscription is on a trial period.
if ($subscription->onTrial()) { // Subscription is on trial }
cancel()
Cancel the subscription at the end of the billing period.
$subscription->cancel();
cancelNow()
Cancel the subscription immediately.
$subscription->cancelNow();
Gateway Configuration
CCBill
CCBill uses OAuth 2.0 for API authentication and supports:
- Payment token charging for subscriptions and one-time payments
- Subscription cancellation via DataLink (legacy CGI system)
- Webhook events with HMAC SHA256 signature validation
- ISO 4217 numeric currency codes (USD=840, EUR=978, GBP=826, etc.)
Webhook Events Supported:
| CCBill Event | Normalized Type |
|---|---|
NewSaleSuccess |
subscription.created |
NewSaleFailure |
subscription.failed |
RenewalSuccess |
payment.succeeded |
RenewalFailure |
payment.failed |
Cancellation |
subscription.cancelled |
Chargeback |
subscription.chargeback |
Refund |
payment.refunded |
Expiration |
subscription.expired |
SegPay
🚧 Coming Soon: SegPay integration is planned for a future release. All SegPay gateway methods currently throw a
GatewayExceptionwith the message "SegPay gateway is not yet implemented".
Fake Gateway
The FakeGateway is perfect for testing and development:
- No external API calls
- Instant responses
- Predictable behavior
- Static state storage for test assertions
shouldFail()method for simulating failuresreset()method for test isolation
use Zen\Obsidian\Gateways\FakeGateway; // Reset state between tests FakeGateway::reset(); // Simulate a failure FakeGateway::shouldFail('Payment declined', 402); // Access internal state $subscriptions = FakeGateway::getSubscriptions(); $charges = FakeGateway::getCharges();
Security
Webhook Signature Validation
All webhooks are validated using HMAC SHA256 signatures to ensure they come from your payment processor.
Configure your webhook secrets in .env:
CCBILL_WEBHOOK_SECRET=your_secret_here SEGPAY_WEBHOOK_SECRET=your_secret_here
Redirect URL Validation
The VerifyRedirectUrl middleware prevents open redirect vulnerabilities by ensuring redirect URLs match your application's host.
Troubleshooting
Webhooks Not Working
- Verify webhook URLs are configured correctly in your payment processor dashboard
- Check webhook secrets match between your
.envand processor settings - Review logs for signature validation errors
- Ensure your application is accessible from the internet (use ngrok for local testing)
Subscription Not Activating
- Check that the subscription exists in your database
- Verify the
gateway_subscription_idmatches the processor's ID - Review webhook logs to ensure events are being received
- Check that the subscription status is being updated correctly
Payment Failures
- Verify API credentials are correct in
.env - Check that payment tokens are valid and not expired
- Review gateway-specific error messages in logs
- Ensure your merchant account is active and in good standing
Contributing
Please see CONTRIBUTING.md for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
License
The MIT License (MIT). Please see License File for more information.
Credits
- Built by Jetstream Labs
- Inspired by Laravel Cashier
- Designed for adult content platforms and high-risk merchants
Support
- Documentation: Coming soon ...
- Issues: GitHub Issues