• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Android Get user's currency

Speederman

Member
Hello, everybody. Is there any way to know which currency a user has when they purchase an item? I mean, iap_product_details returns some info about the item, like the price (as a string) and the title, but I need the ISO 4217 currency code (USD, EUR,...) to send the transaction properly to my analytics provider. Google Play returns that value to the app as price_currency_code, but I can't find any way of accessing that value in GMS' documentation. Does someone know how to do it?

Thanks in advance.
 

Speederman

Member
Finally I got it working on Android... These are the steps I followed:

First of all I edited the GetSkuDetails.java file inside the GooglePlayServicesIAPExtension so whenever we request the items details, it automatically sends the currency code also. This is the full modified file:
Code:
package ${YYAndroidPackageName};

import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;

import java.util.Arrays;
import java.util.ArrayList;

import ${YYAndroidPackageName}.BillingRequest;
import ${YYAndroidPackageName}.RunnerBilling;
import com.yoyogames.runner.RunnerJNILib;

import com.android.vending.billing.IInAppBillingService;

/**
 * Wrapper class that sends a RESTORE_TRANSACTIONS message to the server.
 */
class GetSkuDetails extends BillingRequest
{
    private static final int EVENT_OTHER_SOCIAL = 70;
    String[] mProductIds;
    IRunnerBilling.IBillingCallback mCallback;

    public GetSkuDetails(String[] productIds, IRunnerBilling.IBillingCallback callback)
    {
        mProductIds = productIds;
        mCallback = callback;
    }

    @Override
    protected void run(IInAppBillingService billingService) throws RemoteException
    {            
        ArrayList<String> skuList = new ArrayList<String>(Arrays.asList(mProductIds));
        ArrayList<String> responseList = new ArrayList<String>();

        // getSkuDetails() has a 20 item limit. Not that Google mention this in their documentation at all...
        int responseCode = RunnerBilling.BILLING_RESPONSE_RESULT_OK;
        for (int i = 0; i < skuList.size(); i += 20)
        {
            Log.i("yoyo", "BILLING: getSkuDetails() at index " + i);

            ArrayList<String> limitedSkuList = new ArrayList<String>();
            limitedSkuList.addAll(skuList.subList(i, (i + 20) > skuList.size() ? skuList.size() : i + 20));
                
            Bundle querySkus = new Bundle();
            querySkus.putStringArrayList("ITEM_ID_LIST", limitedSkuList);
            Bundle skuDetails = billingService.getSkuDetails(RunnerBilling.API_VERSION, getPackageName(), "inapp", querySkus);

            if (!skuDetails.containsKey("DETAILS_LIST")) {

                responseCode = getResponseCodeFromBundle(skuDetails);
                if (responseCode != RunnerBilling.BILLING_RESPONSE_RESULT_OK) {
                
                    Log.i("yoyo", "BILLING: getSkuDetails() failed at index " + i + " with response " + RunnerBilling.getResponseDesc(responseCode));                
                }
                else {
                    Log.i("yoyo", "BILLING: getSkuDetails() returned a bundle with neither an error nor a detail list at index " + i + ".");                                    
                }
            }
            else {
                ArrayList<String> detailsList = skuDetails.getStringArrayList("DETAILS_LIST");
                responseList.addAll(detailsList);
            
                if (!detailsList.isEmpty()) {
                   String item0 = detailsList.get(0);
            
                   int dsMapIndex = RunnerJNILib.jCreateDsMap(null, null, null);
                   RunnerJNILib.DsMapAddString( dsMapIndex, "BILLING_ITEM_DETAILS", item0 );
                   RunnerJNILib.CreateAsynEventWithDSMap(dsMapIndex, EVENT_OTHER_SOCIAL);
                   //Log.i("yoyo", "BILLING: BILLING_ITEM_DETAILS " + i + ": " + item0);
                }
            }
        }
        mCallback.onComplete(responseCode, responseList);
    }
}
The last thing to do is to prepare GMS to receive the data. I created an asynchronous social event and added this code:
Code:
if ds_map_exists(async_load, "BILLING_ITEM_DETAILS") //This is the data we get from the extension.
    {
     var details_billing = ds_map_find_value(async_load, "BILLING_ITEM_DETAILS"); //We receive the details of the first item (just enough)
     if !is_undefined(details_billing) and is_string(details_billing) and string_length(details_billing)>10 //Checking...
        {
         var map_item0 = json_decode(details_billing); //and turn them into a map.
         if ds_exists(map_item0, ds_type_map)
           {
            if !ds_map_empty(map_item0)
             {
                var currency_billing = map_item0[? "price_currency_code"]; //This key holds the ISO 4217 currency code.
                if !is_undefined(currency_billing) and is_string(currency_billing)
                   {
                    global.store_currency = currency_billing; //The value can be stored to send it to our analytics provider.
                   }
             }
            ds_map_destroy(map_item0);
           }
        }
    }
 
