How to Integrate Payment Services Like Razorpay, Stripe and More in Web Apps
Hey everyone! I'm Ganesh and in this comprehensive guide, I'll walk you through how to integrate popular payment gateways like Razorpay, Stripe and more into your web applications. We'll focus on how to use them securely and efficiently using frontend frameworks like React/Next.js and backend setups like Node.js, Django, etc.
Table of Contents
- 01.Why Secure Payment Integration Matters
- 02.Payment Gateway Workflow
- 03.Setting Up Razorpay
- 04.Setting Up Stripe
- 05.React/Next.js Integration
- 06.Backend Implementation
- 07.Webhook Handling
- 08.Security Best Practices
- 09.Testing & Error Handling
- 10.Production Deployment
- 11.Advanced Features
- 12.Real-World Case Studies
Integrating payments might sound complicated at first, but once you get the basic structure and security principles right, it becomes an easy and reusable process. So whether you're working on a small e-commerce site or a SaaS platform, this blog will help you understand:
- Security First:Why we need to integrate payments securely
- Core Workflow:The fundamental process of any payment gateway
- Gateway Setup:Setting up Razorpay, Stripe with examples
- Implementation:Implementing them in React, Node.js, Django
- Security Measures:Protecting sensitive data and user information
- Real Experience:Tips based on personal integration experience
1. Why Secure Payment Integration Matters
As developers, it's easy to plug in a payment SDK and call it a day. But when real money is involved, so is real responsibility. Payment security isn't just about protecting data—it's about building trust with your users and ensuring compliance with financial regulations.
Critical Security Requirements
Personal Experience
When I built my first app with Razorpay, I made the rookie mistake of putting the secret key on the client side. A friend pointed it out and I quickly learned how critical it is to handle all sensitive operations server-side.
2. Payment Gateway Workflow
Understanding the payment flow is crucial for building secure and reliable payment systems. Here's how the process works across different components:
Payment Integration Flow
Frontend (React/Next.js)
Backend (Node.js/Django)
Payment Gateway
Production Considerations
3. Setting Up Razorpay
Razorpay is one of India's leading payment gateways, offering comprehensive payment solutions with excellent developer experience. Let's set it up step by step.
Quick Setup Steps
Razorpay Features
Razorpay Webhooks
Webhooks help confirm transactions on the backend even if the frontend doesn't send confirmation properly. You can register these key webhook events:
payment.authorized
Payment is authorized but not captured
payment.failed
Payment attempt failed
order.paid
Order payment is successful
@csrf_exempt
def razorpay_webhook(request):
payload = request.body
signature = request.headers.get('X-Razorpay-Signature')
try:
razorpay_client.utility.verify_webhook_signature(
payload, signature, "YOUR_SECRET")
data = json.loads(payload)
# Process payment authorized event
return JsonResponse({'status': 'ok'})
except:
return JsonResponse({'status': 'failed'}, status=400)
React Implementation with Promises & Toasts
import { toast } from 'react-toastify';
const openRazorpay = async () => {
try {
const res = await fetch('/api/create-order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount: 500 })
});
const data = await res.json();
const options = {
key: process.env.REACT_APP_RAZORPAY_KEY_ID,
amount: data.amount,
order_id: data.id,
handler: function (response) {
toast.success('Payment Successful!');
},
prefill: { name: 'Ganesh', email: 'ganesh@example.com' }
};
const rzp = new window.Razorpay(options);
rzp.open();
} catch (err) {
toast.error('Something went wrong');
}
};
4. Setting Up Stripe
Stripe is a global payment platform that offers comprehensive payment solutions with excellent developer experience and extensive documentation. Let's explore its setup and advanced features.
Stripe CLI for Webhook Testing
Stripe CLI is essential for testing webhooks locally during development.
stripe listen --forward-to localhost:5000/webhook
Advanced Backend (Node.js with Express)
app.post('/webhook', express.raw({type: 'application/json'}), (request, response) => {
const sig = request.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
} catch (err) {
response.status(400).send('Webhook Error: ' + err.message);
return;
}
// Handle event
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
// Mark order paid in DB
}
response.status(200).end();
});
Recurring Payments / Subscriptions
const customer = await stripe.customers.create({
email: 'ganesh@example.com'
});
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: 'price_1Nxyz...' }],
});
You can even manage cancellations, upgrades and invoice previews with Stripe's subscription API.
Stripe Features
5. Security Measures
Security is paramount when handling payments. Here are the essential security measures you must implement to protect your application and users.
CSRF Protection
Especially in Django or Express apps. This prevents attackers from initiating payments from unauthorized sessions.
Input Sanitization
Don't trust frontend amount values. Instead, fetch from DB using item ID on the server.
Webhook Signature Verification
All gateways offer secret signing for webhooks. Always verify signatures before trusting the event.
Rate Limiting & Logging
Protect your payment endpoints with rate-limiting libraries like express-rate-limit
or Django middleware.
Critical Security Practices
6. Django Backend Integration
Django provides excellent support for payment integrations with its robust ORM, security features and extensive ecosystem. Here's how to implement Stripe integration with Django.
Stripe Session Creation
import stripe
stripe.api_key = settings.STRIPE_SECRET
def create_checkout_session(request):
session = stripe.checkout.Session.create(
payment_method_types=['card'],
line_items=[{
'price_data': {
'currency': 'usd',
'unit_amount': 1500,
'product_data': {
'name': 'Ganesh Python Course',
},
},
'quantity': 1,
}],
mode='payment',
success_url='http://localhost:3000/success',
cancel_url='http://localhost:3000/cancel',
)
return JsonResponse({'id': session.id})
7. Webhooks and Event Handling
Webhooks are critical in modern payment integrations for reliable transaction processing and real-time updates. They ensure your application stays synchronized with payment gateway events.
Critical Webhook Events
Webhook Best Practices
8. Multi-Currency and International Support
Both Stripe and Razorpay support international cards, but they handle currency conversion differently. Stripe handles currency conversion on the fly, while Razorpay primarily supports INR with some international options.
Stripe Multi-Currency
Razorpay International
Implementation Best Practices
9. Handling Refunds and Subscriptions
Refunds and subscriptions are essential features for any payment system. Both Stripe and Razorpay provide comprehensive APIs for handling these operations.
Stripe Refund
const refund = await stripe.refunds.create({
payment_intent: 'pi_1...',
amount: 500
});
Razorpay Refund
razorpay.payments.refund(payment_id, {
amount: 5000
});
Refund Best Practices
10. Final Deployment Tips
Based on real-world experience, here are the essential deployment tips to ensure your payment integration works smoothly in production.
Production Setup
Additional Deployment Considerations
- Backup webhook logs and transaction metadata
- Add UI status indicators: loader, error messages, success page
- Use Try/Catch blocks in every async payment-related call
- Set up a cron job to reconcile payments nightly
- Monitor for suspicious activity (such as repeated failed payments)
- Consider using fraud detection tools provided by your payment gateway
Conclusion
Payment gateway integration isn't rocket science, but it requires careful implementation. With providers like Razorpay, Stripe and others offering detailed SDKs and docs, you can build a seamless payment experience.
Remember: frontend handles the interface; backend handles the security.
If you're building a startup, e-commerce project, or freelancing — this guide will save you weeks of trial and error. If you're stuck at any point or want help testing a specific integration, feel free to drop a message — I'm Ganesh and I'd love to help you debug or improve your app securely.
Until next time, keep coding and keep building!
Visualizing the Payment Flow
Understanding the payment flow is crucial for successful integration. Here are visual representations of how the frontend, backend and payment gateway interact.
Payment Flow Diagram
Here's a simple diagram showing how the frontend, backend and payment gateway interact:
graph TD; A[User] -->|Clicks Pay| B(Frontend) B -->|Request order/session| C(Backend) C -->|Create order/session with secret key| D(Payment Gateway) D -->|Order/session ID| C C -->|Order/session ID| B B -->|Open SDK/Popup| D D -->|Payment Result| C C -->|Verify & Update DB| E[Database] D -->|Webhook (optional)| C
Webhook Event Flow
Sequence diagram showing webhook processing:
sequenceDiagram participant Gateway participant YourServer participant DB Gateway->>YourServer: POST /webhook (event) YourServer->>YourServer: Verify signature alt Valid event YourServer->>DB: Update payment status YourServer-->>Gateway: 200 OK else Invalid event YourServer-->>Gateway: 400 Error end
Choosing the Right Payment Gateway
Selecting the right payment gateway is crucial for your business success. Different gateways excel in different regions and use cases.
Global Gateways
Regional Gateways
Selection Criteria
Recommendations
Case Study: A Real Integration Challenge
Real-world experiences provide valuable lessons. Here's a case study from an actual integration project that highlights the importance of security practices.
The Challenge
When integrating Stripe for a SaaS client, we once missed verifying webhook signatures. A malicious actor sent fake payment events, causing our system to mark unpaid orders as paid.
Lesson: Never trust incoming webhooks blindly!
The Solution
Common Pitfalls
Learning from others' mistakes can save you time and prevent security issues. Here are the most common pitfalls developers encounter when integrating payment gateways.
Security Pitfalls
Implementation Pitfalls
Critical Warning
Never log or expose full card numbers or CVVs anywhere in your system.
Production Checklist
Before going live with your payment integration, ensure you've completed this comprehensive checklist to avoid common issues and ensure a smooth launch.
Security Setup
.env
filesTesting & Monitoring
Next.js API Routes Integration
Next.js provides excellent support for payment integrations through its API routes. You can securely handle payment logic in /pages/api/
or /app/api/
routes.
Stripe Payment Intent API Route
Example for creating a Stripe Payment Intent:
// pages/api/create-payment-intent.js
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export default async function handler(req, res) {
if (req.method !== 'POST') return res.status(405).end();
try {
const { amount, currency } = req.body;
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency,
});
res.status(200).json({ clientSecret: paymentIntent.client_secret });
} catch (err) {
res.status(500).json({ error: err.message });
}
}
On the frontend, fetch clientSecret
from this endpoint and use Stripe.js to complete the payment.
React Payment Form Example
Here's a complete React component example for integrating Stripe payments in your frontend application.
Complete React Payment Form
import { useState } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import { Elements, CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_KEY);
function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const [status, setStatus] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
setStatus('Processing...');
const res = await fetch('/api/create-payment-intent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount: 500, currency: 'usd' })
});
const { clientSecret } = await res.json();
const result = await stripe.confirmCardPayment(clientSecret, {
payment_method: { card: elements.getElement(CardElement) }
});
if (result.error) setStatus('Payment failed: ' + result.error.message);
else if (result.paymentIntent.status === 'succeeded') setStatus('Payment successful!');
};
return (
<form onSubmit={handleSubmit}>
<CardElement />
<button type="submit">Pay</button>
<div>{status}</div>
</form>
);
}
export default function PaymentPage() {
return (
<Elements stripe={stripePromise}>
<CheckoutForm />
</Elements>
);
}
Webhook Handler in Next.js
Webhooks are essential for reliable payment processing. Here's how to implement a secure webhook handler in Next.js API routes.
Next.js Webhook Handler
// pages/api/webhook.js
import { buffer } from 'micro';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export const config = { api: { bodyParser: false } };
export default async function handler(req, res) {
if (req.method !== 'POST') return res.status(405).end();
const sig = req.headers['stripe-signature'];
const buf = await buffer(req);
let event;
try {
event = stripe.webhooks.constructEvent(buf, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (err) {
return res.status(400).send('Webhook Error: ' + err.message);
}
// Handle event
if (event.type === 'payment_intent.succeeded') {
// Update DB, send email, etc.
}
res.status(200).json({ received: true });
}
Managing Environment Variables
Proper secret management is crucial for security. Always store API keys in environment variables and never commit them to version control.
Environment Variables Setup
Store your API keys in .env.local
(never commit this file):
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_KEY=pk_test_...
RAZORPAY_KEY_ID=rzp_test_...
RAZORPAY_KEY_SECRET=your_secret_here
WEBHOOK_SECRET=whsec_your_webhook_secret
Access them in your code with process.env.STRIPE_SECRET_KEY
(backend) and process.env.NEXT_PUBLIC_STRIPE_KEY
(frontend).
Security Best Practices
.env.local
to version controlError Handling & Testing
Robust error handling and comprehensive testing are essential for a reliable payment system. Here's how to implement proper error handling and testing strategies.
Error Handling
Testing Strategies
Pro Tips
Accessibility & PCI Compliance
Ensuring your payment forms are accessible and PCI compliant is not just good practice—it's essential for legal compliance and user experience.
Accessibility
PCI Compliance
Important Notes
Real-World Scenarios & Pro Tips
Beyond basic integration, there are several advanced scenarios and best practices that will make your payment system production-ready and scalable.
Advanced Features
Operational Excellence
Frequently Asked Questions
Here are answers to the most common questions developers have when implementing payment integrations.
Can I use multiple gateways in one app?
Yes! Route users to the gateway that fits their region or preference. Many apps use Stripe for global users and Razorpay for Indian users.
How do I handle failed payments?
Listen for failure events, show clear messages to users and allow them to retry. Always log failed transactions for analysis.
Is it safe to store card data?
No. Always use gateway-hosted fields or popups to avoid PCI scope. Never store raw card data in your database.
How do I test webhooks locally?
Use Stripe CLI (stripe listen
) or Razorpay's webhook simulator. These tools forward webhook events to your local development environment.
Further Learning & Resources
Continue your learning journey with these comprehensive resources and official documentation.
Official Documentation
Advanced Security Practices
Modern payment gateways use tokenization and vaulting to ensure that sensitive card data never touches your servers. When a user enters their card details, the gateway returns a token that represents the card, which you can safely store and use for future payments or subscriptions. Always use Content Security Policy (CSP) headers to prevent XSS attacks on your payment pages and set HTTP headers like Strict-Transport-Security
and X-Content-Type-Options
for extra protection. Rotate your webhook secrets and API keys regularly and store them in a secure secrets manager (not in your codebase or plain .env files).
Testing & QA for Payment Flows
Automate your payment flow tests using tools like Cypress or Playwright. Simulate real user journeys: successful payments, failed payments, 3D Secure authentication and cancellations. Both Stripe and Razorpay provide a wide range of test cards for different scenarios. Use their webhook simulators or Stripe CLI to test webhook handling locally and in CI. Always test edge cases, such as network failures, duplicate submissions and slow responses.
Handling Edge Cases
Sometimes a payment succeeds but the webhook fails to reach your server. Always reconcile your database with the payment gateway dashboard daily. Use idempotency keys to prevent double payments if a user retries a transaction. Handle timeouts and user cancellations gracefully, showing clear status messages and allowing users to retry. If a webhook fails, implement a retry mechanism and alert your team for manual intervention if needed.
Scaling Payments for Growth
As your business grows, payment volume increases. Use idempotency keys for all payment and refund requests to prevent duplicates. For SaaS platforms, consider multi-tenant strategies: separate payment accounts per tenant, or use connected accounts (Stripe Connect). For subscriptions, handle proration, upgrades, downgrades and dunning (failed payment recovery) automatically. Queue payment events and process them asynchronously if you expect high traffic.
UX/UI Best Practices
Design a frictionless checkout: use progress indicators, clear error states and mobile-friendly layouts. Always show a loading spinner during payment processing. For accessibility, ensure your forms are keyboard-navigable, have proper ARIA labels and maintain high color contrast. Localize your payment flows by supporting multiple languages, currencies and address formats. Show users a clear summary before they confirm payment and provide instant feedback on success or failure.
Legal & Compliance
Stay up to date with regulations like GDPR (data privacy), SCA (Strong Customer Authentication in Europe) and local laws. Always obtain user consent for storing payment methods. Handle chargebacks and disputes by keeping detailed records, responding promptly and communicating clearly with users. Generate and store invoices/receipts for every transaction and make them easily accessible to users for tax and compliance purposes.
Integrating with Accounting/ERP
Sync your payment data with accounting tools like QuickBooks or Xero using their APIs or third-party connectors. Automate invoice generation and reconciliation to reduce manual work and errors. For larger businesses, integrate with ERP systems to keep your financial records, inventory and payments in sync.
Emerging Payment Methods
Stay ahead by supporting new payment methods: UPI (India), Buy Now Pay Later (BNPL) services and even crypto payments. Evaluate new methods based on your audience and region. Most gateways let you enable new methods with minimal code changes. Monitor adoption and feedback to decide which methods to keep or expand.
Real-World Case Studies
Case 1: A SaaS startup once faced a major issue when a webhook endpoint went down during a product launch. Payments were processed, but user accounts weren't upgraded. The team quickly built a reconciliation script to match gateway records with their database and issued manual upgrades and apologies. Lesson: Always monitor webhook health and have a reconciliation plan.
Case 2: An e-commerce site saw a spike in double payments during a flash sale. Investigation revealed users were clicking "Pay" multiple times due to slow responses. Adding idempotency keys and a loading spinner solved the issue. Lesson: UX and backend safeguards go hand in hand.
Developer Resources
- Open-source payment integration templates: Stripe Samples, Razorpay Node SDK
- Community forums: DEV Payments, Stack Overflow (Stripe)
- Newsletters: Payments Weekly, Fintech Weekly
- YouTube channels: Stripe Developers, Fireship