Skip to main content
This tutorial was written by Claude Code (an AI) and has not yet been reviewed. Follow along with caution. If the tutorial was helpful or a specific part was not clear/correct, please provide feedback at the bottom of the page. Thank you.
This guide covers integrating Razorpay with CoCart Preview API. Requires CoCart v4.6+ and a configured Razorpay payment gateway.

Overview

Razorpay integration with CoCart uses Razorpay Checkout for secure payment processing with automatic tokenization. This ensures PCI DSS Level 1 compliance while providing access to 100+ payment methods popular in India and Southeast Asia. Razorpay is the leading payment gateway in India, serving over 10 million businesses with support for UPI, cards, wallets, net banking, and EMI.

Prerequisites

Before implementing Razorpay checkout, ensure you have:
  1. Razorpay payment gateway configured in WooCommerce
  2. Razorpay API keys (Key ID and Key Secret)
  3. A valid cart with items added
  4. Customer billing address information
  5. Razorpay account (test mode available)

Integration Flow

1

Create Order

Generate a Razorpay order from your server
2

Load Razorpay Checkout

Initialize the Razorpay Checkout JavaScript library
3

Configure Checkout

Set up checkout options with order details
4

Open Checkout Modal

Display the Razorpay payment modal to customer
5

Handle Payment Response

Capture payment details and signature
6

Complete Checkout

Submit payment details to CoCart for verification

Step 1: Load Razorpay Checkout

Include the Razorpay Checkout script in your page:
<!-- Load Razorpay Checkout -->
<script src="https://checkout.razorpay.com/v1/checkout.js"></script>
The Razorpay Checkout library is always served over HTTPS and automatically stays up to date.

Step 2: HTML Structure

Create a checkout form for customer information:
<form id="checkout-form">
    <!-- Customer Information -->
    <div class="billing-section">
        <h3>Billing Information</h3>
        <input type="text" name="billing_first_name" placeholder="First Name" required>
        <input type="text" name="billing_last_name" placeholder="Last Name" required>
        <input type="email" name="billing_email" placeholder="Email" required>
        <input type="tel" name="billing_phone" placeholder="Phone" required>
        <input type="text" name="billing_address_1" placeholder="Address" required>
        <input type="text" name="billing_city" placeholder="City" required>
        <input type="text" name="billing_state" placeholder="State" required>
        <input type="text" name="billing_postcode" placeholder="Postal Code" required>
        <select name="billing_country" required>
            <option value="IN">India</option>
            <option value="MY">Malaysia</option>
            <option value="SG">Singapore</option>
            <option value="AE">United Arab Emirates</option>
            <!-- Add other supported countries -->
        </select>
    </div>

    <!-- Order Summary -->
    <div class="order-summary">
        <h3>Order Summary</h3>
        <div id="cart-items"></div>
        <div class="total">
            <strong>Total:</strong>
            <span id="order-total">₹0.00</span>
        </div>
    </div>

    <div id="razorpay-error-message" class="error-message" style="display: none;"></div>

    <button type="submit" id="submit-button">
        <span id="button-text">Pay with Razorpay</span>
        <span id="button-spinner" class="spinner" style="display: none;"></span>
    </button>
</form>

Step 3: Create Razorpay Order

Request a Razorpay order from your server via CoCart:
async function createRazorpayOrder() {
    const cartKey = localStorage.getItem('cart_key');

    const response = await fetch('/wp-json/cocart/preview/checkout/payment-context', {
        method: 'POST',
        headers: {
            'Cart-Key': cartKey,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            payment_method: 'razorpay'
        })
    });

    if (!response.ok) {
        const error = await response.json();
        throw new Error(error.message || 'Failed to create Razorpay order');
    }

    const context = await response.json();

    // Context contains:
    // - order_id: Razorpay order ID
    // - amount: Order amount in smallest currency unit (paise for INR)
    // - currency: Currency code (INR, MYR, etc.)
    // - key_id: Your Razorpay Key ID
    // - name: Store name
    // - description: Order description
    // - image: Store logo URL

    return context;
}

