SlideShare a Scribd company logo
@hpique
Android In-app Billing
        Demystified




       Hermés Piqué
         @hpique
Agenda

• In-app Billing Overview
• Google Play Billing Service
• Android Billing Library
• Security Best Practices
Top Grossing
is dominated
    by IAB
Freemium
 Digital goods
Virtual currency
 Subscriptions
User experience
In-app billing terms

• Powered by Google Wallet
• 30% of the sale price
• No refunds (kinda)
• Only for digital goods
• Flexible pricing (unlike iOS)
Agenda

• In-app Billing Overview
• Google Play Billing Service
• Android Billing Library
• Security Best Practices
Google Play Billing
       Service

• Google Play only
• Android 1.6 upwards (API level 4)
• Now at version 2 with subscriptions
  support
Product types
• In-app products
 •   Managed (per user account): premium, digital
     content

 •   Unmanaged: virtual currency, consumable virtual
     goods

• Subscriptions
 •   Monthly & yearly with free trials support
Pre-requisites

• Services
• AIDL
• BroadcastReceiver
• PendingIntent
Wait, there’s more

• SQLite
• Obfuscation
• Signature validation
• 57 pages of doc!
Architecture overview

  app
                Android
    IAB         Market
  requests
                 Server
IAB requests

• CHECK_BILLING_SUPPORTED
• REQUEST_PURCHASE
• GET_PURCHASE_INFORMATION
• CONFIRM_NOTIFICATIONS
• RESTORE_TRANSACTIONS
IAB requests
• MarketBillingService interface defined in an
  Android Interface Definition Language file
  (IMarketBillingService.aidl)
• IAB requests sent by single IPC method
  (sendBillingRequest()) of the interface
• Request type and parameters are sent as a
  Bundle
Binding to
          MarketBillingService
try {
  boolean bindResult = mContext.bindService(
    new Intent("com.android.vending.billing.MarketBillingService.BIND"), this,
    Context.BIND_AUTO_CREATE);
  if (bindResult) {
    Log.i(TAG, "Service bind successful.");
  } else {
    Log.e(TAG, "Could not bind to the MarketBillingService.");
  }
} catch (SecurityException e) {
  Log.e(TAG, "Security exception: " + e);
}


public void onServiceConnected(ComponentName name, IBinder service) {
  Log.i(TAG, "MarketBillingService connected.");
  mService = IMarketBillingService.Stub.asInterface(service);
}
Request bundle
           parameters
•   Shared

    •   BILLING_REQUEST: request type

    •   API_VERSION: “1” for in-app products, “2” for
        subscriptions

    •   PACKAGE_NAME: app package

•   Specific

    •   ITEM_ID, ITEM_TYPE, NONCE, NOTIFY_ID,
        DEVELOPER_PAYLOAD
Request bundle

protected Bundle makeRequestBundle(String method) {
  Bundle request = new Bundle();
  request.putString(BILLING_REQUEST, method);
  request.putInt(API_VERSION, 1);
  request.putString(PACKAGE_NAME, getPackageName());
  return request;
}
Making a request


Bundle request = makeRequestBundle("REQUEST_PURCHASE");
request.putString(ITEM_ID, mProductId);
Bundle response = mService.sendBillingRequest(request);
IAB responses

• The IAB service responds to every request
  with a synchronous response
• Followed by 0..N asynchronous responses
  depending of the request type
Synchronous responses
• RESPONSE_CODE: status information and
  error information about a request
• REQUEST_ID: used to match
  asynchronous responses with requests
• PURCHASE_INTENT: PendingIntent, which
  you use to launch the checkout activity
 •   REQUEST_PURCHASE only
Asynchronous
      responses
• Broadcast intents:
 • RESPONSE_CODE
 • IN_APP_NOTIFY
 • PURCHASE_STATE_CHANGED
