Learn how to easily add a secure payment gateway to your web app with our step-by-step guide. Boost sales and user trust today!

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
The 30-Second Summary
Adding a payment gateway to your web app isn't just a technical task—it's a business decision that impacts user experience, conversion rates, and your bottom line. In this guide, I'll walk you through integrating payment processing that balances security, user experience, and development resources.
Not all payment gateways are created equal. Your choice should align with your business model, target audience, and growth plans.
Decision factors that actually matter:
Depending on your requirements, you have three main integration approaches:
What it is: The payment gateway handles the entire checkout process on their servers.
Implementation example with Stripe Checkout:
// Server-side code (Node.js with Express)
const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');
const express = require('express');
const app = express();
app.post('/create-checkout-session', async (req, res) => {
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: 'usd',
product_data: {
name: 'Premium Subscription',
},
unit_amount: 2000, // $20.00
},
quantity: 1,
},
],
mode: 'payment',
success_url: 'https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'https://yoursite.com/cancel',
});
res.json({ id: session.id });
});
// Client-side code
document.getElementById('checkout-button').addEventListener('click', async () => {
const response = await fetch('/create-checkout-session', {
method: 'POST',
});
const session = await response.json();
// Redirect to Stripe Checkout
const stripe = Stripe('pk_test_YOUR_PUBLISHABLE_KEY');
stripe.redirectToCheckout({ sessionId: session.id });
});
Pros:
Cons:
What it is: Payment form elements are embedded in your site, but sensitive data is sent directly to the gateway.
Implementation example with Stripe Elements:
<!-- Client-side HTML -->
<form id="payment-form">
<div id="card-element">
<!-- Stripe Elements will insert the card input here -->
</div>
<div id="card-errors" role="alert"></div>
<button type="submit">Pay Now</button>
</form>
// Client-side JavaScript
const stripe = Stripe('pk_test_YOUR_PUBLISHABLE_KEY');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
const form = document.getElementById('payment-form');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const {paymentMethod, error} = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
});
if (error) {
// Show error to customer
const errorElement = document.getElementById('card-errors');
errorElement.textContent = error.message;
} else {
// Send paymentMethod.id to your server
const response = await fetch('/process-payment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
payment_method_id: paymentMethod.id,
amount: 2000, // $20.00
}),
});
const result = await response.json();
handleServerResponse(result);
}
});
function handleServerResponse(response) {
if (response.error) {
// Show error
} else if (response.requires_action) {
// Handle 3D Secure authentication if needed
stripe.handleCardAction(response.payment_intent_client_secret)
.then(handleStripeJsResult);
} else {
// Payment successful!
window.location.href = '/success';
}
}
// Server-side code (Node.js with Express)
app.post('/process-payment', async (req, res) => {
const {payment_method_id, amount} = req.body;
try {
// Create a PaymentIntent with the payment method
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency: 'usd',
payment_method: payment_method_id,
confirmation_method: 'manual',
confirm: true,
});
if (paymentIntent.status === 'requires_action') {
// 3D Secure is required
res.json({
requires_action: true,
payment_intent_client_secret: paymentIntent.client_secret
});
} else {
// Payment successful
res.json({success: true});
}
} catch (err) {
res.json({error: err.message});
}
});
Pros:
Cons:
What it is: You build the entire payment flow and communicate directly with the payment gateway's API.
Implementation example with Stripe API:
// This approach requires a secure, PCI-compliant environment
// I'm providing a simplified example with Stripe.js to maintain security
// Client-side: Create a token instead of handling card data directly
const tokenizeCard = async (cardDetails) => {
const stripe = Stripe('pk_test_YOUR_PUBLISHABLE_KEY');
const {token, error} = await stripe.createToken('card', {
number: cardDetails.number,
exp_month: cardDetails.expMonth,
exp_year: cardDetails.expYear,
cvc: cardDetails.cvc
});
if (error) {
throw new Error(error.message);
}
return token.id;
};
// Then send this token to your server
const processPayment = async (tokenId, amount) => {
const response = await fetch('/api/process-direct-payment', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
token: tokenId,
amount: amount
})
});
return response.json();
};
// Server-side API endpoint
app.post('/api/process-direct-payment', async (req, res) => {
const {token, amount} = req.body;
try {
const charge = await stripe.charges.create({
amount,
currency: 'usd',
source: token,
description: 'Custom payment flow charge'
});
// Store transaction details in your database
await saveTransactionToDatabase({
id: charge.id,
amount: charge.amount,
status: charge.status,
customer: req.user.id // Assuming authenticated user
});
res.json({success: true, transaction: charge.id});
} catch (error) {
res.status(400).json({error: error.message});
}
});
Pros:
Cons:
Test cards are your friends. Most payment gateways provide test card numbers that simulate different scenarios:
Example test cards for Stripe:
Successful payment: 4242 4242 4242 4242
Requires authentication: 4000 0027 6000 3184
Insufficient funds: 4000 0000 0000 9995
Implement webhook handling for asynchronous events:
// Webhook handler for Stripe events
app.post('/webhook', express.raw({type: 'application/json'}), async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
// Verify the event came from Stripe
event = stripe.webhooks.constructEvent(
req.body,
sig,
'whsec_YOUR_WEBHOOK_SECRET'
);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the event
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
// Update order status in your database
await updateOrderStatus(paymentIntent.metadata.order_id, 'paid');
break;
case 'payment_intent.payment_failed':
const failedPayment = event.data.object;
// Notify customer of failed payment
await notifyCustomerOfFailure(failedPayment.metadata.customer_id);
break;
// Handle other event types
default:
console.log(`Unhandled event type ${event.type}`);
}
res.status(200).send({received: true});
});
Payment security isn't a feature—it's a foundation. Regardless of your integration approach:
Payment failures happen. Your integration should gracefully handle these scenarios:
// Client-side error handling example
try {
const result = await processPayment(paymentDetails);
if (result.success) {
// Show success message and redirect
showSuccessUI();
redirectToOrderConfirmation(result.orderId);
} else {
// Payment was processed but declined
handleDeclinedPayment(result.declineCode);
}
} catch (error) {
// Something went wrong with the API request
if (error.isNetworkError) {
// Connection issue - save payment intent for retry
savePaymentIntentToLocalStorage();
showRetryMessage("Connection issue. Your card wasn't charged.");
} else {
// Other API error
logErrorToMonitoring(error);
showGenericErrorMessage();
}
}
// Function to handle specific decline reasons
function handleDeclinedPayment(declineCode) {
switch(declineCode) {
case 'insufficient_funds':
showMessage("Your card has insufficient funds. Please try another payment method.");
highlightPaymentMethodSelector();
break;
case 'expired_card':
showMessage("This card has expired. Please update your card details.");
focusOnCardExpiryField();
break;
default:
showMessage("Your payment was declined. Please try another payment method.");
}
}
A complete payment system is more than just collecting money. Consider these additional components:
Example refund implementation:
// Server-side refund endpoint
app.post('/api/refunds', async (req, res) => {
const {order_id, amount, reason} = req.body;
// Retrieve the payment ID from your database
const order = await db.orders.findOne({id: order_id});
if (!order) {
return res.status(404).json({error: 'Order not found'});
}
try {
// Process refund through Stripe
const refund = await stripe.refunds.create({
payment_intent: order.payment_intent_id,
amount: amount || undefined, // If undefined, refunds the full amount
reason: reason || 'requested_by_customer'
});
// Update order status in your database
await db.orders.update({
id: order_id,
status: amount ? 'partially_refunded' : 'refunded',
refund_id: refund.id,
refunded_amount: refund.amount
});
// Notify customer about the refund
await sendRefundNotification(order.customer_email, {
order_id,
amount: refund.amount,
reason
});
res.json({success: true, refund_id: refund.id});
} catch (error) {
res.status(400).json({error: error.message});
}
});
Quantify the ROI of your payment integration:
Payment gateway integration isn't a one-and-done project. As your business grows, you'll need to:
Start with the simplest integration that meets your needs, then iterate based on customer feedback and business metrics. The best payment experience is one that customers barely notice because it works so smoothly.
Explore the top 3 payment gateway use cases to seamlessly integrate payments into your web app.
A payment gateway integration that handles secure card transactions, digital wallets, and alternative payment methods on your online store. It creates a trusted purchase environment by managing the entire payment lifecycle from authorization to settlement while shielding your business from sensitive payment data.
A payment solution that handles recurring billing cycles, automated retry logic, and subscription lifecycle events (upgrades, downgrades, pauses). It transforms one-time purchases into predictable revenue streams through sophisticated billing models with minimal development overhead.
A payment infrastructure that facilitates transactions between multiple parties while managing escrow, split payments, and automated payouts to sellers/service providers. It handles the complex money movement that powers platform business models while maintaining regulatory compliance.
From startups to enterprises and everything in between, see for yourself our incredible impact.
Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.Â