Skip to main content

Promo Codes

The promo code system supports percentage and fixed-amount discounts, Stripe coupon synchronization, token-based invitations, and reseller bulk purchases.

Discount Types

TypeBehaviorExample
PERCENTAGEReduces price by percentageSAVE20 gives 20% off
FIXED_AMOUNTReduces price by flat amount (currency-specific)TEN_OFF gives $10 off

Validation Checks

When a user redeems a code, the backend runs 8 sequential checks before applying the discount:

#CheckError on failure
1Code existsnot found
2Code is active (isActive: true)deactivated
3Code not expired (expiresAt > now)expired
4Usage limit not reached (usageCount < usageLimit)limit reached
5User has not already redeemedalready redeemed
6Code applies to selected plan (applicablePlanIds)not applicable
7User is in allowed list (restrictedToUserIds / restrictedToCompanyIds)not authorized
8User holds invitation if requiresInvitation: trueinvitation required

Stripe Coupon Integration

When a promo code is created, the backend creates a matching Stripe coupon. On redemption, the coupon is applied to the Stripe checkout session so the discount appears on the invoice.

Invitation System

Codes with requiresInvitation: true can only be redeemed by invited users.

FieldDescription
invitedEmailRecipient email address
messageOptional personal message
tokenAuto-generated UUID for acceptance link
expiresAt7-day expiry from creation
statusPENDING / ACCEPTED / EXPIRED / DECLINED

The acceptPromoCodeInvitation mutation is public (no JWT required) so unauthenticated recipients can accept via email link.

Reseller / Bulk Purchases

Codes with isPersonal: true restrict usage to the owner. Combined with a high usageLimit, this supports reseller scenarios where a partner buys in bulk and distributes via invitations.

// Reseller code example
{
code: "PARTNER_2025",
discountType: "PERCENTAGE",
discountValue: 30,
isPersonal: true,
requiresInvitation: true,
usageLimit: 500,
campaignName: "Q1 Reseller Program"
}

Computed Fields

These fields are resolved automatically on every PromoCode query:

FieldTypeDescription
remainingUsesIntusageLimit - usageCount (null if unlimited)
isExpiredBooleanexpiresAt < now
canBeUsedBooleanActive, not expired, uses remaining

GraphQL Operations

Mutations

MutationAuthPurpose
createPromoCode(input)JWTCreate a new code with discount config
redeemPromoCode(input)JWTApply code at checkout (code + planId)
deactivatePromoCode(id)JWTSet isActive: false
inviteToPromoCode(input)JWTSend email invitation to redeem
acceptPromoCodeInvitation(token)PublicAccept invitation via token

Queries

QueryAuthPurpose
myPromoCodesJWTList codes owned by current user
promoCode(id)JWTSingle code with details
myPromoCodeStatisticsJWTUsage and revenue stats
mySentPromoCodeInvitationsJWTOutbound invitations
myReceivedPromoCodeInvitationsJWTInbound invitations

Common Patterns

// Percentage discount with usage cap
{ code: "SAVE20", discountType: "PERCENTAGE", discountValue: 20, usageLimit: 100 }

// Fixed discount with currency
{ code: "TEN_OFF", discountType: "FIXED_AMOUNT", discountValue: 10, currency: "usd", usageLimit: 50 }

// Invitation-only exclusive code
{ code: "EXCLUSIVE", discountType: "PERCENTAGE", discountValue: 30, requiresInvitation: true, isPersonal: true }

// Time-limited flash sale
{ code: "FLASH50", discountType: "PERCENTAGE", discountValue: 50, expiresAt: "2026-12-31T23:59:59Z", usageLimit: 10 }
warning

Deactivating a code (deactivatePromoCode) does not revoke already-applied Stripe coupons. Active subscriptions with the discount continue until renewal.