Purchases and prices
Pass prices to the SDK by package ID, run your own checkout, and report purchases that finish elsewhere.
Miniwall renders; it does not charge. Prices and payment come from your site, matched to the screen by package ID. The SDK never sees card data or amounts you charge.
Package IDs
Each package card in the editor has an ID, such as monthly or annual (see Packages and pricing). Your site gives each ID a price. getPackageIds(paywallJson) lists the IDs a screen uses. The Install SDK page also lists them.
const paywall = await UIKit.loadPaywall({ placement: 'upgrade_button' });
UIKit.getPackageIds(paywall); // ['annual', 'monthly']If a screen uses an ID with no price, the SDK logs [UIKit] No price for package(s) ... Pass them in products and that card shows without a price.
products
interface ProductInfo {
price: number; // major units: 9.99, not 999
currency: string; // 'USD'
period?: Period; // billing period
title?: string; // shown by {{ product.store_product_name }}
offer?: { price: number; period: Period }; // intro or trial offer; a free trial is price 0
}
type Period = 'day' | 'week' | 'month' | 'year' | 'lifetime' | { unit: 'day' | 'week' | 'month' | 'year'; count: number };products: {
monthly: { price: 9.99, currency: 'USD', period: 'month' },
annual: { price: 59.99, currency: 'USD', period: 'year', offer: { price: 0, period: { unit: 'day', count: 7 } } },
lifetime: { price: 199, currency: 'USD', period: 'lifetime' },
}Text variables the SDK fills
Screen text can use these (the editor lists them under Price and plan; see Variables). Prices are formatted for the screen's language.
| Variable | Value |
|---|---|
product.price | $9.99 |
product.currency_code, product.currency_symbol | USD, $ |
product.period | month, 2 weeks, lifetime |
product.period_abbreviated | mo, 2 wk |
product.period_with_unit | 1 month, 2 weeks |
product.period_in_days, _weeks, _months, _years | Rounded counts (years round down) |
product.periodly | monthly, every 2 weeks |
product.price_per_day, _week, _month, _year | The price converted to that unit |
product.price_per_period | $9.99/month |
product.price_per_period_abbreviated | $9.99/mo |
product.store_product_name | title |
product.relative_discount | 33%. Compares each recurring package's monthly price with the most expensive one. Only for cheaper packages, and only when at least two recurring packages exist. |
product.offer_price, product.offer_period and the other offer_ forms | From offer |
custom.NAME | From customVariables |
Period variables need a period. price_per_* is not set for lifetime. Use variablesPerPackage to override any value as text, per package. buildProductVariables(products, locale?) returns the computed values if you want to inspect them.
Stripe prices
productsFromStripe turns Stripe Price objects, fetched by your server, into products. It handles zero-decimal currencies and skips prices with no unit_amount.
// server
const prices = {
monthly: await stripe.prices.retrieve('price_...', { expand: ['product'] }),
annual: await stripe.prices.retrieve('price_...', { expand: ['product'] }),
};
res.json(productsFromStripe(prices));Checkout
Run it in onPurchase. See Callbacks. Redirecting away is fine, see below.
reportPurchase
If checkout leaves the page (for example Stripe Checkout), the visitor lands on your success page and the SDK has not heard of the purchase. Call reportPurchase there:
function reportPurchase(details?: { packageId?: string; paywallId?: string }): void;UIKit.configure({ apiKey: 'pk_...' });
UIKit.reportPurchase({ packageId: 'annual' });- It credits the screen where the visitor last tapped buy (remembered in
localStorageasuikit:pending-purchase). - Without a recent buy tap, it credits the last placement the visitor met (shown or held out) in the last 30 days. If they were held out it records a holdout purchase.
- Pass
paywallIdto name the screen yourself. - It sends at once. It does nothing when analytics are off or there is nothing to credit.
- With a holdout, call it after every purchase so the groups compare fairly.