wayofdev/laravel-webhook-client

在 Laravel 应用中处理 Stripe webhook,支持 Cycle-ORM。

v1.3.22 2024-04-12 00:16 UTC

This package is auto-updated.

Last update: 2024-09-20 14:29:46 UTC


README


Dark WOD logo for light theme Light WOD logo for dark theme

Build Status Total Downloads Latest Stable Version Software License Commits since latest release

在 Laravel 应用中接收 webhook

Webhook 提供了一种机制,允许一个应用程序通知另一个应用程序关于特定事件,通常使用简单的 HTTP 请求。

由 wayofdev/laravel-webhook-client 包促进 Laravel 应用中 webhook 的接收,利用 cycle-orm 的力量。功能包括验证签名调用、存储有效负载数据和在队列作业中处理有效负载。

此包受 spatie/laravel-webhook-client 的启发并重新编写,以支持 Cycle-ORM。


💿 安装

→ 使用 composer

作为依赖项要求

$ composer req wayofdev/laravel-webhook-client

→ 配置包

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

php artisan vendor:publish \
	--provider="WayOfDev\WebhookClient\Bridge\Laravel\Providers\WebhookClientServiceProvider" \
	--tag="config"

这是将发布到 config/webhook-client.php 的文件内容

<?php

use WayOfDev\WebhookClient\Entities\WebhookCall;
use WayOfDev\WebhookClient\Persistence\ORMWebhookCallRepository;
use WayOfDev\WebhookClient\Profile\ProcessEverythingWebhookProfile;
use WayOfDev\WebhookClient\Response\DefaultRespondsTo;
use WayOfDev\WebhookClient\SignatureValidator\DefaultSignatureValidator;

return [
    'configs' => [
        [
            /*
             * This package supports multiple webhook receiving endpoints. If you only have
             * one endpoint receiving webhooks, you can use 'default'.
             */
            'name' => 'default',

            /*
             * We expect that every webhook call will be signed using a secret. This secret
             * is used to verify that the payload has not been tampered with.
             */
            'signing_secret' => env('WEBHOOK_CLIENT_SECRET'),

            /*
             * The name of the header containing the signature.
             */
            'signature_header_name' => 'Signature',

            /*
             * This class will verify that the content of the signature header is valid.
             *
             * It should implement \WayOfDev\WebhookClient\Contracts\SignatureValidator
             */
            'signature_validator' => DefaultSignatureValidator::class,

            /*
             * This class determines if the webhook call should be stored and processed.
             */
            'webhook_profile' => ProcessEverythingWebhookProfile::class,

            /*
             * This class determines the response on a valid webhook call.
             */
            'webhook_response' => DefaultRespondsTo::class,

            /*
             * The classname of the entity to be used to store webhook calls. The class should
             * be equal or extend WayOfDev\WebhookClient\Entities\WebhookCall.
             */
            'webhook_entity' => WebhookCall::class,

            /*
             * The classname of the repository to be used to store webhook calls. The class should
             * implement WayOfDev\WebhookClient\Contracts\WebhookCallRepository.
             */
            'webhook_entity_repository' => ORMWebhookCallRepository::class,

            /*
             * In this array, you can pass the headers that should be stored on
             * the webhook call entity when a webhook comes in.
             *
             * To store all headers, set this value to `*`.
             */
            'store_headers' => [
                '*',
            ],

            /*
             * The class name of the job that will process the webhook request.
             *
             * This should be set to a class that extends \WayOfDev\WebhookClient\Jobs\ProcessWebhookJob.
             */
            'process_webhook_job' => '',
        ],
    ],

    /*
     * The integer amount of days after which database records should be deleted.
     *
     * 7 deletes all records after 1 week. Set to null if no database records should be deleted.
     */
    'delete_after_days' => 30,
];

在配置文件的 signing_secret 键中,您应该添加一个有效的 webhook 密钥。此值应由发送 webhook 的应用程序提供。

此包将尽可能快地存储和响应 webhook。请求的有效负载处理是通过队列作业完成的。建议不要使用 sync 驱动器,而使用真实的队列驱动器。您应该在配置文件的 process_webhook_job 中指定将处理 webhook 请求的作业。任何扩展 WayOfDev\WebhookClient\Bridge\Laravel\Jobs\ProcessWebhookJob 并具有 handle 方法的有效类都是有效的作业。

