62
@hpique

Android In-app Billing @ Droidcon Murcia

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: Android In-app Billing @ Droidcon Murcia

@hpique

Page 2: Android In-app Billing @ Droidcon Murcia

Android In-app BillingDemystified

Hermés Piqué @hpique

Page 3: Android In-app Billing @ Droidcon Murcia

Agenda

• In-app Billing Overview

• Google Play Billing Service

• Android Billing Library

• Security Best Practices

Page 4: Android In-app Billing @ Droidcon Murcia

Top Grossing is dominated

by IAB

Page 5: Android In-app Billing @ Droidcon Murcia

Freemium

Digital goods

Virtual currency

Subscriptions

Page 6: Android In-app Billing @ Droidcon Murcia

User experience

Page 7: Android In-app Billing @ Droidcon Murcia

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

Agenda

• In-app Billing Overview

• Google Play Billing Service

• Android Billing Library

• Security Best Practices

Page 9: Android In-app Billing @ Droidcon Murcia

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

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

Pre-requisites

• Services

• AIDL

• BroadcastReceiver

• PendingIntent

Page 12: Android In-app Billing @ Droidcon Murcia

Wait, there’s more

• SQLite

• Obfuscation

• Signature validation

• 57 pages of doc!

Page 13: Android In-app Billing @ Droidcon Murcia

Architecture overview

app

AndroidMarketServer

IABrequests

Page 14: Android In-app Billing @ Droidcon Murcia

IAB requests

•CHECK_BILLING_SUPPORTED

•REQUEST_PURCHASE

•GET_PURCHASE_INFORMATION

•CONFIRM_NOTIFICATIONS

•RESTORE_TRANSACTIONS

Page 15: Android In-app Billing @ Droidcon Murcia

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

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

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

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

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

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

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

Asynchronous responses

• Broadcast intents:

• RESPONSE_CODE• IN_APP_NOTIFY• PURCHASE_STATE_CHANGED

Page 23: Android In-app Billing @ Droidcon Murcia

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

Check Billing Supported

Page 25: Android In-app Billing @ Droidcon Murcia

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

Request Purchase

Page 27: Android In-app Billing @ Droidcon Murcia

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

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

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

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

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

Purchase states

• Purchased (0)

• Cancelled (1)

• Refunded (2)

• Expired (3): subscriptions only

Page 33: Android In-app Billing @ Droidcon Murcia

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

Unsolicited In-app Notify

• Purchase when app is running in various devices

• Refunds

• Subscription expired (?)

Page 35: Android In-app Billing @ Droidcon Murcia

Unsolicited In-app Notify

Page 36: Android In-app Billing @ Droidcon Murcia

Restore Transactions

Page 37: Android In-app Billing @ Droidcon Murcia

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

Security Controls

• Signed purchase data

• In-app notify nonces

Page 39: Android In-app Billing @ Droidcon Murcia

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
Page 41: Android In-app Billing @ Droidcon Murcia

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

Obligatory image of Justin Bieber to wake you up

Page 43: Android In-app Billing @ Droidcon Murcia

Agenda

• In-app Billing Overview

• Google Play Billing Service

• Android Billing Library

• Security Best Practices

Page 44: Android In-app Billing @ Droidcon Murcia

requestPurchase("com.example.item");

Page 45: Android In-app Billing @ Droidcon Murcia

Android Billing Library!

• Open-source on github

• Better than starting from scratch

tiny.cc/android-billing

Page 46: Android In-app Billing @ Droidcon Murcia

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

Overview

•BillingController

•IBillingObserver

•BillingController.IConfiguration

•ISignatureValidator

Page 48: Android In-app Billing @ Droidcon Murcia

Overview

Page 49: Android In-app Billing @ Droidcon Murcia

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

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

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

Suggested implementation

Page 53: Android In-app Billing @ Droidcon Murcia

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

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

Agenda

• In-app Billing Overview

• Google Play Billing Service

• Android Billing Library

• Security Best Practices

Page 56: Android In-app Billing @ Droidcon Murcia

Best Practices

• Random nonces

• Obfuscate purchase data

• Embedding public key

• Code obfuscation

• Server-side signature validation

Page 57: Android In-app Billing @ Droidcon Murcia

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

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

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

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

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

Thanks!