Offering discounts and promotions is a powerful strategy for e-commerce businesses to increase conversion rates, reduce cart abandonment, and build customer loyalty. Stripe Checkout provides robust support for implementing coupon systems, allowing you to offer various types of discounts to your customers seamlessly.
This guide will walk you through the process of integrating coupon functionality into your Stripe Checkout implementation, from basic setup to advanced promotion strategies.
Before diving into implementation, it's important to understand how Stripe handles discounts and promotions:
This separation gives you flexibility to create various promotional strategies while maintaining a clean data model.
The simplest way to create coupons is through the Stripe Dashboard:
WELCOME25
)While the dashboard is convenient for creating one-off coupons, you'll likely want to create coupons programmatically for dynamic promotions.
To create coupons via the Stripe API:
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
async function createPercentageCoupon(id, percentOff, duration = 'once') {
try {
const coupon = await stripe.coupons.create({
id,
percent_off: percentOff,
duration,
// Optional: Set an expiration
redeem_by: Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60), // 30 days
});
console.log(`Created ${percentOff}% coupon with ID: ${coupon.id}`);
return coupon;
} catch (error) {
console.error('Error creating coupon:', error);
throw error;
}
}
async function createFixedAmountCoupon(id, amountOff, currency = 'usd', duration = 'once') {
try {
const coupon = await stripe.coupons.create({
id,
amount_off: amountOff * 100, // Convert to cents
currency,
duration,
});
console.log(`Created $${amountOff} coupon with ID: ${coupon.id}`);
return coupon;
} catch (error) {
console.error('Error creating coupon:', error);
throw error;
}
}
// Example usage
createPercentageCoupon('SPRING25', 25);
createFixedAmountCoupon('WELCOME10', 10, 'usd');
Promotion codes are what customers actually enter at checkout. A single coupon can have multiple promotion codes, allowing you to track marketing campaigns effectively:
async function createPromotionCode(couponId, code, maxRedemptions = null) {
try {
const promotionCode = await stripe.promotionCodes.create({
coupon: couponId,
code,
max_redemptions: maxRedemptions,
// Optional: Set restrictions
restrictions: {
minimum_amount: 1000, // $10.00
minimum_amount_currency: 'usd',
},
});
console.log(`Created promotion code: ${promotionCode.code}`);
return promotionCode;
} catch (error) {
console.error('Error creating promotion code:', error);
throw error;
}
}
// Create multiple codes for the same coupon
createPromotionCode('SPRING25', 'TWITTER25', 100); // 100 max uses
createPromotionCode('SPRING25', 'EMAIL25'); // Unlimited uses
Now that we've created coupons and promotion codes, let's integrate them into Stripe Checkout. There are two primary approaches:
The simplest approach is to enable Stripe's built-in promotion code field:
const session = await stripe.checkout.sessions.create({
line_items: [
{
price: 'price_1234567890',
quantity: 1,
},
],
mode: 'payment',
allow_promotion_codes: true, // Enable the promotion code input field
success_url: 'https://example.com/success',
cancel_url: 'https://example.com/cancel',
});
This adds a "Add promotion code" link in the Checkout interface that, when clicked, reveals an input field where customers can enter codes.
Alternatively, you can automatically apply a coupon without requiring customer input:
const session = await stripe.checkout.sessions.create({
line_items: [
{
price: 'price_1234567890',
quantity: 1,
},
],
discounts: [
{
coupon: 'WELCOME25',
},
],
mode: 'payment',
success_url: 'https://example.com/success',
cancel_url: 'https://example.com/cancel',
});
This is useful for automatically applying discounts from URL parameters, email campaigns, or special landing pages.
For a better user experience, you might want to validate coupon codes before redirecting to Checkout. Here's how to implement a custom coupon input field:
// Client-side validation
async function validateCoupon(code) {
try {
const response = await fetch('/api/validate-coupon', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code }),
});
const result = await response.json();
if (result.valid) {
document.getElementById('coupon-feedback').textContent =
`Coupon applied: ${result.discount}% off`;
// Store the validated code to use when creating the Checkout session
sessionStorage.setItem('validCoupon', code);
} else {
document.getElementById('coupon-feedback').textContent =
`Invalid coupon: ${result.message}`;
}
} catch (error) {
console.error('Error validating coupon:', error);
}
}
// When the customer clicks "Checkout"
async function handleCheckout() {
const validCoupon = sessionStorage.getItem('validCoupon');
const response = await fetch('/api/create-checkout-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
items: getCartItems(),
couponCode: validCoupon,
}),
});
const { sessionId } = await response.json();
// Redirect to Checkout
const stripe = Stripe('pk_test_your_public_key');
stripe.redirectToCheckout({ sessionId });
}
Then implement the server-side validation endpoint:
// Server-side validation
app.post('/api/validate-coupon', async (req, res) => {
const { code } = req.body;
try {
// Retrieve the promotion code
const promotionCodes = await stripe.promotionCodes.list({
code,
active: true,
});
if (promotionCodes.data.length === 0) {
return res.json({ valid: false, message: 'Coupon not found' });
}
const promoCode = promotionCodes.data[0];
// Check if the code is valid
if (!promoCode.active) {
return res.json({ valid: false, message: 'Coupon is inactive' });
}
if (promoCode.max_redemptions && promoCode.times_redeemed >= promoCode.max_redemptions) {
return res.json({ valid: false, message: 'Coupon has been fully redeemed' });
}
if (promoCode.expires_at && promoCode.expires_at < Math.floor(Date.now() / 1000)) {
return res.json({ valid: false, message: 'Coupon has expired' });
}
// Return discount information
const coupon = await stripe.coupons.retrieve(promoCode.coupon.id);
let discountInfo;
if (coupon.percent_off) {
discountInfo = `${coupon.percent_off}%`;
} else if (coupon.amount_off) {
discountInfo = `${(coupon.amount_off / 100).toFixed(2)} ${coupon.currency.toUpperCase()}`;
}
return res.json({
valid: true,
discount: discountInfo,
couponId: coupon.id,
});
} catch (error) {
console.error('Error validating coupon:', error);
return res.status(500).json({ valid: false, message: 'Server error' });
}
});
Finally, apply the validated coupon when creating the checkout session:
app.post('/api/create-checkout-session', async (req, res) => {
const { items, couponCode } = req.body;
// Build line items from cart
const lineItems = items.map(item => ({
price: item.priceId,
quantity: item.quantity,
}));
// Session parameters
const sessionParams = {
line_items: lineItems,
mode: 'payment',
success_url: `${req.headers.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${req.headers.origin}/cart`,
};
// Apply coupon if provided
if (couponCode) {
try {
// Verify the coupon is still valid
const promotionCodes = await stripe.promotionCodes.list({
code: couponCode,
active: true,
});
if (promotionCodes.data.length > 0) {
sessionParams.discounts = [
{
promotion_code: promotionCodes.data[0].id,
},
];
}
} catch (error) {
console.error('Error applying coupon:', error);
// Continue checkout without the coupon
}
}
try {
const session = await stripe.checkout.sessions.create(sessionParams);
res.json({ sessionId: session.id });
} catch (error) {
console.error('Error creating checkout session:', error);
res.status(500).json({ error: error.message });
}
});
Stripe allows you to place restrictions on coupons to meet specific business needs:
const coupon = await stripe.coupons.create({
percent_off: 15,
duration: 'once',
// Only apply to specific products
applies_to: {
products: ['prod_12345', 'prod_67890'],
},
// Set minimum purchase amount
restrictions: {
minimum_amount: 5000, // $50.00
minimum_amount_currency: 'usd',
},
});
To offer discounts only to new customers:
app.post('/api/create-checkout-session', async (req, res) => {
const { email } = req.body;
// Check if this email has made a purchase before
const customers = await stripe.customers.list({
email,
limit: 1,
});
const sessionParams = {
line_items: [...], // Your line items
mode: 'payment',
customer_email: email,
success_url: `${req.headers.origin}/success`,
cancel_url: `${req.headers.origin}/cart`,
};
// If no previous purchases, apply first-time discount
if (customers.data.length === 0) {
sessionParams.discounts = [
{
coupon: 'FIRSTTIME20',
},
];
}
const session = await stripe.checkout.sessions.create(sessionParams);
res.json({ sessionId: session.id });
});
For flash sales or holiday promotions, create coupons with expiry dates:
const blackFridayCoupon = await stripe.coupons.create({
id: 'BLACKFRIDAY',
percent_off: 30,
duration: 'once',
redeem_by: Math.floor(new Date('2025-11-30T23:59:59Z').getTime() / 1000),
});
Offer different discount levels based on purchase amount:
async function applyTieredDiscount(amount) {
let couponId;
if (amount >= 20000) { // $200+
couponId = 'TIER3_25OFF';
} else if (amount >= 10000) { // $100+
couponId = 'TIER2_15OFF';
} else if (amount >= 5000) { // $50+
couponId = 'TIER1_10OFF';
}
if (couponId) {
return { coupon: couponId };
}
return null;
}
app.post('/api/create-checkout-session', async (req, res) => {
const { items } = req.body;
// Calculate total amount
const amount = items.reduce((total, item) => {
return total + (item.price * item.quantity);
}, 0);
const discount = applyTieredDiscount(amount);
const sessionParams = {
line_items: [...], // Your line items
mode: 'payment',
success_url: `${req.headers.origin}/success`,
cancel_url: `${req.headers.origin}/cart`,
};
if (discount) {
sessionParams.discounts = [discount];
}
const session = await stripe.checkout.sessions.create(sessionParams);
res.json({ sessionId: session.id });
});
Understanding how your coupons perform is crucial for optimizing your promotion strategy:
async function getCouponMetrics(couponId) {
try {
// Get the coupon
const coupon = await stripe.coupons.retrieve(couponId);
// Get all promotion codes for this coupon
const promotionCodes = await stripe.promotionCodes.list({
coupon: couponId,
limit: 100,
});
// Aggregate redemption data
let totalRedemptions = 0;
const codeRedemptions = {};
for (const code of promotionCodes.data) {
codeRedemptions[code.code] = code.times_redeemed;
totalRedemptions += code.times_redeemed;
}
// Calculate redemption rate
const redemptionRate = coupon.metadata.sent_count
? (totalRedemptions / parseInt(coupon.metadata.sent_count) * 100).toFixed(2)
: 'Unknown';
return {
couponId,
discountType: coupon.percent_off ? 'percentage' : 'fixed_amount',
discountValue: coupon.percent_off || (coupon.amount_off / 100),
totalRedemptions,
codeRedemptions,
redemptionRate: `${redemptionRate}%`,
status: coupon.valid ? 'Active' : 'Inactive',
};
} catch (error) {
console.error('Error fetching coupon metrics:', error);
throw error;
}
}
To track revenue impact, you can use Stripe webhooks:
app.post('/webhook', bodyParser.raw({ type: 'application/json' }), async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
// Get the applied discount, if any
if (session.total_details && session.total_details.discount) {
const discountAmount = session.total_details.discount.amount;
const couponId = session.discount ? session.discount.coupon.id : 'unknown';
// Log the discount usage
console.log(`Coupon ${couponId} used with discount amount: ${discountAmount / 100}`);
// Save to your database for reporting
await saveDiscountUsage({
checkoutSessionId: session.id,
couponId,
discountAmount: discountAmount / 100,
currency: session.currency,
customerEmail: session.customer_details.email,
timestamp: new Date(),
});
}
}
res.json({ received: true });
});
To optimize your discount strategy, implement A/B testing:
app.post('/api/create-checkout-session', async (req, res) => {
const { email } = req.body;
// Simple A/B test - assign users to either a percentage or fixed discount
const testGroup = Math.random() < 0.5 ? 'A' : 'B';
let discountConfig;
if (testGroup === 'A') {
// Group A: Percentage discount
discountConfig = {
coupon: 'PERCENT15', // 15% off
};
} else {
// Group B: Fixed amount discount
discountConfig = {
coupon: 'AMOUNT10', // $10 off
};
}
// Record the test group assignment
await saveABTestData({
email,
testGroup,
timestamp: new Date(),
});
const session = await stripe.checkout.sessions.create({
line_items: [...], // Your line items
mode: 'payment',
customer_email: email,
discounts: [discountConfig],
success_url: `${req.headers.origin}/success?session_id={CHECKOUT_SESSION_ID}&test_group=${testGroup}`,
cancel_url: `${req.headers.origin}/cart?test_group=${testGroup}`,
});
res.json({ sessionId: session.id });
});
SUMMER2025
instead of DISC15
).If a coupon isn't applying correctly:
If the discount amount seems incorrect:
For problems with the coupon input field:
allow_promotion_codes: true
is set in the Checkout SessionImplementing coupons in Stripe Checkout can significantly enhance your e-commerce platform by providing flexible discount options that drive conversions and foster customer loyalty. By following this guide, you can create a robust coupon system that supports various promotional strategies while maintaining control over discount economics.
Remember that successful promotions go beyond technical implementation—they require careful planning, continuous monitoring, and adjustment based on performance data. Regularly analyze your coupon metrics to refine your approach and maximize the ROI of your discount strategy.
With Stripe's powerful API and the implementation techniques covered in this guide, you have all the tools needed to create a sophisticated coupon system that delights your customers and drives business growth.
Happy selling!
Start your journey with us today and get access to our resources and tools.