The library
Add the library
The library draws every payment screen for you. You add its folders, one permission, one small config file and one line of code.
The files
Download the library from your app's Install tab. It unzips to a wearcheckout folder.
| Path | What it does |
|---|---|
lib/ | Apps: the check with our server, the buy screen drawn from the server's answer, its time limit, and the unlock with its tone and buzz. |
lib/ | active(), unlocked() and trialLeft(), annotated (:glance): for apps without a background service. |
lib/ | The same, annotated (:glance, :background): for apps with a background service. Use exactly one of the two. |
lib/, PayFaceScope.mc | Watch faces and data fields: the background check, drawQr and the settings link. See Watch faces and data fields. |
lib/ | Only if you sell Reviews and replies or use a storefront: the review prompt, your replies and the "Bought it?" screen. See Extras in your app. Watches older than Connect IQ 3.1 skip it. |
lib/ | Only if you sell Export to phone: your app's data as a spreadsheet, encrypted on the watch, with its link sent to the buyer's phone. |
lib/ | Only if your app uses data feeds: weather, sun and moon, prayer times, tides and more. See Data feeds. |
lib/ | The 4 lines the buy screen shows without the server, in English. |
lib/ | The same 4 lines in the 7 other languages. |
Add it to your project
Unzip the library next to your app's folder:
IntervalTimer/
manifest.xml
monkey.jungle
resources/
source/
PaywallConfig.mc
wearcheckout/
lib/watch/...Then add its folders to your jungle:
base.sourcePath = source;../wearcheckout/lib/watch/app;../wearcheckout/lib/watch/glance
base.resourcePath = resources;../wearcheckout/lib/watch/resources- Relative paths only. A space in an absolute path breaks the jungle.
- Or copy the files. Copied into your own
sourceandresourcesfolders (and eachresources-<lang>folder), the library needs no jungle line. - No annotations to set. One build serves every watch. The Instinct's subscreen is found at run time.
With a background service
If your app has its own background service, use glance-bg in place of glance, annotate PaywallConfig (:glance, :background), and add the Background permission. Any (:background) code makes the compiler ask for that permission, which is why there are two versions.
base.sourcePath = source;../wearcheckout/lib/watch/app;../wearcheckout/lib/watch/glance-bgAllow the network
The watch asks our server over the phone's connection, or over Wi-Fi on watches that have it. Add the Communications permission:
<iq:permissions>
<iq:uses-permission id="Communications"/>
</iq:permissions>PaywallConfig.mc
Download it from your app's Install tab: it has your app's real address. The address is public, not a secret.
// PaywallConfig.mc: from your dashboard (Interval Timer). Public, not a secret.
import Toybox.Lang;
(:glance)
module PaywallConfig {
const URL = "https://pay.example/api/K7Q2/c"; // your app's address
const TRIAL_DAYS = 7;
}URL: your app's address. Every check goes there.TRIAL_DAYS: leave it out for no trial. Keep it the same as the trial in your dashboard: the watch never counts more days thanTRIAL_DAYS.- The annotation matches the
PayScope.mcyou use:(:glance), or(:glance, :background)withglance-bg.
The one line
In getInitialView(), return your views through the library:
return Paywall.gate([new MainView(), new MainDelegate()]); // the whole app is paid for
return Paywall.start([new MainView(), new MainDelegate()]); // or: only some features aregatestarts the trial, asks the server when it's time, and while locked shows the buy screen in your app's place. BACK leaves the app. Once paid, BACK or START opens your own views.startopens your app as usual, with the buy screen once when the trial ends. Each paid feature then checks first:
if (Paywall.unlocked()) { showHistory(); } else { Paywall.open(); }With either one, a purchase made on the phone while the buy screen is closed opens the unlock by itself when the watch hears of it. Gating paid features lists every call.
A Premium item in your settings
A settings menu's "Premium" item opens the buy screen. If one is showing already, you get that one: there's never a second.
Paywall.open();Delete data
If your app has a "Delete data" option, keep the keys Paywall.keys() returns. The rest comes back from the server at the next start.
// Delete data: everything except the payment keys
var st = Application.Storage;
var keep = Paywall.keys();
var saved = [];
for (var i = 0; i < keep.size(); i++) {
saved.add(st.getValue(keep[i]));
}
st.clearValues();
for (var j = 0; j < keep.size(); j++) {
if (saved[j] != null) {
st.setValue(keep[j], saved[j]);
}
}Extras in your app
Three of the paid extras need their own folder, so apps without them stay small. Watch statistics needs none.
Reviews and replies, and "Bought it?"
Add the extras folder, then turn it on before the one line:
base.sourcePath = source;../wearcheckout/lib/watch/app;../wearcheckout/lib/watch/glance;../wearcheckout/lib/watch/extrasPayExtras.on();
return Paywall.gate([new MainView(), new MainDelegate()]);- The review prompt shows on day 3, 7 or 14: the stars, then a QR code that opens your store page and a note to you.
- Your reply shows once over your app. On small watches a long reply takes two screens: START shows the rest.
- "Bought it?" shows a new watch's code once when its buyer paid on your storefront page first.
- Without the folder, that app's watches get no prompt, your replies wait on the server, and a storefront buyer scans the normal buy screen. Watches older than Connect IQ 3.1 skip the folder too.
Export to phone
Add the export folder, offer the export while PayExport.ready() is true, and start it with a method that returns one row at a time:
base.sourcePath = source;../wearcheckout/lib/watch/app;../wearcheckout/lib/watch/glance;../wearcheckout/lib/watch/export// Row 0 is the header; rows 1 to n are your data.
function row(i as Number) as Array {
if (i == 0) { return ["Date", "Minutes"]; }
var s = sessions[i - 1];
return [s[:date], s[:minutes]];
}
if (PayExport.ready()) {
PayExport.start(method(:row), sessions.size());
}- Values can be text, numbers, true or false, or
null. - The library does the rest: it sends the encrypted parts, then sends the link to the buyer's phone as a Garmin Connect notification. The buyer taps it, and their phone opens the spreadsheet. START sends the link again. The link needs the buyer's phone connected to the watch.
- Formulas never run. Text starting with
=,+,-or@gets a'first.
Data feeds
Add the feeds folder, then ask for a feed where your app needs it. Your method gets the HTTP code and the answer:
base.sourcePath = source;../wearcheckout/lib/watch/app;../wearcheckout/lib/watch/glance;../wearcheckout/lib/watch/feedsPayFeeds.get("weather", {
"lat" => lat, "lon" => lon,
"tz" => System.getClockTime().timeZoneOffset
}, method(:onWeather));
function onWeather(code as Number, data as Dictionary or Null) as Void {
if (code == 200) {
temp = data["now"]["temp"];
WatchUi.requestUpdate();
}
}- The feeds: sun, moon, prayer times, world clock, exchange rates, weather, weather alerts (US), earthquakes, aurora, tides (US) and public holidays. Each one's parameters and fields are on the dashboard's Data feeds page, where you can try them.
- Kept on the watch. Each answer stays good for its
ttlin seconds. Ask again sooner, for the same feed and nearly the same place, and the kept answer comes back at once without asking. - Places are degrees. Times come as Unix seconds, units metric.
- Show the source's line where the feed's card asks for one, for example "Weather: MET Norway".
- Other codes bring
null: 403 means Extras are off for this app, 429 or 503 means try again later, a negative code is Garmin's (no phone, no network). Test watches have every feed free. - Cost: $2 a million requests from live watches, on your monthly bill. An answer kept on the watch costs nothing.
Watch statistics
No code: the watch's model comes with every check. To see your app's versions too, add a version to PaywallConfig:
const VERSION = "1.3";Languages
The library speaks English, German, French, Spanish, Portuguese, Korean, and Chinese (Simplified and Traditional). The watch's own language picks. Any other language gets English.
- The screens come from our server in the watch's language, so a wording fix needs no app update.
- Only 4 lines live on the watch, for when there's no answer from the server yet.
| String | English | When it shows |
|---|---|---|
PwWait | Loading… | Before the first answer |
PwPhone | Offline | No phone, Bluetooth or internet |
PwBusy | Try later | The server didn't answer properly |
PwBack | BACK to close | With no answer to show at all |
Add one jungle line per language. Keep your own folder first; leave that part out for a language your app has no folder for.
base.lang.deu = resources-deu;../wearcheckout/lib/watch/resources-deu
base.lang.fre = resources-fre;../wearcheckout/lib/watch/resources-fre
base.lang.spa = resources-spa;../wearcheckout/lib/watch/resources-spa
base.lang.por = resources-por;../wearcheckout/lib/watch/resources-por
base.lang.kor = resources-kor;../wearcheckout/lib/watch/resources-kor
base.lang.zhs = resources-zhs;../wearcheckout/lib/watch/resources-zhs
base.lang.zht = resources-zht;../wearcheckout/lib/watch/resources-zhtThings to check
- Your app's name string. With no answer to show, the buy screen shows your
AppNamestring. Keep that string in your resources. - Timers. The buy screen runs one timer, and
PayExtrasone more. Watches allow only a few, and "Too Many Timers" ends the app: if yours already runs two or more, check you stay within the limit. - Button holds. Holding a button too long opens the watch's own menu. The buy screen reacts to presses only.
- Older calls still work.
Paywall.startTrial(); Paywall.launch();ingetInitialView(), andPaywall.showOver()in your first screen'sonShow(), do whatgateandstartdo in one line.