→ 准备数据库

默认情况下,所有 webhook 调用都将保存到数据库中。

要创建包含 webhook 调用的表

  1. 您必须在您的 Laravel 项目中配置并运行 wayofdev/laravel-cycle-orm-adapter 包。

  2. 编辑 cycle.php 配置以将 WebhookCall 实体添加到搜索路径

    // ...
    
    'tokenizer' => [
        /*
         * Where should class locator scan for entities?
         */
        'directories' => [
            __DIR__ . '/../src/Domain', // Your current project Entities
            __DIR__ . '/../vendor/wayofdev/laravel-webhook-client/src/Entities', // Register new Entity
        ],
      
      	// ...
    ],
  3. 编辑配置后,运行命令以从新出现的实体生成新的迁移

    $ php artisan cycle:orm:migrate

    (可选) 查看要执行的迁移列表

    $ php artisan cycle:migrate:status
  4. 使用以下命令运行悬而未决的迁移

    $ php artisan cycle:migrate

→ 注意路由

最后,让我们处理路由。在发送 webhook 的应用程序中,您可能已经配置了一个您希望 webhook 请求发送到的 URL。在您的应用程序的路由文件中,您必须将此路由传递给 Route::webhooks。以下是一个示例

Route::webhooks('webhook-receiving-url');

在幕后,默认情况下这将注册一个到由此包提供的控制器的 POST 路由。因为发送 webhook 到您的应用程序的应用程序无法获取 csrf-token,您必须将此路由添加到 VerifyCsrfToken 中间件的 except 数组。

protected $except = [
    'webhook-receiving-url',
];

💻 使用

完成安装后,以下是该包功能的一个全面概述

  1. 签名验证
    • 该包首先验证传入请求的签名。
    • 如果签名未通过验证,则会抛出异常,触发 InvalidSignatureEvent 事件,并且请求不会被保存到数据库。
  2. Webhook 配置评估
    • 每个入站请求都与一个webhook配置文件交互,这个配置文件本质上是一个类,用于评估请求是否应该在您的应用程序中保存和处理。
    • 此配置文件可以根据应用程序的要求过滤特定的webhook请求。
    • 您可以根据需要创建自己的自定义webhook配置文件,以更改或扩展此逻辑。您的自定义webhook配置文件
  3. 存储与处理
    • 如果配置文件允许,则请求首先被保存在webhook_calls表中。
    • 随后,一个排队的工作处理WebhookCall实体。
    • Webhooks通常期望快速响应,因此通过排队作业,我们可以快速响应。
    • 处理webhook作业的配置位于webhook-client配置文件中的process_webhook_job下。
    • 如果在作业排队过程中出现任何问题,该包将在WebhookCall实体的exception字段中记录异常。
  4. Webhook响应
    • 一旦作业被分发,webhook响应就负责。此类确定请求的HTTP响应。
    • 默认情况下,返回带有'ok'消息的200状态码。但是,您也可以自定义webhook响应。了解如何轻松创建自己的webhook响应

→ 验证入站webhook的签名

此包假设入站webhook请求有一个可以用来验证有效负载未被篡改的头部。包含签名的头部的名称可以在配置文件的signature_header_name键中进行配置。默认情况下,该包使用DefaultSignatureValidator来验证签名。这是该类将如何计算签名的。

$computedSignature = hash_hmac(
  'sha256',
  $request->getContent(),
  $configuredSigningSecret
);

如果$computedSignature与值匹配,则请求将被传递给webhook配置文件。如果$computedSignature与签名头部中的值不匹配,则该包将响应500并丢弃请求。

→ 创建自己的签名验证器

签名验证器是实现WayOfDev\WebhookClient\Contracts\SignatureValidator的任何类。下面是这个接口的样子。

<?php

declare(strict_types=1);

namespace WayOfDev\WebhookClient\Contracts;

use Illuminate\Http\Request;
use WayOfDev\WebhookClient\Config;

interface SignatureValidator
{
    public function isValid(Request $request, Config $config): bool;
}

WebhookConfig是一个数据传输对象,它允许您轻松检索webhook请求的配置(包含包含签名的头部名称和密钥)。

在创建自己的SignatureValidator之后,您必须在webhook-client配置文件中的signature_validator中注册它。

→ 确定哪些webhook请求应该被存储和处理

