Android In-app Billing @ Droidcon Murcia

1,625 views

Published on

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
1,625
On SlideShare
0
From Embeds
0
Number of Embeds
24
Actions
Shares
0
Downloads
31
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Android In-app Billing @ Droidcon Murcia

  1. 1. @hpique
  2. 2. Android In-app Billing Demystified Hermés Piqué @hpique
  3. 3. Agenda• In-app Billing Overview• Google Play Billing Service• Android Billing Library• Security Best Practices
  4. 4. Top Grossingis dominated by IAB
  5. 5. Freemium Digital goodsVirtual currency Subscriptions
  6. 6. User experience
  7. 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. 8. Agenda• In-app Billing Overview• Google Play Billing Service• Android Billing Library• Security Best Practices
  9. 9. Google Play Billing Service• Google Play only• Android 1.6 upwards (API level 4)• Now at version 2 with subscription support
  10. 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. 11. Pre-requisites• Services• AIDL• BroadcastReceiver• PendingIntent
  12. 12. Wait, there’s more• SQLite• Obfuscation• Signature validation• 57 pages of doc!
  13. 13. Architecture overview app Android IAB Market requests Server
  14. 14. IAB requests•CHECK_BILLING_SUPPORTED•REQUEST_PURCHASE•GET_PURCHASE_INFORMATION•CONFIRM_NOTIFICATIONS•RESTORE_TRANSACTIONS
  15. 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. 16. Binding to MarketBillingServicetry {  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. 17. Making a requestBundle request = makeRequestBundle("REQUEST_PURCHASE");request.putString(ITEM_ID, mProductId);Bundle response = mService.sendBillingRequest(request);
  18. 18. 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
  19. 19. Request bundleprotected 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;}
  20. 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. 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. 22. Asynchronous responses• Broadcast intents: • RESPONSE_CODE • IN_APP_NOTIFY • PURCHASE_STATE_CHANGED
  23. 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);    }  }
  24. 24. Check Billing Supported
  25. 25. Check Billing SupportedParameters SharedSync response keys RESPONSE_CODE RESULT_OK RESULT_BILLING_UNAVAILABLEResponse codes RESULT_ERROR RESULT_DEVELOPER_ERRORAsync response RESPONSE_CODE
  26. 26. Request Purchase
  27. 27. Request Purchase Shared ITEM_IDParameters ITEM_TYPE DEVELOPER_PAYLOAD RESPONSE_CODESync response keys PURCHASE_INTENT REQUEST_ID RESULT_OKResponse codes RESULT_ERROR RESULT_DEVELOPER_ERROR RESPONSE_CODEAsync response IN_APP_NOTIFY
  28. 28. Get Purchase Information SharedParameters NONCE NOTIFY_IDS RESPONSE_CODESync response keys REQUEST_ID RESULT_OKResponse codes RESULT_ERROR RESULT_DEVELOPER_ERROR RESPONSE_CODEAsync response PURCHASE_STATE_CHANGED
  29. 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. 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. 31. JSON fields (2)• productId: set in the Developer Console• purchaseTime: time of purchase• purchaseState: purchased, cancelled, refunded or expired• purchaseToken: subscription id• developerPayload: optional value provided in REQUEST_PURCHASE
  32. 32. Purchase states• Purchased (0)• Cancelled (1)• Refunded (2)• Expired (3): subscriptions only
  33. 33. Confirm Notifications SharedParameters NONCE NOTIFY_IDS RESPONSE_CODESync response keys REQUEST_ID RESULT_OKResponse codes RESULT_ERROR RESULT_DEVELOPER_ERRORAsync response RESPONSE_CODE
  34. 34. Unsolicited In-app Notify• Purchase when app is running in various devices• Refunds• Subscription expired (?)
  35. 35. Unsolicited In-app Notify
  36. 36. Restore Transactions
  37. 37. Restore Transactions SharedParameters NONCE RESPONSE_CODESync response keys REQUEST_ID RESULT_OKResponse codes RESULT_ERROR RESULT_DEVELOPER_ERROR RESPONSE_CODEAsync response PURCHASE_STATE_CHANGED
  38. 38. Security Controls• Signed purchase data• In-app notify nonces
  39. 39. Purchase State Changed extras• inapp_signed_data: Signed JSON string (unencrypted)• inapp_signature: Use the Google Play public key to validate
  40. 40. IAB limitations• No API for product details & price• To fully test you need to pay for real• Sometimes async messages are really async
  41. 41. Obligatory image of Justin Bieber to wake you up
  42. 42. Agenda• In-app Billing Overview• Google Play Billing Service• Android Billing Library• Security Best Practices
  43. 43. requestPurchase("com.example.item");
  44. 44. Android Billing Library! tiny.cc/android-billing• Open-source on github• Better than starting from scratch
  45. 45. Features• Full Android IAB Service implementation• Auto-confirmations• Obfuscated purchase database• Implements security best practices• Half-decent unit testing coverage
  46. 46. Overview•BillingController•IBillingObserver•BillingController.IConfigura tion•ISignatureValidator
  47. 47. Overview
  48. 48. 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);" " }" }
  49. 49. Request PurchaseBillingController.requestPurchase(this, productId);@Overridepublic void onPurchaseIntent(String itemId, PendingIntent purchaseIntent) { BillingController.startPurchaseIntent(activity, purchaseIntent, null);}@Overridepublic void onRequestPurchaseResponse(String itemId, ResponseCode response) {}@Overridepublic void onPurchaseStateChanged(String itemId, Order order) {}
  50. 50. Restore Transactionsif (!mBillingObserver.isTransactionsRestored()) { BillingController.restoreTransactions(this); Toast.makeText(this, R.string.restoring_transactions,Toast.LENGTH_LONG).show();}@Overridepublic void onTransactionsRestored() { final SharedPreferences preferences =PreferenceManager.getDefaultSharedPreferences(activity); final Editor editor = preferences.edit(); editor.putBoolean(KEY_TRANSACTIONS_RESTORED, true); editor.commit();}
  51. 51. Suggestedimplementation
  52. 52. 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" /> <actionandroid:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" /> </intent-filter> </receiver> </application>
  53. 53. 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"; } }); }
  54. 54. Agenda• In-app Billing Overview• Google Play Billing Service• Android Billing Library• Security Best Practices
  55. 55. Best Practices• Random nonces• Obfuscate purchase data• Embedding public key• Code obfuscation• Server-side signature validation
  56. 56. 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?)
  57. 57. Obfuscate purchase data • Do not store purchase data plainly • Handled by ABL • Uses salt, installation id, device id and app id to perform obfuscation
  58. 58. Embedding public key• Do not embed the public key plainly• Only embed the public key if you can’t perform server-side signature validation
  59. 59. 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
  60. 60. 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
  61. 61. Thanks!

×