DEV Community

slawekluzny
slawekluzny

Posted on • Originally published at 24ad.info

How We Fixed the Silent Payment Bug That Cost Us Real Money

How We Fixed the Silent Payment Bug That Cost Us Real Money

This morning I was reviewing Stripe logs when I noticed something odd—successfully paid "Premium Listing" purchases where the posts never actually got premium status. Our users were paying, but the promised visibility boost wasn't happening. Oops.

The Bug That Lived in Production for Approximately 13-19 Days

The issue was straightforward, yet nasty:

  1. User pays for a premium listing package via Stripe Checkout
  2. Client-side completes the payment flow
  3. Backend marks payment as completed in our database
  4. Critical step missing: The actual package benefits (featuredUntil/highlightedUntil timestamps) never got applied to the post

We discovered this when a user emailed saying "I paid £4.99 but my post looks the same."

The Anatomy of the Failure

Our payment flow had two parallel paths:

Admin Bypass Path (Working)

// Working admin flow
await markPaymentCompleted(paymentId);
await applyPackageToPost(postId, packageType); // ✅
Enter fullscreen mode Exit fullscreen mode

Stripe Checkout Path (Broken)

// Broken production flow until v1.4.4
await markPaymentCompleted(paymentId); // ❌ Stopped here
// applyPackageToPost() was never called
Enter fullscreen mode Exit fullscreen mode

The missing line was a classic copy-paste omission—we'd abstracted the package application logic but forgot to call it in the Stripe verification path.

The Fix (v1.4.4)

We patched it with surgical precision:

  1. Added applyPackageToPost() to the checkout verification flow
  2. Made the admin bypass use the same unified function
  3. Added transaction wrapping to ensure atomic updates
// Fixed in v1.4.4
const verified = await verifyCheckoutSession(sessionId);
if (verified) {
  await db.transaction(async (tx) => {
    await markPaymentCompleted(paymentId, tx);
    await applyPackageToPost(postId, packageType, tx); // ✅ Now called
  });
}
Enter fullscreen mode Exit fullscreen mode

Key details:

  • Uses Drizzle ORM transactions
  • Same function handles both Stripe and admin flows
  • Logs package application separately for auditing

Why This Hurt More Than Typical Bugs

  1. Silent failure - No errors in logs, payments showed as "completed"
  2. Delayed detection - Users assumed premium features were subtle
  3. Reputation risk - Paying customers weren't getting what they paid for

Lessons Learned

  1. Payment flows need dual verification

    We now:

    • Log package application separately from payment completion
    • Have a nightly job checking for paid-but-unapplied packages
  2. Transactional boundaries matter

    The fix wrapped everything in a DB transaction—no more partial updates.

  3. Admin tools should use the same paths as users

    The admin bypass worked because it called the full flow. The Stripe path didn't.

  4. Monitor what you monetize

    We added Stripe webhook logging and dashboard monitoring for:

    • Payments completed vs packages applied (tracked via checkout session IDs)
    • Time delta between payment and feature activation (logged in checkout verification flow)

The Stack That Caught It

Our monitoring eventually flagged the issue through user reports and internal checks.

The Stripe webhook handler is a stub. Package activation happens in the verify-checkout-session flow.

Current Architecture (Post-Fix)

graph TD
    A[Stripe Checkout] -->|Session ID| B(verifyCheckoutSession)
    B --> C{Valid?}
    C -->|Yes| D[markPaymentCompleted]
    D --> E[applyPackageToPost]
    E --> F[Update post.featuredUntil]
    C -->|No| G[Log failed verification]
    H[Admin Bypass] --> E
Enter fullscreen mode Exit fullscreen mode

Key improvements:

  1. Single code path for both flows
  2. Transactional integrity
  3. Audit logging at each step

If We Had to Do It Again

We'd:

  1. Write the failing test first - No test covered the post-payment state
  2. Monitor payment-to-feature latency - Would've caught it within hours
  3. Add feature toggle checks - Verify premium posts actually appear differently

The fix shipped in v1.4.4. Since then, zero reported issues—and our premium purchase conversion rate increased. Turns out people buy more when the features actually work. Who knew?

Top comments (0)