From 2550ec5695d8470b2f26faccd0c8332516d7d279 Mon Sep 17 00:00:00 2001 From: Myron Blair Date: Wed, 3 Jun 2026 03:41:30 +0000 Subject: [PATCH] Fix duplicate order confirmation email on Stripe Checkout When using Stripe Checkout, both checkout.session.completed and payment_intent.succeeded fire for the same payment. After the stripe.php change propagated order_id into PI metadata, the PI handler also found an order_id and sent a second confirmation email. Fix: fetch the order first in payment_intent.succeeded and skip if already confirmed. Also records stripe_payment_intent in this path for direct PI flows that bypass checkout.session.completed. --- api/webhook.php | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/api/webhook.php b/api/webhook.php index 15b02e5..8756b68 100644 --- a/api/webhook.php +++ b/api/webhook.php @@ -57,21 +57,25 @@ switch ($eventType) { break; case 'payment_intent.succeeded': - // Payment Intent flow (embedded checkout) — metadata.order_id set directly on PI + // Payment Intent flow (embedded/direct) - skip if already confirmed by checkout.session.completed $paymentIntentId = $data['id'] ?? ''; $orderId = $data['metadata']['order_id'] ?? ''; if ($orderId) { - db()->update('orders', - [ - 'payment_status' => 'paid', - 'order_status' => 'confirmed', - ], - 'order_id = :id', - ['id' => $orderId] - ); $order = db()->fetch("SELECT * FROM orders WHERE order_id = :id", ['id' => $orderId]); - if ($order) { - sendOrderConfirmationEmail($order); + if ($order && $order['order_status'] !== 'confirmed') { + db()->update('orders', + [ + 'payment_status' => 'paid', + 'order_status' => 'confirmed', + 'stripe_payment_intent' => $paymentIntentId, + ], + 'order_id = :id', + ['id' => $orderId] + ); + $order = db()->fetch("SELECT * FROM orders WHERE order_id = :id", ['id' => $orderId]); + if ($order) { + sendOrderConfirmationEmail($order); + } } } break;