The tip is a Play Billing consumable that unlocks nothing
Context
An app built from this template is free: no ads, no paid tier, nothing locked. Some users want to pay anyway, and the developer would like to let them. The question is the route.
Play’s Payments policy §3.2 allows a peer-to-peer tip outside Play Billing when the whole amount reaches the creator and nothing unlocks. Enforcement has not agreed with the text: StreetComplete was rejected in February 2022 for in-app Patreon, Liberapay and GitHub Sponsors links, and for a link to its own home page because that page carried donation information. A Buy Me a Coffee link in the app this template was extracted from was flagged the same way. The first two apps built from the template each held the tip back for that reason, then shipped it through Play Billing once they were live (2026-10-11), with the same products, the same two surfaces and the same code. This ADR is that outcome, written down so the next app starts there.
Decision
The tip ships in the template, as four consumable one-time products sold through Google Play Billing, and it unlocks nothing.
- Products:
tip_small,tip_medium,tip_large,tip_xlarge, at 5, 10, 20 and 50 PLN in Poland and Play’s own conversions elsewhere, each with multi-quantity on — the nearest Play comes to an amount the user picks.TIP_PRODUCT_IDSintip/TipJar.ktmust match the Console exactly; a product missing there is simply not offered. - Every purchase is consumed, on every foreground. Consuming is what lets the same tip be given
twice, and it is also the acknowledgement Play requires within three days, after which it refunds.
MainApplicationsettles owned purchases whenever the app comes to the foreground, so a payment that clears while the app is closed, or one caught by a process death, is consumed the next time the user opens it. There is no entitlement, so nothing about a purchase is ever stored. TipJarlives inAppContainer, not aViewModel, because a purchase can finish after the screen that opened Play’s sheet is gone.- Two surfaces. A section on Help and feedback, present only where Play returns products — no
Play, the debug
applicationIdor products not yet created all leave it absent rather than broken. And one card on the home screen, once per install, to someone who has had the app 30 days and recorded ten items (tip/TipAsk.kt); either button answers it for good on that install. The answer is a preference, andAppBackupAgentbacks up only the database and the thumbnails (ADR-0005), so a user who moves phones is asked once more, a month after the move. - Prices come from Play only. The UI shows
formattedPrice; a literal “5 zł” would be wrong everywhere outside Poland. - The wording is “tip the developer”, never “donate” or “support the project”: it is a payment to a person, and it buys nothing.
INTERNETstays out. The billing library depends on Google’sdatatransport, which mergesINTERNETfor its own logging. The manifest removes it withtools:node="remove", andscripts/aab-permissions.pykeeps it forbidden. Billing never needed it: a purchase is IPC to the Play Store app, which does the networking in its own process.
Alternatives
- A personal payment link (§3.2). Free of Google’s cut and of any dependency, and the policy text permits it. Lost on enforcement: rejection reaches links the reviewer reads as external payment, including pages the listing links to, and the cost lands on a closed test or a release.
- No tip at all. Costs nothing and risks nothing, and leaves generous users with no way to pay.
Each app can still choose it —
optional-modules.mdlists what to delete. - A tier, a badge or a thank-you unlock. Rejected on principle: every feature ships to everyone. It would also turn the tip into a product with an entitlement to restore across devices, which is the machinery this decision avoids.
- Subscriptions. A recurring charge for nothing is not a tip.
Consequences
- Google takes its cut — 15% on the reduced tier, which the developer account enrols in once.
- The listing shows Contains in-app purchases once the products exist, on an app whose pitch is that nothing is for sale. The tip’s own wording is the answer: it unlocks nothing.
- The content rating moves in Brazil. IARC’s does the app allow users to purchase digital goods?
is now Yes, and ClassInd rates any such app 14+, tip or not. Every other authority stayed at
its lowest band for both apps that re-rated (
play-app-content.md). - Products cannot be created before the code ships. Play Console refuses a one-time product until
a bundle carrying
com.android.vending.BILLINGis on a track, so the first build with the tip goes to internal testing with no products, and shows no tip, by design. - One more dependency and one more permission: the billing library,
lifecycle-process, andBILLINGin the artifact. The licences screen links Google’s terms for the Play services artifacts billing brings in; their text is not ours to ship.
Amendment — 2026-10-31
Darek has no Items to count, so the ask’s “ten items” half is gone: the card shows once an install
is 30 days old and the ask has not been answered on that install (tip/TipAsk.kt). It lives on
Darek’s Home screen. The answer is stored as the PackageInfo.firstInstallTime it was given under and
judged at the read: it counts only while that equals the current install’s. With the custom
BackupAgent gone, Auto Backup carries the preference to the user’s next phone, whose install time
differs, so the new install is asked once more, a month after the move, as before.
Amendment — 2026-11-01
INTERNET stays out holds until the phase that builds fuchy, which declares INTERNET for Darek
itself (ADR-0008). From then the manifest no
longer removes it, and datatransport’s log upload is held off by ADR-0008’s rule instead: its
CctBackendFactory backend is removed, so it has nothing to upload through. Billing still needs no
network of its own.