Implementing Promo Codes and Discounts in Mobile Apps
Promo codes in mobile applications are three different mechanisms with different implementation complexity: native App Store/Google Play promo codes, custom server-side logic with your own codes, and promotional offers through RevenueCat/Adapty. Mixing them without understanding the limitations is a typical mistake.
Native App Store promo codes
Apple allows generating up to 1000 promo codes per app version (for paid apps) or IAP. User enters the code in App Store → gets the product. This comes to the app as a regular transaction — handled by standard StoreKit flow.
Problem: App Store promo codes cannot be applied inside the app. User must be redirected to App Store. For most monetization scenarios (discount for a specific user, referral program) this doesn't work.
Custom promo codes — server-side implementation
Custom promo codes live entirely on the backend:
Data schema:
promo_codes: id, code, discount_type (percent|fixed|trial_days), value,
max_uses, used_count, expires_at, product_ids[], user_id (optional)
promo_redemptions: id, code_id, user_id, created_at, purchase_id
Client flow:
- User enters code → client calls
POST /api/promo/validatewith code and product_id - Server returns discount type and applicable price
- Client shows final price
- Purchase through native IAP at full price → on server after transaction verification we apply the discount (credit the difference with currency, extend trial, etc.)
Direct price change for IAP on the client is impossible — Apple and Google don't allow dynamically changing product cost. All discount logic is implemented post-purchase on the server or through separate products with already reduced price.
Promotional offers as discount mechanism
For subscriptions on iOS — SKPaymentDiscount / SubscriptionOffer in StoreKit 2. Create an offer in App Store Connect with the desired price, server generates a signature for a specific user. This is a legal way to offer a discount without bypassing native billing.
// Get offer from server
let offerSignature = await serverAPI.getPromoSignature(userID: user.id, offerID: "discount_50")
let product = try await Product.products(for: ["premium_monthly"]).first!
let purchaseOption = product.subscriptionOffer(
offerID: "discount_50",
keyID: offerSignature.keyID,
nonce: offerSignature.nonce,
signature: offerSignature.signature,
timestamp: offerSignature.timestamp
)
let result = try await product.purchase(options: [purchaseOption!])
Promo code input in UI
Promo code input field — separate screen or bottom sheet with debounce validation (request to server 300ms after last character). Important: show loading during validation and handle errors — "code expired", "code already used", "not applicable to selected product".
Estimated time — 2–3 days: server promo code model, validation and application API, UI component, integration with IAP flow.







