支持Laravel.io的持续发展 →

如何在Laravel中配置多模型使用Cashier

2021年1月2日 阅读时间:12分钟

最近,我在一个的需求包含两个可验证模型(带有独立订阅的)的 Laravel 项目上工作。该项目自然使用 Laravel Cashier 来管理用户订阅。

默认情况下,Laravel Cashier 假设 App\Model\User 类作为一个账单模型。我们可以将其配置为使用不同的模型,但在我们的情况下有两个不同的模型。所以,我必须采取不同的方法。

CASHIER_MODEL=App\Models\User

提示:这将是一个非常长的教程!我将从创建模型、更新迁移、配置webhook等一切进行说明。

但如果你很着急,这里就是你的解决方案,技巧是在运行时使用 config 辅助函数设置 Cashier 的账单模型。

config(['cashier.model' => 'App\Models\Seller']);

初始设置

让我们从假设我们的应用程序有两个账单模型,一个是 User,另一个是 Seller。这两个模型都将具有订阅。使用 Cashier 与多个模型的方式有多种,但我们为了简单起见,将把它们的订阅详情存储在不同的表中。

让我们首先安装 Cashier 包的条纹功能。

composer require laravel/cashier

Cashier 将使用 subscriptionssubscription_items 表来存储有关用户订阅的信息。让我们发布 Cashier 的默认迁移,以便我们可以看一下表结构。

php artisan vendor:publish --tag="cashier-migrations"

现在我们应在 database/migrations 目录中看到以下文件。

  1. 2019_05_03_000001_create_customer_columns.php
  2. 2019_05_03_000002_create_subscriptions_table.php
  3. 2019_05_03_000003_create_subscription_items_table.php

这些文件包含订阅表的模式信息。不要担心,我们稍后会来处理这些文件。

User模型配置

首先,我们来设置我们的第一个计费模型,使用出纳功能设置User模型。将Billable特性添加到我们的第一个计费模型,该模型位于App\Models\User

use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}

此用户模型的订阅信息将存储在subscriptions表中。

接下来,让我们创建我们的第二个计费模型并为其添加迁移。

Seller模型配置

我们的Seller模型将像User模型一样包含订阅。但是,我们不仅仅只需要将Billable特性添加到我们的模型中。我们还需要为Seller模型添加迁移、配置认证保护等。

除了Seller模型,我们还将创建另外两个模型:SellerSubscriptionSellerSubscriptionItemSellerSubscription将包含Seller模型的订阅信息,而SellerSubscriptionItem模型将负责处理多计划订阅

简而言之,我们为Seller模型需要以下模型和表。

  1. 带有sellers表的Seller模型。
  2. 带有seller_subscriptions表的SellerSubscription模型。
  3. 带有seller_subscription_items表的SellerSubscriptionItem模型。

让我们首先使用以下 artisan 命令生成我们的模型。还可以通过在我们的命令中添加-m标志来生成模型和迁移文件。

php artisan make:model Seller -m

它应该在以下位置生成以下两个文件。

  1. Seller.php(位于/app/models目录)- Seller模型
  2. 2021_XX_XX_XXXXXX_create_sellers_table.php(位于/database/migrations目录)- 迁移文件

现在,让我们设置我们的Seller模型。就像User模型一样,我们需要将Billable特性添加到位于App\Models\SellerSeller模型中。

use Laravel\Cashier\Billable;

class Seller extends Authenticatable
{
    use Billable;
}

在创建sellers表的迁移文件(2021_XX_XX_XXXXXX_create_sellers_table.php)中,添加以下模式内容。我们还将引入在发布出纳默认迁移后从2019_05_03_000001_create_customer_columns.php获得的列。

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateSellersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('sellers', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();

            // Stripe Cashier's columns
            $table->string('stripe_id')->nullable()->index();
            $table->string('card_brand')->nullable();
            $table->string('card_last_four', 4)->nullable();
            $table->timestamp('trial_ends_at')->nullable();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('sellers');
    }
}

