承接 emeroid/laravel-billing-core 相关项目开发

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

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

emeroid/laravel-billing-core

Composer 安装命令:

composer require emeroid/laravel-billing-core

包简介

A robust, driver-based, multi-gateway billing package for Laravel. Supports Paystack, PayPal, plan swapping, and dunning.

README 文档

README

Laravel Billing Core is a robust, driver-based, multi-gateway billing package for Laravel. It provides a simple, fluent API to manage one-time payments, subscriptions, plan swapping, and dunning logic for your SaaS application.

Stop rebuilding billing logic for every project — this package is the plug-and-play foundation you need.

🚀 Features

  • Driver-Based: Switch gateways just by changing your .env.
  • Multi-Gateway: Out-of-the-box support for Paystack and PayPal.
  • Subscription Management: A complete subscription lifecycle (subscribe, cancel, swapPlan).
  • Grace Periods: Automatically handles “cancel on end of billing period”.
  • Plan Swapping: Fluent API to upgrade or downgrade users.
  • Dunning: Listens for invoice.payment_failed webhooks to set past_due status.
  • Event-Based: Fires events like SubscriptionStarted, SubscriptionCancelled, etc.
  • Billable Trait: A powerful interface you can add to your User model.

🧩 Installation

composer require emeroid/laravel-billing-core

1. Configuration

Publish the Config File

php artisan vendor:publish --provider="Emeroid\Billing\BillingServiceProvider" --tag="billing-config"

This creates config/billing.php.

Publish the Migrations

php artisan vendor:publish --provider="Emeroid\Billing\BillingServiceProvider" --tag="billing-migrations"

This adds the plans, subscriptions, and transactions tables.

Run the Migrations

php artisan migrate

Update Your .env File

# --- BILLING CORE ---
BILLING_DEFAULT_DRIVER=paystack
BILLING_MODEL=\App\Models\User

# --- PAYSTACK ---
PAYSTACK_PUBLIC_KEY=pk_...
PAYSTACK_SECRET_KEY=sk_...

# --- PAYPAL ---
PAYPAL_CLIENT_ID=...
PAYPAL_SECRET=...
PAYPAL_MODE=sandbox
PAYPAL_WEBHOOK_ID=WH-...

Add the Billable Trait

// app/Models/User.php

use Emeroid\Billing\Traits\Billable;
use Illuminate{...};

class User extends Authenticatable
{
    use Billable, HasFactory, Notifiable;
    // ...
}

💳 Usage

2. Creating Plans

Before creating subscriptions, define your plans in the plans table — typically via a seeder:

// database/seeders/PlanSeeder.php

use Emeroid\Billing\Models\Plan;
use Illuminate\Database\Seeder;

class PlanSeeder extends Seeder
{
    public function run(): void
    {
        Plan::create([
            'name' => 'Pro Plan',
            'slug' => 'pro-plan',
            'amount' => 500000, // 5000 NGN (in kobo)
            'interval' => 'monthly',
            'paystack_plan_id' => 'PL_abc123',
            'paypal_plan_id' => 'P-xyz456',
        ]);

        Plan::create([
            'name' => 'Business Plan',
            'slug' => 'business-plan',
            'amount' => 1000000, // 10000 NGN
            'interval' => 'monthly',
            'paystack_plan_id' => 'PL_def789',
            'paypal_plan_id' => 'P-ghi123',
        ]);
    }
}

3. One-Time Payments

use Emeroid\Billing\Facades\Billing;
use Illuminate\Http\Request;

class PaymentController
{
    public function startPayment(Request $request)
    {
        $user = $request->user();
        $amountInKobo = 50000; // 5000 NGN

        try {
            $payment = Billing::purchase($amountInKobo, $user->email, [
                'user_id' => $user->id,
                'currency' => 'NGN',
            ]);

            return redirect()->away($payment['authorization_url']);

        } catch (\Emeroid\Billing\Exceptions\PaymentInitializationFailedException $e) {
            return back()->with('error', $e->getMessage());
        }
    }
}

4. Subscriptions

use Emeroid\Billing\Facades\Billing;
use Emeroid\Billing\Models\Plan;
use Illuminate\Http\Request;

class SubscriptionController
{
    public function startSubscription(Request $request)
    {
        $user = $request->user();
        $plan = Plan::where('slug', 'pro-plan')->firstOrFail();

        $gatewayPlanId = config('billing.default') === 'paypal'
            ? $plan->paypal_plan_id
            : $plan->paystack_plan_id;

        try {
            $subscription = Billing::subscribe(
                $gatewayPlanId,
                $user->email,
                [
                    'amount' => $plan->amount,
                    'user_id' => $user->id,
                    'currency' => 'NGN',
                ]
            );

            return redirect()->away($subscription['authorization_url']);

        } catch (\Emeroid\Billing\Exceptions\PaymentInitializationFailedException $e) {
            return back()->with('error', $e->getMessage());
        }
    }
}

5. Handling Callbacks

After payment, users are redirected to your site. The built-in CallbackController handles:

  • Verifying the transaction via Billing::verifyTransaction(...)
  • Creating the Subscription record
  • Firing events (TransactionSuccessful, SubscriptionStarted)
  • Redirecting to success/failure URLs (defined in config/billing.php)

All of this happens automatically.

6. Handling Webhooks

Add these URLs to your gateway dashboards:

