You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修改Laravel Cashier实现订阅特定套餐后添加用户账户积分

实现Laravel Cashier订阅成功后给用户添加积分的方案

针对你用Laravel Cashier搭配Stripe管理订阅,想要在用户成功订阅特定套餐后添加对应积分的需求,我整理了几种可靠的实现方式,按推荐程度排序:

方法一:利用Stripe Webhook(最可靠)

这种方式通过监听Stripe的订阅状态变更事件,确保只有当订阅真正变为活跃状态(付款成功)时才发放积分,避免了同步处理可能出现的付款失败、3D验证延迟等问题。

步骤1:配置Stripe Webhook

在Stripe后台的「Webhooks」页面,添加你的应用端点(比如https://your-domain.com/stripe/webhook),勾选customer.subscription.updated事件,并复制Webhook签名密钥到.env文件:

STRIPE_WEBHOOK_SECRET=你的签名密钥

步骤2:生成并修改Webhook控制器

先生成Cashier自带的Webhook控制器:

php artisan cashier:webhook

打开app/Http/Controllers/StripeWebhookController.php,添加订阅更新的处理逻辑:

namespace App\Http\Controllers;

use Laravel\Cashier\Http\Controllers\WebhookController as CashierController;
use Symfony\Component\HttpFoundation\Response;

class StripeWebhookController extends CashierController
{
    /**
     * 处理订阅状态更新事件
     */
    public function handleCustomerSubscriptionUpdated($payload)
    {
        // 从Stripe payload中获取订阅记录
        $subscription = $this->getSubscriptionFromPayload($payload);
        if (!$subscription) {
            return response()->json(['message' => '订阅记录不存在'], Response::HTTP_NOT_FOUND);
        }

        $user = $subscription->user;
        $subscriptionStatus = $payload['data']['object']['status'];
        $stripePlanId = $payload['data']['object']['items']['data'][0]['price']['id'];

        // 仅当订阅变为活跃状态时处理积分奖励
        if ($subscriptionStatus !== 'active') {
            return response()->json(['message' => '订阅未激活,跳过积分奖励']);
        }

        // 检查用户是否已获得过该套餐的积分(避免重复发放)
        $hasReceivedReward = \App\Models\UserSubscriptionReward::where([
            'user_id' => $user->id,
            'stripe_plan_id' => $stripePlanId
        ])->exists();

        if ($hasReceivedReward) {
            return response()->json(['message' => '用户已获得过该套餐积分']);
        }

        // 定义套餐对应的积分规则
        $planPointsMap = [
            'price_123Bronze' => 300, // 替换成你Stripe后台的Bronze套餐价格ID
            'price_456Silver' => 600,
            'price_789Gold' => 1000
        ];

        if (isset($planPointsMap[$stripePlanId])) {
            // 给用户添加积分(假设用户模型有points字段)
            $user->increment('points', $planPointsMap[$stripePlanId]);
            
            // 记录奖励历史,防止重复发放
            \App\Models\UserSubscriptionReward::create([
                'user_id' => $user->id,
                'stripe_plan_id' => $stripePlanId,
                'points' => $planPointsMap[$stripePlanId],
                'reason' => '订阅'.$stripePlanId.'套餐奖励'
            ]);
        }

        return response()->json(['message' => '积分奖励发放成功']);
    }
}

步骤3:创建奖励历史模型(可选但推荐)

生成UserSubscriptionReward模型和迁移,用来记录用户的积分奖励历史:

php artisan make:model UserSubscriptionReward -m

在迁移文件中添加字段:

public function up()
{
    Schema::create('user_subscription_rewards', function (Blueprint $table) {
        $table->id();
        $table->foreignId('user_id')->constrained()->onDelete('cascade');
        $table->string('stripe_plan_id');
        $table->integer('points');
        $table->text('reason')->nullable();
        $table->timestamps();
    });
}

运行迁移:

php artisan migrate

方法二:监听Laravel Cashier事件(优雅且便捷)

如果你的业务场景不需要严格依赖Stripe的异步回调,可以通过监听Cashier自带的订阅事件来处理积分发放。

步骤1:生成事件监听器

php artisan make:listener AwardPointsOnSubscription

步骤2:注册事件监听

在app/Providers/EventServiceProvider.php的$listen数组中添加事件与监听器的绑定:

protected $listen = [
    \Laravel\Cashier\Events\SubscriptionUpdated::class => [
        \App\Listeners\AwardPointsOnSubscription::class,
    ],
];

步骤3:编写监听器逻辑

打开app/Listeners/AwardPointsOnSubscription.php:

namespace App\Listeners;

use Laravel\Cashier\Events\SubscriptionUpdated;

class AwardPointsOnSubscription
{
    public function handle(SubscriptionUpdated $event)
    {
        $subscription = $event->subscription;
        $user = $subscription->user;

        // 仅处理活跃状态的订阅
        if (!$subscription->active()) {
            return;
        }

        $stripePlanId = $subscription->stripe_price;
        $planPointsMap = [
            'price_123Bronze' => 300,
            'price_456Silver' => 600,
            'price_789Gold' => 1000
        ];

        // 检查是否已发放过积分
        $hasReceivedReward = \App\Models\UserSubscriptionReward::where([
            'user_id' => $user->id,
            'stripe_plan_id' => $stripePlanId
        ])->exists();

        if (!$hasReceivedReward && isset($planPointsMap[$stripePlanId])) {
            $user->increment('points', $planPointsMap[$stripePlanId]);
            
            \App\Models\UserSubscriptionReward::create([
                'user_id' => $user->id,
                'stripe_plan_id' => $stripePlanId,
                'points' => $planPointsMap[$stripePlanId],
                'reason' => '订阅套餐奖励'
            ]);
        }
    }
}

方法三:同步处理(简单但有风险)

如果你的订阅流程是完全同步且能确保付款100%成功,可以直接在订阅控制器中添加积分逻辑,但不推荐用于生产环境(比如遇到3D验证时,订阅会处于pending状态,同步代码不会触发):

public function subscribe(Request $request)
{
    $user = auth()->user();

    try {
        $subscription = $user->newSubscription('default', 'price_123Bronze')
            ->create($request->paymentMethodId);

        if ($subscription->active()) {
            $user->increment('points', 300);
        }

        return redirect()->route('dashboard')->with('success', '订阅成功,已获得300积分!');
    } catch (\Exception $e) {
        return back()->withErrors(['message' => '订阅失败:'.$e->getMessage()]);
    }
}
关键注意事项
  • 必须验证订阅状态为active,避免给付款失败的用户发放积分。
  • 加入奖励历史记录逻辑,防止用户重复订阅同一套餐时重复获得积分(如果业务允许重复发放可忽略)。
  • 本地开发测试Webhook时,需要用ngrok等工具暴露本地服务,确保Stripe能正常回调。

内容的提问来源于stack exchange,提问作者user7747472

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:39:28