spatie/laravel-stripe-webhooks

在 Laravel 应用中处理 stripe webhooks

3.9.0 2024-07-22 08:17 UTC

README

Latest Version on Packagist GitHub Workflow Status Check & fix styling Total Downloads

Stripe 可以通过 webhooks 通知您的应用程序事件。此包可以帮助您处理这些 webhooks。默认情况下,它将验证所有传入请求的 Stripe 签名。所有有效的调用都将记录到数据库中。您可以轻松定义在特定事件击中应用程序时应分发的作业或事件。

此包不会处理 webhook 请求验证后应该执行的操作。您仍需自行编写任何工作(例如,关于支付的工作)的代码。

在开始使用此包之前,我们强烈建议您阅读 Stripe 上关于 webhooks 的完整文档

支持我们

我们投入了大量资源来创建 一流的开源包。您可以通过 购买我们的付费产品之一 来支持我们。

我们非常感谢您从家乡寄给我们明信片,注明您正在使用我们的哪些包。您可以在 我们的联系页面 上找到我们的地址。我们将所有收到的明信片发布在我们的 虚拟明信片墙上

升级

有关详细信息,请参阅 UPGRADING

安装

您可以通过 composer 安装此包

composer require spatie/laravel-stripe-webhooks

服务提供程序将自动注册自己。

您必须使用以下命令发布配置文件

php artisan vendor:publish --provider="Spatie\StripeWebhooks\StripeWebhooksServiceProvider"

这是将在 config/stripe-webhooks.php 下发布的配置文件的内容

return [
    /*
     * Stripe will sign each webhook using a secret. You can find the used secret at the
     * webhook configuration settings: https://dashboard.stripe.com/account/webhooks.
     */
    'signing_secret' => env('STRIPE_WEBHOOK_SECRET'),

    /*
     * You can define a default job that should be run for all other Stripe event type
     * without a job defined in next configuration.
     * You may leave it empty to store the job in database but without processing it.
     */
    'default_job' => '',

    /*
     * You can define the job that should be run when a certain webhook hits your application
     * here. The key is the name of the Stripe event type with the `.` replaced by a `_`.
     *
     * You can find a list of Stripe webhook types here:
     * https://stripe.com/docs/api#event_types.
     */
    'jobs' => [
        // 'source_chargeable' => \App\Jobs\StripeWebhooks\HandleChargeableSource::class,
        // 'charge_failed' => \App\Jobs\StripeWebhooks\HandleFailedCharge::class,
    ],

    /*
     * The classname of the model to be used. The class should equal or extend
     * Spatie\WebhookClient\Models\WebhookCall.
     */
    'model' => \Spatie\WebhookClient\Models\WebhookCall::class,

    /**
     * This class determines if the webhook call should be stored and processed.
     */
    'profile' => \Spatie\StripeWebhooks\StripeWebhookProfile::class,

    /*
     * Specify a connection and or a queue to process the webhooks
     */
    'connection' => env('STRIPE_WEBHOOK_CONNECTION'),
    'queue' => env('STRIPE_WEBHOOK_QUEUE'),

    /*
     * When disabled, the package will not verify if the signature is valid.
     * This can be handy in local environments.
     */
    'verify_signature' => env('STRIPE_SIGNATURE_VERIFY', true),
];

在配置文件的 signing_secret 键中,您应该添加一个有效的 webhook 密钥。您可以在 Stripe 控制台上的 webhook 配置设置 中找到该密钥。

接下来,您必须使用以下命令发布迁移

php artisan vendor:publish --provider="Spatie\WebhookClient\WebhookClientServiceProvider" --tag="webhook-client-migrations"

在迁移发布后,您可以通过运行迁移来创建 webhook_calls

php artisan migrate

最后,处理路由:在 Stripe 控制台 中,您必须配置 Stripe webhooks 应该击中应用程序的 URL。在您的应用程序的路由文件中,您必须将此路由传递给 Route::stripeWebhooks

Route::stripeWebhooks('webhook-route-configured-at-the-stripe-dashboard');

幕后,这将注册一个到由本包提供的控制器的 POST 路由。因为 Stripe 没有获取 csrf-token 的方法,所以您必须将此路由添加到 VerifyCsrfToken 中间件的 except 数组中

