Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Taking your users' money - In-app billing from start to best practices

Taking your users' money - In-app billing from start to best practices

So, you’ve built an app. It'd be nice to make some money with it and instead of only charging a one-off purchase price you’re maybe considering a free app with extra features. Or you might think about a subscription model to create an ongoing cashflow by selling a service.

Depending on your actual occupation in the Android-ecosystem you might never had the need to look into monetisation of an app. In a nutshell, it’s a Google Play service - implementation should be smooth and easy and super-straight-forward, right? RIGHT?

This talk was given at Android Makers 2019
https://androidmakers.fr/schedule/2019-04-24?sessionId=ZNT-7569

Kai Koenig

April 24, 2019
Tweet

More Decks by Kai Koenig

Other Decks in Programming

Transcript

  1. HELLO HELLO, MY NAME IS KAI Software Engineer in the

    Web and Android space. Work: Android & Java, Kotlin, JS, CFML, Flutter Non-work: Aviation & flying small aircraft, Nintendo video games, Chickens, Linux Twitter: @AgentK He/Him
  2. AGENDA ▸ Mobile payment methods with Google ▸ Google Play

    In-app Billing API v3 ▸ Google Play Billing Library ▸ Products and subscriptions ▸ Integration concerns ▸ Testing and 3rd-party libraries OVERVIEW
  3. BUT THERE’S MORE ▸ In-app billing is a “niche” API

    in that ▸ not many apps use it; ▸ even in a larger team, there might be only one developer working on in-app billing. ▸ In-app billing has some documentation and functional issues. ▸ Building and integrating a solution with existing systems can be surprisingly quirky. WHY THIS TALK?
  4. HISTORY ▸ Google Wallet (2011) ▸ US-based P2P payment service,

    later with optional physical card ▸ Since 2013 also integrated with Gmail & iOS support ▸ Card/NFC discontinued in 2016 ▸ Now: Google Pay Send ▸ Android Pay (2015) ▸ NFC-based wallet app, Android only MOBILE PAYMENTS WITH GOOGLE
  5. GOOGLE PAY ▸ Launched in 2018 ▸ Unification of Google

    Wallet and Android Pay ▸ Android app based on NFC to pay in stores through associated cards ▸ Boarding passes and event tickets were added later 2018 ▸ APIs for web and Android ▸ Can only be used for physical goods and services MOBILE PAYMENTS WITH GOOGLE
  6. NON-PHYSICAL GOODS IN APPS ▸ Google Play in-app billing MOBILE

    PAYMENTS WITH GOOGLE (from https://developers.google.com/pay/api/faq)
  7. OVERVIEW ▸ Service that lets you sell digital content on

    Android: ▸ Access to features in an app ▸ Access to virtual products or virtual currency ▸ One-off purchases (consumable and non-consumable) ▸ Recurring subscriptions ▸ Can be accessed via IPC/AIDL or Google Play Billing library (GPBL) GOOGLE PLAY BILLING
  8. Client Server APP Google Play Google Play Server G P

    B L Backend application Google Play Developer API
  9. FROM 30,000 FEET (I) ▸ In your app: Google Play

    Billing library ▸ Communicates with Google Play Store app and Play Store infrastructure ▸ Fully asynchronous ▸ Retrieve products, subscriptions, prices etc ▸ Check previous purchases ▸ Buy and consume products and start subscriptions ▸ Centred around BillingClient and PurchasesUpdatedListener GOOGLE PLAY BILLING
  10. FROM 30,000 FEET (II) ▸ On the back end: Google

    Play Developer API ▸ Implement an integration between your server and the REST API ▸ Core features: ▸ Subscriptions und Google Play Billing API: Verify product purchases or subscriptions ▸ Real-time notifications: Notify back end when customers change or cancel subscriptions GOOGLE PLAY BILLING
  11. SETUP GOOGLE PLAY BILLING dependencies { ... implementation 'com.android.billingclient:billing:1.2.2' }

    private BillingClient billingClient; billingClient = BillingClient .newBuilder(context) .setListener(this) .build(); <uses-permission android:name="com.android.vending.BILLING" />
  12. CONNECTION GOOGLE PLAY BILLING billingClient.startConnection(new BillingClientStateListener() { @Override public void

    onBillingSetupFinished(@BillingResponse int billingResponseCode) { if (billingResponseCode == BillingResponse.OK) { // Ready to go } } @Override public void onBillingServiceDisconnected() { // We lost connection } });
  13. GOTCHAS ▸ The this-reference in .setListener(this) in the setup process

    points to the PurchasesUpdatedListener interface. ▸ Make sure you override onBillingServiceDisconnected and do something in there - otherwise you can end up with weird errors (often while Google Play or Play Services get updated). GOOGLE PLAY BILLING
  14. GENERAL ▸ All in-app purchase options are managed in the

    Google Play Console: ▸ Title and Description ▸ Product ID (in GPBL: SKU - Stock Keeping Unit) ▸ Price and Default Price ▸ 1-to-many relationship between app and in-app purchase options ▸ Watch out for complexities around international pricing or sets of apps with similar/identical prices. PRODUCTS AND SUBSCRIPTIONS
  15. PRODUCTS ▸ One-time product: ▸ Single, non-recurring charge to a

    payment method ▸ “Managed product” in Google Play Console ▸ Rewarded product: ▸ New since GPBL 1.2.1 (mid-March 2019) ▸ In-app product requiring user to watch a video ad (->AdMob) ▸ Type: INAPP in Google Play Billing library PRODUCTS AND SUBSCRIPTIONS
  16. SUBSCRIPTIONS ▸ In-app product with recurring billing: ▸ Weekly ▸

    Monthly (1, 3 and 6 months) ▸ Annually ▸ Free trial and introduction pricing ▸ Since GPBL 1.2 (October 2018) support for a pricing change flow ▸ Type: SUBS in Google Play Billing library PRODUCTS AND SUBSCRIPTIONS
  17. GETTING A LIST OF PRODUCTS PRODUCTS AND SUBSCRIPTIONS List<String> skuList

    = new ArrayList<> (); skuList.add("SKU1"); skuList.add("SKU2"); SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder(); params.setSkusList(skuList) .setType(SkuType.INAPP); billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() { @Override public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) { // Do something with the result } });
  18. ANYTHING WEIRD HERE? ▸ We had to specify a list

    of SKUs. ▸ We had to specify what product type we want to query. ▸ There is NO way to get a list of all your products or products across multiple types. Your app needs to know the SKU identifiers. ▸ Where and how to store them? ▸ What are the implication on your system’s resilience? PRODUCTS AND SUBSCRIPTIONS
  19. THE SKUDETAILS OBJECT PRODUCTS AND SUBSCRIPTIONS getDescription() getPrice() €3.99 getPriceAmountMicros()

    3990000 getPriceCurrencyCode() ISO4217 getSKU() getTitle() getType() INAPP / SUBS getIntroductoryPrice() getIntroductoryPriceCycles() getIntroductoryPricePeriod() ISO8601 getFreeTrialPeriod() ISO8601 getSubscriptionPeriod() ISO8601 isRewarded()
  20. PURCHASE FLOW (I) ▸ GBPL uses the Google Play app

    to drive the purchase flow. ▸ Depending on version of Android, Google Play app and Play Services, certain feature might not be available. ▸ BillingClient.isFeatureSupported() PRODUCTS AND SUBSCRIPTIONS
  21. PURCHASE FLOW (II) PRODUCTS AND SUBSCRIPTIONS BillingFlowParams flowParams = BillingFlowParams.newBuilder()

    .setSkuDetails(skuDetails) .build(); int responseCode = billingClient.launchBillingFlow(flowParams); ▸ Response gets delivered to onPurchasesUpdated() in the PurchasesUpdatedListener.
  22. GENERAL ▸ So far: basic flow throw a purchase/subscription scenario.

    ▸ What about further backend integration with other features and APIs? ▸ Also: Commonly apps integrate with their own backend systems that need to get updated with product and subscription information. INTEGRATION CONCERNS
  23. VERIFICATION OF A PURCHASE ▸ When a purchase/subscription was successful,

    you get a purchase token. ▸ Google Play Developer API: ▸ Pass token and customer ID to your own backend ▸ From there, call out to REST API and make sure the token is valid and a purchase/subscription exists. ▸ Inside of the app: ▸ Check signature of original JSON payload in onPurchasesUpdated() INTEGRATION CONCERNS
  24. UPDATE YOUR OWN RECORDS ▸ Use case: ▸ App has

    its own member/customer management backend which tracks purchases from other sources as well. ▸ Needs to be kept in sync with in-app purchases. ▸ Similar to verification, call out to own backend. ▸ Additionally make sure to track the order ID for potential refunds. INTEGRATION CONCERNS
  25. CONSUMPTION AND RESTORING PURCHASES (I) ▸ A product can usually

    only be purchased once per Google Play account. ▸ Unfortunately there are use cases where this concept doesn’t work. ▸ First approach: ▸ Consume purchase with BillingClient.consumeAsync() ▸ Callback via onConsumeResponse() in ConsumeResponseListener INTEGRATION CONCERNS
  26. CONSUMPTION AND RESTORING PURCHASES (II) ▸ Problem: ▸ Products with

    a successful consumeAsync() callback sometimes still show up in BillingClient.queryPurchases() & .queryPurchaseHistoryAsync() ▸ Edge cases around caching on the API side ▸ Edge cases with apps ANRing or crashing and calls not being finished ▸ Real solution (again): ▸ Use the server side API to check if a product really has been consumed. INTEGRATION CONCERNS
  27. SUBSCRIPTION CANCELLATIONS ▸ Use case: ▸ Customers can cancel subscriptions

    in the Google Play app. ▸ Needs to be communicated to app to restrict access to features. ▸ BillingClient.queryPurchaseHistoryAsync() will work initially: ▸ From first renewal, you won’t get the renewal date stamp, but only the original subscription date. ▸ Better use Google Play Developer API real-time notifications from the start. INTEGRATION CONCERNS
  28. REAL-TIME NOTIFICATIONS ▸ RT Notifications provide real-time information on state

    changes of subscriptions. INTEGRATION CONCERNS GCP Pub/Sub Backend application Google Play Developer API
  29. PRICING CHANGES ▸ Up to version 1.1, Google Play Billing

    library didn’t support pricing changes. ▸ Solution: code SKUs in parseable ways and re-create the product with a new SKU INTEGRATION CONCERNS gold_1m_v_1 gold_1m_v_2 gold_1m_v_3 … ▸ GPBL 1.2+ has a UI flow for pricing changes: BillingClient.launchPriceChangeConfirmationFlow()
  30. TESTING ▸ Testing has changed over time: ▸ Unpublished/local build

    with draft Google Play products ▸ Unpublished/local build with static product SKUs to trigger responses ▸ Published build with actual Google Play products (closed testing track) ▸ Fiddling with user accounts ▸ Only physical device ▸ “Licensed” testers TESTING AND 3RD PARTY LIBRARIES
  31. GOTCHAS ▸ The APK you uploaded needs to the one

    you’re testing with when it comes to version code, version name and keystore signature. ▸ You can not use your Play Console developer account for testing on the device. TESTING AND 3RD PARTY LIBRARIES
  32. REGISTER (I) ▸ Register is an Android library for easier

    testing of Google Play's In-app Billing. ▸ Register comes with a companion app that acts as a mock billing server. TESTING AND 3RD PARTY LIBRARIES if (shouldUseTest) { googleServiceProvider = new GoogleServiceProviderTesting(); } else { googleServiceProvider = new GoogleServiceProviderImpl(); }
  33. REGISTER (II) TESTING AND 3RD PARTY LIBRARIES ▸ Great approach

    - doesn’t properly support GPBL yet though. ▸ There’s been recent activity towards a 1.0.0 release.
  34. BILLINGX TESTING AND 3RD PARTY LIBRARIES ▸ Extension for Google

    Play Billing Library to do fake purchases in debug builds. ▸ BillingX doesn’t have all the configuration bells and whistles of Register at this stage: ▸ No config activity ▸ Product consumption was just recently added debugImplementation 'com.pixiteapps.billingx:billingx:0.8.2' releaseImplementation 'com.android.billingclient:billing:1.0'
  35. BILLINGX - RELEASE TESTING AND 3RD PARTY LIBRARIES object BillingClientFactory

    { fun createBillingClient( activity: Activity, updateListener: PurchasesUpdatedListener): BillingClient { return BillingClient .newBuilder(activity) .setListener(updateListener) .build() } }
  36. BILLINGX - DEBUG TESTING AND 3RD PARTY LIBRARIES object BillingClientFactory

    { fun createBillingClient( activity: Activity, updateListener: PurchasesUpdatedListener): BillingClient { return DebugBillingClient( activity = activity, backgroundExecutor = Executors.diskIO, purchasesUpdatedListener = updateListener ) } }
  37. TRANSACTION FEES ▸ In-app products: 70% (you) / 30% (Google)

    ▸ In-app subscriptions after 12 months of retention: 85% (you) / 15% (Google) FINAL THOUGHTS
  38. GOOD ▸ Generally in-app billing is a reasonably easy-to-use API.

    ▸ Google Play Billing Library is a huge improvement over the old AIDL-based approach. ▸ Active development and progression, albeit slow. FINAL THOUGHTS
  39. PROBLEMATIC ▸ Inconsistent documentation ▸ There are functional gaps in

    the API. ▸ Don’t fall for the implied rhetoric of not needing a backend. ▸ Testing is a nightmare (3rd party libs help a lot though). FINAL THOUGHTS
  40. OTHER THINGS RESOURCES (I) ▸ Google Play Billing library release

    notes: https://developer.android.com/google/play/billing/billing_library_releases_notes.html ▸ Google Play Billing library developer docs: https://developer.android.com/google/play/billing/billing_overview ▸ Google Play Developer API: https://developers.google.com/android-publisher/ ▸ Google Play Developer API for creating/editing in-app products: https://developers.google.com/android-publisher/api-ref/inappproducts ▸ Gradle Play Publisher: https://github.com/Triple-T/gradle-play-publisher
  41. OTHER THINGS RESOURCES (II) ▸ Various in-app-billing sample apps: https://github.com/googlesamples/android-play-billing

    ▸ Google Play Developer API for purchases and subscriptions: https://developers.google.com/android-publisher/api-ref/purchases/products https://developers.google.com/android-publisher/api-ref/purchases/subscriptions ▸ Google Play Billing Testing documentation: https://developer.android.com/google/play/billing/billing_testing.html ▸ NY Times Register: https://github.com/NYTimes/Register ▸ BillingX: https://github.com/pixiteapps/billingx
  42. OTHER THINGS GET IN TOUCH Kai Koenig Email: [email protected] Work:

    http://www.ventego-creative.co.nz Twitter: @AgentK Slides: https://speakerdeck.com/therealagentk (previously: http://www.slideshare.com/agentk)