Skip to content

Commit

Permalink
Merge pull request #4 from dystcz/feature/stripe-webhooks
Browse files Browse the repository at this point in the history
Handle webhooks on queue
  • Loading branch information
theimerj authored Feb 6, 2024
2 parents 3091797 + db64917 commit 3a72360
Show file tree
Hide file tree
Showing 19 changed files with 1,115 additions and 548 deletions.
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
],
"require": {
"php": "^8.2",
"dystcz/lunar-api": "^0.7",
"dystcz/lunar-api": "dev-feature/update-payments as 0.7",
"illuminate/contracts": "^10.0",
"lunarphp/stripe": "^0.7.0"
"lunarphp/stripe": "^0.7.0",
"spatie/laravel-stripe-webhooks": "^3.6"
},
"require-dev": {
"laravel/pint": "^1.0",
Expand Down
1,027 changes: 607 additions & 420 deletions composer.lock

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions config/stripe-webhooks.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?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' => \Dystcz\LunarApiStripeAdapter\Jobs\Webhooks\HandleOtherEvent::class,

/*
* 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' => [
'payment_intent_created' => \Dystcz\LunarApiStripeAdapter\Jobs\Webhooks\HandlePaymentIntentCreated::class,
'payment_intent_succeeded' => \Dystcz\LunarApiStripeAdapter\Jobs\Webhooks\HandlePaymentIntentSucceeded::class,
'payment_intent_payment_failed' => \Dystcz\LunarApiStripeAdapter\Jobs\Webhooks\HandlePaymentIntentFailed::class,
'payment_intent_cancelled' => \Dystcz\LunarApiStripeAdapter\Jobs\Webhooks\HandlePaymentIntentCancelled::class,
// 'payment_intent_processing' => \Dystcz\LunarApiStripeAdapter\Jobs\Webhooks\HandlePaymentIntentProcessing::class,
// 'payment_intent_requires_action' => \Dystcz\LunarApiStripeAdapter\Jobs\Webhooks\HandlePaymentIntentRequiresAction::class,
// 'source_chargeable' => \Dystcz\LunarApiStripeAdapter\Jobs\Webhooks\HandleChargeableSource::class,
// 'charge_failed' => \Dystcz\LunarApiStripeAdapter\Jobs\Webhooks\HandleChargeFailed::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),
];
6 changes: 0 additions & 6 deletions config/stripe.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,4 @@
return [
'driver' => 'stripe',
'type' => 'stripe',

'payment_intent_status_map' => [
'payment_intent.succeeded' => 'succeeded',
'payment_intent.canceled' => 'canceled',
'payment_intent.payment_failed' => 'failed',
],
];
23 changes: 23 additions & 0 deletions database/migrations/create_webhook_calls_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

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

return new class extends Migration
{
public function up()
{
Schema::create('webhook_calls', function (Blueprint $table) {
$table->bigIncrements('id');

$table->string('name');
$table->string('url');
$table->json('headers')->nullable();
$table->json('payload')->nullable();
$table->text('exception')->nullable();

$table->timestamps();
});
}
};
9 changes: 5 additions & 4 deletions routes/webhooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Route;

$adapterType = Config::get('lunar-api.stripe.type', 'stripe');

Route::post(
'/stripe/webhook',
"/{$adapterType}/webhook",
fn (Request $request) => App::make(HandlePaymentWebhookController::class)(
Config::get('lunar-api.stripe.driver'),
Config::get('lunar-api.stripe.driver', 'stripe'),
$request
)
)
->name('payments.webhook');
)->name("payments.webhook.{$adapterType}");
28 changes: 28 additions & 0 deletions src/Jobs/Webhooks/HandleChargeFailed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Dystcz\LunarApiStripeAdapter\Jobs\Webhooks;

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

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

public WebhookCall $webhookCall;

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

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

// you can access the payload of the webhook call with `$this->webhookCall->payload`
}
}
28 changes: 28 additions & 0 deletions src/Jobs/Webhooks/HandleChargeableSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Dystcz\LunarApiStripeAdapter\Jobs\Webhooks;

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

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

public WebhookCall $webhookCall;

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

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

// you can access the payload of the webhook call with `$this->webhookCall->payload`
}
}
32 changes: 32 additions & 0 deletions src/Jobs/Webhooks/HandleOtherEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Dystcz\LunarApiStripeAdapter\Jobs\Webhooks;

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

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

public WebhookCall $webhookCall;

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

public function handle(): void
{
try {
$event = Event::constructFrom($this->webhookCall->payload);
} catch (Throwable $e) {
$this->fail($e);
}
}
}
48 changes: 48 additions & 0 deletions src/Jobs/Webhooks/HandlePaymentIntentCancelled.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace Dystcz\LunarApiStripeAdapter\Jobs\Webhooks;

use Dystcz\LunarApi\Domain\Orders\Actions\FindOrderByIntent;
use Dystcz\LunarApi\Domain\Orders\Events\OrderPaymentCanceled;
use Dystcz\LunarApi\Domain\Payments\Data\PaymentIntent;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
use Lunar\Stripe\Facades\StripeFacade;
use Spatie\WebhookClient\Models\WebhookCall;
use Stripe\Event;
use Throwable;

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

public WebhookCall $webhookCall;

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

public function handle(): void
{
try {
$event = Event::constructFrom($this->webhookCall->payload);
$paymentIntent = new PaymentIntent(intent: $event->data->object);
} catch (Throwable $e) {
$this->fail($e);
}

try {
$order = App::make(FindOrderByIntent::class)($paymentIntent);
} catch (Throwable $e) {
$this->fail($e);
}

$paymentAdapter = StripeFacade::getFacadeRoot();

OrderPaymentCanceled::dispatch($order, $paymentAdapter, $paymentIntent);
}
}
32 changes: 32 additions & 0 deletions src/Jobs/Webhooks/HandlePaymentIntentCreated.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Dystcz\LunarApiStripeAdapter\Jobs\Webhooks;

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

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

public WebhookCall $webhookCall;

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

public function handle(): void
{
try {
$event = Event::constructFrom($this->webhookCall->payload);
} catch (Throwable $e) {
$this->fail($e);
}
}
}
48 changes: 48 additions & 0 deletions src/Jobs/Webhooks/HandlePaymentIntentFailed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace Dystcz\LunarApiStripeAdapter\Jobs\Webhooks;

use Dystcz\LunarApi\Domain\Orders\Actions\FindOrderByIntent;
use Dystcz\LunarApi\Domain\Orders\Events\OrderPaymentFailed;
use Dystcz\LunarApi\Domain\Payments\Data\PaymentIntent;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
use Lunar\Stripe\Facades\StripeFacade;
use Spatie\WebhookClient\Models\WebhookCall;
use Stripe\Event;
use Throwable;

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

public WebhookCall $webhookCall;

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

public function handle(): void
{
try {
$event = Event::constructFrom($this->webhookCall->payload);
$paymentIntent = new PaymentIntent(intent: $event->data->object);
} catch (Throwable $e) {
$this->fail($e);
}

try {
$order = App::make(FindOrderByIntent::class)($paymentIntent);
} catch (Throwable $e) {
$this->fail($e);
}

$paymentAdapter = StripeFacade::getFacadeRoot();

OrderPaymentFailed::dispatch($order, $paymentAdapter, $paymentIntent);
}
}
34 changes: 34 additions & 0 deletions src/Jobs/Webhooks/HandlePaymentIntentProcessing.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Dystcz\LunarApiStripeAdapter\Jobs\Webhooks;

use Dystcz\LunarApi\Domain\Payments\Data\PaymentIntent;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Spatie\WebhookClient\Models\WebhookCall;
use Stripe\Event;
use Throwable;

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

public WebhookCall $webhookCall;

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

public function handle(): void
{
try {
$event = Event::constructFrom($this->webhookCall->payload);
$paymentIntent = new PaymentIntent(intent: $event->data->object);
} catch (Throwable $e) {
$this->fail($e);
}
}
}
Loading

0 comments on commit 3a72360

Please sign in to comment.