Discount codes sound simple: customer enters a code, gets a percentage or flat-amount deduction, pays less. But custom discount logic is deceptively messy. Validate the code on the frontend (insecure — anyone can reverse-engineer it). Validate on the backend (latency + extra API calls). Track usage to prevent double-redemption. Apply it to the right price tier. Handle edge cases (expired codes, max redemptions, one-time-use gates). Stripe Coupons and Promotion Codes eliminate all of it. Velocity X uses them for Black Friday campaigns (-20%), partner gift codes, and affiliate referrals. The math: 30 minutes to set up beats 6 hours of custom validation code.
Coupons vs. Promotion Codes — What's the Difference?
Both are Stripe discount objects, but they live in different workflows. A Coupon is a reusable discount template (e.g., "20% off all purchases") you define once in the Stripe Dashboard and attach to Charges or Invoices via API. A Promotion Code is a customer-facing code (e.g., BLACKFRIDAY2026) that wraps a Coupon and adds redemption rules: max uses per code, per customer, expiry date, minimum purchase amount.
For Velocity X's Payment Links, Promotion Codes are the play. Create one Promotion Code per campaign, link it to a Coupon (the discount %), and attach the code to your Payment Link URL. Customers see a "Enter a promo code" field, type the code, and Stripe auto-applies the discount before checkout completes. No backend work. No custom validation. Stripe enforces the rules.
Why Stripe Coupons Beat Custom Discount Logic
Custom discount code validation lives in a grey area: it's payment-adjacent, so it has to be bulletproof, but it's not payment processing, so it feels safe to build custom. That false confidence kills you. You hardcode the discount logic in your backend, forget to validate on the frontend, or miss a race condition where two customers use the same one-time code simultaneously. Meanwhile, Stripe's discount system has been stress-tested at scale. One hundred thousand simultaneous "BLACKFRIDAY" code redemptions? Stripe's already handled worse.
Custom codes also couple your business logic to your payment layer. Change the discount from "20% off" to "20% off + free upgrade"? Now you're versioning discount logic alongside your product schema. Stripe Coupons keep that coupling loose — the discount is defined once, reused everywhere, and if you need to kill it, you deactivate the Promotion Code and all links stop accepting it immediately.
The metadata tracking is a free bonus. Every charge that used a Promotion Code has discount.coupon.id and promotion_code.id in the Charge object, so you know exactly which campaign drove which sale. Query your Stripe charges, group by discount.coupon.id, and boom — "Black Friday did $45K, partner gifting did $8K, affiliate codes did $3K." Zero logging code.
How It Works: Dashboard to Payment Link
Step one: create a Coupon in the Stripe Dashboard. Go to Billing → Coupons, click "New coupon", set the discount (percentage or fixed amount), duration (one-time, repeating monthly, or forever), and validity period. A coupon named DISCOUNT_20_PERCENT with 20% off, valid forever, takes 90 seconds.
Step two: create a Promotion Code. Go to Billing → Promotion codes, click "New promotion code", select the coupon, then set customer-facing rules: the code string itself (BLACKFRIDAY2026), max uses (e.g., 100 redemptions total), max uses per customer (1, so one person can't redeem it 50 times), expiry date, and minimum purchase amount. Stripe validates all of this at payment time — no backend calls needed.
Step three: attach the Promotion Code to your Payment Link. When you create a Payment Link (API or Dashboard), pass the Promotion Code ID in the promotion_codes array. Customers see a "Promo code" field at checkout, enter the code, and Stripe applies the discount in real-time. The final Charge object contains the discount metadata, and your Stripe webhook (if you have one listening to charge.succeeded) logs the sale with full campaign attribution.
// Create a Payment Link with Promotion Code attached (Stripe API)
{"\n"}const link = await stripe.paymentLinks.create({"{"}
{"\n"} line_items: [{{ price: "price_xyz", quantity: 1 }}],
{"\n"} promotion_codes: ["promo_BLACKFRIDAY2026"],
{"\n"} metadata: {"{"}
{"\n"} campaign: "black_friday_2026",
{"\n"} tier: "large"
{"\n"} {"}"}
{"\n"}{"}"});
{"\n"}{"\n"}// Later, when charge succeeds:
{"\n"}// charge.discount.coupon.id === "DISCOUNT_20_PERCENT"
{"\n"}// charge.promotion_code.id === "promo_BLACKFRIDAY2026"
That's it. Customers redeem the code at Payment Link checkout. Stripe enforces the rules (expiry, max uses, min amount). Your database is never touched. The discount data flows straight from Stripe to your analytics dashboard.
Campaign Tracking and Analytics
The real win is attribution. Every charge that uses a Promotion Code carries that code's ID in the metadata. Build a simple query to group charges by charge.discount.coupon.id, and you know exactly which campaign each customer bought into. For Velocity X, Black Friday, partner gifts, and affiliate codes are separate Promotion Codes, so the reporting is clean: X customers used BLACKFRIDAY, Y customers used PARTNER_GIFT_ALICE, Z customers used AFF_PROD001. No guessing. No pixel-tracking fallback.
Stripe's Dashboard also shows Coupon and Promotion Code analytics out of the box. Click on the code, see total redemptions, revenue generated, average order value, and time-series graphs. For a campaign that ran 48 hours, you can see hour-by-hour conversion and identify peak discount times. That data is worth its own ROI conversation with your manager.
Time-Limited Campaigns, Partner Gifts, and Affiliate Promo
Three common use cases: Time-limited campaigns (Black Friday: valid Dec 1–3, max 500 uses, -20%). Partner gift codes (give a partner 25 unique codes to hand out; each code one-time-use, -$500 flat). Affiliate codes (create a unique code per affiliate, track which affiliate drove which customer, reward them monthly based on redemptions).
For partner gifts, create one Promotion Code, set max uses to 25, max per customer to 1, and set the coupon to -$500 flat. Give the code to your partner. As long as they don't exceed 25 uses, Stripe enforces it. For affiliate codes, create one Promotion Code per affiliate (e.g., AFF_ALICE_JUN, AFF_BOB_JUN), each pointing to the same coupon (-15%) but tracked separately. Query redemptions by promotion code ID, and you know exactly which affiliates earned their commission that month.
One-Time Codes and Unique Per-Customer Redemption
If you want truly unique codes (one per customer, like affiliate unique keys), Stripe Coupons can't do it — they're templated. But you can work around it: create a Promotion Code, set max uses per customer to 1, and distribute the same code to many people. Each person redeems it once; Stripe prevents them from using it twice. This works great for "free trial upgrade" codes (every free-trial member gets the code, each can use it once, gets 50% off their first paid month).
For truly unique-per-person codes (e.g., a 32-character token), you'd need custom backend logic — but honestly, that's rare. For 95% of promo campaigns, "one-time use per customer + expiry date" covers it, and Stripe handles the enforcement.
Frequently Asked Questions
Can I attach multiple Promotion Codes to one Payment Link?
Yes. Pass an array of Promotion Code IDs to promotion_codes: [...], and customers see a dropdown or autocomplete field. Stripe applies the one they select. Useful if you want to run "Black Friday OR Cyber Monday" codes simultaneously, letting customers pick whichever gives them the better discount.
What if a customer enters an expired or exhausted code?
Stripe shows a validation error inline ("This code is expired" or "This code has reached its maximum uses"). The customer sees it immediately before entering payment info, so they don't get surprised after filling out their card. They can either remove the code or abandon checkout — no failed payment attempts.
Can I change a Promotion Code's rules mid-campaign?
Partially. You can't edit an existing Promotion Code, but you can deactivate it and create a new one with updated rules. The old code stops working immediately; new customers get the new code. For running campaigns, deactivation is cleaner than deletion because Stripe keeps the old code's redemption history, so you can always look back and see "BLACKFRIDAY2025 had 832 uses and drove $203K revenue."
How do I prevent a customer from stacking multiple codes?
Stripe allows only one discount (Promotion Code or Coupon) per charge. If a customer tries to add a second code, Stripe replaces the first. This is automatic — you don't configure it.
Do Promotion Codes work with Payment Links across currencies?
A Coupon applies its discount uniformly. If you define "-20%", that 20% comes off regardless of currency (AUD, USD, EUR). A fixed-amount coupon ("-$500") applies the exact amount, so use percentage coupons if you're running multi-currency campaigns. Percentage always works cross-border.
Can I track which promotion code a customer used after checkout?
Yes. Query the Charge object via API, and charge.promotion_code.id and charge.discount.coupon.id are populated. Store that in your database when you process the webhook, and you've got a permanent audit trail. For Velocity X, a simple Supabase trigger listening to the webhook ingests the promotion code ID into a sales table, so your dashboard queries can group by code.
The Bottom Line
Stripe Coupons and Promotion Codes move discount validation from "your problem" to "Stripe's problem," which is where it belongs. No custom backend logic. No race conditions. No double-redemption bugs. Create the code in the Dashboard, attach it to a Payment Link, and let Stripe enforce the rules. Campaign tracking is automatic — every charge carries the promotion code ID, so attribution is clean and queryable. Black Friday, partner gifts, affiliate promos — all simpler, all tracked, all auditable. Start with a Payment Link, then add a promo code when you're ready to run a campaign. Payment Links docs cover the foundation.