protected $except = [
    'webhook-route-configured-at-the-stripe-dashboard',
];

使用

Stripe 将为几种事件类型发送 webhooks。您可以在 Stripe 文档中找到 事件类型完整列表

Stripe 将为击中应用程序 webhook url 的所有请求签名。此包将自动验证签名是否有效。如果不有效,则请求可能不是由 Stripe 发送的。

除非出现严重错误,此包在webhook请求中始终会响应200。发送200将防止Stripe重复发送相同的事件。Stripe可能会偶尔发送重复的webhook请求多次。此包确保每个请求只处理一次。所有带有有效签名的webhook请求都将记录在webhook_calls表中。该表有一个payload列,其中保存了传入webhook的整个负载。

如果签名无效,请求将不会记录在webhook_calls表中,但会抛出Spatie\StripeWebhooks\WebhookFailed异常。如果在处理webhook请求期间出现错误,则抛出的异常将保存在exception列中。在这种情况下,控制器将发送500而不是200

此包有两种方式让您处理webhook请求:您可以选择排队一个工作或监听包将引发的事件。

使用作业处理webhook请求

如果您想在特定事件类型到达时执行某些操作,您可以定义一个执行工作的作业。以下是一个此类作业的示例

<?php

namespace App\Jobs\StripeWebhooks;

use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Spatie\WebhookClient\Models\WebhookCall;

class HandleChargeableSource implements ShouldQueue
{
    use InteractsWithQueue, Queueable, SerializesModels;

    /** @var \Spatie\WebhookClient\Models\WebhookCall */
    public $webhookCall;

    public function __construct(WebhookCall $webhookCall)
    {
        $this->webhookCall = $webhookCall;
    }

    public function handle()
    {
        // do your work here

        // you can access the payload of the webhook call with `$this->webhookCall->payload`
    }
}

我们强烈建议您将此作业设置为可排队,因为这将最小化webhook请求的响应时间。这允许您处理更多的Stripe webhook请求并避免超时。

创建您的作业后,必须在stripe-webhooks.php配置文件的jobs数组中注册它。键应该是Stripe事件类型的名称,但用.替换为_。值应该是完全限定类名。

// config/stripe-webhooks.php

'jobs' => [
    'source_chargeable' => \App\Jobs\StripeWebhooks\HandleChargeableSource::class,
],

如果您想将一个作业配置为默认作业以处理所有未定义的事件,可以在stripe-webhooks.php配置文件的default_job中设置作业。值应该是完全限定类名。

默认情况下,配置是一个空字符串'',这只会将事件存储在数据库中,但不会处理。

// config/stripe-webhooks.php
'default_job' => \App\Jobs\StripeWebhooks\HandleOtherEvent::class,

使用事件处理webhook请求

您可以选择在webhook请求到达时监听此包将引发的事件,而不是排队作业执行某些工作。每当有效请求击中您的应用程序时,该包将引发一个stripe-webhooks::<事件名称>事件。

事件的负载将是为传入请求创建的WebhookCall实例。

让我们看看您如何监听此类事件。在EventServiceProvider中,您可以注册监听器。

/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    'stripe-webhooks::source.chargeable' => [
        App\Listeners\ChargeSource::class,
    ],
];

以下是一个此类监听器的示例

<?php

namespace App\Listeners;

use Illuminate\Contracts\Queue\ShouldQueue;
use Spatie\WebhookClient\Models\WebhookCall;

class ChargeSource implements ShouldQueue
{
    public function handle(WebhookCall $webhookCall)
    {
        // do your work here

        // you can access the payload of the webhook call with `$webhookCall->payload`
    }
}

我们强烈建议您将事件监听器设置为可排队,因为这将最小化webhook请求的响应时间。这允许您处理更多的Stripe webhook请求并避免超时。

上面的示例只是Laravel中处理事件的一种方法。要了解其他选项,请阅读Laravel关于处理事件的文档

高级用法

重试处理webhook

所有传入的webhook请求都写入数据库。当处理webhook调用时发生错误时,这非常有价值。您可以在调查并修复失败原因后轻松重试处理webhook调用,如下所示

use Spatie\WebhookClient\Models\WebhookCall;
use Spatie\StripeWebhooks\ProcessStripeWebhookJob;

