Stripe Payment Integration in MERN Stack: Zero to Hero
Online payments can feel intimidating because money, cards, security, banks and backend systems are involved. However, the basic idea is straightforward: Your application cr
MSMuhammad SufiyanSoftware Engineer · Jul 25
Backend Engineering HubT-
#
Online payments can feel intimidating because money, cards, security, banks and backend systems are involved.
However, the basic idea is straightforward:
Your application creates a payment request, Stripe securely collects the payment information, and Stripe informs your backend whether the payment succeeded or failed.
In this guide, we will integrate Stripe into a MERN Stack application using:
React
Node.js
Express.js
MongoDB
Stripe Checkout
Stripe Payment Element
Stripe webhooks
By the end of this guide, you will understand:
What a payment gateway does
How Stripe payments work
Test mode and live mode
Publishable and secret keys
Stripe Checkout Sessions
Payment Intents
Stripe Elements
Payment webhooks
MongoDB order management
Payment verification
Refunds
Recurring subscriptions
Stripe Customer Portal
Security best practices
Common integration mistakes
Production deployment requirements
1. An Important Note for Pakistani Developers
As of July 25, 2026, Pakistan is not listed on Stripe’s official supported account-country page.
This means that a Pakistan-based business cannot simply activate Stripe using fake foreign business, banking or address details.
To use Stripe for live payments, the business must have a legitimate setup in a Stripe-supported country, including the legal, banking and identity information Stripe requires.
Stripe does list countries such as the United States, United Kingdom and United Arab Emirates, but Pakistan is not currently included in its supported payments-country list.
For classroom learning:
Students can understand the complete integration in test mode
No real money is transferred in test mode
Test cards can simulate successful and failed payments
Students should never use fake legal or banking information
The technical concepts learned through Stripe are also useful when integrating other payment providers.
2. What Is a Payment Gateway?
Suppose a customer wants to purchase an online course for $49.
The customer enters their card details:
Card number
Expiry date
CVC
Billing information
Your application should not directly save and process this sensitive card information.
Instead, a payment provider such as Stripe securely collects and processes it.
The basic flow is:
Customer
↓
Your website
↓
Stripe
↓
Card network and bank
↓
Payment approved or declined
↓
Stripe informs your backend
Stripe acts as part of the infrastructure connecting your application to the broader payment system.
3. The Parties Involved in a Card Payment
A card payment normally involves several parties.
Customer
The person purchasing your product or service.
Merchant
The business receiving the payment.
In our project, the MERN application represents the merchant.
Payment processor
The service processing the transaction between the merchant and financial system.
The financial infrastructure handling payments for the merchant.
As developers, we usually do not communicate directly with each party. Stripe provides APIs that simplify this process.
4. What Does Stripe Do?
Stripe provides APIs and prebuilt components for:
One-time payments
Recurring subscriptions
Saved payment methods
Refunds
Invoices
Customer billing management
Multiple payment methods
Marketplace payments
Payment authentication
Webhook notifications
Stripe Checkout provides a prebuilt hosted or embedded checkout experience, while Stripe Elements provides secure UI components for developers who need greater control over the payment interface.
5. The Most Important Security Rule
Never create your own normal input field for card information:
Do not send raw card information to your Express backend.
Do not save card details in MongoDB.
Do not log card details.
Instead, use:
Stripe Checkout
Stripe Elements
Stripe Payment Element
These components send payment details directly to Stripe rather than through your application server. Stripe’s browser libraries tokenize sensitive payment information so that the card data does not need to touch your backend.
6. The Three Easiest Ways to Integrate Stripe
Stripe offers different integration levels.
Option 1: Stripe Payment Links
You create a payment link from the Stripe Dashboard and share it with customers.
Customer
↓
Stripe payment link
↓
Stripe-hosted payment page
This requires little or no backend code.
It is useful for:
Quick product sales
Donations
Basic events
Early prototypes
However, it provides less application-level control.
For production financial calculations, store monetary values as integers instead of floating-point numbers.
Prefer:
const amountInCents = 4900;
instead of:
const amount = 49.00;
13. The Complete Beginner Project
We will create a simple course-purchase application.
The customer will:
1. View a course 2. Click Buy Now 3. Be redirected to Stripe Checkout 4. Enter a Stripe test card 5. Complete the payment 6. Return to the React application 7. Have their order marked as paid through a webhook
In a real application, these products should usually come from:
MongoDB
PostgreSQL
Stripe Products and Prices
A trusted internal catalog service
20. Normal Express Middleware
There is one critical webhook-related rule:
The Stripe webhook needs the original raw request body.
Therefore, the webhook route must be registered before normal express.json() middleware.
The correct order is:
// Stripe webhook route comes first
app.post(
"/api/stripe/webhook",
express.raw({ type: "application/json" }),
stripeWebhookHandler
);
// Normal middleware comes afterward
app.use(express.json());
Stripe warns that parsing or modifying the webhook body before signature verification causes verification to fail. In Express, express.json() must not process the webhook route before constructEvent() receives the raw body.
We will implement the complete webhook shortly.
21. Creating a Checkout Session Endpoint
Import the models:
const Order = require("./models/Order");
const WebhookEvent = require("./models/WebhookEvent");
Create a helper:
function normalizeCartItems(clientItems) {
if (!Array.isArray(clientItems) || clientItems.length === 0) {
throw new Error("At least one item is required");
}
return clientItems.map((clientItem) => {
const product = PRODUCT_CATALOG[clientItem.productId];
if (!product) {
throw new Error(
`Invalid product: ${clientItem.productId}`
);
}
const quantity = Number(clientItem.quantity);
if (
!Number.isInteger(quantity) ||
quantity < 1 ||
quantity > 10
) {
throw new Error("Quantity must be between 1 and 10");
}
return {
productId: clientItem.productId,
name: product.name,
quantity,
unitAmount: product.unitAmount,
currency: product.currency,
};
});
}
Stripe Checkout Sessions can use existing Stripe Price IDs or create price information dynamically through price_data. Checkout Session metadata can also connect the Stripe object to an internal order.
22. Understanding the Checkout Session Code
mode: "payment"
mode: "payment"
This creates a one-time payment.
Other values include:
payment → One-time payment
subscription → Recurring subscription
setup → Save payment details for later
If the customer cancels the checkout, Stripe redirects them here.
metadata
metadata: {
orderId: order._id.toString(),
}
Metadata connects the Stripe Checkout Session with our MongoDB order.
Do not store card information, passwords or other sensitive personal information in Stripe metadata. Stripe recommends metadata for useful internal references, but not sensitive information.
React sends product ID
↓
Express validates product
↓
Express calculates amount
↓
MongoDB order created as pending
↓
Express creates Stripe Checkout Session
↓
Stripe returns checkout URL
↓
React redirects to Stripe
↓
Customer completes payment
At this point, the customer may be redirected to your success page.
However, there is still one critical problem.
27. Never Trust the Success Page
A beginner may think:
Customer reached /success
↓
Payment must be successful
That assumption is unsafe.
A customer could manually open:
http://localhost:5173/payment/success
They could also close the browser before the redirect while the payment still succeeds.
The success page is for customer experience.
It is not the authoritative payment confirmation.
The reliable source is:
A verified webhook sent by Stripe to your backend.
Stripe explicitly recommends using webhooks for payment fulfillment because redirects and client-side flows are not sufficiently reliable for business-critical processing.
28. What Is a Webhook?
A webhook is an HTTP endpoint on your backend that another system calls when an event happens.
Normal API:
Your application asks Stripe:
Did something happen?
Webhook:
Stripe tells your application:
Something happened.
Examples:
Payment succeeded
Payment failed
Subscription created
Invoice paid
Refund created
Subscription cancelled
Stripe sends real-time event payloads to a registered HTTPS webhook endpoint.
29. Webhook Architecture
Customer pays on Stripe Checkout
↓
Stripe processes payment
↓
Stripe sends event to Express webhook
↓
Express verifies Stripe signature
↓
Express finds MongoDB order
↓
Express marks order as paid
↓
Express triggers fulfillment
Stripe inserts secure payment fields into the Payment Element. The sensitive payment details are sent directly to Stripe rather than through your Express server.
For production, inspect the actual subscription status and billing rules before deciding exactly when access should be granted or removed.
55. Handling invoice.paid
case "invoice.paid": {
const invoice = event.data.object;
console.log(
`Invoice paid: ${invoice.id}`
);
// Locate the user through the customer
// or subscription relationship.
// Confirm continued paid access.
break;
}
This event is useful because subscription payments repeat over time.
The original Checkout Session happens only during signup.
Future renewals are represented through invoices and subscription events.
56. Handling invoice.payment_failed
case "invoice.payment_failed": {
const invoice = event.data.object;
console.log(
`Invoice payment failed: ${invoice.id}`
);
// Mark the subscription as past_due,
// notify the user,
// and request updated payment details.
break;
}
Do not permanently delete user data immediately after a single failed renewal.
A better flow may be:
Payment fails
↓
Mark subscription as past_due
↓
Notify customer
↓
Allow billing recovery period
↓
Update access according to business policy
57. Stripe Customer Portal
Instead of building an entire subscription-management interface, Stripe can host a Customer Portal.
Customers can use it to:
Update billing information
Change payment methods
View invoices
Manage subscriptions
Cancel subscriptions
Stripe provides a hosted Customer Portal for billing and subscription self-service.
Create a portal session:
app.post(
"/api/billing/create-portal-session",
async (req, res) => {
try {
// Get this user through authentication.
const user = await User.findById(
req.body.userId
);
if (!user?.stripeCustomerId) {
return res.status(400).json({
success: false,
message:
"Stripe customer account not found",
});
}
const portalSession =
await stripe.billingPortal.sessions.create({
customer: user.stripeCustomerId,
return_url:
`${process.env.CLIENT_URL}/billing`,
});
return res.status(201).json({
success: true,
portalUrl: portalSession.url,
});
} catch (error) {
console.error(
"Customer Portal error:",
error
);
return res.status(500).json({
success: false,
message:
"Unable to open billing portal",
});
}
}
);
In production, do not accept userId freely from the body. Use the authenticated user:
const userId = req.user.id;
58. What Is Idempotency?
Imagine that your server sends a payment-creation request to Stripe.
The network connection fails before your backend receives the response.
Your backend does not know whether Stripe created the object.
If it blindly retries, it might accidentally create another object.
An idempotency key tells Stripe:
This retry belongs to the same logical operation.
Stripe supports idempotency keys for safely retrying POST requests without unintentionally performing the same operation twice.
Store processed event IDs and use atomic database updates.
Mistake 7: Assuming webhooks always arrive in order
Design your handlers around the current Stripe object and current database state.
Mistake 8: Doing heavy work inside the webhook
Avoid generating large PDFs, sending many emails or performing slow third-party calls before responding.
Use a queue such as BullMQ for heavy fulfillment.
Mistake 9: Using test keys in production
Check:
Test:
pk_test_
sk_test_
Live:
pk_live_
sk_live_
Mistake 10: Testing live payments with real cards
Use Stripe test mode and official test cards while developing.
63. Deployment Checklist
Before going live:
Activate a legitimate Stripe account
Complete Stripe business verification
Configure live API keys
Store secrets in the deployment environment
Use HTTPS
Configure a production webhook endpoint
Use the production webhook signing secret
Select only required webhook events
Test successful payments
Test declined payments
Test 3D Secure
Test duplicate webhook delivery
Test delayed webhook delivery
Test refunds
Test partial refunds
Test server restarts
Test database failures
Test email or fulfillment retries
Add application monitoring
Add structured logging
Add alerting for payment failures
Define refund and cancellation policies
Restrict admin payment endpoints
Never use fake business or banking information
Stripe recommends subscribing only to the event types your integration requires instead of receiving unnecessary events.
64. Recommended Classroom Plan
Session 1: Payment Fundamentals
Teach:
What a payment gateway is
Stripe’s role
Customer, merchant and bank
Test mode vs live mode
Publishable key vs secret key
Amounts in cents
Checkout vs Payment Element
Why raw card data must not reach the backend
Practical:
Create Stripe test environment
Explore Stripe Dashboard
View API keys
Create a test Product and Price
Session 2: Stripe Checkout
Teach:
Checkout Session
Server-side pricing
Success and cancel URLs
Metadata
MongoDB pending orders
Practical:
Create Express endpoint
Create Checkout Session
Redirect from React
Complete test payment
Session 3: Webhooks
Teach:
Why success pages are not trusted
Raw request body
Webhook signatures
Event types
Duplicate events
Idempotent processing
Order-state updates
Practical:
Install Stripe CLI
Forward webhooks
Verify signature
Mark order paid
Test decline and authentication cards
Session 4: Payment Element
Teach:
PaymentIntent
Client secret
Stripe Elements
React Stripe.js
confirmPayment
PaymentIntent webhooks
Practical:
Build custom payment page
Confirm payment
Handle success and failure
Session 5: Refunds and Subscriptions
Teach:
Full refunds
Partial refunds
Recurring prices
Subscription status
Invoice webhooks
Customer Portal
Practical:
Admin refund endpoint
Subscription Checkout
Portal session
Subscription cancellation handling
65. Final Student Project
Project: Course Marketplace Payment System
Students must create a MERN application where users can purchase courses.
Functional requirements
1. Display courses from MongoDB. 2. Let users add courses to a cart. 3. Send product IDs and quantities to the backend. 4. Calculate all prices on the backend. 5. Create a pending MongoDB order. 6. Create a Stripe Checkout Session. 7. Redirect users to Stripe. 8. Handle successful payment webhooks. 9. Handle failed payment webhooks. 10. Prevent duplicate webhook processing. 11. Mark paid orders in MongoDB. 12. Grant course access after payment. 13. Display order history. 14. Add an administrator refund endpoint. 15. Support partial refunds. 16. Add a monthly premium subscription. 17. Add the Stripe Customer Portal. 18. Protect all sensitive routes. 19. Add payment logs. 20. Document test cards in the README.
Bonus requirements
BullMQ payment-fulfillment queue
Redis idempotency
Email confirmation
PDF invoice generation
Coupon support
Tax handling
Shipping address collection
Docker deployment
AWS EC2 deployment
GitHub Actions
Nginx
Webhook monitoring
Payment failure alerts
66. Stripe Interview Questions
What is Stripe?
Stripe is a payments platform that provides APIs and user-interface components for accepting and managing online payments.
What is Stripe Checkout?
Stripe Checkout is a prebuilt payment page that can be hosted by Stripe or embedded into an application.
What is a PaymentIntent?
A PaymentIntent tracks the complete lifecycle of one payment.
What is a Checkout Session?
A Checkout Session controls a Stripe Checkout flow, including items, prices, payment mode and redirect URLs.
What is a webhook?
A webhook is an HTTP endpoint that receives event notifications from Stripe.
Why should we not trust the success page?
The customer can manually open the success URL, fail to reach it or close the browser. A verified webhook provides server-to-server payment confirmation.
What is the publishable key?
The publishable key initializes Stripe in the frontend and can safely be included in browser code.
What is the secret key?
The secret key authorizes sensitive Stripe API operations and must remain on the backend.
What is the webhook signing secret?
It verifies that a webhook request was generated by Stripe.
Why does the webhook require the raw body?
Stripe calculates the webhook signature using the original request payload. Parsing or modifying the body can cause signature verification to fail.
Why should prices be calculated on the backend?
Frontend values can be modified by users. The backend must use trusted product data.
Why are Stripe amounts integers?
Amounts are generally passed in the currency’s smallest unit, such as cents for US dollars.
What is idempotency?
Idempotency ensures that retrying the same operation does not accidentally create duplicate effects.
Can Stripe send duplicate webhook events?
Yes. Webhook handlers should track event IDs and process events idempotently.
Does Stripe guarantee webhook event order?
No. Applications should not depend on guaranteed event ordering.
What is Stripe Elements?
Stripe Elements is a collection of secure payment UI components.
What is the Payment Element?
Payment Element is a Stripe UI component that can display multiple eligible payment methods inside an application.
What is the difference between Checkout and Payment Element?
Checkout provides a prebuilt checkout experience. Payment Element gives developers more control over the payment page design and flow.
How do refunds work?
The backend creates a refund against a successful PaymentIntent or Charge. It can refund the full or a partial amount.
What is Stripe Billing?
Stripe Billing provides recurring payments, subscriptions, invoices and billing-management features.
What is the Customer Portal?
It is a Stripe-hosted page where customers can manage billing information, invoices, payment methods and subscriptions.
Customer chooses product
↓
Backend validates product and price
↓
Backend creates pending order
↓
Backend creates Stripe payment
↓
Stripe securely collects payment information
↓
Stripe processes payment
↓
Stripe sends signed webhook
↓
Backend verifies webhook
↓
Backend marks order paid
↓
Backend performs fulfillment
The most important lessons are:
1. Never trust prices from the frontend. 2. Never expose the Stripe secret key. 3. Never store raw card details. 4. Never mark an order paid only because the user reached the success page. 5. Always verify Stripe webhook signatures. 6. Always make webhook processing idempotent. 7. Always keep Stripe IDs connected to your internal database records. 8. Use Stripe Checkout before moving to a fully custom payment flow. 9. Use Payment Intents when you need greater control. 10. Use webhooks as the trusted source for payment and subscription changes.
The easiest sentence to remember is:
The frontend starts the payment experience, Stripe processes the payment, and the verified webhook confirms the result to your backend.
HIRINGMINE CAREER SIGNAL
This writing is proof of expertise.
Explore the author’s verified skills, projects and availability—or start a professional conversation.