4

I am currently trying to code up in-app purchases. I was looking for documentation, information, tutorials on best practices for some of the stuff Google doesn't handle.

What I have done so far:

I have a Billing service running that handles talking to Google Play. This service can complete the "sample" transactions and my app receives the message.

I want to now delivery the content to the device. What I think needs to happen next:

  1. My app needs to contact my server and show some proof of the successful transaction. Do some Cert hand shaking or some such nonsense.

  2. Then I will download the content and place that content into a database. I should probably encrypt the database with a device unique encryption of some sort.

I'm looking to learn how to do the 2 above things and anything else that needs to be done. I would like a reasonable amount of security/encryption. Any documentation / tutorials / sample projects would be great, I've tried searching for this stuff and haven't found what I was looking for.

naradlov
  • 107
  • 1
  • 9

1 Answers1

3

You'll have to make some changes to the billing service client code from the sample.

First, you should call your server to get the nonce that will be used to either RestoreTransactions or make the purchase, to make things as secure as possible.

Let's follow what happens. Here is the BillingReceiver that gets called by Google Play:

/**
 * This is called when Android Market sends information about a purchase state
 * change. The signedData parameter is a plaintext JSON string that is
 * signed by the server with the developer's private key. The signature
 * for the signed data is passed in the signature parameter.
 * @param context the context
 * @param signedData the (unencrypted) JSON string
 * @param signature the signature for the signedData
 */
private void purchaseStateChanged(Context context, String signedData, String signature) {
    Intent intent = new Intent(Consts.ACTION_PURCHASE_STATE_CHANGED);
    intent.setClass(context, BillingService.class);
    intent.putExtra(Consts.INAPP_SIGNED_DATA, signedData);
    intent.putExtra(Consts.INAPP_SIGNATURE, signature);
    context.startService(intent);
}

If you look at handleCommand within BillingService.java, it handles this intent:

/**
 * The {@link BillingReceiver} sends messages to this service using intents.
 * Each intent has an action and some extra arguments specific to that action.
 * @param intent the intent containing one of the supported actions
 * @param startId an identifier for the invocation instance of this service
 */
public void handleCommand(Intent intent, int startId) {
    String action = intent.getAction();
    if (Consts.DEBUG) {
        Log.i(TAG, "handleCommand() action: " + action);
    }
    if (Consts.ACTION_CONFIRM_NOTIFICATION.equals(action)) {
        String[] notifyIds = intent.getStringArrayExtra(Consts.NOTIFICATION_ID);
        confirmNotifications(startId, notifyIds);
    } else if (Consts.ACTION_GET_PURCHASE_INFORMATION.equals(action)) {
        String notifyId = intent.getStringExtra(Consts.NOTIFICATION_ID);
        getPurchaseInformation(startId, new String[] { notifyId });
    } else if (Consts.ACTION_PURCHASE_STATE_CHANGED.equals(action)) {
        String signedData = intent.getStringExtra(Consts.INAPP_SIGNED_DATA);
        String signature = intent.getStringExtra(Consts.INAPP_SIGNATURE);
        purchaseStateChanged(startId, signedData, signature);
    } else if (Consts.ACTION_RESPONSE_CODE.equals(action)) {
        long requestId = intent.getLongExtra(Consts.INAPP_REQUEST_ID, -1);
        int responseCodeIndex = intent.getIntExtra(Consts.INAPP_RESPONSE_CODE,
                ResponseCode.RESULT_ERROR.ordinal());
        ResponseCode responseCode = ResponseCode.valueOf(responseCodeIndex);
        checkResponseCode(requestId, responseCode);
    }
}

This then calls thePurchaseStateChanged function. This function should be replaced by a call to your server to create a session for your content delivery. The code from Security.java should be ported to the server side to validate the transaction within the cloud.

/**
 * Verifies that the data was signed with the given signature, and calls
 * {@link ResponseHandler#purchaseResponse(Context, PurchaseState, String, String, long)}
 * for each verified purchase.
 * @param startId an identifier for the invocation instance of this service
 * @param signedData the signed JSON string (signed, not encrypted)
 * @param signature the signature for the data, signed with the private key
 */
private void purchaseStateChanged(int startId, String signedData, String signature) {
    ArrayList<Security.VerifiedPurchase> purchases;
    purchases = Security.verifyPurchase(signedData, signature);
    if (purchases == null) {
        return;
    }

    ArrayList<String> notifyList = new ArrayList<String>();
    for (VerifiedPurchase vp : purchases) {
        if (vp.notificationId != null) {
            notifyList.add(vp.notificationId);
        }
        ResponseHandler.purchaseResponse(this, vp.purchaseState, vp.productId,
                vp.orderId, vp.purchaseTime, vp.developerPayload);
    }
    if (!notifyList.isEmpty()) {
        String[] notifyIds = notifyList.toArray(new String[notifyList.size()]);
        confirmNotifications(startId, notifyIds);
    }
}

Make sure to put your public key on the server side in the ported Security.java file.

dagalpin
  • 1,297
  • 8
  • 8
  • Thanks! Few other questions: Do I just dump BillingSecurity.java onto a server somewhere? And then how do I connect to it to get the nounce and compare public keys, etc? – naradlov May 04 '12 at 14:13
  • I've put in on an AppEngine instance before, so that's my experience. You should have a call to your server to save off the nonce into a DB (on the server side) and associate it with the transaction at hand. I usually use a combination of a timestamp and some data I get from the phone, such as ANDROID_ID. Then that nonce gets sent into the billing client. The Security.java module contains the code to validate the signature based upon your public key from the developer dashboard. – dagalpin May 04 '12 at 16:13
  • Thanks, I still have some work to do. This will help out a lot. – naradlov May 04 '12 at 18:55
  • What if I first purchase an item and then after a while I want to download it (app was not reinstalled)? Yes, I can trigger in my app that item is purchased and app will allow do download this content only if it is marked as purchased. But it looks not securely because "purchased" status is stored on client side. – Solvek Oct 25 '12 at 15:46