dispatch(new ProcessStripeWebhookJob(WebhookCall::find($id)));

执行自定义逻辑

您可以通过指定自己的模型来在stripe-webhooks配置文件的model键中,在排队的作业调度之前和/或之后添加一些应执行的自定义逻辑。您可以通过指定自己的模型在stripe-webhooks配置文件的model键中来实现,该类应扩展Spatie\StripeWebhooks\ProcessStripeWebhookJob

以下是一个示例

use Spatie\StripeWebhooks\ProcessStripeWebhookJob;

class MyCustomStripeWebhookJob extends ProcessStripeWebhookJob
{
    public function handle()
    {
        // do some custom stuff beforehand

        parent::handle();

        // do some custom stuff afterwards
    }
}

确定是否应该处理请求

您可以使用自己的逻辑来决定是否处理请求。您可以通过在 stripe-webhooks 配置文件中的 profile 键中指定自己的配置文件来实现。类应该实现 Spatie\WebhookClient\WebhookProfile\WebhookProfile

在本例中,我们将确保只处理之前未处理过的请求。

use Illuminate\Http\Request;
use Spatie\WebhookClient\Models\WebhookCall;
use Spatie\WebhookClient\WebhookProfile\WebhookProfile;

class StripeWebhookProfile implements WebhookProfile
{
    public function shouldProcess(Request $request): bool
    {
        return ! WebhookCall::where('payload->id', $request->get('id'))->exists();
    }
}

处理多个签名密钥

当使用 Stripe Connect 时,您可能希望包能够处理多个端点和密钥。以下是配置该行为的步骤。

如果您正在使用 Route::stripeWebhooks 宏,可以按如下方式追加 configKey

Route::stripeWebhooks('webhook-url/{configKey}');

或者,如果您正在手动定义路由,您可以添加 configKey 如此:

Route::post('webhook-url/{configKey}', '\Spatie\StripeWebhooks\StripeWebhooksController');

如果此路由参数存在,验证中间件将使用不同的配置键查找密钥,通过将给定的参数值追加到默认配置键。例如,如果 Stripe 向 webhook-url/my-named-secret 发送请求,您将添加一个新的配置,名为 signing_secret_my-named-secret

Connect 的示例配置可能如下所示:

// secret for when Stripe posts to webhook-url/account
'signing_secret_account' => 'whsec_abc',
// secret for when Stripe posts to webhook-url/connect
'signing_secret_connect' => 'whsec_123',

将 Webhook 负载转换为 Stripe 对象

您可以将 Webhook 负载转换为 Stripe 对象,以帮助访问其各种方法和属性。

为此,使用 Stripe\Event::constructFrom($payload) 方法与 WebhookCall 的负载一起使用。

use Stripe\Event;

// ...

public function handle(WebhookCall $webhookCall)
{
    /** @var \Stripe\StripeObject|null */
    $stripeObject = Event::constructFrom($webhookCall->payload)->data?->object;
}

例如,如果您已设置 Stripe webhook 用于 invoice.created 事件,您可以将负载转换为 StripeInvoice 对象。

/** @var \Stripe\StripeInvoice|null */
$stripeInvoice = Event::constructFrom($webhookCall->payload)->data?->object;

// $stripeInvoice->status
// $stripeInvoice->amount_due
// $stripeInvoice->amount_paid
// $stripeInvoice->amount_remaining

foreach ($stripeInvoice->lines as $invoiceLine) {
    // ...
}

关于 Cashier

Laravel Cashier 允许您轻松处理 Stripe 订阅。您可以将它与 laravel-stripe-webhooks 一起安装在同一应用程序中。没有已知的冲突。

更新日志

请参阅 CHANGELOG 了解最近更改的更多信息。

测试

composer test

贡献

请参阅 CONTRIBUTING 了解详细信息。

安全性

如果您发现有关安全的错误,请通过邮件 security@spatie.be 而不是使用问题跟踪器。

鸣谢

非常感谢 Sebastiaan Luca,他慷慨地分享了其 Stripe webhook 解决方案,这激发了本包的灵感。

许可

MIT 许可证 (MIT)。请参阅 许可文件 了解更多信息。