Receiving async
                  responses
 public void onReceive(Context context, Intent intent) {
    String action = intent.getAction();
    if (ACTION_PURCHASE_STATE_CHANGED.equals(action)) {
      String signedData = intent.getStringExtra(INAPP_SIGNED_DATA);
      String signature = intent.getStringExtra(INAPP_SIGNATURE);
      // Do something with the signedData and the signature.
    } else if (ACTION_NOTIFY.equals(action)) {
      String notifyId = intent.getStringExtra(NOTIFICATION_ID);
      // Do something with the notifyId.
    } else if (ACTION_RESPONSE_CODE.equals(action)) {
      long requestId = intent.getLongExtra(INAPP_REQUEST_ID, -1);
      int responseCodeIndex = intent.getIntExtra(INAPP_RESPONSE_CODE,
        ResponseCode.RESULT_ERROR.ordinal());
      // Do something with the requestId and the responseCodeIndex.
    } else {
      Log.w(TAG, "unexpected action: " + action);
    }
  }
Check Billing
 Supported
Check Billing
               Supported
Parameters                     Shared
Sync response keys        RESPONSE_CODE
                             RESULT_OK
                     RESULT_BILLING_UNAVAILABLE
Response codes
                            RESULT_ERROR
                      RESULT_DEVELOPER_ERROR
Async response            RESPONSE_CODE
Request Purchase
Request Purchase
                                Shared
                               ITEM_ID
Parameters
                             ITEM_TYPE
                       DEVELOPER_PAYLOAD
                         RESPONSE_CODE
Sync response keys      PURCHASE_INTENT
                            REQUEST_ID
                            RESULT_OK
Response codes             RESULT_ERROR
                     RESULT_DEVELOPER_ERROR
                         RESPONSE_CODE
Async response
                          IN_APP_NOTIFY
Get Purchase
             Information
                               Shared
Parameters                    NONCE
                             NOTIFY_IDS
                          RESPONSE_CODE
Sync response keys
                            REQUEST_ID
                             RESULT_OK
Response codes             RESULT_ERROR
                      RESULT_DEVELOPER_ERROR
                          RESPONSE_CODE
Async response
                     PURCHASE_STATE_CHANGED
Purchase State Changed
          JSON
{ "nonce" : 1836535032137741465,
  "orders" :
    [{ "notificationId" : "android.test.purchased",
       "orderId" : "transactionId.android.test.purchased",
       "packageName" : "com.example.dungeons",
       "productId" : "android.test.purchased",
       "developerPayload" : "bGoa+V7g/yqDXvKRq",
       "purchaseTime" : 1290114783411,
       "purchaseState" : 0,
       "purchaseToken" : "rojeslcdyyiapnqcynkjyyjh" }]
}
JSON fields (1)
• nonce: to verify the integrity of the
  message
• notificationId: to match with
  IN_APP_NOTIFY
• orderId: Google Wallet order id
• packageName: your app package
JSON fields (2)
• productId: set in the Developer Console
• purchaseTime: time of purchase
• purchaseState: purchased, cancelled,
  refunded or expired
• purchaseToken: subscriptionId
• developerPayload: optional value
  provided in REQUEST_PURCHASE
Purchase states

• Purchased (0)
• Cancelled (1)
• Refunded (2)
• Expired (3): subscriptions only
Confirm Notifications
                              Shared
Parameters                   NONCE
                            NOTIFY_IDS
                         RESPONSE_CODE
Sync response keys
                           REQUEST_ID
                            RESULT_OK
Response codes            RESULT_ERROR
                     RESULT_DEVELOPER_ERROR
Async response           RESPONSE_CODE
Unsolicited In-app
       Notify
• Purchase when app is running in
  various devices
• Refunds
• Subscription expired (?)
Unsolicited In-app
     Notify
Restore Transactions
Restore Transactions
                                Basic
Parameters
                              NONCE
                          RESPONSE_CODE
Sync response keys
                            REQUEST_ID
                             RESULT_OK
Response codes             RESULT_ERROR
                      RESULT_DEVELOPER_ERROR
                          RESPONSE_CODE
Async response
                     PURCHASE_STATE_CHANGED
