Android in-app billing @ Google DevFest Barcelona 2012
Upcoming SlideShare
Loading in...5
×
 

Android in-app billing @ Google DevFest Barcelona 2012

on

  • 1,508 views

 

Statistics

Views

Total Views
1,508
Views on SlideShare
1,496
Embed Views
12

Actions

Likes
2
Downloads
37
Comments
1

2 Embeds 12

https://twitter.com 10
https://si0.twimg.com 2

Accessibility

Categories

Upload Details

Uploaded via as Apple Keynote

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

11 of 1

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • POR FAVOR ESTA PROPUESTA DE NEGOCIO, ESCRIBA EN MI ESPALDA ID si está interesado.
    ------------------------------------

    Feliz mes nuevo abundante de noviembre,

    Hola.

    ¿Cómo estás hoy?
    Espero que estés bien y que todo está bien con usted? gracias God.My nombre es jenifer PETERSON. (estoy buscando una buena relación y además que tenga propuesta de negocios con usted) si lo desea. por favor, escríbeme mensaje a mi buzón de correo electrónico
    Thanks,>

    jeniferpeterson1 en / yh / dt / cum
    ---------------------

    PLEASE THIS BUSINESS PROPOSAL, WRITE ON MY ID BACK IF INTERESTED.
    ------------------------------------

    Happy abundant new month of November,

    Hello.

    how are you today?
    I hope you are fine and all is well with you ? thank God.My name is JENIFER PETERSON .(i am looking for a good relationship and also to have business proposal with you )if you want. please write me message to my email box
    THANKS,>

    jeniferpeterson1 at / yh / dt / cum
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n

Android in-app billing @ Google DevFest Barcelona 2012 Android in-app billing @ Google DevFest Barcelona 2012 Presentation Transcript

  • @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 Grossingis dominated by IAB
  • Freemium Digital goodsVirtual 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 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);}
  • 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 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;}
  • Making a requestBundle 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 SupportedParameters SharedSync response keys RESPONSE_CODE RESULT_OK RESULT_BILLING_UNAVAILABLEResponse codes RESULT_ERROR RESULT_DEVELOPER_ERRORAsync response RESPONSE_CODE
  • Request Purchase
  • 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
  • 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
  • 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 SharedParameters NONCE NOTIFY_IDS RESPONSE_CODESync response keys REQUEST_ID RESULT_OKResponse codes RESULT_ERROR RESULT_DEVELOPER_ERRORAsync 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 BasicParameters NONCE RESPONSE_CODESync response keys REQUEST_ID RESULT_OKResponse codes RESULT_ERROR RESULT_DEVELOPER_ERROR RESPONSE_CODEAsync 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
  • 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 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) {}
  • 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();}
  • Suggestedimplementation
  • 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>
  • 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!