feature: implement stripe webhook

This commit is contained in:
kusowl 2026-03-24 18:49:49 +05:30
parent a51a2cd436
commit 14cb5a36ae
4 changed files with 93 additions and 0 deletions

View File

@ -0,0 +1,27 @@
<?php
namespace App\Actions;
use App\Enums\PaymentStatusEnum;
use App\Models\Payment;
use App\Models\PaymentStatus;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
final readonly class MarkPaymentAsPaidAction
{
/**
* Execute the action.
*
* @throws NotFoundResourceException
*/
public function execute(Payment $payment): bool
{
$status = PaymentStatus::whereName(PaymentStatusEnum::Paid->value)->value('id');
if (! $status) {
throw new NotFoundResourceException('Paid Status not found');
}
$payment->payment_status_id = $status;
return $payment->save();
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Enums;
enum StripeEventType: string
{
case CheckoutSessionCompleted = 'checkout.session.completed';
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Http\Controllers;
use App\Actions\MarkPaymentAsPaidAction;
use App\Enums\StripeEventType;
use App\Models\Payment;
use Illuminate\Http\Request;
use Log;
use Stripe\Exception\SignatureVerificationException;
use Stripe\Webhook;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use UnexpectedValueException;
class StripeWebhookController extends Controller
{
public function __construct(private readonly MarkPaymentAsPaidAction $paidAction)
{
}
public function __invoke(Request $request)
{
$payload = $request->getContent();
$sigHeader = $request->header('Stripe-Signature');
try {
$event = Webhook::constructEvent($payload, $sigHeader, config('services.stripe.webhook'));
} catch (SignatureVerificationException|UnexpectedValueException $e) {
Log::error('Stripe webhook signature verification error.', [$e->getMessage()]);
throw new BadRequestHttpException('Invalid Signature');
}
if ($event->type === StripeEventType::CheckoutSessionCompleted->value) {
$sessionId = $event->data->object->id ?? null;
if ($sessionId) {
$this->handleCheckoutSessionCompleted($sessionId);
} else {
throw new NotFoundHttpException('Session id not found in event');
}
}
}
private function handleCheckoutSessionCompleted(string $sessionId): void
{
$payment = Payment::where('transaction_id', $sessionId)->first();
if (! $payment) {
Log::error('Stripe Webhook: Payment record not found.', ['session_id' => $sessionId]);
throw new NotFoundHttpException('Payment record not found');
}
$this->paidAction->execute($payment);
Log::info('Stripe Webhook: Payment successfully marked as paid', ['order_id' => $payment->order_id]);
}
}

View File

@ -9,6 +9,7 @@
use App\Http\Controllers\ProductController;
use App\Http\Controllers\ProductImagesController;
use App\Http\Controllers\RegisteredUserController;
use App\Http\Controllers\StripeWebhookController;
use App\Http\Controllers\UserAddressController;
use Illuminate\Support\Facades\Route;
@ -34,3 +35,5 @@
});
Route::get('/categories', [ProductCategoryController::class, 'index']);
Route::apiResource('products', ProductController::class);
Route::post('/webhook/stripe', StripeWebhookController::class);