在验证入站webhook请求的签名后,请求将被传递给webhook配置文件。webhook配置文件是一个类,用于确定请求是否应该被存储和处理。如果发送webhook的应用程序发送了您的应用程序不感兴趣的需求,则可以使用此类过滤出此类事件。

默认情况下,使用\WayOfDev\WebhookClient\Profile\ProcessEverythingWebhookProfile类。正如其名称所暗示的,这个默认类将确定所有入站请求都应该被存储和处理。

→ 创建自己的webhook配置文件

webhook配置文件是实现\WayOfDev\WebhookClient\Contracts\WebhookProfile的任何类。下面是这个接口的样子。

<?php

declare(strict_types=1);

namespace WayOfDev\WebhookClient\Contracts;

use Illuminate\Http\Request;

interface WebhookProfile
{
    public function shouldProcess(Request $request): bool;
}

在创建自己的WebhookProfile之后,您必须在webhook-client配置文件中的webhook_profile键中注册它。

→ 存储和处理webhook

在验证签名并且webhook配置文件确定请求应该被处理之后,该包将存储并处理请求。

请求首先将被存储在webhook_calls表中,涉及WebhookCall实体和WebhookCallRepository

如果您想自定义表名或存储行为中的任何内容,该包提供了灵活性,可以使用替代实体。这可以通过在 webhook_entity 中设置所需的实体来实现。

请确保您的实体继承自 WayOfDev\WebhookClient\Entities\WebhookCall。为了存储目的,WebhookCallRepository 接口及其实现 ORMWebhookCallRepository 都会发挥作用。可以通过 webhook_entity_repository 来修改默认存储库。

您可以通过覆盖 ORMWebhookCallRepositorystore 方法来更改 webhook 的存储方式。在 store 方法中,您应该返回持久化实体。

接下来,新创建的 WebhookCall 实体将被传递给一个队列作业,该作业将处理请求。任何扩展 \WayOfDev\WebhookClient\Bridge\Laravel\Jobs\ProcessWebhookJob 的类都是一个有效的作业。以下是一个示例

<?php

declare(strict_types=1);

namespace Infrastructure\Jobs;

use WayOfDev\WebhookClient\Bridge\Laravel\Jobs\ProcessWebhookJob as AbstractProcessWebhookJob;

class ProcessWebhookJob extends AbstractProcessWebhookJob
{
    public function handle()
    {
        // $this->webhookCall // contains an instance of `WebhookCall`

        // perform the work here
    }
}

您应该在 webhook-client 配置文件的 process_webhook_job 中指定您的作业类的名称。

→ 创建自己的 webhook 响应

webhook 响应是任何实现了 \WayOfDev\WebhookClient\Contracts\RespondsToWebhook 接口的类。以下是该接口的示例

<?php

declare(strict_types=1);

namespace WayOfDev\WebhookClient\Contracts;

use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use WayOfDev\WebhookClient\Config;

interface RespondsToWebhook
{
    public function respondToValidWebhook(Request $request, Config $config): Response;
}

创建自己的 WebhookResponse 后,您必须在 webhook-client 配置文件的 webhook_response 键中注册它。

→ 处理多个应用的 incoming webhook 请求

该包允许从多个不同的应用接收 webhook。让我们看看一个示例配置文件,其中我们添加了对两个 webhook URL 的支持。为了简洁起见,所有配置中的注释都已删除。

<?php
  
declare(strict_types=1);

use WayOfDev\WebhookClient\Entities\WebhookCall;
use WayOfDev\WebhookClient\Persistence\ORMWebhookCallRepository;
use WayOfDev\WebhookClient\Profile\ProcessEverythingWebhookProfile;
use WayOfDev\WebhookClient\Response\DefaultRespondsTo;
use WayOfDev\WebhookClient\SignatureValidator\DefaultSignatureValidator;

