承接 romansh/laravel-creem 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

romansh/laravel-creem

Composer 安装命令:

composer require romansh/laravel-creem

包简介

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

README 文档

README

A Laravel package for Creem.io payment provider. Built with Laravel-native patterns, clean architecture, and developer experience as top priorities.

Latest Version Total Downloads License

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

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

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

A full-featured demo app is available as a separate package: 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 http://localhost/creem-demo — 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

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

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']);

For direct or off-session payment flows, pass an explicit price_id rather than only a product_id. Hosted checkout can apply provider-side regional price selection, but saved-card style charges should identify the exact regional price to avoid currency mismatches.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

The package includes comprehensive tests:

# Run all tests
composer test

# Run with coverage
composer test -- --coverage

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

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

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

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

Custom HTTP Settings

Modify config/creem.php:

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

Custom Webhook Path

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

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

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

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

// 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

Creem profile 'xyz' not found in configuration.

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

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

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

License

The MIT License (MIT). Please see License File for more information.

Support

romansh/laravel-creem 适用场景与选型建议

romansh/laravel-creem 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 519 次下载、GitHub Stars 达 4, 最近一次更新时间为 2026 年 02 月 23 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「payment」 「checkout」 「payments」 「subscription」 「laravel」 「saas」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 romansh/laravel-creem 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 romansh/laravel-creem 我们能提供哪些服务?
定制开发 / 二次开发

基于 romansh/laravel-creem 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 519
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 4
  • 点击次数: 37
  • 依赖项目数: 2
  • 推荐数: 0

GitHub 信息

  • Stars: 4
  • Watchers: 0
  • Forks: 2
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-02-23