https://your-app.com/billing-webhooks/paystack
https://your-app.com/billing-webhooks/paypal

The package automatically handles:

  • charge.success → verifies payments

  • subscription.create → creates subscriptions

  • subscription.disable → triggers SubscriptionCancelled

  • Dunning events:

    • invoice.payment_failed (Paystack)
    • BILLING.SUBSCRIPTION.PAYMENT.FAILED (PayPal)

7. The Billable Trait API

$user = auth()->user();

// STATUS CHECKS
$user->isSubscribed();
$user->onGracePeriod();
$user->hasActiveSubscription();
$user->isSubscribedTo('pro-plan');
$user->isPastDue();

// MANAGEMENT
$subscription = $user->getSubscription('SUB_abc');

$user->cancelSubscription($subscription->gateway_subscription_id);
$user->swapPlan($subscription->gateway_subscription_id, 'business-plan');
$user->syncSubscription($subscription->gateway_subscription_id);

8. Events

Listen for billing events in your EventServiceProvider:

// app/Providers/EventServiceProvider.php

use Emeroid\Billing\Events\{
    TransactionSuccessful,
    SubscriptionStarted,
    SubscriptionCancelled,
    SubscriptionPlanSwapped,
    SubscriptionPaymentFailed
};

protected $listen = [
    TransactionSuccessful::class => [
        'App\Listeners\GrantAccessToProduct',
        'App\Listeners\SendInvoiceEmail',
    ],
    SubscriptionStarted::class => [
        'App\Listeners\ActivateProFeatures',
    ],
    SubscriptionCancelled::class => [
        'App\Listeners\RevokeProFeaturesAtPeriodEnd',
    ],
    SubscriptionPlanSwapped::class => [
        'App\Listeners\HandlePlanSwap',
    ],
    SubscriptionPaymentFailed::class => [
        'App\Listeners\SendDunningEmail',
    ],
];

Emeroid Billing Package Customization

This document explains how to configure the Emeroid Billing package to correctly identify the billable entity when the customer’s payment gateway email does not directly match the billable model’s email field.

In many SaaS applications, a subscription belongs to a Team or Organization, even though the User making the payment uses their personal email.

1. Custom Billable Lookup Strategy

By default, the AbstractDriver attempts to locate the Billable Model (e.g., Team) by matching the email directly on that model.

If your Billable Model does not store the customer’s email, you must implement a custom lookup strategy.

Implementation: The BillableLookup Class

Create a lookup class that identifies the Billable Model (Team) through an associated User.

This method must have the exact signature:

public static function findByEmail(string $email): ?Model

Example: app/Lookups/BillableLookup.php

<?php

namespace App\Lookups;

use App\Models\User;
use App\Models\Team;
use Illuminate\Database\Eloquent\Model;

class BillableLookup
{
    /**
     * Custom strategy to find the billable model instance (Team) from an email.
     *
     * @param string $email The email address received from the payment gateway.
     * @return Model|null The Billable Model instance (Team) or null if not found.
     */
    public static function findByEmail(string $email): ?Model
    {
        // 1. Find the User by email.
        $user = User::where('email', $email)->first();

        if (!$user) {
            return null;
        }

        // 2. Return the Billable model (Team) associated with this User.
        // Example: Team is linked to User via the 'owner_id' foreign key.
        return Team::where('owner_id', $user->id)->first();
    }
}

2. Configuration

Register the custom lookup method inside config/billing.php under your gateway settings:

// File: config/billing.php

'email_lookup' => [
    \App\Lookups\BillableLookup::class,
    'findByEmail'
],

// ... rest of your config

3. How the Driver Uses the Strategy

The Emeroid\Billing\Drivers\AbstractDriver is designed to check for your custom lookup first:

// Excerpt from Emeroid\Billing\Drivers\AbstractDriver.php

protected function findBillableByEmail(string $email): ?Model
{
    // 1. Check for a custom lookup strategy defined in the billing config.
    $customLookup = config('billing.email_lookup');

    if (is_callable($customLookup)) {
        // Use the custom callable if defined.
        return call_user_func($customLookup, $email);
    }

    // 2. Fallback: Check the billable model directly.
    $modelClass = $this->billableModelClass();
    return $modelClass::where('email', $email)->first();
}

Conclusion

This customization ensures reliable billing behavior even when your architecture involves multi-layer ownership structures (e.g., users owning teams). The billing package will correctly identify the billable entity through your defined strategy, preventing mismatches and failed invoice processing.

🧪 Testing

composer test

🤝 Contributing

Please see CONTRIBUTING.md for details.

🔒 Security

If you discover any security-related issues, please email 📧 threalyongbug@gmail.com instead of using the issue tracker.

📄 License

Licensed under the MIT License (MIT). See the License File for more information.

❤️ Sponsorship

This project is free and open-source. If it helps you build your business, please consider supporting its development.

Sponsor @emeroid on GitHub.

emeroid/laravel-billing-core 适用场景与选型建议

emeroid/laravel-billing-core 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 14 次下载、GitHub Stars 达 0, 最近一次更新时间为 2025 年 11 月 11 日, 在 PHP 生态内属于活跃度较高的组件。

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

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

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

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

BUG 修复 & 性能优化

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

项目外包 & 长期维护

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

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

统计信息

  • 总下载量: 14
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 23
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

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

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-11-11