PrestaShop Stripe troubleshooting

Fix the PrestaShop Stripe Double-Charge Bug

A real double charge is a payment incident, not an SEO topic. This guide starts from the payment path: Stripe event IDs, PrestaShop carts, order_payment rows, webhook delivery, redirect confirmation, and the exact guardrails a safe PR should add.

store.example.com/order
SimpleReview
PrestaShop 8.1Customer checkout
Cart $128.00
Stripe payment
Cart ID#41892
Payment intentpi_3P...A2
Webhookpayment_intent.succeeded received twice
Customer action
Place order
Risk found: redirect and webhook can both call validateOrder() for cart #41892.
PrestaShop module scanFound Stripe confirmation in modules/stripe_official/.
Duplicate guardAdd cart plus payment-intent lock before validateOrder().
PR readyLogs event ID, blocks double submit, adds rollback note.
Comment to SimpleReview
Prevent duplicate Stripe order creation
Fix it
SimpleReview selects the risky checkout path, adds a duplicate-payment guard, and opens a PR for human review before production.

Short answer

If a PrestaShop Stripe customer reports a double charge, do not start by guessing. First decide whether you have two real Stripe charges, one Stripe charge with duplicate PrestaShop records, or duplicate-looking order references caused by PrestaShop order splitting or race conditions.

Research notes before the fix

SourceWhat matters for this bug
PrestaShop payment module docs External payment modules can receive both a customer return and a server-to-server notification. The docs also emphasize checking id_cart, amount source, and callback signature before order validation.
Stripe idempotent requests Stripe supports idempotency keys on POST requests so a retry does not accidentally create a second object or operation.
Stripe PrestaShop configuration The Stripe PrestaShop module exposes payment-flow, capture, logging, and refund settings that change how the order and charge lifecycle behaves.
PrestaShop duplicate reference issue Duplicate-looking orders are not always duplicate payments. One long-running PrestaShop issue documents several different causes, including legitimate split orders and race-condition style behavior.

Step 1: classify the incident

Use the payment provider as the source of truth for money movement, and PrestaShop as the source of truth for order state. The dangerous mistake is refunding or patching before you know which system duplicated what.

PatternLikely meaningFirst action
Two Stripe payment IDs for one intended cart Real duplicate charge or duplicate checkout session. Refund the duplicate after matching amount, customer, and timestamps. Then add idempotency or submit-lock protection.
One Stripe payment ID, two PrestaShop order rows Order creation or confirmation path duplicated the local record. Inspect module confirmation code, webhook logs, and order_payment.
Same PrestaShop reference across multiple rows May be split order, address/shipping edge case, invoice race, or a real bug. Check cart contents, carriers, addresses, warehouse settings, and invoice number behavior.
Pending order in PrestaShop, paid in Stripe Webhook or return handling failed after the charge. Enable module logging, inspect Stripe webhook delivery, and reconcile the order status.

Step 2: inspect the duplicate path

The common integration bug is not "Stripe randomly charged twice." It is usually one of these local paths:

Double submit

The customer clicks Place Order twice, the browser retries, or a slow checkout leaves the button active. Fix: disable the submit button after the first attempt and re-enable only on a real payment error.

Webhook plus redirect

The redirect controller and webhook handler both call order confirmation logic. Fix: one path creates or validates the order; the other only reconciles status after checking the existing payment lock.

Missing idempotency

A retry creates a second Stripe object. Fix: use an idempotency key derived from the cart and intended payment attempt when the module creates a PaymentIntent or Checkout Session.

Duplicate local display

PrestaShop may show duplicate-looking orders or payments even when only one charge exists. Fix: verify order_payment, orders, and Stripe IDs before touching money.

Step 3: add a safe guard in the module

The exact module path depends on your Stripe connector, but the guard belongs before order validation. The shape is simple: for a cart/payment pair, one process wins; every later callback sees the existing record and exits safely.

// Pseudocode for a custom PrestaShop payment guard.
$cartId = (int) $cart->id;
$paymentIntent = $stripePayload['payment_intent'] ?? null;

if (!$paymentIntent) {
    throw new PaymentException('Missing Stripe payment intent');
}

if ($this->paymentAttemptExists($cartId, $paymentIntent)) {
    $this->logDuplicateCallback($cartId, $paymentIntent, $stripeEventId);
    return $this->redirectToExistingOrder($cartId, $paymentIntent);
}

$this->createPaymentAttemptLock($cartId, $paymentIntent, $stripeEventId);
$this->validateOrder($cartId, $paidAmountFromStripe, $paymentIntent);
Important: this is intentionally pseudocode. Payment modules differ, and a production fix must match the installed Stripe module, PrestaShop version, multishop state, and order flow setting. Do not paste a generic payment patch into a live store.

Step 4: what SimpleReview should open as a PR

A good PR for this issue is small and auditable. It should not rewrite checkout. It should add observability and prevent the duplicate path.

How to test before production

  1. Run the checkout on staging with Stripe test mode and module extended logging enabled.
  2. Double-click Place Order, refresh after submit, and use browser back to retry the payment step.
  3. Replay the same webhook event or trigger a retry from Stripe test tooling if available.
  4. Confirm there is one order for one intended payment attempt, or one existing order redirect on duplicate callback.
  5. Confirm refunds still work from PrestaShop and Stripe dashboards.
  6. Test mobile checkout, cached checkout page behavior, and guest checkout.

FAQ

Is the Stripe module always responsible?

No. The issue can be theme JavaScript, a custom checkout module, a third-party payment module, webhook retries, multishop configuration, or a PrestaShop order-display edge case. Start from payment IDs and logs.

Should the fix live in an override?

Prefer the smallest maintainable path. If the bug is in custom code, patch custom code. If it is in a vendor module, avoid editing licensed source directly unless you own the maintenance path. A wrapper, hook, or custom module may be safer.

Can this be solved only with JavaScript?

No. Disabling the button helps user double-clicks, but webhooks, retries, and server races still need a server-side guard.

Make the payment fix reviewable

Let SimpleReview draft the narrow diff, then use Vibers human review before merging payment code. This is the right split: AI for file discovery and PR scaffolding, human review for money movement.

Sources

More PrestaShop resources: PrestaShop hub · PrestaShop fix guide · SimpleReview