elitexp/laravel-stripe-webhooks

在Laravel应用程序中处理Stripe Webhooks

4.0.1 2023-03-23 05:49 UTC

README

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

使用Webhooks,Stripe可以通知您的应用程序事件。此包可以帮助您处理这些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,

    /*
     * 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发送的。

除非发生严重错误,本包将始终以 200 响应 webhook 请求。发送 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::<event-name> 事件。

事件的负载将是为传入请求创建的 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 键中来添加一些应该在排队的作业调度之前和/或之后执行的自定义逻辑。类应该扩展 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;
}

例如,如果您为invoice.created事件设置了Stripe webhook,则可以将有效负载转换为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一起安装到同一应用程序中。没有已知的冲突。

更新日志

有关最近更改的更多信息,请参阅更新日志

测试

composer test

贡献

有关详细信息,请参阅贡献指南

安全

如果您发现了关于安全性的bug,请通过security@spatie.be发送邮件,而不是使用问题跟踪器。

致谢

非常感谢Sebastiaan Luca慷慨地分享了他的Stripe webhook解决方案,该解决方案启发了这个包。

许可协议

MIT许可协议(MIT)。有关更多信息,请参阅许可文件