Security Controls


• Signed purchase data
• In-app notify nonces
Purchase State
   Changed Extras

• inapp_signed_data: Signed JSON
  string (unencrypted)
• inapp_signature: Use the Google
  Play public key to validate
Android in-app billing @ Google DevFest Barcelona 2012
IAB limitations

• No API for product details & price
• To fully test you need to pay for real
• Sometimes async messages are really async
Obligatory image of Justin Bieber to wake you up
Agenda

• In-app Billing Overview
• Google Play Billing Service
• Android Billing Library
• Security Best Practices
requestPurchase("com.example.item");
Android Billing Library

     tiny.cc/android-billing


• Open-source on github
• “Better than starting from scratch”™
Features

• Full Android IAB Service implementation
• Auto-confirmations
• Obfuscated purchase database
• Implements security best practices
• Half-decent unit testing coverage
Overview

• BillingController
• IBillingObserver
• BillingController.IConfiguration
• ISignatureValidator
Overview
Check Billing
                Supported
"   @Override
"   public void onCreate(Bundle savedInstanceState) {
"   " // ...
"   " BillingController.registerObserver(mBillingObserver);
"   " BillingController.checkBillingSupported(this);
"   " // ...
"   }

"   public void onBillingChecked(boolean supported) {
"   " if (!supported) {
"   " " showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
"   " }
"   }
Request Purchase
BillingController.requestPurchase(this, productId);


@Override
public void onPurchaseIntent(String itemId, PendingIntent purchaseIntent) {
	 BillingController.startPurchaseIntent(activity, purchaseIntent, null);
}

@Override
public void onRequestPurchaseResponse(String itemId, ResponseCode response) {

}

@Override
public void onPurchaseStateChanged(String itemId, Order order) {

}
Restore Transactions
if (!mBillingObserver.isTransactionsRestored()) {
	 BillingController.restoreTransactions(this);
	 Toast.makeText(this, R.string.restoring_transactions,
Toast.LENGTH_LONG).show();
}

@Override
public void onTransactionsRestored() {
	 final SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(activity);
	 final Editor editor = preferences.edit();
	 editor.putBoolean(KEY_TRANSACTIONS_RESTORED, true);
	 editor.commit();
}
Suggested
implementation
AndroidManifest.xml
    <!-- Add this permission to your manifest -->
    <uses-permission android:name="com.android.vending.BILLING" />
    <application>
    	 <!-- Add this service and receiver to your application -->
        <service android:name="net.robotmedia.billing.BillingService" />
        <receiver android:name="net.robotmedia.billing.BillingReceiver">
            <intent-filter>
                <action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
                <action android:name="com.android.vending.billing.RESPONSE_CODE" />
                <action
android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />
            </intent-filter>
        </receiver>
    </application>