Step 4: Initialize Razorpay Checkout

Set up and configure Razorpay Checkout:
async function setupRazorpayCheckout() {
    try {
        // Get form data
        const form = document.getElementById('checkout-form');
        const formData = new FormData(form);
        const billingAddress = getBillingAddressFromForm(formData);

        // Create Razorpay order
        const orderContext = await createRazorpayOrder();

        // Configure Razorpay options
        const options = {
            key: orderContext.key_id,
            amount: orderContext.amount,
            currency: orderContext.currency,
            name: orderContext.name || 'Your Store Name',
            description: orderContext.description || 'Order Payment',
            image: orderContext.image || '',
            order_id: orderContext.order_id,

            // Pre-fill customer information
            prefill: {
                name: `${billingAddress.first_name} ${billingAddress.last_name}`,
                email: billingAddress.email,
                contact: billingAddress.phone
            },

            // Customer notes
            notes: {
                address: billingAddress.address_1,
                city: billingAddress.city,
                state: billingAddress.state,
                postal_code: billingAddress.postcode
            },

            // Theme customization
            theme: {
                color: '#6a42d7' // Match your brand color
            },

            // Payment success handler
            handler: async function(response) {
                await handleRazorpaySuccess(response, billingAddress);
            },

            // Modal callbacks
            modal: {
                ondismiss: function() {
                    console.log('Razorpay checkout cancelled');
                    showError('Payment cancelled. Please try again.');
                },
                // Escape key handler
                escape: true,
                // Animation
                animation: true
            },

            // Retry settings for failed payments
            retry: {
                enabled: true,
                max_count: 3
            }
        };

        // Create Razorpay instance
        const razorpay = new Razorpay(options);

        // Handle payment failure
        razorpay.on('payment.failed', function(response) {
            handleRazorpayFailure(response);
        });

        // Open Razorpay checkout
        razorpay.open();

    } catch (error) {
        console.error('Razorpay setup error:', error);
        showError('Payment setup failed. Please try again.');
    }
}

Step 5: Handle Payment Success

Process successful payment response:
async function handleRazorpaySuccess(response, billingAddress) {
    try {
        // Response contains:
        // - razorpay_payment_id: Payment ID
        // - razorpay_order_id: Order ID
        // - razorpay_signature: Signature for verification

        console.log('Payment successful:', response.razorpay_payment_id);

        // Prepare payment data
        const paymentData = {
            razorpay_payment_id: response.razorpay_payment_id,
            razorpay_order_id: response.razorpay_order_id,
            razorpay_signature: response.razorpay_signature
        };

        // Submit to CoCart for verification and order creation
        await processRazorpayCheckout(billingAddress, paymentData);

    } catch (error) {
        console.error('Payment processing error:', error);
        showError(error.message || 'Payment processing failed. Please contact support.');
    }
}