我们的Seller模型基本完成了。但是我们还需要添加特定的卖家订阅模型和迁移。

SellerSubscription模型配置

默认情况下,App\Models\User模型的订阅信息将存储在subscriptions表中。通过使用Billable特性,我们向Laravel指令,User模型将具有与Laravel\Cashier\Subscription模型的hasMany关系。我们可以通过ManagesSubscriptions特性来确认这一点。

在这里的Laravel\Cashier\Concerns\ManagesSubscriptions特性中,我们可以看到subscriptions方法,它定义了与Laravel\Cashier\Subscription模型的hasMany关系。

use Laravel\Cashier\Subscription;

public function subscriptions()
{
    return $this->hasMany(Subscription::class, $this->getForeignKey())->orderBy('created_at', 'desc');
}

所以,我们将创建一个名为SellerSubscription的类,该类继承自Laravel\Cashier\Subscription模型并继承其属性。

在控制台中运行以下命令以生成SellerSubscription模型和迁移。

php artisan make:model SellerSubscription -m

这将为以下文件生成以下文件。

  1. SellerSubscription.php(位于/app/models目录)
  2. 2021_XX_XX_XXXXXX_create_seller_subscriptions_table.php(位于/database/migrations目录)

SellerSubscriptionItem模型配置

User模型具有存储在subscription_items表中的多计划订阅。那么我们为什么要把Seller模型排除在外呢?让我们通过定义一个新的模型SellerSubscriptionItemSeller模型添加多计划订阅的功能。

在终端中运行以下命令以生成SellerSubscriptionItem模型和相关迁移。

php artisan make:model SellerSubscriptionItem -m

该命令将生成以下文件。

  1. SellerSubscriptionItem.php(位于/app/models目录)
  2. 文件名:2021_XX_XX_XXXXXX_create_seller_subscription_items_table.php (位于 /database/migrations 目录)

接下来,稍微修改一下 SellerSubscription 类,使其扩展 Laravel\Cashier\Subscription 类。同时,还需要定义与 Seller 类的 belongsTo 关系和与 SellerSubscriptionItem 类的 hasMany 关系。

<?php

namespace App\Models;

use Laravel\Cashier\Subscription;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class SellerSubscription extends Subscription
{
    use HasFactory;

    public function owner()
    {
        return $this->belongsTo(Seller::class);
    }

    public function items()
    {
        return $this->hasMany(SellerSubscriptionItem::class);
    }
}

接下来,也修改 SellerSubscriptionItem 类,使其扩展 Laravel\Cashier\SubscriptionItem 类。同样地,定义与 SellerSubscription 类的 belongsTo 关系。

<?php

namespace App\Models;

use Laravel\Cashier\SubscriptionItem;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class SellerSubscriptionItem extends SubscriptionItem
{
    use HasFactory;

    public function subscription()
    {
        return $this->belongsTo(SellerSubscription::class);
    }
}

现在,是时候从 Cashier 的默认迁移中汲取灵感,并相应地更新 seller_subscriptionsseller_subscription_items 迁移了。

请更新 seller_subscriptions 表的迁移文件 2021_XX_XX_XXXXXX_create_seller_subscriptions_table.php,使用以下架构结构。注意引用键 seller_id 并根据您的自定义模型进行修改。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateSellerSubscriptionsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('seller_subscriptions', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('seller_id');
            $table->string('name');
            $table->string('stripe_id');
            $table->string('stripe_status');
            $table->string('stripe_plan')->nullable();
            $table->integer('quantity')->nullable();
            $table->timestamp('trial_ends_at')->nullable();
            $table->timestamp('ends_at')->nullable();
            $table->timestamps();

            $table->index(['seller_id', 'stripe_status']);
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('seller_subscriptions');
    }
}

