Razorpay Payment Gateway Integration

:pushpin: Introduction

This guide provides a comprehensive walkthrough for integrating Razorpay payment gateway into your Node.js backend application. Razorpay is a popular payment solution that enables businesses to accept online payments securely and efficiently.

:rocket: Quick Setup

Prerequisites

  • Node.js (v14+ recommended)
  • npm or Yarn
  • Razorpay Account

Installation

npm install razorpay
# or
yarn add razorpay

Configuration

  1. Obtain your API keys from the Razorpay Dashboard
  2. Set up environment variables:
RAZORPAY_KEY_ID=your_key_id
RAZORPAY_KEY_SECRET=your_key_secret

:magnifying_glass_tilted_left: Deep Dive: Integration Workflow

1. Initializing Razorpay Client

const Razorpay = require('razorpay');

const razorpay = new Razorpay({
  key_id: process.env.RAZORPAY_KEY_ID,
  key_secret: process.env.RAZORPAY_KEY_SECRET
});

2. Order Creation Process

Understanding Razorpay Orders

In Razorpay, an Order is a crucial component of the payment lifecycle. It serves several important purposes:

  • Unique Identification: Each order gets a unique order_id
  • Amount Protection: Prevents tampering with transaction amounts
  • Payment Tracking: Consolidates multiple payment attempts
  • Fraud Prevention: Provides an additional layer of transaction security

Orders go through different states:

  • created: Initial order state
  • attempted: First payment attempt made
  • paid: Successful payment completed

Order Creation Requirements

Mandatory Parameters
  1. Amount
  • Required: Always specify the payment amount
  • Format: Smallest currency unit (paisa for INR, cents for USD)
  • Examples:
    • ₹100 = 10000 (in paisa)
    • $50 = 5000 (in cents)
  • Validation: Must be a positive integer
  1. Currency
  • Required: Specify the transaction currency
  • Supported Currencies:
    • INR (Indian Rupees)
    • USD (US Dollars)
    • EUR (Euros)
    • Other supported Razorpay currencies
  • Format: 3-letter ISO currency code

Order Creation Example

const orderOptions = {
  amount: 10000,         // Required: 100 INR in paisa
  currency: 'INR',       // Required: Currency type
  receipt: 'order_rcptid_' + Date.now(), // Recommended: Unique internal reference
  payment_capture: 1     // Optional: Auto-capture payment
};

const order = await razorpay.orders.create(orderOptions);

Additional Recommended Parameters

  1. Receipt
  • Unique identifier for your internal tracking
  • Helps in reconciliation
  • Typically uses timestamp or unique order number
  1. Notes
  • Optional metadata
  • Can include additional order details
  • Maximum 15 key-value pairs allowed

Validation Checks

  • Ensure amount is a valid number
  • Verify currency support
  • Generate unique receipt identifier
  • Protect against duplicate orders

Common Mistakes to Avoid

  • Incorrect amount conversion
  • Using unsupported currencies
  • Omitting required parameters
  • Generating non-unique receipts

3. Passing Order ID to Frontend Razorpay Checkout

Purpose of Sending Order ID

When creating a Razorpay order, the backend generates a unique order_id that serves critical functions:

  1. Transaction Uniqueness: Provides a distinct identifier for each payment attempt
  2. Amount Protection: Prevents client-side manipulation of payment amounts
  3. Payment Tracking: Allows Razorpay to link frontend payment attempts to the original order

Backend Response

res.json({
  success: true,
  order_id: order.id,
  amount: order.amount,
});

What Frontend Will Do with This Data

  • Use orderId to initiate Razorpay checkout
  • Validate transaction amount
  • Complete payment process

4. Webhook Implementation

Why Webhooks are Crucial

Webhooks are essential for real-time payment status updates. They provide several key benefits:

  • Instant notification of payment events
  • Reliable tracking of transaction statuses
  • Reduced need for manual payment reconciliation

Webhook Event Types

Razorpay supports multiple webhook events:

  • payment.captured: Successful payment
  • payment.failed: Payment unsuccessful
  • refund.processed: Refund completed
  • dispute.created: Payment dispute initiated

Webhook Handler Implementation

const crypto = require('crypto');

function handleWebhook(req, res) {
  const secret = process.env.RAZORPAY_WEBHOOK_SECRET;
  const signature = req.headers['x-razorpay-signature'];

  // Verify webhook signature
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(req.rawBody)
    .digest('hex');

  // Signature validation
  if (signature !== expectedSignature) {
    return res.status(400).send('Invalid signature');
  }

  const event = req.body;

  // Handle different payment events
  switch (event.event) {
    case 'payment.captured':
      // Update order status to paid
      updateOrderStatus(event.payload.payment.entity, 'PAID');
      break;
    
    case 'payment.failed':
      // Handle failed payment
      updateOrderStatus(event.payload.payment.entity, 'FAILED');
      break;
    
    case 'refund.processed':
      // Process refund
      handleRefund(event.payload.payment.entity);
      break;
  }

  res.status(200).send('Webhook processed');
}

// Update order status in database
async function updateOrderStatus(paymentDetails, status) {
  try {
    const order = await Order.findOne({
      razorpayOrderId: paymentDetails.order_id
    });

    if (order) {
      order.status = status;
      order.paymentDetails = paymentDetails;
      await order.save();
    }
  } catch (error) {
    console.error('Error updating order status', error);
  }
}

:light_bulb: Best Practices

Amount Handling

  • Always convert amounts to the smallest currency unit
  • Examples:
    • ₹299.00 → 29900
    • $50.25 → 5025

Security Considerations

  • Use environment variables for sensitive information
  • Implement webhook signature verification
  • Use HTTPS for all transactions
  • Store API keys securely

:hammer_and_wrench: Error Handling

async function processPayment(order) {
  try {
    // Payment processing logic
  } catch (error) {
    // Categorize and handle different types of errors
    if (error.code === 'BAD_REQUEST_ERROR') {
      // Handle specific Razorpay errors
    } else {
      // Generic error handling
      logger.error('Payment processing failed', error);
    }
  }
}

:test_tube: Testing Strategies

Test Mode

  • Use Razorpay test credentials
  • Simulate various payment scenarios
  • Test different payment methods

Test Card & UPI ID Details

Razorpay offers dedicated test payment details to validate different scenarios.

:bank: Test Card Details

  • Use Razorpay test card numbers to simulate payments.
  • Test transactions with different card networks (Visa, MasterCard, etc.).
  • Validate success, failure, and insufficient funds scenarios.

:pushpin: Refer to Test Card Details for more information.

:link: Test UPI ID Details

  • Utilize test UPI IDs to simulate domestic (Indian) UPI payments.
  • Test both successful and failed transactions for one-time payments.

:pushpin: Refer to Test UPI ID Details for UPI-specific test cases.

:rocket: Transitioning to Live Mode

Pre-Live Preparation Checklist

  • Complete comprehensive testing of all payment flows
  • Verify all integration points
  • Obtain live Razorpay account credentials
  • Replace test API keys with live credentials
  • Secure production environment

:ship: Deployment Checklist

  1. Replace test API keys with live keys
  2. Verify webhook endpoints
  3. Implement comprehensive logging
  4. Set up monitoring for payment flows

:books: Additional Resources

:sos_button: Troubleshooting

  • Check API key permissions
  • Verify webhook configurations
  • Monitor error logs
  • Use Razorpay dashboard for transaction insights

Conclusion

Integrating Razorpay with your Node.js backend is straightforward when following these guidelines. Always prioritize security, handle edge cases, and provide a smooth payment experience for your users.

4 Likes