The library
Gating paid features
Sell the whole app with Paywall.gate, or some features with Paywall.start and one check per feature. This page lists every public call and constant in the library, and how trials work.
Whole app or some features
Paywall.gate(views) | Paywall.start(views) | |
|---|---|---|
| Use it for | An app paid for as a whole | An app that sells some features |
| At launch, locked | The buy screen in your app's place | Your app, as usual |
| During a trial | Your app, as usual | Your app, as usual |
| When the trial ends | The buy screen at each launch, titled "Trial over" | The buy screen once, titled "Trial over" |
| BACK on the buy screen | Leaves the app | Goes to your app |
| Once paid | BACK or START opens your views | BACK or START goes to your app |
| Each paid feature | Nothing to add | Checks Paywall.unlocked() first |
Both take the same array you'd return from getInitialView(): a view, or a view and its delegate.
Gating a feature
With start, check where each paid feature begins: a button, a menu item, a page.
function onHistory() as Void {
if (Paywall.unlocked()) {
showHistory();
} else {
Paywall.open();
}
}- One lock opens everything. One purchase opens every paid feature in the app. There is one price per app.
- In a glance, call only
active(),unlocked()andtrialLeft(). The buy screen is app-only. - Thank-you touches that should show only to buyers, not during a trial, check
Paywall.active().
Reference
Everything here lives in module Paywall (Pay.mc and the PayScope.mc you use), unless the table says otherwise.
Calls you make
| Call | Returns | What it does | Works in |
|---|---|---|---|
Paywall.gate(views) | The views to return | In getInitialView(), for an app paid for as a whole. Starts the trial, asks the server when it's time, and while locked returns the buy screen in your app's place. Once paid, BACK or START opens your views. | App |
Paywall.start(views) | The views to return | In getInitialView(), for an app that sells some features. Starts the trial, asks the server when it's time, and returns your views. Shows the buy screen once when the trial ends. | App |
Paywall.unlocked() | Boolean | Paid, or in the trial (all of it, before the trial starts). The check for every paid feature. | App, glance |
Paywall.active() | Boolean | Paid, and confirmed by the server within the last 30 days. False during a trial. A "paid" stamp more than 5 minutes in the future (a clock set back) doesn't count. | App, glance |
Paywall.trialLeft() | Number | Whole days of trial left, rounded up. Never more than TRIAL_DAYS. All of TRIAL_DAYS before the trial starts; 0 once it's over, or with no trial. | App, glance |
Paywall.open() | PaywallView | Pushes the buy screen, or returns the one already showing. For a paid feature's else, and a "Premium" item in your settings. | App |
Paywall.keys() | Array | The storage keys your "Delete data" must keep: "pp", "ts" and "te". | App |
Paywall.startTrial() | Void | Older call. Starts the time trial at first launch; nothing without TRIAL_DAYS. gate and start call it. | App |
Paywall.launch() | Void | Older call. Asks the server when its time has come, or at once if the watch's clock has gone back. gate and start call it. | App |
Paywall.showOver() | Void | Older call, from your first screen's onShow(). From then on, a purchase made away from the watch opens the unlock by itself; with a trial, it opens the buy screen once when the trial ends. gate and start do this for you. | App |
Called by the library itself
The library calls these itself. Don't call them from your code.
| Function | What it does |
|---|---|
check() | Sends one ask to the server, never two at once. An ask with no answer for a minute stops counting. |
onAnswer(code, data) | Handles the server's reply. Keeps only a whole answer, one with a version and a next check. On a failure, sets "Offline" or "Try later" and the wait before the next try. |
take(d) | Stores an answer, the trial's end and "paid". Opens the unlock when a purchase lands. |
first(views) | The shared part of gate and start. |
close() | Leaves the buy screen: to your views once paid (or after start's trial-end screen), out of a locked gate app, or back to where open() was called. |
cheer() | The watch's success tone and a buzz, if the watch has them on. |
Constants
| Constant | Value | What it is |
|---|---|---|
K_ANS | "pa" | Storage key: the server's last answer. |
K_PAID | "pp" | Storage key: when the server last said "paid", in seconds. None: not paid. |
K_TRIAL | "ts" | Storage key: when the trial ends. Set at first launch, then by the server. |
K_OVER | "te" | Storage key: the "Trial over" screen has shown. |
STALE | 2592000 | 30 days in seconds: how long "paid" counts without a new answer from the server. |
DAY | 86400 | A day in seconds. |
Module variables
The buy screen's own state. Read them if you need to; never set them.
| Variable | What it holds |
|---|---|
listener | The open buy screen, or null. |
status | The problem line ("Offline" or "Try later"), or null. |
busy, sentAt | An ask is out, and when it went. |
hold, held | How long the server may hold an ask while the buy screen shows (15 seconds at first, halved if the phone cuts it short). |
changedAt, limit | When the screen last changed, and how many seconds it may stay open without a change. |
fails, nextAt | Asks failed in a row, and when the next may go. |
shown | Your app's first screen is up: a purchase may open the unlock by itself. |
quick | The buy screen has opened: its first ask comes back at once. |
app, whole | Your views while the buy screen stands in for them, and whether gate was used. |
Classes
| Class | What it is |
|---|---|
PaywallView | The buy screen. It draws the server's screen and plays the unlock. |
PaywallDelegate | The buy screen's buttons: START and BACK. |
PayReply | The web request's callback object. |
PayBg | Faces and data fields only: the background service that asks the server. |
PaywallConfig
| Constant | What it is |
|---|---|
URL | Required. Your app's address, from your dashboard. |
TRIAL_DAYS | Apps: optional, leave it out for no trial. Faces and data fields: required, 0 for no trial. |
Watch faces and data fields
Faces and data fields use lib/watch/face, whose module Paywall has its own calls. active(), unlocked() and trialLeft() work as above.
| Call | What it does |
|---|---|
Paywall.startTrial() | Every start, from getInitialView(): starts the trial the first time, and lets a finished sign-in page wake the background. |
Paywall.schedule(false) | Every start, from getInitialView(): books the next background check. |
Paywall.take(d) | From onBackgroundData(d): stores the background's answer and books the next check. |
Paywall.soon() | From onSettingsChanged(): a check as soon as Garmin allows, and the sign-in notification again while locked. |
Paywall.drawQr(dc, x, y, side) | Draws the QR code side wide on a white square, centered at x, y. Nothing before the first answer. |
Paywall.book(n) | Called by the library: books a background check, never sooner than 5 minutes after the last. |
K_SIGN | "pg": storage key for the sign-in notification (1 to send, 2 sent). |
The whole setup is on Watch faces and data fields.
Trials
- Set
TRIAL_DAYSinPaywallConfig, the same as your dashboard's free trial. Leave it out for no trial: then "Trial over" never shows. - The trial starts at first launch.
Paywall.unlocked()is true until it ends. - Reinstalling never restarts it. Our server counts the trial from the first time it saw that watch, and its count replaces the watch's at the first answer.
- A clock set back can't stretch it. The server sends time left, not a date, and the watch never counts more days than
TRIAL_DAYS. - The buy screen's title shows "7 days left", then "Last day", then "Trial over".
A trial your app counts
For a trial by use instead of days, leave TRIAL_DAYS out, keep your own counter in storage (and through "Delete data"), and check active():
// 20 free sessions, then Premium
if (Paywall.active() || sessionsUsed < 20) {
startSession();
} else {
Paywall.open();
}What the library stores
- Apps:
"pa"(the answer),"pp"(when "paid" was last confirmed),"ts"(the trial's end) and"te"("Trial over" shown). - Faces and data fields:
"pa","pp","ts"and"pg", plus theunlockproperty for the settings link. - Use other names for your own keys.