Question

We developed our app using Titanium Studio and Appcelerator offers a free module for In-App Billing which only support API v2 (I know we should use v3 but we're an indie group so $100 for the module that supports API v3 is a no for the time being).

I've read the documentation on the In-App Billing workflow for API v2, and so far the purchase logic works like a charm.

That is until we got to the part of the refunds. We have managed products and subscriptions in our app.

According to the documentation, when a refund is made, the IN_APP_NOTIFY event is triggered, which requires us to make a getPurchaseInformation call. What we have doubt is what happens after we make the getPurchaseInformation call. Does a PURCHASE_STATE_CHANGED event is triggered? If it is, then the signed data with the orders is returned updated?

For example, let's say a user Uriel buys Managed Product A, then Uriel asks for a refund. This would trigger an IN_APP_NOTIFY event and a getPurchaseInformation call is made.

What event will be triggered so I can update the app to lock the content previously available to the user? Will PURCHASE_STATE_CHANGED be triggered? If it is, then the signed data with the orders information will be returned empty? In my PURCHASE_STATE_CHANGED I validate if there are orders, if no orders are found, then all of the content is blocked.

So in short, is my guessing right or the work flow for refunds is different?

For reference, here are my PURCHASE_STATE_CHANGED function:

function PURCHASE_STATE_CHANGED_EVENT(e)
{
    Ti.API.info('PURCHASE STATE CHANGED CALLED');
    Ti.API.info('Signature Verification Result:\n' + VerificationString(e.result));
    Ti.API.info('Signed Data:\n' + e.signedData);
    /*
     * We receive an object that might look like this
     * {
     *  "nonce":-2458019374247073515,
     *  "orders":[
     *   {
     *    "notificationId":"notificationId",
     *    "orderId":"transactionId",
     *    "packageName":"my.package",
     *    "productId":"in_app_identifier",
     *    "purchaseTime":1394062412214,
     *    "purchaseState":0
     *   }
     *  ]
     * }
     */

    if (e.signedData != null) {
        var response = JSON.parse(e.signedData);
        /*
         * We are not guaranteed to have any orders returned so 
         * we need to make sure that this one exists before using it.
         * 
         * If there is no notificationId then there is no need to confirmNotifications().
         * This happens when restoreTransactions() triggers a PURCHASE_STATE_CHANGED_EVENT.
         */
        if(response.orders.length === 0)
        {
            Ti.API.info('nothing to restore');
            setNoTransactionsToRestore();
            return;
        }
        for(var i = 0; i < response.orders.length; i++)
        {
            if(typeof response.orders[i] !== 'undefined')
            {
                Ti.API.info('set purchase flag');
                setPurchaseFlag(response.orders[i].productId);
            }
            if (response.orders[i] && response.orders[i].notificationId) 
            {

                Ti.API.info('confirming notification for order ' + JSON.stringify(response.orders[i]));
                var synchronousResponse = InAppBilling.confirmNotifications({
                    notificationIds: [response.orders[i].notificationId]
                });
                displaySynchronousResponseCodes(synchronousResponse);
            }
        }
    }
    else
    {
        Ti.API.info('signed data was null');
    }
}

InAppBilling.addEventListener(InAppBilling.PURCHASE_STATE_CHANGED_EVENT, PURCHASE_STATE_CHANGED_EVENT);

and my IN_APP_NOTIFY function:

function NOTIFY_EVENT(e)
{
    Ti.API.info('NOTIFY CALLED \n' + 'Notify Id:\n' + e.notifyId);

    var synchronousResponse = InAppBilling.getPurchaseInformation({
        notificationIds: [e.notifyId]
    });
    displaySynchronousResponseCodes(synchronousResponse);
}
InAppBilling.addEventListener(InAppBilling.NOTIFY_EVENT, NOTIFY_EVENT);
Was it helpful?

Solution

I read a piece of documentation that gives more details on the workflow, especially the IN_APP_NOTIFY event. Here is what I found:

Handling refunds and other unsolicited IN_APP_NOTIFY messages

There are two cases where your application may receive IN_APP_NOTIFY broadcast intents even though your application has not sent a REQUEST_PURCHASE message. Figure 5 shows the messaging sequence for both of these cases. Request types for each sendBillingRequest() method are shown in bold, broadcast intents are shown in italic. For clarity, figure 5 does not show the RESPONSE_CODE broadcast intents that are sent for every request.

Figure 5. Message sequence for refunds and other unsolicited IN_APP_NOTIFY messages.

In the first case, your application may receive an IN_APP_NOTIFY broadcast intent when a user has your application installed on two (or more) devices and the user makes an in-app purchase from one of the devices. In this case, Google Play sends an IN_APP_NOTIFY message to the second device, informing the application that there is a purchase state change. Your application can handle this message the same way it handles the response from an application-initiated REQUEST_PURCHASE message, so that ultimately your application receives a PURCHASE_STATE_CHANGED broadcast intent message that includes information about the item that has been purchased. This applies only to items that have their product type set to "managed per user account."

In the second case, your application can receive an IN_APP_NOTIFY broadcast intent when Google Play receives a refund notification from Google Wallet. In this case, Google Play sends an IN_APP_NOTIFY message to your application. Your application can handle this message the same way it handles responses from an application-initiated REQUEST_PURCHASE message so that ultimately your application receives a PURCHASE_STATE_CHANGED message that includes information about the item that has been refunded. The refund information is included in the JSON string that accompanies the PURCHASE_STATE_CHANGED broadcast intent. Also, the purchaseState field in the JSON string is set to 2.

This means any refund made from the Merchant Center will trigger an IN_APP_NOTIFY, when a getPurchaseInformation call is made, a PURCHASE_STATE_CHANGED event will trigger and the refunded order will no longer appear, which means my validation for no orders will trigger and the content will block.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top