return [
    'configs' => [
        [
            'name' => 'webhook-sending-app-1',
            'signing_secret' => 'secret-for-webhook-sending-app-1',
            'signature_header_name' => 'Signature-for-app-1',
            'signature_validator' => DefaultSignatureValidator::class,
            'webhook_profile' => ProcessEverythingWebhookProfile::class,
            'webhook_response' => DefaultRespondsTo::class,
            'webhook_entity' => WebhookCall::class,
            'webhook_entity_repository' => ORMWebhookCallRepository::class,
            'process_webhook_job' => '',
        ],
        [
            'name' => 'webhook-sending-app-2',
            'signing_secret' => 'secret-for-webhook-sending-app-2',
            'signature_header_name' => 'Signature-for-app-2',
            'signature_validator' => DefaultSignatureValidator::class,
            'webhook_profile' => ProcessEverythingWebhookProfile::class,
            'webhook_response' => DefaultRespondsTo::class,
            'webhook_entity' => WebhookCall::class,
            'webhook_entity_repository' => ORMWebhookCallRepository::class,
            'process_webhook_job' => '',
        ],
    ],
];

当为包注册路由时,您应该将配置的 name 作为第二个参数传递。

Route::webhooks('receiving-url-for-app-1', 'webhook-sending-app-1');
Route::webhooks('receiving-url-for-app-2', 'webhook-sending-app-2');

→ 修改路由方法

作为 incoming webhook 客户端,有时您可能想要使用除了默认的 post 之外的路线方法。您有灵活性,可以将标准 post 方法修改为 getputpatchdelete 等方法。

Route::webhooks('receiving-url-for-app-1', 'webhook-sending-app-1', 'get');
Route::webhooks('receiving-url-for-app-1', 'webhook-sending-app-1', 'put');
Route::webhooks('receiving-url-for-app-1', 'webhook-sending-app-1', 'patch');
Route::webhooks('receiving-url-for-app-1', 'webhook-sending-app-1', 'delete');

→ 不使用控制器使用包

如果您不想使用宏提供的路由和控制器,您可以为您的控制器程序化地添加对 webhook 的支持。

WayOfDev\WebhookClient\WebhookProcessor 是一个类,它验证签名,调用 web 配置文件,存储 webhook 请求,并启动一个队列作业以处理存储的 webhook 请求。该包提供的控制器也使用该类 内部

它可以这样使用

use WayOfDev\WebhookClient\Entities\WebhookCall;
use WayOfDev\WebhookClient\Persistence\ORMWebhookCallRepository;
use WayOfDev\WebhookClient\Profile\ProcessEverythingWebhookProfile;
use WayOfDev\WebhookClient\Response\DefaultRespondsTo;
use WayOfDev\WebhookClient\SignatureValidator\DefaultSignatureValidator;
use WayOfDev\WebhookClient\Config;
use WayOfDev\WebhookClient\WebhookProcessor;

$webhookConfig = new Config([
    'name' => 'webhook-sending-app-1',
    'signing_secret' => 'secret-for-webhook-sending-app-1',
    'signature_header_name' => 'Signature',
    'signature_validator' => DefaultSignatureValidator::class,
    'webhook_profile' => ProcessEverythingWebhookProfile::class,
    'webhook_response' => DefaultRespondsTo::class,
    'webhook_entity' => WebhookCall::class,
    'webhook_entity_repository' => ORMWebhookCallRepository::class,
    'process_webhook_job' => '',
]);

(new WebhookProcessor($request, $webhookConfig))->process();

→ 删除实体

每当有 webhook 传入时,此包都会将其存储为 WebhookCall 实体。过一段时间后,您可能想要删除旧实体。

@todo Laravel 版本使用可大量删除的特质,因此,实体删除逻辑应使用 Laravel 控制台命令或通过提交到 cycle-orm 重新编写。

在此示例中,所有实体在 30 天后都将被删除。

return [
    'configs' => [
        // ...
    ],

    'delete_after_days' => 30,
];

🧪 运行测试

→ PHPUnit 测试

要运行测试,请运行以下命令

$ make test

→ 静态分析

使用 PHPStan 进行代码质量检查

$ make lint-stan

→ 代码规范修复

使用 The PHP Coding Standards Fixer (PHP CS Fixer) 修复代码以遵循我们的标准

$ make lint-php

🤝 许可证

Licence


🧱 贡献和有用资源

此存储库基于 spatie/laravel-webhook-client 的工作。


🙆🏼‍♂️ 作者信息

lotyp / wayofdev 创建于 2023


🙌 想要贡献?

感谢您考虑为wayofdev社区做出贡献!我们欢迎所有类型的贡献。如果您想

  • 🤔 提出一个功能建议
  • 🐛 报告一个问题
  • 📖 改进文档
  • 👨‍💻 为代码做出贡献