PHPackages                             romansh/laravel-creem - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Payment Processing](/categories/payments)
4. /
5. romansh/laravel-creem

ActiveLibrary[Payment Processing](/categories/payments)

romansh/laravel-creem
=====================

A Laravel package for Creem.io payment provider with support for products, checkouts, subscriptions, transactions, licenses, and discount codes

v1.2.10(1mo ago)335—0%2MITPHPPHP ^8.1|^8.2|^8.3|^8.4

Since Feb 13Pushed 1mo agoCompare

[ Source](https://github.com/romansh/laravel-creem)[ Packagist](https://packagist.org/packages/romansh/laravel-creem)[ RSS](/packages/romansh-laravel-creem/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (24)Versions (18)Used By (2)

LaravelCreem Package
====================

[](#laravelcreem-package)

A Laravel package for [Creem.io](https://creem.io) payment provider. Built with Laravel-native patterns, clean architecture, and developer experience as top priorities.

[![Latest Version](https://camo.githubusercontent.com/ad18d0e88ae276d53bfb955a48a3fab149b1b48097f5aa2b9ac9cea57b48f1df/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f637265656d2f6c61726176656c2e737667)](https://packagist.org/packages/creem/laravel)[![Total Downloads](https://camo.githubusercontent.com/2f5c680a1073c540c63ea4d72a8a035ed76e14058ab888063b564f195e0d866d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f637265656d2f6c61726176656c2e737667)](https://packagist.org/packages/creem/laravel)[![License](https://camo.githubusercontent.com/dfccbc2fed45f1d6eff96f70f1eba82120211c121ca985735ff2121e5ae51746/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f637265656d2f6c61726176656c2e737667)](https://packagist.org/packages/creem/laravel)

Features
--------

[](#features)

- **Laravel-Native**: Built on `Illuminate\Http\Client` with automatic retries and timeouts
- **Multi-Profile Configuration**: Support multiple API keys and environments
- **Complete API Coverage**: Products, Checkouts, Customers, Subscriptions, Transactions, Licenses, and Discounts
- **Webhooks**: Built-in signature verification and event dispatching
- **Type-Safe**: Full PHPDoc annotations and Laravel IDE helper compatible
- **Well-Tested**: Comprehensive unit and feature tests
- **Event-Driven**: Laravel events for all webhook types
- **Artisan Commands**: Test webhooks locally with ease
- **PSR-12 Compliant**: Clean, readable, maintainable code

Requirements
------------

[](#requirements)

- PHP 8.1 or higher
- Laravel 10.x, 11.x, 12.x

Installation
------------

[](#installation)

Install via Composer:

```
composer require romansh/laravel-creem
```

Publish the configuration file:

```
php artisan vendor:publish --tag=creem-config
```

Add your Creem credentials to `.env`:

```
CREEM_API_KEY=your_api_key_here
CREEM_TEST_MODE=false
CREEM_WEBHOOK_SECRET=your_webhook_secret_here
```

Demo Application
----------------

[](#demo-application)

A full-featured demo app is available as a separate package: [romansh/laravel-creem-demo](https://github.com/romansh/laravel-creem-demo)

```
composer create-project romansh/laravel-creem-demo my-creem-app
cd my-creem-app
cp .env.example .env
docker-compose up -d
```

Open **** — configure API keys, webhook secret, and webhook URL directly in the browser. No `.env` editing required.

The demo covers products, subscriptions, checkouts, discounts, transactions, and webhook event handling with live logs. Docker setup includes optional Cloudflare Tunnel for webhook testing.

Configuration
-------------

[](#configuration)

The package supports multiple configuration profiles. Open `config/creem.php` to configure:

```
return [
    'profiles' => [
        'default' => [
            'api_key' => env('CREEM_API_KEY'),
            'test_mode' => env('CREEM_TEST_MODE', false),
            'webhook_secret' => env('CREEM_WEBHOOK_SECRET'),
        ],

        // Add more profiles as needed
        'product_a' => [
            'api_key' => env('CREEM_PRODUCT_A_KEY'),
            'test_mode' => true,
            'webhook_secret' => env('CREEM_PRODUCT_A_WEBHOOK_SECRET'),
        ],
    ],
];
```

Usage
-----

[](#usage)

### Basic Usage (Default Profile)

[](#basic-usage-default-profile)

```
use Romansh\LaravelCreem\Facades\Creem;

// List products
$products = Creem::products()->list();

// Find a product
$product = Creem::products()->find('prod_123');

// Create a checkout
$checkout = Creem::checkouts()->create([
    'product_id' => 'prod_123',
    'success_url' => 'https://example.com/success',
    'customer' => [
        'email' => 'user@example.com',
    ],
]);

// Redirect customer to checkout
return redirect($checkout['checkout_url']);
```

### Using Named Profiles

[](#using-named-profiles)

```
use Romansh\LaravelCreem\Facades\Creem;

// Use the 'product_a' profile
$checkout = Creem::profile('product_a')
    ->checkouts()
    ->create([
        'product_id' => 'prod_123',
        'success_url' => 'https://example.com/success',
    ]);
```

### Using Inline Configuration

[](#using-inline-configuration)

```
use Romansh\LaravelCreem\Facades\Creem;

// Use inline configuration (does not affect global state)
$checkout = Creem::withConfig([
    'api_key' => 'custom_api_key',
    'test_mode' => true,
])->checkouts()->create([
    'product_id' => 'prod_123',
    'success_url' => 'https://example.com/success',
]);
```

Services
--------

[](#services)

### Products

[](#products)

```
use Romansh\LaravelCreem\Facades\Creem;

// Create a product
$product = Creem::products()->create([
    'name' => 'Premium Plan',
    'description' => 'Monthly subscription',
    'price' => 2999, // In cents
    'currency' => 'USD',
    'billing_type' => 'recurring',
    'billing_period' => 'every-month',
]);

// Find a product
$product = Creem::products()->find('prod_123');

// List products (paginated)
$products = Creem::products()->list($page = 1, $pageSize = 20);

// List products with filters
$products = Creem::products()->list($page = 1, $pageSize = 20, [
    'status' => 'active',
    'billing_type' => 'recurring',
]);
```

### Checkouts

[](#checkouts)

```
use Romansh\LaravelCreem\Facades\Creem;

// Create a checkout session
$checkout = Creem::checkouts()->create([
    'product_id' => 'prod_123',
    'success_url' => 'https://example.com/success',
    'customer' => [
        'email' => 'user@example.com',
        'name' => 'John Doe',
    ],
    'metadata' => [
        'user_id' => auth()->id(),
        'source' => 'web',
    ],
]);

// Find a checkout session
$checkout = Creem::checkouts()->find('chk_123');

// Redirect to checkout URL
return redirect($checkout['checkout_url']);
```

### Customers

[](#customers)

```
use Romansh\LaravelCreem\Facades\Creem;

// Find customer by ID
$customer = Creem::customers()->find('cust_123');

// Find customer by email
$customer = Creem::customers()->findByEmail('user@example.com');

// List customers (paginated)
$customers = Creem::customers()->list($page = 1, $pageSize = 20);

// Convenience total alias derived from pagination.total_records
$totalCustomers = $customers['total'];

// Generate customer portal link
$portalLink = Creem::customers()->createPortalLink('cust_123');
return redirect($portalLink);
```

### Subscriptions

[](#subscriptions)

```
use Romansh\LaravelCreem\Facades\Creem;

// Find a subscription
$subscription = Creem::subscriptions()->find('sub_123');

// List subscriptions
$subscriptions = Creem::subscriptions()->list($page = 1, $limit = 20);

// List subscriptions with filters
$subscriptions = Creem::subscriptions()->list($page = 1, $limit = 20, [
    'status' => 'active',
    'customer_id' => 'cust_123',
]);

// Cancel a subscription
$subscription = Creem::subscriptions()->cancel('sub_123');

// Pause a subscription
$subscription = Creem::subscriptions()->pause('sub_123');

// Resume a paused subscription
$subscription = Creem::subscriptions()->resume('sub_123');

// Upgrade/change subscription to a different product
$subscription = Creem::subscriptions()->upgrade(
    subscriptionId: 'sub_123',
    productId: 'prod_456',
    updateBehavior: 'proration-charge-immediately'
);

// Update subscription data
$subscription = Creem::subscriptions()->update('sub_123', [
    'metadata' => ['updated' => true],
]);
```

### Transactions

[](#transactions)

```
use Romansh\LaravelCreem\Facades\Creem;

// Find a transaction by ID
$transaction = Creem::transactions()->find('txn_123');

// List all transactions (paginated)
$transactions = Creem::transactions()->list([], $page = 1, $pageSize = 20);

// List transactions with filters
$transactions = Creem::transactions()->list([
    'customer_id' => 'cust_123',
    'product_id' => 'prod_456',
    'order_id' => 'ord_789',
], $page = 1, $pageSize = 20);

// Convenience total alias derived from pagination.total_records
$totalTransactions = $transactions['total'];

// Get transactions for a specific customer
$transactions = Creem::transactions()->byCustomer('cust_123');

// Get transactions for a specific order
$transactions = Creem::transactions()->byOrder('ord_456');

// Get transactions for a specific product
$transactions = Creem::transactions()->byProduct('prod_789');
```

### Licenses

[](#licenses)

```
use Romansh\LaravelCreem\Facades\Creem;

// Validate a license key
$license = Creem::licenses()->validate(
    key: 'ABC123-XYZ456-XYZ456-XYZ456',
    instanceId: 'inst_123'
);

if ($license['status'] === 'active') {
    // Grant access to premium features
}

// Activate a license on a new device
$license = Creem::licenses()->activate(
    key: 'ABC123-XYZ456-XYZ456-XYZ456',
    instanceName: 'johns-macbook-pro'
);

$instanceId = $license['instance']['id'];

// Deactivate a license instance
$license = Creem::licenses()->deactivate(
    key: 'ABC123-XYZ456-XYZ456-XYZ456',
    instanceId: 'inst_123'
);
```

### Discount Codes

[](#discount-codes)

```
use Romansh\LaravelCreem\Facades\Creem;

// Create a percentage discount
$discount = Creem::discounts()->create([
    'name' => 'Summer Sale 2024',
    'code' => 'SUMMER50',
    'type' => 'percentage',
    'percentage' => 50,
    'duration' => 'once',
    'max_redemptions' => 100,
    'expiry_date' => '2024-12-31T23:59:59Z',
]);

// Create a fixed amount discount
$discount = Creem::discounts()->create([
    'name' => 'Welcome Bonus',
    'code' => 'WELCOME20',
    'type' => 'fixed',
    'amount' => 2000, // $20.00 in cents
    'currency' => 'USD',
    'duration' => 'once',
]);

// Find discount by ID
$discount = Creem::discounts()->find('disc_123');

// Find discount by code
$discount = Creem::discounts()->findByCode('SUMMER50');

// Delete a discount code
$result = Creem::discounts()->delete('disc_123');
```

Webhooks
--------

[](#webhooks)

### Automatic Setup

[](#automatic-setup)

Webhook routes are automatically registered. The default endpoint is:

```
POST /creem/webhook

```

Configure the webhook URL in your Creem dashboard:

```
https://yourdomain.com/creem/webhook

```

### Webhook Events

[](#webhook-events)

The package dispatches Laravel events for all Creem webhook types. Each webhook is mapped to a corresponding event class under `Romansh\LaravelCreem\Events`. All Creem webhook events extend `CreemEvent` and expose the following typed properties:

- `$eventId` — unique Creem event id
- `$eventType` — the original event string (e.g. `checkout.completed`)
- `$createdAt` — unix timestamp in milliseconds
- `$object` — the decoded Creem resource object
- `$payload` — the full raw webhook payload

Common webhook event classes provided by the package include:

- `CheckoutCompleted`
- `DisputeCreated`
- `RefundCreated`
- `PaymentFailed`
- `SubscriptionCreated`
- `SubscriptionActive`
- `SubscriptionPaid`
- `SubscriptionCanceled`
- `SubscriptionExpired`
- `SubscriptionPastDue`
- `SubscriptionPaused`
- `SubscriptionTrialing`
- `SubscriptionScheduledCancel`
- `SubscriptionUpdate`

Additionally the package emits two application-level events to simplify access provisioning logic:

- `GrantAccess` — dispatched automatically after `checkout.completed` and `subscription.paid`. It receives `(array $customer, array $metadata, array $payload)` where `$customer` is the Creem customer object and `$metadata` is merchant-defined metadata from the originating resource.
- `RevokeAccess` — dispatched automatically after `subscription.canceled` and `subscription.expired`. It also receives `(array $customer, array $metadata, array $payload)`.

### Listening to Events

[](#listening-to-events)

Register event listeners in `app/Providers/EventServiceProvider.php`:

```
protected $listen = [
    \Romansh\LaravelCreem\Events\CheckoutCompleted::class => [
        \App\Listeners\SendPurchaseConfirmation::class,
        \App\Listeners\ProvisionUserAccess::class,
    ],
    \Romansh\LaravelCreem\Events\SubscriptionCanceled::class => [
        \App\Listeners\RevokeUserAccess::class,
    ],
    // Listen for application-level access events as well
    \Romansh\LaravelCreem\Events\GrantAccess::class => [
        \App\Listeners\ProvisionUserAccess::class,
    ],
    \Romansh\LaravelCreem\Events\RevokeAccess::class => [
        \App\Listeners\RevokeUserAccess::class,
    ],
];
```

Create a listener example:

```
namespace App\Listeners;

use Romansh\LaravelCreem\Events\GrantAccess;

class ProvisionUserAccess
{
    public function handle(GrantAccess $event)
    {
        $customer = $event->customer;
        $metadata = $event->metadata;

        // Use metadata (e.g. referenceId) to find internal user and provision access
        // $userId = $metadata['referenceId'] ?? null;
    }
}
```

### Custom Webhook Handling

[](#custom-webhook-handling)

You can also create a custom webhook controller and apply the `VerifyCreemWebhook` middleware:

```
namespace App\Http\Controllers;

use Romansh\LaravelCreem\Http\Middleware\VerifyCreemWebhook;
use Illuminate\Http\Request;

class CustomWebhookController extends Controller
{
    public function __construct()
    {
        $this->middleware(VerifyCreemWebhook::class);
    }

    public function handle(Request $request)
    {
        $event = $request->input('event');
        $data = $request->input('data');

        // Handle webhook...

        return response()->json(['message' => 'Processed']);
    }
}
```

### Testing Webhooks Locally

[](#testing-webhooks-locally)

Use the built-in Artisan command to test webhooks:

```
# Send a test checkout.completed event
php artisan creem:test-webhook checkout.completed

# Test with a specific profile
php artisan creem:test-webhook subscription.created --profile=product_a

# Available event types
php artisan creem:test-webhook checkout.completed
php artisan creem:test-webhook subscription.created
php artisan creem:test-webhook subscription.canceled
php artisan creem:test-webhook payment.failed
```

Error Handling
--------------

[](#error-handling)

The package throws specific exceptions for different error scenarios:

```
use Romansh\LaravelCreem\Exceptions\ApiException;
use Romansh\LaravelCreem\Exceptions\ConfigurationException;

try {
    $checkout = Creem::checkouts()->create([...]);
} catch (ApiException $e) {
    // API error (400, 403, 404, etc.)
    $statusCode = $e->getStatusCode();
    $messages = $e->getMessages();
    $traceId = $e->getTraceId(); // Include in support requests

    return back()->withErrors($messages);
} catch (ConfigurationException $e) {
    // Configuration error (missing profile, invalid API key, etc.)
    logger()->error($e->getMessage());
}
```

Testing
-------

[](#testing)

The package includes comprehensive tests:

```
# Run all tests
composer test

# Run with coverage
composer test -- --coverage
```

### Using HTTP Fakes in Tests

[](#using-http-fakes-in-tests)

```
use Illuminate\Support\Facades\Http;

public function test_can_create_checkout()
{
    Http::fake([
        'test-api.creem.io/v1/checkouts' => Http::response([
            'id' => 'checkout_123',
            'checkout_url' => 'https://checkout.creem.io/123',
        ], 200),
    ]);

    $checkout = Creem::checkouts()->create([
        'product_id' => 'prod_123',
        'success_url' => 'https://example.com/success',
    ]);

    $this->assertEquals('checkout_123', $checkout['id']);
}
```

Example Controllers
-------------------

[](#example-controllers)

### Checkout Controller

[](#checkout-controller)

```
namespace App\Http\Controllers;

use Romansh\LaravelCreem\Facades\Creem;
use Illuminate\Http\Request;

class CheckoutController extends Controller
{
    public function store(Request $request)
    {
        $validated = $request->validate([
            'product_id' => 'required|string',
            'email' => 'required|email',
        ]);

        $checkout = Creem::checkouts()->create([
            'product_id' => $validated['product_id'],
            'customer' => [
                'email' => $validated['email'],
            ],
            'success_url' => route('checkout.success'),
            'metadata' => [
                'user_id' => auth()->id(),
            ],
        ]);

        return redirect($checkout['checkout_url']);
    }

    public function success()
    {
        return view('checkout.success');
    }
}
```

### Subscription Management Controller

[](#subscription-management-controller)

```
namespace App\Http\Controllers;

use Romansh\LaravelCreem\Facades\Creem;
use Illuminate\Http\Request;

class SubscriptionController extends Controller
{
    public function cancel(Request $request, string $subscriptionId)
    {
        $subscription = Creem::subscriptions()->cancel($subscriptionId);

        return back()->with('success', 'Subscription canceled successfully');
    }

    public function upgrade(Request $request, string $subscriptionId)
    {
        $productId = $request->input('product_id');

        $subscription = Creem::subscriptions()->upgrade(
            $subscriptionId,
            $productId
        );

        return back()->with('success', 'Subscription upgraded successfully');
    }
}
```

### License Validation Controller

[](#license-validation-controller)

```
namespace App\Http\Controllers;

use Romansh\LaravelCreem\Facades\Creem;
use Illuminate\Http\Request;

class LicenseController extends Controller
{
    public function validate(Request $request)
    {
        $validated = $request->validate([
            'license_key' => 'required|string',
            'instance_id' => 'required|string',
        ]);

        try {
            $license = Creem::licenses()->validate(
                $validated['license_key'],
                $validated['instance_id']
            );

            if ($license['status'] === 'active') {
                return response()->json([
                    'valid' => true,
                    'expires_at' => $license['expires_at'],
                ]);
            }

            return response()->json(['valid' => false], 403);
        } catch (\Exception $e) {
            return response()->json(['valid' => false], 403);
        }
    }
}
```

Advanced Configuration
----------------------

[](#advanced-configuration)

### Custom HTTP Settings

[](#custom-http-settings)

Modify `config/creem.php`:

```
'http' => [
    'timeout' => 30,
    'retry' => [
        'times' => 3,
        'sleep' => 100,
    ],
],
```

### Custom Webhook Path

[](#custom-webhook-path)

```
'webhook' => [
    'path' => '/custom/webhook/path',
    'middleware' => ['api', 'throttle:60,1'],
],
```

### Multiple Webhook Endpoints

[](#multiple-webhook-endpoints)

You can set up different webhook endpoints for different profiles:

```
// routes/web.php
use Romansh\LaravelCreem\Http\Controllers\WebhookController;
use Romansh\LaravelCreem\Http\Middleware\VerifyCreemWebhook;

Route::post('/webhooks/product-a', WebhookController::class)
    ->middleware([VerifyCreemWebhook::class.':product_a']);

Route::post('/webhooks/product-b', WebhookController::class)
    ->middleware([VerifyCreemWebhook::class.':product_b']);
```

API Reference
-------------

[](#api-reference)

### Profile Resolution Rules

[](#profile-resolution-rules)

1. **String (Profile Name)**: Loads named profile from config

    ```
    Creem::profile('product_a')->checkouts()->create([...]);
    ```
2. **No Profile (Default)**: Uses 'default' profile

    ```
    Creem::checkouts()->create([...]);
    ```
3. **Array (Inline Config)**: Uses provided configuration

    ```
    Creem::withConfig(['api_key' => '...'])->checkouts()->create([...]);
    ```

Troubleshooting
---------------

[](#troubleshooting)

### Invalid Webhook Signature

[](#invalid-webhook-signature)

Ensure your webhook secret matches between `.env` and Creem dashboard.

```
# Check your configuration
php artisan tinker
>>> config('creem.profiles.default.webhook_secret')
```

### API Key Issues

[](#api-key-issues)

```
// Verify your API key is loaded
config('creem.profiles.default.api_key')

// Check if test mode is enabled
config('creem.profiles.default.test_mode')
```

### Missing Profile Exception

[](#missing-profile-exception)

```
Creem profile 'xyz' not found in configuration.

```

Solution: Add the profile to `config/creem.php` or use an existing profile name.

Contributing
------------

[](#contributing)

Contributions are welcome! Please ensure:

- PSR-12 code style compliance
- All tests pass
- New features include tests
- Documentation is updated

Run Laravel Pint for code formatting:

```
./vendor/bin/pint
```

Security
--------

[](#security)

If you discover a security vulnerability, please email the package maintainer.

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

Support
-------

[](#support)

- **Creem Documentation**:
- **Package Issues**:

###  Health Score

46

—

FairBetter than 93% of packages

Maintenance90

Actively maintained with recent releases

Popularity14

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity60

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Recently: every ~8 days

Total

17

Last Release

50d ago

Major Versions

v1.2.1 → v2.22026-02-25

PHP version history (2 changes)v1.0.0PHP ^8.1

v1.1PHP ^8.1|^8.2|^8.3|^8.4

### Community

Maintainers

![](https://www.gravatar.com/avatar/b453f266005966bf6243a4834c93882f8c2de3e66194f8d086dc7484db29e7cb?d=identicon)[romansh](/maintainers/romansh)

---

Top Contributors

[![romansh](https://avatars.githubusercontent.com/u/10420314?v=4)](https://github.com/romansh "romansh (25 commits)")

---

Tags

laravelbillingpaymentpaymentssubscriptioncheckoutsaaslicense-managementcreemdiscount-codes

###  Code Quality

TestsPHPUnit

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/romansh-laravel-creem/health.svg)

```
[![Health](https://phpackages.com/badges/romansh-laravel-creem/health.svg)](https://phpackages.com/packages/romansh-laravel-creem)
```

###  Alternatives

[sebdesign/laravel-viva-payments

A Laravel package for integrating the Viva Payments gateway

4845.9k](/packages/sebdesign-laravel-viva-payments)[asciisd/knet

Knet package is provides an expressive, fluent interface to KNet's payment services.

141.1k](/packages/asciisd-knet)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
