android in-app billing @ droidcon murcia
DESCRIPTION
TRANSCRIPT
![Page 1: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/1.jpg)
@hpique
![Page 2: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/2.jpg)
Android In-app BillingDemystified
Hermés Piqué @hpique
![Page 3: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/3.jpg)
Agenda
• In-app Billing Overview
• Google Play Billing Service
• Android Billing Library
• Security Best Practices
![Page 4: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/4.jpg)
Top Grossing is dominated
by IAB
![Page 5: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/5.jpg)
Freemium
Digital goods
Virtual currency
Subscriptions
![Page 6: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/6.jpg)
User experience
![Page 7: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/7.jpg)
In-app billing terms
• Powered by Google Wallet
• 30% of the sale price
• No refunds (kinda)
• Only for digital goods
• Flexible pricing (unlike iOS)
![Page 8: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/8.jpg)
Agenda
• In-app Billing Overview
• Google Play Billing Service
• Android Billing Library
• Security Best Practices
![Page 9: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/9.jpg)
Google Play Billing Service
• Google Play only
• Android 1.6 upwards (API level 4)
• Now at version 2 with subscription support
![Page 10: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/10.jpg)
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
![Page 11: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/11.jpg)
Pre-requisites
• Services
• AIDL
• BroadcastReceiver
• PendingIntent
![Page 12: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/12.jpg)
Wait, there’s more
• SQLite
• Obfuscation
• Signature validation
• 57 pages of doc!
![Page 13: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/13.jpg)
Architecture overview
app
AndroidMarketServer
IABrequests
![Page 14: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/14.jpg)
IAB requests
•CHECK_BILLING_SUPPORTED
•REQUEST_PURCHASE
•GET_PURCHASE_INFORMATION
•CONFIRM_NOTIFICATIONS
•RESTORE_TRANSACTIONS
![Page 15: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/15.jpg)
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
![Page 16: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/16.jpg)
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);}
![Page 17: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/17.jpg)
Making a request
Bundle request = makeRequestBundle("REQUEST_PURCHASE");request.putString(ITEM_ID, mProductId);Bundle response = mService.sendBillingRequest(request);
![Page 18: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/18.jpg)
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
![Page 19: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/19.jpg)
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;}
![Page 20: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/20.jpg)
IAB responses
• The IAB service responds to every request with a synchronous response
• Followed by 0..N asynchronous responses depending of the request type
![Page 21: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/21.jpg)
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
![Page 22: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/22.jpg)
Asynchronous responses
• Broadcast intents:
• RESPONSE_CODE• IN_APP_NOTIFY• PURCHASE_STATE_CHANGED
![Page 23: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/23.jpg)
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); } }
![Page 24: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/24.jpg)
Check Billing Supported
![Page 25: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/25.jpg)
Check Billing Supported
Parameters Shared
Sync response keys RESPONSE_CODE
Response codes
RESULT_OKRESULT_BILLING_UNAVAILABLE
RESULT_ERRORRESULT_DEVELOPER_ERROR
Async response RESPONSE_CODE
![Page 26: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/26.jpg)
Request Purchase
![Page 27: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/27.jpg)
Request Purchase
Parameters
SharedITEM_ID
ITEM_TYPEDEVELOPER_PAYLOAD
Sync response keysRESPONSE_CODE
PURCHASE_INTENTREQUEST_ID
Response codesRESULT_OK
RESULT_ERRORRESULT_DEVELOPER_ERROR
Async responseRESPONSE_CODEIN_APP_NOTIFY
![Page 28: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/28.jpg)
Get Purchase Information
ParametersSharedNONCE
NOTIFY_IDS
Sync response keysRESPONSE_CODEREQUEST_ID
Response codesRESULT_OK
RESULT_ERRORRESULT_DEVELOPER_ERROR
Async responseRESPONSE_CODE
PURCHASE_STATE_CHANGED
![Page 29: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/29.jpg)
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" }]}
![Page 30: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/30.jpg)
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
![Page 31: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/31.jpg)
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
![Page 32: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/32.jpg)
Purchase states
• Purchased (0)
• Cancelled (1)
• Refunded (2)
• Expired (3): subscriptions only
![Page 33: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/33.jpg)
Confirm Notifications
ParametersSharedNONCE
NOTIFY_IDS
Sync response keysRESPONSE_CODEREQUEST_ID
Response codesRESULT_OK
RESULT_ERRORRESULT_DEVELOPER_ERROR
Async response RESPONSE_CODE
![Page 34: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/34.jpg)
Unsolicited In-app Notify
• Purchase when app is running in various devices
• Refunds
• Subscription expired (?)
![Page 35: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/35.jpg)
Unsolicited In-app Notify
![Page 36: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/36.jpg)
Restore Transactions
![Page 37: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/37.jpg)
Restore Transactions
ParametersSharedNONCE
Sync response keysRESPONSE_CODEREQUEST_ID
Response codesRESULT_OK
RESULT_ERRORRESULT_DEVELOPER_ERROR
Async responseRESPONSE_CODE
PURCHASE_STATE_CHANGED
![Page 38: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/38.jpg)
Security Controls
• Signed purchase data
• In-app notify nonces
![Page 39: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/39.jpg)
Purchase State Changed extras
• inapp_signed_data: Signed JSON string (unencrypted)
• inapp_signature: Use the Google Play public key to validate
![Page 40: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/40.jpg)
![Page 41: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/41.jpg)
IAB limitations
• No API for product details & price
• To fully test you need to pay for real
• Sometimes async messages are really async
![Page 42: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/42.jpg)
Obligatory image of Justin Bieber to wake you up
![Page 43: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/43.jpg)
Agenda
• In-app Billing Overview
• Google Play Billing Service
• Android Billing Library
• Security Best Practices
![Page 44: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/44.jpg)
requestPurchase("com.example.item");
![Page 45: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/45.jpg)
Android Billing Library!
• Open-source on github
• Better than starting from scratch
tiny.cc/android-billing
![Page 46: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/46.jpg)
Features
• Full Android IAB Service implementation
• Auto-confirmations
• Obfuscated purchase database
• Implements security best practices
• Half-decent unit testing coverage
![Page 47: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/47.jpg)
Overview
•BillingController
•IBillingObserver
•BillingController.IConfiguration
•ISignatureValidator
![Page 48: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/48.jpg)
Overview
![Page 49: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/49.jpg)
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);" " }" }
![Page 50: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/50.jpg)
Request Purchase
BillingController.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) {
}
![Page 51: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/51.jpg)
Restore Transactions
if (!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();}
![Page 52: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/52.jpg)
Suggested implementation
![Page 53: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/53.jpg)
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>
![Page 54: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/54.jpg)
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, 115, 1, 73, 57, 110, 48, -116}; }
@Override public String getPublicKey() { return "your public key here"; } }); }
![Page 55: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/55.jpg)
Agenda
• In-app Billing Overview
• Google Play Billing Service
• Android Billing Library
• Security Best Practices
![Page 56: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/56.jpg)
Best Practices
• Random nonces
• Obfuscate purchase data
• Embedding public key
• Code obfuscation
• Server-side signature validation
![Page 57: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/57.jpg)
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?)
![Page 58: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/58.jpg)
Obfuscate purchase data
• Do not store purchase data plainly
• Handled by ABL
• Uses salt, installation id, device id and app id to perform obfuscation
![Page 59: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/59.jpg)
Embedding public key
• Do not embed the public key plainly
• Only embed the public key if you can’t perform server-side signature validation
![Page 60: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/60.jpg)
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
![Page 61: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/61.jpg)
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
![Page 62: Android In-app Billing @ Droidcon Murcia](https://reader034.vdocuments.net/reader034/viewer/2022042613/54b44b264a79590a2e8b45a8/html5/thumbnails/62.jpg)
Thanks!