Set Configuration
	   public void onCreate() {
	   	 super.onCreate();
	   	 BillingController.setDebug(true);
	   	 BillingController.setConfiguration(new BillingController.IConfiguration() {
	   	 	
	   	 	 @Override
	   	 	 public byte[] getObfuscationSalt() {
	   	 	 	 return new byte[] {41, -90, -116, -41, 66, -53, 122, -110, -127, -96, -88, 77, 127
	   	 	 }

	   	   	 @Override
	   	   	 public String getPublicKey() {
	   	   	 	 return "your public key here";
	   	   	 }
	   	   });
	   }
Agenda

• In-app Billing Overview
• Google Play Billing Service
• Android Billing Library
• Security Best Practices
Best Practices

• Random nonces
• Obfuscate purchase data
• Embedding public key
• Code obfuscation
• Server-side signature validation
Random nonces

• Sent with
  GET_PURCHASE_INFORMATION and
  RESTORE_TRANSACTION requests
• Handled by ABL
• Server-side nonce generation & verification
  not supported by ABL (but really?)
Obfuscate purchase data

 • Do not store purchase data plainly
 • Handled by ABL
 • Uses salt, installation id, device id and app id
   to perform obfuscation
Embedding public key


• Do not embed the public key plainly
• Only embed the public key if you can’t
  perform server-side signature validation
Code obfuscation

• Obfuscate your app code to make it harder
  to hack
• Problem: ABL is open-source!
• Use ProGuard and consider making
  changes to the ABL code
Server-side validation
• Perform the signature validation on a
  server
• Supported by ABL
• Provide your own ISignatureValidator;
  validation is performed with AsyncTask
• Return null on IConfiguration.getKey
Thanks!

More Related Content

KEY
Android In-App Billing @ Droidcon 2011
PPT
[Android] Google Play in app billing
KEY
In-App Purchase
PDF
Infinum Android Talks #18 - In-app billing by Ivan Marić
KEY
Android In-App Billing @ Barcelona GTUG
PPTX
Monetize your app_with_google_subscriptions_v3_services_intuit
PDF
Testing In App Billing
PDF
Payments in Mobile Apps
Android In-App Billing @ Droidcon 2011
[Android] Google Play in app billing
In-App Purchase
Infinum Android Talks #18 - In-app billing by Ivan Marić
Android In-App Billing @ Barcelona GTUG
Monetize your app_with_google_subscriptions_v3_services_intuit
Testing In App Billing
Payments in Mobile Apps

Similar to Android in-app billing @ Google DevFest Barcelona 2012 (20)

PDF
Android In-app Billing @ Droidcon Murcia
PDF
June2013 Meetup : In-App Billing by Soham & Senthil
PDF
In App Purchase - Transcript.pdf
PDF
Samsung IAP SDK
PDF
Newsstand
PDF
Easy Mobile Payments
PDF
Why and How to Add In-App Purchasing and Subscriptions to your Apps - Mario V...
PPTX
Using the GSMA OneAPI Gateway
PPTX
iOS In-App Purchase
PPTX
HTML5 Gaming Payment Platforms
PPTX
In-App Purchase API
PDF
In-app purchases for BlackBerry 10
KEY
In App Purchases
PPT
Dodaii Detail English -tokyocamp-
PPTX
2012 SVCodeCamp: In App Payments with HTML5
PPTX
Mobile commerce
PDF
Mobi: "In-app Payments by Google & Apple"
PDF
IAP auto renewable in practice
PDF
Droidcon DE 2013
PDF
mobilecamp Hamburg
Android In-app Billing @ Droidcon Murcia
June2013 Meetup : In-App Billing by Soham & Senthil
In App Purchase - Transcript.pdf
Samsung IAP SDK
Newsstand
Easy Mobile Payments
Why and How to Add In-App Purchasing and Subscriptions to your Apps - Mario V...
Using the GSMA OneAPI Gateway
iOS In-App Purchase
HTML5 Gaming Payment Platforms
In-App Purchase API
In-app purchases for BlackBerry 10
In App Purchases
Dodaii Detail English -tokyocamp-
2012 SVCodeCamp: In App Payments with HTML5
Mobile commerce
Mobi: "In-app Payments by Google & Apple"
IAP auto renewable in practice
Droidcon DE 2013
mobilecamp Hamburg
Ad

Recently uploaded (20)

PDF
cuic standard and advanced reporting.pdf
PPTX
Big Data Technologies - Introduction.pptx
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PPTX
Programs and apps: productivity, graphics, security and other tools
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
PDF
Empathic Computing: Creating Shared Understanding
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
Encapsulation theory and applications.pdf
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PDF
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PPT
Teaching material agriculture food technology
PDF
Assigned Numbers - 2025 - Bluetooth® Document
PPTX
Spectroscopy.pptx food analysis technology
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
PDF
Machine learning based COVID-19 study performance prediction
PPTX
A Presentation on Artificial Intelligence
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
cuic standard and advanced reporting.pdf
Big Data Technologies - Introduction.pptx
Per capita expenditure prediction using model stacking based on satellite ima...
Programs and apps: productivity, graphics, security and other tools
“AI and Expert System Decision Support & Business Intelligence Systems”
Empathic Computing: Creating Shared Understanding
Diabetes mellitus diagnosis method based random forest with bat algorithm
Encapsulation theory and applications.pdf
Dropbox Q2 2025 Financial Results & Investor Presentation
20250228 LYD VKU AI Blended-Learning.pptx
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
The Rise and Fall of 3GPP – Time for a Sabbatical?
Teaching material agriculture food technology
Assigned Numbers - 2025 - Bluetooth® Document
Spectroscopy.pptx food analysis technology
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
Machine learning based COVID-19 study performance prediction
A Presentation on Artificial Intelligence
Digital-Transformation-Roadmap-for-Companies.pptx
Ad

Android in-app billing @ Google DevFest Barcelona 2012

  • 2. Android In-app Billing Demystified Hermés Piqué @hpique
  • 3. Agenda • In-app Billing Overview • Google Play Billing Service • Android Billing Library • Security Best Practices
  • 5. Freemium Digital goods Virtual currency Subscriptions
  • 7. In-app billing terms • Powered by Google Wallet • 30% of the sale price • No refunds (kinda) • Only for digital goods • Flexible pricing (unlike iOS)
  • 8. Agenda • In-app Billing Overview • Google Play Billing Service • Android Billing Library • Security Best Practices
  • 9. Google Play Billing Service • Google Play only • Android 1.6 upwards (API level 4) • Now at version 2 with subscriptions support
  • 10. Product types • In-app products • Managed (per user account): premium, digital content • Unmanaged: virtual currency, consumable virtual goods • Subscriptions • Monthly & yearly with free trials support
  • 11. Pre-requisites • Services • AIDL • BroadcastReceiver • PendingIntent
  • 12. Wait, there’s more • SQLite • Obfuscation • Signature validation • 57 pages of doc!
  • 13. Architecture overview app Android IAB Market requests Server
  • 14. IAB requests • CHECK_BILLING_SUPPORTED • REQUEST_PURCHASE • GET_PURCHASE_INFORMATION • CONFIRM_NOTIFICATIONS • RESTORE_TRANSACTIONS
  • 15. IAB requests • MarketBillingService interface defined in an Android Interface Definition Language file (IMarketBillingService.aidl) • IAB requests sent by single IPC method (sendBillingRequest()) of the interface • Request type and parameters are sent as a Bundle
  • 16. Binding to MarketBillingService try {   boolean bindResult = mContext.bindService(     new Intent("com.android.vending.billing.MarketBillingService.BIND"), this,     Context.BIND_AUTO_CREATE);   if (bindResult) {     Log.i(TAG, "Service bind successful.");   } else {     Log.e(TAG, "Could not bind to the MarketBillingService.");   } } catch (SecurityException e) {   Log.e(TAG, "Security exception: " + e); } public void onServiceConnected(ComponentName name, IBinder service) {   Log.i(TAG, "MarketBillingService connected.");   mService = IMarketBillingService.Stub.asInterface(service); }
  • 17. Request bundle parameters • Shared • BILLING_REQUEST: request type • API_VERSION: “1” for in-app products, “2” for subscriptions • PACKAGE_NAME: app package • Specific • ITEM_ID, ITEM_TYPE, NONCE, NOTIFY_ID, DEVELOPER_PAYLOAD
  • 18. Request bundle protected Bundle makeRequestBundle(String method) {   Bundle request = new Bundle();   request.putString(BILLING_REQUEST, method);   request.putInt(API_VERSION, 1);   request.putString(PACKAGE_NAME, getPackageName());   return request; }
  • 19. Making a request Bundle request = makeRequestBundle("REQUEST_PURCHASE"); request.putString(ITEM_ID, mProductId); Bundle response = mService.sendBillingRequest(request);
  • 20. IAB responses • The IAB service responds to every request with a synchronous response • Followed by 0..N asynchronous responses depending of the request type
  • 21. Synchronous responses • RESPONSE_CODE: status information and error information about a request • REQUEST_ID: used to match asynchronous responses with requests • PURCHASE_INTENT: PendingIntent, which you use to launch the checkout activity • REQUEST_PURCHASE only
  • 22. Asynchronous responses • Broadcast intents: • RESPONSE_CODE • IN_APP_NOTIFY • PURCHASE_STATE_CHANGED
  • 23. Receiving async responses public void onReceive(Context context, Intent intent) {     String action = intent.getAction();     if (ACTION_PURCHASE_STATE_CHANGED.equals(action)) {       String signedData = intent.getStringExtra(INAPP_SIGNED_DATA);       String signature = intent.getStringExtra(INAPP_SIGNATURE);       // Do something with the signedData and the signature.     } else if (ACTION_NOTIFY.equals(action)) {       String notifyId = intent.getStringExtra(NOTIFICATION_ID);       // Do something with the notifyId.     } else if (ACTION_RESPONSE_CODE.equals(action)) {       long requestId = intent.getLongExtra(INAPP_REQUEST_ID, -1);       int responseCodeIndex = intent.getIntExtra(INAPP_RESPONSE_CODE,         ResponseCode.RESULT_ERROR.ordinal());       // Do something with the requestId and the responseCodeIndex.     } else {       Log.w(TAG, "unexpected action: " + action);     }   }
  • 25. Check Billing Supported Parameters Shared Sync response keys RESPONSE_CODE RESULT_OK RESULT_BILLING_UNAVAILABLE Response codes RESULT_ERROR RESULT_DEVELOPER_ERROR Async response RESPONSE_CODE
  • 27. Request Purchase Shared ITEM_ID Parameters ITEM_TYPE DEVELOPER_PAYLOAD RESPONSE_CODE Sync response keys PURCHASE_INTENT REQUEST_ID RESULT_OK Response codes RESULT_ERROR RESULT_DEVELOPER_ERROR RESPONSE_CODE Async response IN_APP_NOTIFY
  • 28. Get Purchase Information Shared Parameters NONCE NOTIFY_IDS RESPONSE_CODE Sync response keys REQUEST_ID RESULT_OK Response codes RESULT_ERROR RESULT_DEVELOPER_ERROR RESPONSE_CODE Async response PURCHASE_STATE_CHANGED
  • 29. Purchase State Changed JSON { "nonce" : 1836535032137741465, "orders" : [{ "notificationId" : "android.test.purchased", "orderId" : "transactionId.android.test.purchased", "packageName" : "com.example.dungeons", "productId" : "android.test.purchased", "developerPayload" : "bGoa+V7g/yqDXvKRq", "purchaseTime" : 1290114783411, "purchaseState" : 0, "purchaseToken" : "rojeslcdyyiapnqcynkjyyjh" }] }
  • 30. JSON fields (1) • nonce: to verify the integrity of the message • notificationId: to match with IN_APP_NOTIFY • orderId: Google Wallet order id • packageName: your app package
  • 31. JSON fields (2) • productId: set in the Developer Console • purchaseTime: time of purchase • purchaseState: purchased, cancelled, refunded or expired • purchaseToken: subscriptionId • developerPayload: optional value provided in REQUEST_PURCHASE
  • 32. Purchase states • Purchased (0) • Cancelled (1) • Refunded (2) • Expired (3): subscriptions only
  • 33. Confirm Notifications Shared Parameters NONCE NOTIFY_IDS RESPONSE_CODE Sync response keys REQUEST_ID RESULT_OK Response codes RESULT_ERROR RESULT_DEVELOPER_ERROR Async response RESPONSE_CODE
  • 34. Unsolicited In-app Notify • Purchase when app is running in various devices • Refunds • Subscription expired (?)
  • 37. Restore Transactions Basic Parameters NONCE RESPONSE_CODE Sync response keys REQUEST_ID RESULT_OK Response codes RESULT_ERROR RESULT_DEVELOPER_ERROR RESPONSE_CODE Async response PURCHASE_STATE_CHANGED
  • 38. Security Controls • Signed purchase data • In-app notify nonces
  • 39. Purchase State Changed Extras • inapp_signed_data: Signed JSON string (unencrypted) • inapp_signature: Use the Google Play public key to validate
  • 41. IAB limitations • No API for product details & price • To fully test you need to pay for real • Sometimes async messages are really async
  • 42. Obligatory image of Justin Bieber to wake you up
  • 43. Agenda • In-app Billing Overview • Google Play Billing Service • Android Billing Library • Security Best Practices
  • 45. Android Billing Library tiny.cc/android-billing • Open-source on github • “Better than starting from scratch”™
  • 46. Features • Full Android IAB Service implementation • Auto-confirmations • Obfuscated purchase database • Implements security best practices • Half-decent unit testing coverage
  • 47. Overview • BillingController • IBillingObserver • BillingController.IConfiguration • ISignatureValidator
  • 49. Check Billing Supported " @Override " public void onCreate(Bundle savedInstanceState) { " " // ... " " BillingController.registerObserver(mBillingObserver); " " BillingController.checkBillingSupported(this); " " // ... " } " public void onBillingChecked(boolean supported) { " " if (!supported) { " " " showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID); " " } " }
  • 50. Request Purchase BillingController.requestPurchase(this, productId); @Override public void onPurchaseIntent(String itemId, PendingIntent purchaseIntent) { BillingController.startPurchaseIntent(activity, purchaseIntent, null); } @Override public void onRequestPurchaseResponse(String itemId, ResponseCode response) { } @Override public void onPurchaseStateChanged(String itemId, Order order) { }
  • 51. Restore Transactions if (!mBillingObserver.isTransactionsRestored()) { BillingController.restoreTransactions(this); Toast.makeText(this, R.string.restoring_transactions, Toast.LENGTH_LONG).show(); } @Override public void onTransactionsRestored() { final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); final Editor editor = preferences.edit(); editor.putBoolean(KEY_TRANSACTIONS_RESTORED, true); editor.commit(); }
  • 53. AndroidManifest.xml <!-- Add this permission to your manifest --> <uses-permission android:name="com.android.vending.BILLING" /> <application> <!-- Add this service and receiver to your application --> <service android:name="net.robotmedia.billing.BillingService" /> <receiver android:name="net.robotmedia.billing.BillingReceiver"> <intent-filter> <action android:name="com.android.vending.billing.IN_APP_NOTIFY" /> <action android:name="com.android.vending.billing.RESPONSE_CODE" /> <action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" /> </intent-filter> </receiver> </application>
  • 54. Set Configuration public void onCreate() { super.onCreate(); BillingController.setDebug(true); BillingController.setConfiguration(new BillingController.IConfiguration() { @Override public byte[] getObfuscationSalt() { return new byte[] {41, -90, -116, -41, 66, -53, 122, -110, -127, -96, -88, 77, 127 } @Override public String getPublicKey() { return "your public key here"; } }); }
  • 55. Agenda • In-app Billing Overview • Google Play Billing Service • Android Billing Library • Security Best Practices
  • 56. Best Practices • Random nonces • Obfuscate purchase data • Embedding public key • Code obfuscation • Server-side signature validation
  • 57. Random nonces • Sent with GET_PURCHASE_INFORMATION and RESTORE_TRANSACTION requests • Handled by ABL • Server-side nonce generation & verification not supported by ABL (but really?)
  • 58. Obfuscate purchase data • Do not store purchase data plainly • Handled by ABL • Uses salt, installation id, device id and app id to perform obfuscation
  • 59. Embedding public key • Do not embed the public key plainly • Only embed the public key if you can’t perform server-side signature validation
  • 60. Code obfuscation • Obfuscate your app code to make it harder to hack • Problem: ABL is open-source! • Use ProGuard and consider making changes to the ABL code
  • 61. Server-side validation • Perform the signature validation on a server • Supported by ABL • Provide your own ISignatureValidator; validation is performed with AsyncTask • Return null on IConfiguration.getKey

Editor's Notes