BLRInAppPurchase is a thin and simple cross-platform wrapper API to wrap and unify the In-App-Purchase funtionality of native app stores. Currenty it supports the Apple App Store (iOS & Mac) and multiple Android stores via OpenIAB (Google Play, SlideMe, Amazon Store, Nokia Store, Samsung Apps, Yandex.Store, Appland, Aptoide, AppMall and Fortumo). Additionally, this library and API have been designed to make it easy to integrate into any project (especially native code based projects) and easy to create language bindings. While designed for Blurrr SDK users, this module has no dependencies and can be integrated into any project.
This library does not introduce any middle-man servers. As a thin wrapper over the native APIs, your app talks directly to the native app stores. You are expected to setup your products on each app store following their rules and procedures.
Core Workflow
Fortunately, there are many similarities between In-App-Purchase APIs, so the API intentionally resembles the APIs provided by Apple Store Kit and Google Play In App Billing v3 and OpenIAB.
Apple developers know the basic workflow as:
- (Start up) SKPaymentQueue addTransactionObserver:
- (Request products) SKProductsRequest start
- listen for callbacks in productsRequest:didReceiveResponse: delegate
- (Purchase) addPayment:SKPayment
- handle callback in paymentQueue:updatedTransactions: delegate
- (finish) SKPaymentQueue finishTransaction:
Google Play developers know the basic workflow as:
- (Startup) mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener()_
- (Request products) mHelper.queryInventoryAsync(true, additionalSkuList, mQueryFinishedListener);
- listen for callbacks in your mQueryFinishedListener
- (Purchase) mHelper.launchPurchaseFlow(..., PurchaseFinishedListener, ...)
- handle callback in your PurchaseFinishedListener
Android OpenIAB users know the basic workflow as:
- (Start up) new OpenIabHelper(this, new OpenIabHelper.Options.Builder()...).startSetup()
- (Request products) iab_helper.queryInventoryAsync(..., QueryInventoryFinishedListener)
- listen for callbacks in your QueryInventoryFinishedListener
- (Purchase) iab_helper.launchPurchaseFlow(..., OnIabPurchaseFinishedListener)
- handle callback in your OnIabPurchaseFinishedListener
BLRInAppPurchase provides a matching workflow as:
- (Start up) BIAP_CreateAppStore(..., your_product_callback, your_transaction_callback, ...); BIAP_StartAppStore();
- (Request products) BIAP_RequestProducts
- listen for callbacks in your_product_callback
- (Purchase) BIAP_PurchaseProduct
- handle callback in your_transaction_callback
- (finish) BIAP_FinishTransaction
Here is an example that fetches, purchases, and consumes a consumable banana product.
#include "SDL.h"
#if defined(__APPLE__)
#import "TargetConditionals.h"
#if TARGET_OS_IPHONE
static const char* s_productIdentifierListNonConsumable[] =
{
"net.playcontrol.InAppTestiOS.nonconsumable.stuffedturtle",
};
static const char* s_productIdentifierListConsumable[] =
{
"net.playcontrol.InAppTestiOS.consumable.banana"
};
static const char* s_productIdentifierListSubscription[] =
{
"net.playcontrol.InAppTestiOS.subscription.dailyprize"
};
#else // TARGET_OS_OSX
static const char* s_productIdentifierListNonConsumable[] =
{
"net.playcontrol.InAppTestMac.nonconsumable.stuffedturtle",
};
static const char* s_productIdentifierListConsumable[] =
{
"net.playcontrol.InAppTestMac.consumable.banana"
};
static const char* s_productIdentifierListSubscription[] =
{
"net.playcontrol.InAppTestMac.subscription.dailyprize"
};
#endif // TARGET_OS_IPHONE
#else // Android and others
static const char* s_productIdentifierListNonConsumable[] =
{
"net.playcontrol.inapptest.nonconsumable.stuffedturtle",
};
static const char* s_productIdentifierListConsumable[] =
{
"net.playcontrol.inapptest.consumable.banana"
};
static const char* s_productIdentifierListSubscription[] =
{
"net.playcontrol.inapptest.subscription.dailyprize"
};
#endif
{
switch(callback_type)
{
{
for(size_t i=0; i<number_of_valid_products; i++)
{
SDL_Log("Product Identifier: %s\n"
"Product Title: %s\n"
"Product Description: %s\n"
"Localized Price: %s\n",
product_identifier,
product_title,
product_description,
localized_price
);
}
for(size_t i=0; i<number_of_invalid_products; i++)
{
SDL_Log("Invalid Product Identifier: %s", invalid_product_identifier);
}
break;
}
{
SDL_Log("BIAP_EVENT_REQUEST_FAILED: %s", error_string);
break;
}
default:
{
SDL_Log("unexpected EventType in MyProductResponseCallback: %d", callback_type);
break;
}
}
}
{
switch(callback_type)
{
{
SDL_Log("App Store Name: %d", app_store_name);
s_productIdentifierListNonConsumable,
sizeof(s_productIdentifierListNonConsumable)
/sizeof(s_productIdentifierListNonConsumable[0]),
s_productIdentifierListConsumable,
sizeof(s_productIdentifierListConsumable)
/sizeof(s_productIdentifierListConsumable[0]),
s_productIdentifierListSubscription,
sizeof(s_productIdentifierListSubscription)
/sizeof(s_productIdentifierListSubscription[0])
);
break;
}
{
SDL_Log("BIAP_EVENT_INIT_FAILED");
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_PURCHASING");
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_PURCHASED");
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_DEFFERED");
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_CONSUMED");
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_CONSUMED_FAILED");
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
SDL_Log("error string: %s", error_string);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORED");
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORE_FINISHED");
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORE_FAILED");
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
SDL_Log("BIAP_EVENT_REQUEST_FAILED: %s", error_string);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_FAILED: %s", error_string);
SDL_Log("BIAP_FailedTransactionType: %d", fail_type);
{
SDL_Log("failed because user cancelled");
}
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RECEIPT_REQUEST_FINISHED receipt path: %s", receipt_path);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RECEIPT_REQUEST_FAILED: %s", error_string);
break;
}
default:
{
SDL_Log("unexpected EventType in MyProductResponseCallback: %d", callback_type);
break;
}
}
}
static bool s_appDone = false;
void main_loop()
{
SDL_Event the_event;
int the_result;
bool got_quit = false;
do
{
the_result = SDL_PollEvent(&the_event);
if(the_result > 0)
{
switch(the_event.type)
{
case SDL_KEYDOWN:
if(the_event == SDLK_AC_BACK)
{
got_quit = true;
}
break;
case SDL_QUIT:
case SDL_APP_TERMINATING:
got_quit = 1;
break;
default:
break;
}
}
} while(the_result > 0);
s_appDone = got_quit;
}
int main(int argc, char* argv[])
{
SDL_Init(SDL_INIT_VIDEO);
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5F8fASyrDFdaXrkoW8kNtwH5JIkLnNuTD5uE1a37TbI5LDZR"
"VgvMIYAtZ9CAHAfLnJ6OEZt0lvLLJSKVuS47VqYVhGZciOkX8TEihONBRwis6i9A3JnKfyqm0iiT+P0CEktOLuFLROIo13"
"utCIO++6h7A7/WLfxNV+Jnxfs9OEHyyPS+MdHxa0wtZGeAGiaN65BymsBQo7J/ABt2DFyMJP1R/nJM45F8yu4D6wSkUNKz"
"s/QbPfvHJQzq56/B/hbx59EkzkInqC567hrlUlX4bU5IvOTF/B1G+UMuKg80m3I1IcQk4FD2D9oJ3E+8IXG/1UdejrOsmq"
"DAzE7LkMl8xwIDAQAB"
);
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs4SI/obW+q3dhsY3g5B6UggPcL5olWK8WY3tnTa2k3i2U40j"
"QuHRNNs8SqzdJeuoBLsKjaEsdTT0SJtEucOMZrprXMch97QtuLB4Mgu3Gs7USL6dM7NCUSoYrgOgw1Koi+ab+ZvFJkVMb9"
"a2EjYzR3aP0k4xjKyG2gW1rIEMMepxHm22VFjEg6YxBy+ecwRrjqDJOAPJyH6uSl8vUT8AKuG+hcCuYbNvlMdEZJo6MXJ9"
"vPNf/qPHwMy5G+faEprL6zR+HaPfxEqN/d8rbrW0qnr8LpXJ+nPB3/irBiMSZSqA222GC7m12sNNmNnNNlI397F3fRQSTz"
"VSRZt14YdPzwIDAQAB"
);
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5idC9c24V7a7qCJu7kdIyOZsk\n"
"W0Rc7/q+K+ujEXsUaAdb5nwmlOJqpoJeCh5Fmq5A1NdF3BwkI8+GwTkH757NBZAS\n"
"SdEuN0pLZmA6LopOiMIy0LoIWknM5eWMa3e41CxCEFoMv48gFIVxDNJ/KAQAX7+K\n"
"ysYzIdlA3W3fBXXyGQIDAQAB"
);
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwRiedByAoS2"
"DUJAkm6qqfhlhNnpNbONf8W4giGERMBAvRA7mRGKa7+vtgJiepvQ/CX0np5MBAMXcL9t9YFZ30lmp4COBdr5nilTyUdLWns"
"cnhYIxneJIG3rzkmnhXaDsiemOlrLC2PEJu6jcek8qurJmQ7gpP0va45MwiTHHto1lSjjvF8xYAZSrTlbIqLo1f98lxg9xs"
"zHI6sSXwDqDpJfS0JORtw3Rcc731QFR1rR2EOAEZo6Zdo0cD1uOQJgLkv8drU9BDMsR9ErBuGSbZQzn2FAc4Bkmq/gNGYd1"
"HmdFkofwVkqu/dTYWXOumKDIVqRsLQ213vuvC0lzcLaJxQIDAQAB"
);
#if defined(__ANDROID__)
void* native_platform_context = SDL_AndroidGetActivity();
#else
void* native_platform_context = NULL;
#endif
s_appDone = false;
while(! s_appDone)
{
main_loop();
}
SDL_Quit();
return 0;
}
Restoring Purchases & Querying Purchases for Consumption:
Apple requires all apps to provide a mechanism that allows users to restore purchases they have made to their device. The intention is to allow users to buy a new device and then transfer their existing purchases to their new device. (This design can work well with Android too.)
Additionally, the Android stores have a different way of dealing with consumable items. On Android, users are not allowed to buy another instance of an item until after they have consumed it. You must call an explicit API to tell the server that an item has been consumed. (This contrasts to Apple which allows users to keep buying consumables and does not track whether a consumable has been used.) To find out which items a user has purchased, and then use the objects to mark them as consumed, the Android stores provide an API to query all the user's purchases.
To make this simple, BLRInAppPurchase unifies the restore and query mechanisms into the single API, BIAP_RestorePurchases. A transaction callback will be invoked for each individual purchase that has been made, using the event type: BIAP_EVENT_TRANSACTION_RESTORED. This is your chance to unlock any content the user is entitled to, or save the object so you can later mark the purchase as consumed. After all purchases have been restored, a final transaction callback with event type BIAP_EVENT_TRANSACTION_RESTORE_FINISHED will be invoked. (If there is an error, BIAP_EVENT_TRANSACTION_RESTORE_FAILED will be invoked.)
Be advised, this may require the user to authenticate depending on the store. For example, Apple typically requires authentication, while Googe Play may not. You may want to conditionalize these requests based on which store you are on to avoid overly annoying users with login prompts, particularly if you are using the API to retrieve consumable item objects for Android stores.
void CallRestore()
{
}
{
switch(callback_type)
{
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORED");
SDL_Log("Restored product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORE_FINISHED");
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORE_FAILED");
SDL_Log("Error string is: %s", error_string);
break;
}
default:
{
HandleOtherTransactionsElsewhere(callback_type, callback_data, user_data);
return;
}
}
}
Thread Safety Notes & SDL:
Some platforms require calling store APIs from the main UI thread. Additionally, platforms are ambiguous about which threads callbacks happen on. To try to simplify things, remove crashes, and make things more cross-platform, BLRInAppPurchase re-routes threads behind the scenes to the main thread.
For Apple:
- All callbacks are redirected to the main UI thread.
For Android:
- All store API calls are invoked from the main UI thread automatically
- All callbacks are redirected to the main UI thread
For the dummy/stub implementation
- Does nothing with threads
For least amount of headaches, it is encouraged you use BLRInAppPurchase from the main UI thread. (Fighting the operating system leads to pain.)
SDL Users: Unfortunately, SDL's implementation for Android is not on the main UI thread. This requires extra steps by you to avoid crashes and pain. Basically, you must push all callbacks into the SDL event queue. This will redirect the callbacks to the SDL thread where it will be safe for you to do things. Fortunately, this solution is compatible for all platforms so you don't need write two different code paths.
As an example of this technique, we will re-implement the example presented at the beginning of this documentation. We just need to modify it change the callbacks to forward the events to the SDL event queue. Then when processing the SDL events, we can call our original callbacks from the original example. To forward events, we will register 2 new event types with SDL.
#include "SDL.h"
#if defined(__APPLE__)
#import "TargetConditionals.h"
#if TARGET_OS_IPHONE
static const char* s_productIdentifierListNonConsumable[] =
{
"net.playcontrol.InAppTestiOS.nonconsumable.stuffedturtle",
};
static const char* s_productIdentifierListConsumable[] =
{
"net.playcontrol.InAppTestiOS.consumable.banana"
};
static const char* s_productIdentifierListSubscription[] =
{
"net.playcontrol.InAppTestiOS.subscription.dailyprize"
};
#else // TARGET_OS_OSX
static const char* s_productIdentifierListNonConsumable[] =
{
"net.playcontrol.InAppTestMac.nonconsumable.stuffedturtle",
};
static const char* s_productIdentifierListConsumable[] =
{
"net.playcontrol.InAppTestMac.consumable.banana"
};
static const char* s_productIdentifierListSubscription[] =
{
"net.playcontrol.InAppTestMac.subscription.dailyprize"
};
#endif // TARGET_OS_IPHONE
#else // Android and others
static const char* s_productIdentifierListNonConsumable[] =
{
"net.playcontrol.inapptest.nonconsumable.stuffedturtle",
};
static const char* s_productIdentifierListConsumable[] =
{
"net.playcontrol.inapptest.consumable.banana"
};
static const char* s_productIdentifierListSubscription[] =
{
"net.playcontrol.inapptest.subscription.dailyprize"
};
#endif
static Uint32 SDL_INAPP_PRODUCT_EVENT;
static Uint32 SDL_INAPP_TRANSACTION_EVENT;
{
SDL_Event sdl_event;
SDL_zero(sdl_event);
sdl_event.type = SDL_INAPP_PRODUCT_EVENT;
sdl_event.user.code = callback_type;
sdl_event.user.data1 = callback_data;
sdl_event.user.data2 = user_data;
SDL_PushEvent(&sdl_event);
}
{
switch(callback_type)
{
{
for(size_t i=0; i<number_of_valid_products; i++)
{
SDL_Log("Product Identifier: %s\n"
"Product Title: %s\n"
"Product Description: %s\n"
"Localized Price: %s\n",
product_identifier,
product_title,
product_description,
localized_price
);
}
for(size_t i=0; i<number_of_invalid_products; i++)
{
SDL_Log("Invalid Product Identifier: %s", invalid_product_identifier);
}
break;
}
{
SDL_Log("BIAP_EVENT_REQUEST_FAILED: %s", error_string);
break;
}
default:
{
SDL_Log("unexpected EventType in MyProductResponseCallback: %d", callback_type);
break;
}
}
}
{
SDL_Event sdl_event;
SDL_zero(sdl_event);
sdl_event.type = SDL_INAPP_TRANSACTION_EVENT;
sdl_event.user.code = callback_type;
sdl_event.user.data1 = callback_data;
sdl_event.user.data2 = user_data;
SDL_PushEvent(&sdl_event);
}
{
switch(callback_type)
{
{
SDL_Log("App Store Name: %d", app_store_name);
s_productIdentifierListNonConsumable,
sizeof(s_productIdentifierListNonConsumable)
/sizeof(s_productIdentifierListNonConsumable[0]),
s_productIdentifierListConsumable,
sizeof(s_productIdentifierListConsumable)
/sizeof(s_productIdentifierListConsumable[0]),
s_productIdentifierListSubscription,
sizeof(s_productIdentifierListSubscription)
/sizeof(s_productIdentifierListSubscription[0])
);
break;
}
{
SDL_Log("BIAP_EVENT_INIT_FAILED");
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_PURCHASING");
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_PURCHASED");
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_DEFFERED");
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_CONSUMED");
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_CONSUMED_FAILED");
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
SDL_Log("error string: %s", error_string);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORED");
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORE_FINISHED");
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORE_FAILED");
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
SDL_Log("BIAP_EVENT_REQUEST_FAILED: %s", error_string);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_FAILED: %s", error_string);
SDL_Log("BIAP_FailedTransactionType: %d", fail_type);
{
SDL_Log("failed because user cancelled");
}
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RECEIPT_REQUEST_FINISHED receipt path: %s", receipt_path);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RECEIPT_REQUEST_FAILED: %s", error_string);
break;
}
default:
{
SDL_Log("unexpected EventType in MyProductResponseCallback: %d", callback_type);
break;
}
}
}
static bool s_appDone = false;
void main_loop()
{
SDL_Event the_event;
int the_result;
bool got_quit = false;
do
{
the_result = SDL_PollEvent(&the_event);
if(the_result > 0)
{
switch(the_event.type)
{
case SDL_KEYDOWN:
if(the_event == SDLK_AC_BACK)
{
got_quit = true;
}
break;
case SDL_QUIT:
case SDL_APP_TERMINATING:
got_quit = 1;
break;
default:
{
if(the_event.type == SDL_INAPP_PRODUCT_EVENT)
{
}
else if(the_event.type == SDL_INAPP_TRANSACTION_EVENT)
{
}
break;
}
}
}
} while(the_result > 0);
s_appDone = got_quit;
}
int main(int argc, char* argv[])
{
SDL_Init(SDL_INIT_VIDEO);
SDL_INAPP_PRODUCT_EVENT = SDL_RegisterEvents(1);
if(SDL_INAPP_PRODUCT_EVENT == ((Uint32)-1))
{
SDL_Log("Could not register In App Purchase/SDL event type");
}
SDL_INAPP_TRANSACTION_EVENT = SDL_RegisterEvents(1);
if(SDL_INAPP_TRANSACTION_EVENT == ((Uint32)-1))
{
SDL_Log("Could not register In App Purchase/SDL event type");
}
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5F8fASyrDFdaXrkoW8kNtwH5JIkLnNuTD5uE1a37TbI5LDZR"
"VgvMIYAtZ9CAHAfLnJ6OEZt0lvLLJSKVuS47VqYVhGZciOkX8TEihONBRwis6i9A3JnKfyqm0iiT+P0CEktOLuFLROIo13"
"utCIO++6h7A7/WLfxNV+Jnxfs9OEHyyPS+MdHxa0wtZGeAGiaN65BymsBQo7J/ABt2DFyMJP1R/nJM45F8yu4D6wSkUNKz"
"s/QbPfvHJQzq56/B/hbx59EkzkInqC567hrlUlX4bU5IvOTF/B1G+UMuKg80m3I1IcQk4FD2D9oJ3E+8IXG/1UdejrOsmq"
"DAzE7LkMl8xwIDAQAB"
);
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs4SI/obW+q3dhsY3g5B6UggPcL5olWK8WY3tnTa2k3i2U40j"
"QuHRNNs8SqzdJeuoBLsKjaEsdTT0SJtEucOMZrprXMch97QtuLB4Mgu3Gs7USL6dM7NCUSoYrgOgw1Koi+ab+ZvFJkVMb9"
"a2EjYzR3aP0k4xjKyG2gW1rIEMMepxHm22VFjEg6YxBy+ecwRrjqDJOAPJyH6uSl8vUT8AKuG+hcCuYbNvlMdEZJo6MXJ9"
"vPNf/qPHwMy5G+faEprL6zR+HaPfxEqN/d8rbrW0qnr8LpXJ+nPB3/irBiMSZSqA222GC7m12sNNmNnNNlI397F3fRQSTz"
"VSRZt14YdPzwIDAQAB"
);
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5idC9c24V7a7qCJu7kdIyOZsk\n"
"W0Rc7/q+K+ujEXsUaAdb5nwmlOJqpoJeCh5Fmq5A1NdF3BwkI8+GwTkH757NBZAS\n"
"SdEuN0pLZmA6LopOiMIy0LoIWknM5eWMa3e41CxCEFoMv48gFIVxDNJ/KAQAX7+K\n"
"ysYzIdlA3W3fBXXyGQIDAQAB"
);
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwRiedByAoS2"
"DUJAkm6qqfhlhNnpNbONf8W4giGERMBAvRA7mRGKa7+vtgJiepvQ/CX0np5MBAMXcL9t9YFZ30lmp4COBdr5nilTyUdLWns"
"cnhYIxneJIG3rzkmnhXaDsiemOlrLC2PEJu6jcek8qurJmQ7gpP0va45MwiTHHto1lSjjvF8xYAZSrTlbIqLo1f98lxg9xs"
"zHI6sSXwDqDpJfS0JORtw3Rcc731QFR1rR2EOAEZo6Zdo0cD1uOQJgLkv8drU9BDMsR9ErBuGSbZQzn2FAc4Bkmq/gNGYd1"
"HmdFkofwVkqu/dTYWXOumKDIVqRsLQ213vuvC0lzcLaJxQIDAQAB"
);
#if defined(__ANDROID__)
void* native_platform_context = SDL_AndroidGetActivity();
#else
void* native_platform_context = NULL;
#endif
s_appDone = false;
while(! s_appDone)
{
main_loop();
}
SDL_Quit();
return 0;
}
Receipts & Validation:
None of the app stores provide an automatic or easy way to do validation because they all fear it would be too easy to crack. Thus they provide nothing and expect you to come up with something yourself if you care about this. Since this library is just a thin wrapper around the native APIs, it provides no more than the underlying stores.
If you choose to implement validation, in all cases, receipts are usually one of the elements used for validation. Apple and Android have very different ways of getting receipts. This library provides separate APIs to allow you to access them since there is not an easy way to unify the techniques. For Apple, use BIAP_RequestReceiptRefresh & BIAP_GetReceiptPath. For Android use BIAP_GetTransactionOriginalJson.
Additional Notes:
- Note
- Each different Android store requires different permissions. The Blurrr build system automatically sets these permissions for you, except for Fortumo which requires additional phone and SMS permissions users may find unacceptable, particularly for those not using Fortumo. Thus you will need to add Fortumo permissions manually to your AndroidManifest.xml if you use Fortumo. See https://github.com/onepf/OpenIAB/wiki/How-To-add-OpenIAB-to-an-app for all the permissions required.
-
For Android, OpenIAB detects how the app was installed. For testing, you must install through the store or use the adb -i flag to specify which store you want to use.
# install for Google Play:
adb install -i com.android.vending YourApp.apk
# install for Amazon SDK Tester:
adb install -i com.amazon.venezia YourApp.apk
# install for SamsungApps:
adb install -i com.sec.android.app.samsungapps YourApp.apk
# install for Nokia Store:
adb install -i com.nokia.payment.iapenabler YourApp.apk
# install for SlideME:
adb install -i com.slideme.sam.manager YourApp.apk
-
Android also requires you modify your Activity's onActivityResult. This is automatically handled for Blurrr SDK users using the standard templates. But for those not using Blurrr, BLRInAppPurchase provides one of two functions you may use. For those working directly in Java, as a convenience, instead of directly calling this C function, you may call this Java method: public static boolean com.blurrrsdk.blrinapppurchase.BLRInterfaceHelper.handleActivityResult(Activity original_activity, int request_code, int result_code, Intent intent_data). For those woring in C, during the onActivityResult, call bool BIAP_Android_HandleOnActivityResult(void* jni_env, void* BIAP_NONNULL original_activity, int32_t request_code, int32_t result_code, void* intent_data);
-
Remember that all the app stores want you to codesign your app.
-
Remember to conform to the rules each app store lays out. For example, Apple requires a way for users to Restore Purchases on their devices.
-
Apple platforms require an NSAutoreleasePool to already be created. Almost all cross-platform libraries such as SDL and IUP automatically do this for you. But if you are integrating BLRInAppPurchase into your own native project, be aware of this requirement.
- See also
- See BLRInAppPurchase.h for the complete API.
Home Page: https://blurrrsdk.com