# Retry mechanism is safe

- **Pattern:** `ab-000558` (`booking-flow-lifecycle.payment.retry-mechanism`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000558
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000558)

## Why it matters

Network timeouts during payment API calls are a normal operational event, and every retry without an idempotency key is a potential double charge. CWE-605 (multiple binds to the same port) and CWE-841 both describe re-entrancy hazards; in payment systems the re-entrancy produces a duplicate charge that Stripe will honor because each request without an idempotency key is treated as a new, independent charge. A customer who retries a failed payment page reload can be charged multiple times. Idempotency keys that are random UUIDs regenerated on each retry defeat the purpose — the key must be deterministically derived from the booking ID so retries produce the same result.

## Severity rationale

Low because double-charge risk requires a specific failure mode (network timeout + retry) but the consequence — a customer charged twice — demands immediate manual remediation.

## Remediation

Pass a deterministic idempotency key tied to the booking ID in every payment API call. Implement this in `src/lib/payment-service.ts` or your checkout handler.

```typescript
const paymentIntent = await stripe.paymentIntents.create(
  {
    amount,
    currency: 'usd',
    metadata: { bookingId: booking.id },
  },
  {
    idempotencyKey: `booking-${booking.id}`, // deterministic — same key on retry
  }
);
```

Also add a processed-event check in your webhook handler to guard against Stripe replaying events.

## Detection

- **ID:** `retry-mechanism`
- **Label:** Retry mechanism is safe
- **Severity:** `low`
- **What to look for:** Count all payment API calls (create payment intent, create charge, create checkout session) and verify each one includes an idempotency key. Search for `idempotencyKey`, `Idempotency-Key`, `request_id`, or equivalent. Also check webhook handlers for idempotent processing (e.g., checking if the event was already processed before acting on it). Enumerate: "X of Y payment API calls include idempotency keys."
- **Pass criteria:** At least 1 payment API call includes a unique idempotency key (usually the booking ID or a deterministic hash of booking ID + amount). Webhook handlers must also be idempotent (check for duplicate event processing). Report: `"X of Y payment API calls use idempotency keys. Webhook idempotency: [yes/no]."`
- **Fail criteria:** Retries could cause double charges. No idempotency keys used in any payment API call. Do NOT pass when the idempotency key is a random UUID generated on each retry — it must be deterministic (tied to the booking ID or transaction).
- **Skip (N/A) when:** No payment integration (no Stripe, PayPal, Square, or other payment SDK in `package.json` dependencies).
- **Detail on fail:** `"No idempotency key used in Stripe payment API calls. A network timeout and retry could result in double charges."`
- **Cross-reference:** The payment-booking-coupling check in this category provides the booking ID commonly used as the idempotency key.
- **Cross-reference:** The payment-capture-timing check in this category verifies the payment flow where retries could occur.
- **Cross-reference:** The failure-handling check in this category verifies what happens when a retried payment ultimately fails.
- **Remediation:** Pass idempotency keys in all payment API calls (e.g., in `src/lib/payment-service.ts` or your checkout handler).

  ```typescript
  const paymentIntent = await stripe.paymentIntents.create(
    {
      amount,
      currency: 'usd',
      metadata: { bookingId: booking.id }
    },
    {
      idempotencyKey: booking.id // Prevent duplicate charges on retry
    }
  );
  ```

---

## External references

- cwe CWE-605
- cwe CWE-841
- iso-25010 reliability.fault-tolerance

Taxons: error-resilience

HTML version: https://auditbuffet.com/patterns/ab-000558