接下来,也更新 seller_subscription_items 表的迁移文件 2021_XX_XX_XXXXXX_create_seller_subscription_items_table.php,使用以下架构结构。同样注意引用键 seller_subscription_id 并根据您的自定义订阅物品模型进行修改。

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateSellerSubscriptionItemsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('seller_subscription_items', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('seller_subscription_id');
            $table->string('stripe_id')->index();
            $table->string('stripe_plan');
            $table->integer('quantity');
            $table->timestamps();

            // Short key name to support 64 character limit- https://dev.mysqlserver.cn/doc/refman/5.5/en/identifiers.html
            $table->unique(['seller_subscription_id', 'stripe_plan'], 'seller_subscription_id_stripe_plan_unique');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('seller_subscription_items');
    }
}

在定义了 SellerSubscriptionSellerSubscriptionItem 模型之后,通过在 Seller 类中添加 subscriptions 方法来定义一个 hasMany 关系。

最后,运行迁移命令以在数据库中创建/更新表。

php artisan migrate

Seller 模型

现在,修改 Seller 模型以覆盖从 Billable 特性继承来的 subscriptions 关系。而不是将关系定义为 Laravel\Cashier\Subscription 类,应当使用 App\Models\SellerSubscription 定义。

您的完成后的卖家模型应该看起来像这样。

<?php

namespace App\Models;

use Laravel\Cashier\Billable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Seller extends Model
{
    use HasFactory, Billable;

    public function subscriptions()
    {
        return $this->hasMany(SellerSubscription::class)->orderBy('created_at', 'desc');
    }
}

接下来,让我们为 Seller 模型设置 Stripe webhooks。默认情况下,Cashier 将使用 /stripe/webhook 路由来处理默认配置模型的 Stripe webhooks。

Seller 模型的 webhooks。

当客户的支付方式被拒绝或发生许多此类事件时,Stripe 可以通知您的应用。我们需要确保我们的应用正在处理这些事件。Cashier 包通过使用 /stripe/webhook 路由来处理这些事件而变得简单。

默认情况下,它为默认配置模型 App\Models\User 处理这些事件。在我们的情况下,我们应该注册不同的路由来处理我们的自定义模型的 Stripe webhooks。

首先,使用 artisan 命令生成一个名为 SellerWebhookController 的控制器。

php artisan make:controller SellerWebhookController

接下来,修改 SellerWebhookController,通过扩展 Laravel\Cashier\Http\Controllers\WebhookController 类,并修改 getUserByStripeId 方法以使用我们的自定义模型 Seller

<?php

namespace App\Http\Controllers;

use App\Models\Seller;
use Laravel\Cashier\Http\Controllers\WebhookController;

class SellerWebhookController extends WebhookController
{
    protected function getUserByStripeId($stripeId)
    {
        return Seller::where('stripe_id', $stripeId)->first();
    }
}

控制器之后,注册一个新的路由以处理来自 Stripe 的 webhook 事件。

use App\Http\Controllers\SellerWebhookController;

// Route for handling Stripe events
Route::post('/stripe/seller/webhook', [SellerWebhookController::class, 'handleWebhook']);

接下来,在您的 Stripe 控制面板中,您应该在 https://{yourapplication.com}/stripe/seller/webhook URL 上启用以下 webhooks。

  • customer.subscription.updated - 当订阅更新时
  • customer.subscription.deleted - 当订阅取消/删除时。
  • customer.updated - 当客户信息更新时。
  • customer.deleted - 当客户被删除时。
  • invoice.payment_action_required - 当支付方式需要采取行动时。通常是当客户的卡被拒绝时。

就这样,我们的应用现在已准备好处理自定义可计费模型 Seller 的 Stripe webhooks。

配置认证守卫和提供商

在我们的应用中,卖家可以对自己进行认证。因此,将 Seller 模型配置为利用 Laravel 的认证功能是有意义的。

首先,在 config/auth.php 文件的 providers 数组中注册一个新的用户提供者,用于我们的可计费模型 Seller,并在 guards 数组中也注册一个新的防护者。