async function processRazorpayCheckout(billingAddress, paymentData) {
    const cartKey = localStorage.getItem('cart_key');

    const checkoutData = {
        billing_address: billingAddress,
        payment_method: 'razorpay',
        payment_data: paymentData
    };

    const response = await fetch('https://yoursite.com/wp-json/cocart/preview/checkout', {
        method: 'PUT',
        headers: {
            'Cart-Key': cartKey,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(checkoutData)
    });

    const result = await response.json();

    if (!response.ok) {
        // Handle specific Razorpay errors
        if (result.data?.gateway_error) {
            throw new RazorpayError(result.data.gateway_error);
        }
        throw new Error(result.message || `HTTP ${response.status}`);
    }

    // Handle successful checkout
    if (result.order_id) {
        showSuccess(`Order #${result.order_number} completed successfully!`);

        // Clear cart
        localStorage.removeItem('cart_key');

        // Redirect to thank you page
        if (result.payment_result?.redirect_url) {
            setTimeout(() => {
                window.location.href = result.payment_result.redirect_url;
            }, 2000);
        }
    }

    return result;
}

Step 6: Handle Payment Failure

Manage failed payment scenarios:
function handleRazorpayFailure(response) {
    console.error('Payment failed:', response.error);

    // Error response contains:
    // - code: Error code
    // - description: Error description
    // - source: Error source (customer, business, etc.)
    // - step: Step where error occurred
    // - reason: Failure reason
    // - metadata: Additional metadata

    const error = response.error;
    let errorMessage = 'Payment failed. ';

    // Provide user-friendly error messages
    switch (error.code) {
        case 'BAD_REQUEST_ERROR':
            errorMessage += 'Invalid payment details. Please check and try again.';
            break;
        case 'GATEWAY_ERROR':
            errorMessage += 'Payment gateway error. Please try again.';
            break;
        case 'SERVER_ERROR':
            errorMessage += 'Server error. Please try again later.';
            break;
        case 'INSUFFICIENT_FUNDS':
            errorMessage += 'Insufficient funds. Please try a different payment method.';
            break;
        case 'INVALID_CARD':
            errorMessage += 'Invalid card details. Please check your card information.';
            break;
        default:
            errorMessage += error.description || 'Please try again.';
    }

    showError(errorMessage);
}

// Custom error class for Razorpay errors
class RazorpayError extends Error {
    constructor(gatewayError) {
        super(gatewayError.message || 'Payment processing failed');
        this.code = gatewayError.code;
        this.description = gatewayError.description;
        this.field = gatewayError.field;
    }

    getDisplayMessage() {
        // Return user-friendly error messages
        const errorMap = {
            'payment_verification_failed': 'Payment verification failed. Please contact support.',
            'payment_already_captured': 'This payment has already been processed.',
            'payment_cancelled': 'Payment was cancelled.',
            'invalid_signature': 'Invalid payment signature. Please try again.',
            'payment_timeout': 'Payment timed out. Please try again.'
        };

        return errorMap[this.code] || this.description || this.message;
    }
}

Complete Integration Example

Here’s a complete working implementation:
class RazorpayCheckout {
    constructor() {
        this.orderContext = null;
        this.razorpayInstance = null;
        this.formValid = false;
    }

    async initialize() {
        try {
            // Setup form validation
            this.setupFormValidation();

            // Setup form submission
            this.setupFormSubmission();

            console.log('Razorpay checkout initialized successfully');
        } catch (error) {
            console.error('Razorpay initialization error:', error);
            this.showError('Payment system unavailable. Please try again later.');
        }
    }

    setupFormValidation() {
        const form = document.getElementById('checkout-form');
        const inputs = form.querySelectorAll('input[required]');

        inputs.forEach(input => {
            input.addEventListener('blur', () => this.updateFormValidity());
            input.addEventListener('input', () => this.updateFormValidity());
        });

        this.updateFormValidity();
    }

    updateFormValidity() {
        const form = document.getElementById('checkout-form');
        const requiredFields = form.querySelectorAll('input[required]');
        let isValid = true;

        requiredFields.forEach(field => {
            if (!field.value.trim()) {
                isValid = false;
            }
        });

        this.formValid = isValid;
    }

    setupFormSubmission() {
        const form = document.getElementById('checkout-form');

        form.addEventListener('submit', async (event) => {
            event.preventDefault();

            const submitButton = form.querySelector('[type="submit"]');
            const originalText = submitButton.textContent;

            try {
                submitButton.disabled = true;
                submitButton.textContent = 'Processing...';

                if (!this.formValid) {
                    throw new Error('Please fill in all required fields.');
                }

                // Create Razorpay order
                await this.createOrder();

                // Open Razorpay checkout
                await this.openCheckout();

            } catch (error) {
                console.error('Checkout error:', error);
                this.showError(error.message || 'Checkout failed. Please try again.');

                submitButton.disabled = false;
                submitButton.textContent = originalText;
            }
        });
    }

    async createOrder() {
        const cartKey = localStorage.getItem('cart_key');

        const response = await fetch('/wp-json/cocart/preview/checkout/payment-context', {
            method: 'POST',
            headers: {
                'Cart-Key': cartKey,
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ payment_method: 'razorpay' })
        });

        const context = await response.json();

        if (!response.ok) {
            throw new Error(context.message || 'Failed to create Razorpay order');
        }

        this.orderContext = context;
        return context;
    }

    async openCheckout() {
        const form = document.getElementById('checkout-form');
        const formData = new FormData(form);
        const billingAddress = this.getBillingAddressFromForm(formData);

        const options = {
            key: this.orderContext.key_id,
            amount: this.orderContext.amount,
            currency: this.orderContext.currency,
            name: this.orderContext.name || 'Your Store',
            description: this.orderContext.description || 'Order Payment',
            image: this.orderContext.image || '',
            order_id: this.orderContext.order_id,

            prefill: {
                name: `${billingAddress.first_name} ${billingAddress.last_name}`,
                email: billingAddress.email,
                contact: billingAddress.phone
            },

            notes: {
                address: billingAddress.address_1,
                city: billingAddress.city,
                state: billingAddress.state,
                postal_code: billingAddress.postcode
            },

            theme: {
                color: '#6a42d7'
            },

            handler: (response) => this.handleSuccess(response, billingAddress),

            modal: {
                ondismiss: () => {
                    console.log('Payment cancelled');
                    this.showError('Payment cancelled. Please try again.');
                    this.resetForm();
                },
                escape: true,
                animation: true
            },

            retry: {
                enabled: true,
                max_count: 3
            }
        };

        this.razorpayInstance = new Razorpay(options);

        this.razorpayInstance.on('payment.failed', (response) => {
            this.handleFailure(response);
        });

        this.razorpayInstance.open();
    }

    async handleSuccess(response, billingAddress) {
        try {
            const paymentData = {
                razorpay_payment_id: response.razorpay_payment_id,
                razorpay_order_id: response.razorpay_order_id,
                razorpay_signature: response.razorpay_signature
            };

            await this.processCheckout(billingAddress, paymentData);

        } catch (error) {
            console.error('Payment processing error:', error);
            this.showError(error.message || 'Payment processing failed.');
            this.resetForm();
        }
    }

    handleFailure(response) {
        const error = response.error;
        let errorMessage = 'Payment failed. ';

        switch (error.code) {
            case 'BAD_REQUEST_ERROR':
                errorMessage += 'Invalid payment details.';
                break;
            case 'GATEWAY_ERROR':
                errorMessage += 'Payment gateway error.';
                break;
            case 'SERVER_ERROR':
                errorMessage += 'Server error. Please try again later.';
                break;
            default:
                errorMessage += error.description || 'Please try again.';
        }

        this.showError(errorMessage);
        this.resetForm();
    }

    async processCheckout(billingAddress, paymentData) {
        const cartKey = localStorage.getItem('cart_key');

        const response = await fetch('/wp-json/cocart/preview/checkout', {
            method: 'PUT',
            headers: {
                'Cart-Key': cartKey,
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                billing_address: billingAddress,
                payment_method: 'razorpay',
                payment_data: paymentData
            })
        });

        const result = await response.json();

        if (!response.ok) {
            if (result.data?.gateway_error) {
                throw new RazorpayError(result.data.gateway_error);
            }
            throw new Error(result.message || `HTTP ${response.status}`);
        }

        this.handleCheckoutSuccess(result);
        return result;
    }

    getBillingAddressFromForm(formData) {
        return {
            first_name: formData.get('billing_first_name'),
            last_name: formData.get('billing_last_name'),
            email: formData.get('billing_email'),
            phone: formData.get('billing_phone'),
            address_1: formData.get('billing_address_1'),
            city: formData.get('billing_city'),
            state: formData.get('billing_state'),
            postcode: formData.get('billing_postcode'),
            country: formData.get('billing_country')
        };
    }

    handleCheckoutSuccess(result) {
        this.showSuccess(`Order #${result.order_number} completed successfully!`);

        // Clear cart
        localStorage.removeItem('cart_key');

        // Redirect to thank you page
        if (result.payment_result?.redirect_url) {
            setTimeout(() => {
                window.location.href = result.payment_result.redirect_url;
            }, 2000);
        }
    }

    resetForm() {
        const submitButton = document.querySelector('#submit-button');
        submitButton.disabled = false;
        submitButton.textContent = 'Pay with Razorpay';
    }

    showError(message) {
        const errorElement = document.getElementById('razorpay-error-message');
        errorElement.textContent = message;
        errorElement.style.display = 'block';
        errorElement.className = 'error-message';
    }

    showSuccess(message) {
        const errorElement = document.getElementById('razorpay-error-message');
        errorElement.textContent = message;
        errorElement.style.display = 'block';
        errorElement.className = 'success-message';
    }
}

// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', async () => {
    const checkout = new RazorpayCheckout();
    await checkout.initialize();
});

Styling

Add CSS for a polished checkout experience:
/* Billing Section */
.billing-section {
    margin-bottom: 2rem;
}

.billing-section input,
.billing-section select {
    width: 100%;
    padding: 10px;
    margin-bottom: 1rem;
    border: 1px solid #cbd5e0;
    border-radius: 4px;
    font-size: 14px;
}

.billing-section input:focus,
.billing-section select:focus {
    border-color: #6a42d7;
    outline: none;
    box-shadow: 0 0 0 3px rgba(106, 66, 215, 0.1);
}

/* Order Summary */
.order-summary {
    background: #f7fafc;
    padding: 1.5rem;
    border-radius: 8px;
    margin-bottom: 1.5rem;
}

.order-summary .total {
    display: flex;
    justify-content: space-between;
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 2px solid #cbd5e0;
    font-size: 1.25rem;
}

/* Submit Button */
#submit-button {
    width: 100%;
    padding: 14px;
    background: #6a42d7;
    color: white;
    border: none;
    border-radius: 4px;
    font-size: 16px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.2s ease;
}

#submit-button:hover:not(:disabled) {
    background: #5932d1;
}

#submit-button:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

/* Error Messages */
.error-message {
    padding: 0.75rem;
    background-color: #fed7d7;
    border-left: 4px solid #e53e3e;
    color: #742a2a;
    border-radius: 4px;
    margin-bottom: 1rem;
}

