Desi Stack
HomeHomeProjectsProjectsCoursesCoursesGamesGamesPricingPricingServicesServicesContactContact
Get Started

Desi Stack

Empowering developers with production-ready projects, comprehensive courses, and professional services. Learn, build, and launch your next big idea.

contact@desistack.com
India

Product

  • Projects
  • Courses
  • Services

Company

  • About
  • Contact
  • Blog

Resources

  • Course Materials
  • FAQs
  • Support

Legal

  • Privacy Policy
  • Refund Policy
  • Terms of Service
Follow our journey

© 2026 Desi Stack. Made with in India

Subscribe
All posts
paymentsrazorpaysaas

Razorpay Checkout Without Tears

Desi Stack18 Jul 20263 min read

Payments fail in boring ways

The money succeeding while your UI shows an error is more common than founders admit.

A student pays. The popup closes weirdly. Your verify call never runs. Access is not granted. Support gets a screenshot and a bad review.

Razorpay is fine. Your state machine is what saves you.

The mental model (memorize this)

  1. Authenticated user asks to buy (itemType + itemId, optional discount).
  2. Your API validates session, price, and stock/rules.
  3. You create a DB Order with status: pending and a Razorpay order id.
  4. Frontend opens Checkout with that order id + key.
  5. On success, frontend calls verify with payment ids + signature.
  6. Server verifies signature, marks paid, grants access (dashboard item, enrollment, subscription).
  7. Webhook events do the same grant path idempotently if step 5 never happens.

If step 7 does not exist, you do not have a payment system — you have a demo.

Why pending orders matter

Never invent the order after money moves. Persist intent first:

  • You can show “Pending / Failed / Paid” honestly in the dashboard.
  • Support can search by Razorpay ids.
  • Webhooks have a row to attach to.
  • Double-clicks create fewer duplicate “mystery charges.”

Verify signature like an adult

Frontend can be lied to. Signature verification is server-side only with your key secret.

After a valid signature:

  • Transition pending → paid once
  • Grant access once (unique constraints help: one dashboard item per user/item)
  • Send purchase email once (or accept at-least-once with dedupe keys)

If verify fails, leave the order pending and let the webhook or support tools resolve it — do not delete evidence.

Webhooks as backup, not “extra credit”

Configure payment.captured (and failure events you care about) to hit something like /api/payments/webhook.

Good webhook handlers:

  • Validate the webhook secret/signature
  • Log the raw event (we keep a WebhookEvent table for this reason)
  • Load the order by Razorpay order id
  • Call the same grant function used by verify
  • Return 200 quickly when work is done or already applied

Idempotency is the whole game: the same captured event may retry.

UX details buyers feel

  • Show order history with status badges that match reality
  • Offer an invoice page for paid orders
  • Explain “Payment received, access unlocking…” if grant is slightly delayed
  • Never say “Success” before verify or webhook confirms

Test keys vs live keys

Local and staging use rzp_test_*. Production uses rzp_live_*. Swap all of:

  • RAZORPAY_KEY_ID
  • RAZORPAY_KEY_SECRET
  • RAZORPAY_WEBHOOK_SECRET
  • NEXT_PUBLIC_RAZORPAY_KEY_ID

Mismatch here produces signature failures that look random.

Checklist before you sell anything

  • Create-order requires login
  • Price comes from DB, not the client body alone
  • Pending order written before Checkout
  • Verify route checks signature
  • Grant access is idempotent
  • Webhook logs + processes captures
  • Failed payments do not unlock content
  • You ran a full test payment on test mode end-to-end

Want to see it in a real codebase?

Desi Stack products are built around this flow for projects, courses, and subscriptions. Study a full project or ship your own store knowing the rails underneath are boring on purpose — boring payments are the goal.

Keep building

Related reads

Browse projects →

More posts coming soon.