'guards' => [
        // ...

        'seller' => [
            'driver' => 'session',
            'provider' => 'sellers',
        ],
	// ...
],

// ...

'providers' => [
        // ...

        'sellers' => [
            'driver' => 'eloquent',
            'model' => App\Models\Seller::class,
        ],
	// ...
]

完成这一切后,我们应该可以使用 $request->user('seller') 辅助函数来检索认证过的卖家。

用途

经过所有设置,我们终于可以开始使用 Cashier 与 Seller 模型了。现在,我们应该可以使用 $request->user('seller') 来检索认证过的卖家,并使用 config 辅助函数覆盖 Cashier 的默认模型。

// retrieve authenticated seller
$seller = $request->user('seller');

// override cashiers default model at the runtime
config(['cashier.model' => 'App\Models\Seller']);

让我们看看在实际代码中如何使用它。

创建新的计划

在为自定义模型 Seller 创建新的计划时,首先通过使用 $request->user('seller') 辅助函数检索它。然后使用 config 辅助函数在运行时将 Cashier 的可计费模型设置为 Seller

use Illuminate\Http\Request;

Route::post('/seller/subscribe', function (Request $request) {
    config(['cashier.model' => 'App\Models\Seller']);

    $request->user('seller')->newSubscription(
        'default', 'price_premium'
    )->create($request->paymentMethodId);

    // ...
});

检索 Stripe 客户端

当我们需要使用 Stripe ID 检索一个客户时,应使用 Cashier::findBillable()。但在那之前,不要忘记使用 config 辅助函数设置 Cashier 的可计费模型。

use Laravel\Cashier\Cashier;

config(['cashier.model' => 'App\Models\Seller']);

$seller = Cashier::findBillable($stripeId);

Stripe 计费门户

如果您在应用程序中使用 Stripe 的 计费门户,可以像这样将我们的自定义 Seller 模型重定向到其计费门户。

use Illuminate\Http\Request;

Route::get('/billing-portal', function (Request $request) {
    // Override cashier's default model in case
    config(['cashier.model' => 'App\Models\Seller']);

    // Retrive authenticated seller
    $seller = $request->user('seller');

    return $seller->redirectToBillingPortal();
});

这就完成了!这些是使用 Cashier 包处理多个模型的一些方法。您可以在 文档 中找到有关 Cashier 的更多信息。

总结

Laravel Cashier 是一个用于管理 Laravel 订阅的出色包。当然,可以以其他方式使用 Cashier 多个模型。当您有两个以上可计费模型时,这是一种您可以使用的解决方案。

无论如何,这是一篇很长的文章,感谢您阅读!如果您对文章有任何疑问,请在我推特上 @swapnil_bhavsar 上留言。

PS:以下是该项目在 GitHub 上的源代码 - https://github.com/IamSwap/laravel-cashier-multiple-models

最后更新 1 年前。

rajesh, sumit, robindirksen1, ajay1991cse, driesvints 喜欢了这篇文章

5
喜欢这篇文章? 让作者知道,并为他们鼓掌!
swapnil (Swapnil Bhavsar) Laravel & Vue.js 开发者。我是一名日间 Chitranu 的首席开发者,夜间自由职业开发者!

其他您可能会喜欢的文章

2024年3月11日

使用Larastan将您的Laravel应用程序从0到9提升

在您的Laravel应用程序执行之前发现错误,这是可能的,归功于Larastan,它...

阅读文章
2024年7月19日

无需特质标准化API响应

我发现,为API响应创建的大部分库都是使用特性实现的,并且...

阅读文章
2024年7月17日

在您的Laravel项目中通过Discord通知收集反馈

如何在Laravel项目中创建反馈模块,并在收到消息时收到Discord通知...

阅读文章

我们想感谢这些 精彩的公司 对我们的支持

在这里放您的标志吗?

Laravel.io

Laravel解决问题的门户,知识共享和社区建设。

© 2024 Laravel.io - 版权所有。