.success-message {
    padding: 0.75rem;
    background-color: #c6f6d5;
    border-left: 4px solid #38a169;
    color: #22543d;
    border-radius: 4px;
    margin-bottom: 1rem;
}

/* Input validation */
input.error {
    border-color: #e53e3e;
}

Testing

For development and testing with Razorpay:

Test Mode Credentials

Use these credentials from your Razorpay Test Mode dashboard:
  • Key ID: Starts with rzp_test_
  • Key Secret: Use for server-side operations

Test Payment Methods

Test Card Numbers:
  • Success: 4111 1111 1111 1111 (Visa)
  • Success: 5555 5555 5555 4444 (Mastercard)
  • Failure: Any card number ending in 0002
Test Card Details:
  • CVV: Any 3 digits
  • Expiry: Any future date
  • Cardholder: Any name
Test UPI IDs:
  • Success: success@razorpay
  • Failure: failure@razorpay
Test Net Banking:
  • Select any bank from test mode
  • Use credentials provided in test interface

Test Wallets

All wallet payments in test mode are automatically successful.

Supported Payment Methods

Razorpay supports 100+ payment methods:

Cards

  • Credit Cards (Visa, Mastercard, RuPay, Amex)
  • Debit Cards (All major banks)
  • International Cards

UPI

  • Google Pay
  • PhonePe
  • Paytm
  • BHIM UPI
  • Other UPI apps

Wallets

  • Paytm
  • PhonePe
  • Mobikwik
  • FreeCharge
  • Amazon Pay

Net Banking

  • All major Indian banks
  • Corporate net banking