Last edited:
D

Drewster

Guest
Hey Speederman, this is actually very helpful.

The fact the currency is missing in IAPs is a seriously missing bit. Doing anything smart with analytics really requires it.

Did you happen to make a fixed one for the iOS as well?
 

Speederman

Member
Did you happen to make a fixed one for the iOS as well?
Yes, indeed. I created an iOS extension that retrieves that info also. I called it CurrencyRequestExt.

This is the CurrencyRequestExt.h file:
Code:
// Block definition
#import <StoreKit/StoreKit.h>

@interface currencyRequestExt : NSObject <SKProductsRequestDelegate>
{
    NSString *CurrencyCode;
}

- (void) getCurrencyIOS:(char *)_itemTienda;
- (void) productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response;
@end
And here you have the CurrencyRequestExt.mm file:
Code:
#import "currencyRequestExt.h"

extern "C" void dsMapClear(int _dsMap );
extern "C" int dsMapCreate();
extern "C" void dsMapAddInt(int _dsMap, char* _key, int _value);
extern "C" void dsMapAddString(int _dsMap, char* _key, char* _value);
extern "C" int dsListCreate();
extern "C" void dsListAddInt(int _dsList, int _value);
extern "C" void dsListAddString(int _dsList, char* _value);

extern "C" void createSocialAsyncEventWithDSMap(int dsmapindex);

@implementation currencyRequestExt

- (void) getCurrencyIOS:(char *)_itemTienda
{
     NSString *itemString = [NSString stringWithUTF8String:_itemTienda];
     NSSet *PRODUCT_ID = [NSSet setWithObjects: itemString, nil];
     SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers:PRODUCT_ID];
     request.delegate = self;
     [request start];
}

- (void) productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
  NSArray *myProducts = response.products;
  for (SKProduct* product in myProducts) {
  NSLocale* storeLocale = product.priceLocale;
  CurrencyCode = (NSString*)CFLocaleGetValue((CFLocaleRef)storeLocale, kCFLocaleCurrencyCode);
  NSLog(@"Currency Code = %@", CurrencyCode);
  }
 
  [request release];
 
  if (CurrencyCode != nil) {
  int dsMapIndex = dsMapCreate();
  dsMapAddString(dsMapIndex, (char *)"storeCurrency", (char *)[CurrencyCode UTF8String]);
  createSocialAsyncEventWithDSMap(dsMapIndex);
  }
}
@end
All you have to do is to create the extension with those 2 files and add a function that accesses to the external getCurrencyIOS one.

Captura de pantalla 2017-08-16 a las 22.35.00.png

Keep in mind that you must call it passing the id of any of your store iap items as an argument. For example: getCurrencyIOS("super_booster").

And this is the asynchronous social event to receive the value:
Code:
var ios_currency = ds_map_find_value(async_load, "storeCurrency");
if !is_undefined(ios_currency) and is_string(ios_currency) and string_length(ios_currency)>2
       global.store_currency = ios_currency;
 
Last edited:
D

Drewster

Guest
speeder man -- thank you! You really saved me a lot of hassle!
I am really surprised nobody has chased this down previously. If you want to do anything useful with analytics, you want this info.

By the way, your Aliens in Chains looks really good!

The video on the website should probably auto-play.
 

Speederman

Member
You're welcome. A good review for Aliens in Chains on the stores could be enough reward... :p

And thank you for the video suggestion. We are going to make a better video and we'll surely activate the auto play then...
 
S

signal

Guest
Thanks for doing the grunt work on this!

// getSkuDetails() has a 20 item limit. Not that Google mention this in their documentation at all...
Lol at the YoYo dev calling out Google for lack of documentation..

Oh and your game looks very cool.. downloaded and ready to play.
 

clee2005

Member
@Speederman thank you very much for your extension! Exactly what I needed as well. I downloaded and enjoyed your game! Rated it 5 stars... well deserved.

Cheers,
Chris
 
Top