/stripe-guides

How to apply discounts in Stripe Checkout?

Learn how to create and apply discounts, coupon codes, and promotion codes in Stripe Checkout, including code examples and best practices for dynamic discounts.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free consultation

How to apply discounts in Stripe Checkout?

How to Apply Discounts in Stripe Checkout

 

Step 1: Set Up Your Stripe Account

 

Before implementing discounts in Stripe Checkout, ensure you have:

  • A Stripe account
  • Stripe API keys (both publishable and secret)
  • Stripe library installed in your project

For Node.js, install the Stripe library:

npm install stripe

 

Step 2: Create Coupon Codes in Stripe Dashboard

 

You can create coupon codes through the Stripe Dashboard:

  • Log in to your Stripe Dashboard
  • Navigate to "Products" > "Coupons"
  • Click "Create coupon"
  • Set your coupon details (discount type, amount, duration, etc.)
  • Save the coupon

Alternatively, create coupons programmatically:

const stripe = require('stripe')('sk_test_your_secret_key');

async function createCoupon() {
  const coupon = await stripe.coupons.create({
    percent\_off: 20,
    duration: 'once',
    id: 'SUMMER20', // Optional custom coupon code
    max\_redemptions: 100, // Optional limit on number of redemptions
    redeem\_by: Math.floor(Date.now() / 1000) + (30 _ 24 _ 60 \* 60), // Optional expiration (30 days from now)
  });
  
  console.log('Coupon created:', coupon);
  return coupon;
}

createCoupon();

 

Step 3: Create a Promotion Code (Optional)

 

Promotion codes allow you to create multiple codes for the same coupon:

async function createPromotionCode() {
  const promotionCode = await stripe.promotionCodes.create({
    coupon: 'SUMMER20',
    code: 'SUMMER20PROMO', // Custom promotion code
    max\_redemptions: 50, // Optional
    expires\_at: Math.floor(Date.now() / 1000) + (30 _ 24 _ 60 \* 60), // Optional
  });
  
  console.log('Promotion code created:', promotionCode);
  return promotionCode;
}

createPromotionCode();

 

Step 4: Apply Discounts to Checkout Session

 

To apply a discount to a Checkout session, use the discounts parameter when creating a session:

const stripe = require('stripe')('sk_test_your_secret_key');

async function createCheckoutSession() {
  const session = await stripe.checkout.sessions.create({
    payment_method_types: ['card'],
    line\_items: [
      {
        price: 'price\_1234567890', // Your price ID
        quantity: 1,
      },
    ],
    mode: 'payment',
    success_url: 'https://yourwebsite.com/success?session_id={CHECKOUT_SESSION_ID}',
    cancel\_url: 'https://yourwebsite.com/cancel',
    discounts: [
      {
        coupon: 'SUMMER20', // ID of the coupon to apply
      },
    ],
  });
  
  return session;
}

If you're using promotion codes instead of coupons:

discounts: [
  {
    promotion_code: 'promo_1234567890', // ID of the promotion code
  },
],

 

Step 5: Let Customers Add Their Own Coupon Codes

 

Enable a coupon input field in your Checkout session by setting allow_promotion_codes to true:

const session = await stripe.checkout.sessions.create({
  payment_method_types: ['card'],
  line\_items: [
    {
      price: 'price\_1234567890',
      quantity: 1,
    },
  ],
  mode: 'payment',
  success_url: 'https://yourwebsite.com/success?session_id={CHECKOUT_SESSION_ID}',
  cancel\_url: 'https://yourwebsite.com/cancel',
  allow_promotion_codes: true, // Show a coupon field on checkout
});

 

Step 6: Implement a Custom Coupon Form (Optional)

 

If you want to validate coupon codes before redirecting to Checkout:

// Frontend HTML (simplified)
// Frontend JavaScript document.getElementById('apply-coupon').addEventListener('click', async () => { const couponCode = document.getElementById('coupon-code').value; try { const response = await fetch('/validate-coupon', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ code: couponCode }), }); const result = await response.json(); if (result.valid) { document.getElementById('coupon-status').textContent = `Coupon applied: ${result.discount}% off`; sessionStorage.setItem('couponCode', couponCode); } else { document.getElementById('coupon-status').textContent = 'Invalid coupon code'; } } catch (error) { console.error('Error validating coupon:', error); document.getElementById('coupon-status').textContent = 'Error validating coupon'; } });

Server-side coupon validation:

// Node.js/Express server route
app.post('/validate-coupon', async (req, res) => {
  const { code } = req.body;
  
  try {
    // Retrieve the promotion code from Stripe
    const promotionCodes = await stripe.promotionCodes.list({
      code,
      active: true,
    });
    
    if (promotionCodes.data.length > 0) {
      const promo = promotionCodes.data[0];
      
      // Check if coupon is valid (not expired, has redemptions left, etc.)
      if (promo.active) {
        // Retrieve the associated coupon details
        const coupon = await stripe.coupons.retrieve(promo.coupon.id);
        
        res.json({
          valid: true,
          discount: coupon.percent_off || (coupon.amount_off / 100),
          id: promo.id,
        });
        return;
      }
    }
    
    res.json({ valid: false });
  } catch (error) {
    console.error('Error validating coupon:', error);
    res.status(500).json({ error: 'Error validating coupon' });
  }
});

 

