Testing and Debugging Razorpay Payments in Node.js Test Mode and Error Handling
Learn how to test Razorpay payments in Node.js using test mode, test cards, debugging common errors, and testing webhooks locally.
Testing and Debugging Razorpay Payments
Thoroughly testing your payment integration before going live is essential. This guide covers test mode, debugging, and testing webhooks.
Razorpay Test Mode
Test mode allows full testing without real money:
// Test mode keys start with rzp_test_ const razorpay = new Razorpay({ key_id: "rzp_test_XXXXXXXXXX", key_secret: "xxxxxxxxxxxxxxxxxxxxxxxx" });
Test Cards
| Card Type | Number | Result |
|---|---|---|
| Visa success | 4111 1111 1111 1111 | Success |
| Mastercard success | 5104 1111 1111 1111 | Success |
| Visa failure | 4111 1111 1111 1112 | Failure |
| 3D auth required | 4111 1111 1111 1111 | Requires OTP |
| Invalid CVV | Any card + wrong CVV | Failure |
Expiry: Any future date (e.g., 12/30) CVV: Any 3 digits (e.g., 123)
Test UPI
UPI ID: success@razorpay → Success
UPI ID: failure@razorpay → Failure
Testing Webhooks Locally
Razorpay webhooks need a public URL. Use ngrok for local testing:
# Install ngrok npm install -g ngrok # Expose your local server ngrok http 3000 # You get a public URL: # https://abc123.ngrok.io # Set this as your webhook URL in Razorpay Dashboard: # https://abc123.ngrok.io/api/payment/webhook
Testing the Complete Flow
// test/payment.test.js const request = require("supertest"); const app = require("../src/app"); const razorpay = require("../src/config/razorpay"); describe("Payment Flow", () => { let orderId; it("should create an order", async () => { const res = await request(app) .post("/api/payment/create-order") .set("Cookie", `token=${testToken}`) .send({ plan: "premium" }); expect(res.status).toBe(200); expect(res.body.orderId).toBeDefined(); orderId = res.body.orderId; }); it("should verify a payment", async () => { // Simulate a test payment const payment = await razorpay.payments.createTestPayment({ order_id: orderId, method: "card", card: { number: "4111111111111111", expiry_month: "12", expiry_year: "30", cvv: "123" } }); const res = await request(app) .post("/api/payment/verify") .send({ orderId: orderId, paymentId: payment.id, signature: "test_signature" // In test mode, signatures are different }); expect(res.status).toBe(200); expect(res.body.success).toBe(true); }); });
Common Errors and Solutions
1. Invalid API Key
Error: The key_id and key_secret don't match
Solution: Check that you're using the correct key pair from the Razorpay dashboard. Test keys start with rzp_test_, live keys with rzp_live_.
2. Amount Mismatch
Error: The amount doesn't match the order amount
Solution: Ensure the frontend sends the exact order amount. Don't modify the amount after order creation. Amounts are in paise (₹500 = 50000).
3. Order Already Paid
Error: The order has already been paid
Solution: Each order can only be paid once. Create a new order for retry. Don't reuse order IDs.
4. Webhook Not Received
Solutions:
- Check the webhook URL is HTTPS (not HTTP)
- Verify the URL in the Razorpay dashboard
- Use ngrok for local testing
- Check that the webhook secret matches
- View webhook delivery logs in the dashboard
5. Signature Verification Failed
// Debug signature verification console.log("Expected:", expectedSignature); console.log("Received:", signature); console.log("Body:", req.body.toString());
Common causes:
- Using
express.json()instead ofexpress.raw()for webhooks - Different webhook secret in .env vs dashboard
- Body parsing modifies the raw content
Debugging Checklist
- Using test mode keys (rzp_test_)
- Test cards working (4111 1111 1111 1111)
- Webhook URL is HTTPS
- Using express.raw() for webhook route
- Webhook secret matches dashboard
- ngrok running for local webhook testing
- Amounts in paise (not rupees)
- Order ID, payment ID, and signature all present
- No typos in API keys
- Logs show webhook events
The Takeaway
Testing Razorpay involves: using test mode (rzp_test_ keys), test cards (4111 1111 1111 1111 for success), test UPI IDs (success@razorpay), ngrok for local webhook testing, and debugging common errors (invalid keys, amount mismatch, order already paid, webhook not received, signature verification failed). Always test the complete flow in test mode before switching to live mode.
Use test mode with rzp_test_ keys. Use test card 4111 1111 1111 1111 (Visa) with any future expiry and any CVV for success. For UPI, use success@razorpay. No real money is charged. Test all payment methods, failures, and webhooks before going live.
Use ngrok to expose your local server: ngrok http 3000. You get a public HTTPS URL. Set this URL as the webhook URL in the Razorpay dashboard. Now Razorpay can send webhooks to your local server for testing.
Common causes: using express.json() instead of express.raw() for the webhook route (the signature is on the raw body), different webhook secret in .env vs the dashboard, or body parsing modifying the content. Use express.raw({ type: 'application/json' }) for the webhook route.
Invalid API key (check test vs live keys), amount mismatch (ensure frontend sends exact order amount in paise), order already paid (create a new order for retry), webhook not received (check HTTPS URL, use ngrok for local), signature verification failed (use express.raw, check webhook secret).
Visa success: 4111 1111 1111 1111. Mastercard success: 5104 1111 1111 1111. Visa failure: 4111 1111 1111 1112. Use any future expiry date and any 3-digit CVV. For UPI, use success@razorpay (success) or failure@razorpay (failure).
Ready to master Node.js completely?
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.
Master Node.js
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

