Skip to content

Commit

Permalink
Only process events once (#87)
Browse files Browse the repository at this point in the history
* Abort if webhook event was alreay processed

* Make it possible to use a custom profile class

You might want to perform some more logic to determine if the webhook event should be processed or not.

* No need for whereJsonContains -> just use where

* Add test

* Update documentation

* Fix test
  • Loading branch information
aerni authored Jun 18, 2021
1 parent 40eb5de commit d603fe8
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 5 deletions.
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,12 @@ return [
* Spatie\StripeWebhooks\ProcessStripeWebhookJob.
*/
'model' => \Spatie\StripeWebhooks\ProcessStripeWebhookJob::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.
Expand Down Expand Up @@ -107,7 +112,7 @@ Stripe will send out webhooks for several event types. You can find the [full li

Stripe will sign all requests hitting the webhook url of your app. This package will automatically verify if the signature is valid. If it is not, the request was probably not sent by Stripe.

Unless something goes terribly wrong, this package will always respond with a `200` to webhook requests. Sending a `200` will prevent Stripe from resending the same event over and over again. All webhook requests with a valid signature will be logged in the `webhook_calls` table. The table has a `payload` column where the entire payload of the incoming webhook is saved.
Unless something goes terribly wrong, this package will always respond with a `200` to webhook requests. Sending a `200` will prevent Stripe from resending the same event over and over again. Stripe might occasionally send a webhook request [more than once](https://stripe.com/docs/webhooks/best-practices#duplicate-events). This package makes sure that each request will only be processed once. All webhook requests with a valid signature will be logged in the `webhook_calls` table. The table has a `payload` column where the entire payload of the incoming webhook is saved.

If the signature is not valid, the request will not be logged in the `webhook_calls` table but a `Spatie\StripeWebhooks\WebhookFailed` exception will be thrown.
If something goes wrong during the webhook request the thrown exception will be saved in the `exception` column. In that case the controller will send a `500` instead of `200`.
Expand Down Expand Up @@ -241,6 +246,27 @@ class MyCustomStripeWebhookJob extends ProcessStripeWebhookJob
}
}
```

### Determine if a request should be processed

You may use your own logic to determine if a request should be processed or not. You can do this by specifying your own profile in the `profile` key of the `stripe-webhooks` config file. The class should implement `Spatie\WebhookClient\WebhookProfile\WebhookProfile`.

In this example we will only process a request if it has an amount set:

```php
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 collect($request->get('data')['object'])->has('amount');
}
}
```

### Handling multiple signing secrets

When using [Stripe Connect](https://stripe.com/connect) you might want to the package to handle multiple endpoints and secrets. Here's how to configurate that behaviour.
Expand Down
7 changes: 6 additions & 1 deletion config/stripe-webhooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@
* Spatie\StripeWebhooks\ProcessStripeWebhookJob.
*/
'model' => \Spatie\StripeWebhooks\ProcessStripeWebhookJob::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.
Expand Down
15 changes: 15 additions & 0 deletions src/StripeWebhookProfile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Spatie\StripeWebhooks;

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();
}
}
3 changes: 1 addition & 2 deletions src/StripeWebhooksController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use Spatie\WebhookClient\Models\WebhookCall;
use Spatie\WebhookClient\WebhookConfig;
use Spatie\WebhookClient\WebhookProcessor;
use Spatie\WebhookClient\WebhookProfile\ProcessEverythingWebhookProfile;

class StripeWebhooksController
{
Expand All @@ -19,7 +18,7 @@ public function __invoke(Request $request, string $configKey = null)
config('stripe-webhooks.signing_secret'),
'signature_header_name' => 'Stripe-Signature',
'signature_validator' => StripeSignatureValidator::class,
'webhook_profile' => ProcessEverythingWebhookProfile::class,
'webhook_profile' => config('stripe-webhooks.profile'),
'webhook_model' => WebhookCall::class,
'process_webhook_job' => config('stripe-webhooks.model'),
]);
Expand Down
21 changes: 21 additions & 0 deletions tests/IntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,25 @@ public function a_request_with_a_config_key_will_use_the_correct_signing_secret(
->postJson('stripe-webhooks/somekey', $payload, $headers)
->assertSuccessful();
}

/** @test */
public function a_request_will_only_be_processed_once()
{
$payload = [
'type' => 'my.type',
'id' => 'evt_123',
];

$headers = ['Stripe-Signature' => $this->determineStripeSignature($payload)];

$this
->postJson('stripe-webhooks', $payload, $headers)
->assertSuccessful();

$this
->postJson('stripe-webhooks', $payload, $headers)
->assertSuccessful();

$this->assertCount(1, WebhookCall::where('payload->id', $payload['id'])->get());
}
}

0 comments on commit d603fe8

Please sign in to comment.