Step 7: Create Checkout Session with Validated Coupon

 

app.post('/create-checkout-session', async (req, res) => {
  const { couponId } = req.body;
  
  try {
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
      line\_items: [
        {
          price: 'price\_1234567890',
          quantity: 1,
        },
      ],
      mode: 'payment',
      success_url: 'https://yourwebsite.com/success?session_id={CHECKOUT_SESSION_ID}',
      cancel\_url: 'https://yourwebsite.com/cancel',
      discounts: couponId ? [
        {
          promotion\_code: couponId,
        },
      ] : [],
    });
    
    res.json({ id: session.id });
  } catch (error) {
    console.error('Error creating checkout session:', error);
    res.status(500).json({ error: 'Error creating checkout session' });
  }
});

 

Step 8: Verify Discount Application

 

To verify the discount was applied after payment, retrieve the Checkout session:

app.get('/checkout-session', async (req, res) => {
  const { sessionId } = req.query;
  
  try {
    const session = await stripe.checkout.sessions.retrieve(sessionId, {
      expand: ['total\_details.breakdown'],
    });
    
    const discountAmount = session.total\_details.breakdown.discounts.reduce(
      (sum, discount) => sum + discount.amount, 
      0
    );
    
    res.json({
      success: true,
      amountSubtotal: session.amount\_subtotal,
      amountTotal: session.amount\_total,
      discountAmount: discountAmount,
      currency: session.currency,
    });
  } catch (error) {
    console.error('Error retrieving session:', error);
    res.status(500).json({ error: 'Error retrieving session' });
  }
});

 

Step 9: Handle Dynamic/Calculated Discounts

 

For more complex discount scenarios that can't be handled by Stripe coupons (e.g., "buy one get one free"), use the line_items object with custom prices:

const session = await stripe.checkout.sessions.create({
  payment_method_types: ['card'],
  line\_items: [
    {
      price\_data: {
        currency: 'usd',
        product\_data: {
          name: 'T-shirt',
        },
        unit\_amount: 2000, // $20.00
      },
      quantity: 1,
    },
    {
      price\_data: {
        currency: 'usd',
        product\_data: {
          name: 'T-shirt (Free)',
        },
        unit\_amount: 0, // $0.00
      },
      quantity: 1,
    },
  ],
  mode: 'payment',
  success_url: 'https://yourwebsite.com/success?session_id={CHECKOUT_SESSION_ID}',
  cancel\_url: 'https://yourwebsite.com/cancel',
});

 

Step 10: Implement Automatic Discount Logic with Webhook (Advanced)

 

Set up a webhook to automatically apply discounts based on your business rules:

// Install body-parser middleware
const bodyParser = require('body-parser');
app.use('/webhook', bodyParser.raw({ type: 'application/json' }));

app.post('/webhook', async (req, res) => {
  const sig = req.headers['stripe-signature'];
  const endpointSecret = 'whsec_your_webhook\_secret';
  let event;

  try {
    event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Handle checkout.session.completed event
  if (event.type === 'checkout.session.completed') {
    const session = event.data.object;
    
    // Apply post-purchase discounts or update customer's discount eligibility
    await updateCustomerDiscountEligibility(session.customer);
  }

  res.status(200).send();
});

async function updateCustomerDiscountEligibility(customerId) {
  // Example: Add metadata to the customer for future discounts
  await stripe.customers.update(customerId, {
    metadata: { 
      eligible_for_discount: 'true',
      discount\_expiry: Math.floor(Date.now() / 1000) + (30 _ 24 _ 60 \* 60), // 30 days from now
    },
  });
}

 

Step 11: Track Discount Usage in Your Database (Recommended)

 

Maintain a record of discount usage for analytics:

// Example using MongoDB/Mongoose
const mongoose = require('mongoose');

const discountUsageSchema = new mongoose.Schema({
  customerId: String,
  couponId: String,
  checkoutSessionId: String,
  discountAmount: Number,
  originalAmount: Number,
  usedAt: { type: Date, default: Date.now },
});

const DiscountUsage = mongoose.model('DiscountUsage', discountUsageSchema);

// In your checkout success handler
app.get('/success', async (req, res) => {
  const { session\_id } = req.query;
  
  try {
    const session = await stripe.checkout.sessions.retrieve(session\_id, {
      expand: ['total\_details.breakdown', 'customer'],
    });
    
    // Calculate the total discount amount
    const discountAmount = session.total\_details.breakdown.discounts.reduce(
      (sum, discount) => sum + discount.amount, 
      0
    );
    
    // Store the discount usage
    if (discountAmount > 0) {
      await new DiscountUsage({
        customerId: session.customer.id,
        couponId: session.total\_details.breakdown.discounts[0]?.discount?.coupon?.id,
        checkoutSessionId: session.id,
        discountAmount: discountAmount,
        originalAmount: session.amount\_subtotal,
      }).save();
    }
    
    res.redirect('/thank-you');
  } catch (error) {
    console.error('Error handling success:', error);
    res.status(500).send('Error processing your payment');
  }
});

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022