iOS SDK (Swift) / Products & paywalls

Products & paywalls

A paywall renders without this — using the prices someone typed into the dashboard. Those are a preview and an offline fallback, not what the store will charge.

Hand over the catalog

swift
import UpliftFunnel

// After your billing stack has offerings — and again on refresh.
// It REPLACES the catalog rather than merging.
UpliftFunnel.setProducts([
    UpliftFunnelProduct(
        id: "yearly_pro",                  // must match the plan's product_id
        price: "$59.99",                   // exactly as the store formatted it
        priceAmount: 59.99,                // lets per-month / per-week be derived
        currencyCode: "USD",
        period: .year,
        trialDays: 7,
        trialEligible: await store.isEligibleForIntroOffer("yearly_pro"),
        originalPrice: "$155.88",          // the struck-through one
        pricePerMonth: "$5.00",
        savings: "Save 61%"                // your words, your language
    ),
    UpliftFunnelProduct(id: "monthly_pro", price: "$12.99",
                        priceAmount: 12.99, period: .month)
])

The product

FieldTypeDescription
idStringStore product identifier. Must match the plan's product_id (or its platform override) as authored in the dashboard.
priceStringThe localized price string exactly as the store formats it ("₺899,99"). Injected as price.<id>.
priceAmountDouble?The numeric price. Enables derived per-period prices when the explicit overrides are absent.
currencyCodeString?ISO 4217. Informational — derived formatting borrows the symbol from `price`.
periodProductPeriod?.week / .month / .year / .lifetime. Injected as period.<id> and decides which derived prices make sense.
trialDaysInt?Free-trial length. Injected as trial_days.<id>.
trialEligibleBool?Whether THIS user can still take the intro offer — a per-user store check only your app can make. Flow conditions can branch on it.
originalPriceString?Pre-discount price. A plan card's struck-through price binds to it automatically.
pricePerMonth / pricePerWeekString?Explicit per-period strings; they win over derived values.
savingsString?The badge phrase, in your words and your language ("Save 44%", "%44 tasarruf"). Left out, the engine derives an English "Save NN%" from originalPrice and priceAmount, and shows nothing if it cannot.

What the flow can then write

A plan card bound to a product publishes {{product.*}} into everything beneath it, so the card's text never has to name which product it is. Every product also fans out into a flat <field>.<id> namespace at session start, for copy outside a card.

Authored copy
Start your {{product.trial}} trial
Then {{product.price}} / {{product.period}}
{{price.yearly_pro}} billed once a year

From RevenueCat

swift
let offerings = try await Purchases.shared.offerings()
guard let current = offerings.current else { return }

UpliftFunnel.setProducts(current.availablePackages.map { package in
    let p = package.storeProduct
    return UpliftFunnelProduct(
        id: p.productIdentifier,
        price: p.localizedPriceString,
        priceAmount: (p.price as NSDecimalNumber).doubleValue,
        currencyCode: p.currencyCode,
        period: p.subscriptionPeriod.flatMap(period),
        trialDays: p.introductoryDiscount?.subscriptionPeriod.days
    )
})

Adapty and raw StoreKit map the same way — the SDK only wants strings and a period. To get revenue back into your funnel numbers, connect the provider on the dashboard's Integrations page.

Charging

Drawing the plans is this page; taking the money is the purchase handler. Without one, a purchase button simply advances the flow.