EMI

  • Credit Card EMI
  • Debit Card EMI
  • Cardless EMI

Other Methods

  • PayLater (LazyPay, Simpl, etc.)
  • NACH/eNACH
  • Bank Transfer (NEFT/RTGS)

Best Practices

Security

  • Always verify payment signature on server
  • Never expose Key Secret on frontend
  • Use HTTPS for all requests
  • Implement webhook verification
  • Store payment IDs securely
  • Enable fraud detection features

User Experience

  • Pre-fill customer information
  • Show clear order summary
  • Handle modal dismissal gracefully
  • Provide payment retry options
  • Display multiple payment methods
  • Support preferred languages

Compliance

  • PCI DSS Level 1 certified
  • RBI compliant tokenization
  • Implement proper error logging
  • Follow data retention policies
  • Enable auto-refunds when needed
  • Maintain transaction records

Performance

  • Load Checkout.js from CDN
  • Cache order contexts briefly
  • Implement proper timeouts
  • Handle network failures
  • Monitor success rates
  • Set up webhook endpoints

Advanced Features

Payment Capture

By default, payments are auto-captured. For manual capture:
// Create order with manual capture
const options = {
    // ... other options
    payment_capture: 0, // Manual capture

    handler: async function(response) {
        // Payment authorized but not captured
        // Capture later via API
        await capturePayment(response.razorpay_payment_id);
    }
};

Recurring Payments

Set up subscriptions and recurring payments:
const options = {
    // ... other options
    recurring: 1,
    subscription_id: 'sub_xxxxxxxxxxxxx',

    handler: function(response) {
        // Handle subscription payment
    }
};

Offers and Discounts

Apply offers during checkout:
const options = {
    // ... other options
    offers: {
        enabled: true
    },

    handler: function(response) {
        // Offer applied: response.razorpay_offer_id
    }
};

Webhook Handling

Razorpay sends webhooks for payment events:
// Server-side webhook handler (example)
app.post('/webhooks/razorpay', async (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(JSON.stringify(req.body))
        .digest('hex');

    if (signature === expectedSignature) {
        const event = req.body.event;
        const payment = req.body.payload.payment.entity;

        switch (event) {
            case 'payment.captured':
                await handlePaymentCaptured(payment);
                break;
            case 'payment.failed':
                await handlePaymentFailed(payment);
                break;
            case 'refund.created':
                await handleRefundCreated(payment);
                break;
        }

        res.sendStatus(200);
    } else {
        res.sendStatus(400);
    }
});

Error Handling

Handle common Razorpay errors:
function handleRazorpayErrors(error) {
    const errorMap = {
        'BAD_REQUEST_ERROR': 'Invalid request. Please check your details.',
        'GATEWAY_ERROR': 'Payment gateway error. Please try again.',
        'SERVER_ERROR': 'Server error. Please try again later.',
        'TIMEOUT_ERROR': 'Request timed out. Please try again.',
        'INSUFFICIENT_FUNDS': 'Insufficient funds in your account.',
        'INVALID_CARD': 'Invalid card details.',
        'CARD_DECLINED': 'Card declined by bank.',
        'AUTHENTICATION_ERROR': 'Authentication failed.',
        'NETWORK_ERROR': 'Network error. Please check your connection.'
    };

    return errorMap[error.code] || error.description || 'Payment failed';
}

Troubleshooting

Common issues and solutions: Checkout modal not opening: Verify Checkout.js is loaded and Key ID is correct. Payment verification fails: Ensure signature verification logic is implemented correctly on server. Webhook not received: Check webhook URL configuration in Razorpay dashboard. Amount mismatch: Razorpay amounts are in smallest currency unit (paise for INR). Order creation fails: Verify API credentials and order amount is within limits.
Always test your Razorpay integration thoroughly using test mode before going live. Configure webhooks to handle asynchronous payment updates. Monitor your Razorpay dashboard for failed payments and disputes. Implement proper logging for debugging payment issues.

Additional Resources

I