BLRInAppPurchase  1.0.0
BLRInAppPurchase Documentation

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:

Google Play developers know the basic workflow as:

Android OpenIAB users know the basic workflow as:

BLRInAppPurchase provides a matching workflow as:

Here is an example that fetches, purchases, and consumes a consumable banana product.

#include "SDL.h" // For SDL_Log and a cross-platform event-loop
// Unfortunately, Mac and iOS are not allowed to share the same product identifiers.
// We could use a macro trick to make this less repetitive, but macro tricks confuse most people.
#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
void MyProductResponseCallback(enum BIAP_EventType callback_type, struct BIAP_ProductResponseData* callback_data, void* user_data)
{
switch(callback_type)
{
{
size_t number_of_valid_products = BIAP_GetProductsListCount(callback_data);
for(size_t i=0; i<number_of_valid_products; i++)
{
struct BIAP_Product* current_product = BIAP_GetProductInList(callback_data, i);
const char* product_identifier = BIAP_GetProductIdentifier(callback_data, current_product);
const char* product_title = BIAP_GetProductTitle(callback_data, current_product);
const char* product_description = BIAP_GetProductDescription(callback_data, current_product);
const char* localized_price = BIAP_GetProductLocalizedPrice(callback_data, current_product);
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
);
}
size_t number_of_invalid_products = BIAP_GetInvalidProductsListCount(callback_data);
for(size_t i=0; i<number_of_invalid_products; i++)
{
const char* invalid_product_identifier = BIAP_GetInvalidProductIdentifier(callback_data, i);
SDL_Log("Invalid Product Identifier: %s", invalid_product_identifier);
}
// Let's purchase the banana
BIAP_Product* banana_product = BIAP_GetProductWithIdentifier(callback_data, s_productIdentifierListConsumable[0]);
BIAP_PurchaseProduct(callback_data, banana_product);
break;
}
{
const char* error_string = BIAP_GetProductErrorString(callback_data);
SDL_Log("BIAP_EVENT_REQUEST_FAILED: %s", error_string);
break;
}
default:
{
SDL_Log("unexpected EventType in MyProductResponseCallback: %d", callback_type);
break;
}
}
// Always remember to free the callback data when you are done with it.
}
void MyTransactionResponseCallback(enum BIAP_EventType callback_type, struct BIAP_TransactionResponseData* callback_data, void* user_data)
{
switch(callback_type)
{
{
BIAP_AppStore* app_store = BIAP_GetTransactionAppStore(callback_data);
enum BIAP_StoreName app_store_name = BIAP_GetAppStoreName(app_store);
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");
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_PURCHASED");
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
BIAP_FinishTransaction(callback_data);
// Let's immediately consume the banana.
// (Note: If we wanted to consume the banana later
// and didn't have this callback data, we could call BIAP_RestorePurchases)
BIAP_ConsumePurchase(callback_data);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_DEFFERED");
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_CONSUMED");
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
BIAP_FinishTransaction(callback_data);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_CONSUMED_FAILED");
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
const char* error_string = BIAP_GetTransactionErrorString(callback_data);
SDL_Log("error string: %s", error_string);
BIAP_FinishTransaction(callback_data);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORED");
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
BIAP_FinishTransaction(callback_data);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORE_FINISHED");
BIAP_FinishTransaction(callback_data);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORE_FAILED");
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
BIAP_FinishTransaction(callback_data);
break;
}
{
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
const char* error_string = BIAP_GetTransactionErrorString(callback_data);
SDL_Log("BIAP_EVENT_REQUEST_FAILED: %s", error_string);
break;
}
{
const char* error_string = BIAP_GetTransactionErrorString(callback_data);
SDL_Log("BIAP_EVENT_TRANSACTION_FAILED: %s", error_string);
SDL_Log("BIAP_FailedTransactionType: %d", fail_type);
{
SDL_Log("failed because user cancelled");
}
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
BIAP_FinishTransaction(callback_data);
break;
}
{
BIAP_AppStore* app_store = BIAP_GetTransactionAppStore(callback_data);
const char* receipt_path = BIAP_GetReceiptPath(app_store);
SDL_Log("BIAP_EVENT_TRANSACTION_RECEIPT_REQUEST_FINISHED receipt path: %s", receipt_path);
break;
}
{
const char* error_string = BIAP_GetTransactionErrorString(callback_data);
SDL_Log("BIAP_EVENT_TRANSACTION_RECEIPT_REQUEST_FAILED: %s", error_string);
break;
}
default:
{
SDL_Log("unexpected EventType in MyProductResponseCallback: %d", callback_type);
break;
}
}
// Always remember to free the callback data when you are done with it.
}
static bool s_appDone = false;
void main_loop()
{
SDL_Event the_event;
int the_result;
bool got_quit = false;
// Pump event loop
do
{
the_result = SDL_PollEvent(&the_event);
if(the_result > 0)
{
switch(the_event.type)
{
case SDL_KEYDOWN:
// Android back key
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
BIAP_AppStore* app_store = BIAP_CreateAppStore(BIAP_STORE_DEFAULT, native_platform_context, MyProductResponseCallback, MyTransactionResponseCallback, NULL);
BIAP_StartAppStore(app_store);
s_appDone = false; // reset global/static for to avoid Android NDK relaunch problems
while(! s_appDone)
{
main_loop();
}
BIAP_FreeAppStore(app_store);
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.

// somehow your program calls this
void CallRestore()
{
// g_appStore is a global created elsewhere (not shown for brevity)
// watch for the restores in MyTransactionResponseCallback
BIAP_RestorePurchases(g_appStore);
}
void MyTransactionResponseCallback(enum BIAP_EventType callback_type, struct BIAP_TransactionResponseData* callback_data, void* user_data)
{
switch(callback_type)
{
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORED");
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("Restored product identifier: %s", product_identifier);
// unlock the user's content here,
// or consume the object (BIAP_ConsumePurchase)
// or save the object for later (don't BIAP_FreeTransactionResponseData)
// tell the app store that we finished restoring this item
BIAP_FinishTransaction(callback_data);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORE_FINISHED");
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORE_FAILED");
const char* error_string = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("Error string is: %s", error_string);
// Since this failed, we may consider not calling BIAP_FinishTransaction so the Apple App Store will automatically keep trying to restore. But this code is left here to make you think about it.
BIAP_FinishTransaction(callback_data);
break;
}
default:
{
// other cases omitted for brevity
HandleOtherTransactionsElsewhere(callback_type, callback_data, user_data);
return;
}
}
// Always remember to free the callback data when you are done with it. In our case, we are done.
}

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:

For Android:

For the dummy/stub implementation

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" // For SDL_Log and a cross-platform event-loop
// Unfortunately, Mac and iOS are not allowed to share the same product identifiers.
// We could use a macro trick to make this less repetitive, but macro tricks confuse most people.
#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
// These get assigned at start up by SDL_RegisterEvents()
static Uint32 SDL_INAPP_PRODUCT_EVENT;
static Uint32 SDL_INAPP_TRANSACTION_EVENT;
void MyMainThreadProductResponseCallback(enum BIAP_EventType callback_type, struct BIAP_ProductResponseData* callback_data, void* user_data)
{
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);
}
void MySDLThreadProductResponseCallback(enum BIAP_EventType callback_type, struct BIAP_ProductResponseData* callback_data, void* user_data)
{
switch(callback_type)
{
{
size_t number_of_valid_products = BIAP_GetProductsListCount(callback_data);
for(size_t i=0; i<number_of_valid_products; i++)
{
struct BIAP_Product* current_product = BIAP_GetProductInList(callback_data, i);
const char* product_identifier = BIAP_GetProductIdentifier(callback_data, current_product);
const char* product_title = BIAP_GetProductTitle(callback_data, current_product);
const char* product_description = BIAP_GetProductDescription(callback_data, current_product);
const char* localized_price = BIAP_GetProductLocalizedPrice(callback_data, current_product);
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
);
}
size_t number_of_invalid_products = BIAP_GetInvalidProductsListCount(callback_data);
for(size_t i=0; i<number_of_invalid_products; i++)
{
const char* invalid_product_identifier = BIAP_GetInvalidProductIdentifier(callback_data, i);
SDL_Log("Invalid Product Identifier: %s", invalid_product_identifier);
}
// Let's purchase the banana
BIAP_Product* banana_product = BIAP_GetProductWithIdentifier(callback_data, s_productIdentifierListConsumable[0]);
BIAP_PurchaseProduct(callback_data, banana_product);
break;
}
{
const char* error_string = BIAP_GetProductErrorString(callback_data);
SDL_Log("BIAP_EVENT_REQUEST_FAILED: %s", error_string);
break;
}
default:
{
SDL_Log("unexpected EventType in MyProductResponseCallback: %d", callback_type);
break;
}
}
// Always remember to free the callback data when you are done with it.
}
void MyMainThreadTransactionResponseCallback(enum BIAP_EventType callback_type, struct BIAP_TransactionResponseData* callback_data, void* user_data)
{
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);
}
void MySDLThreadTransactionResponseCallback(enum BIAP_EventType callback_type, struct BIAP_TransactionResponseData* callback_data, void* user_data)
{
switch(callback_type)
{
{
BIAP_AppStore* app_store = BIAP_GetTransactionAppStore(callback_data);
enum BIAP_StoreName app_store_name = BIAP_GetAppStoreName(app_store);
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");
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_PURCHASED");
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
BIAP_FinishTransaction(callback_data);
// Let's immediately consume the banana.
// (Note: If we wanted to consume the banana later
// and didn't have this callback data, we could call BIAP_RestorePurchases)
BIAP_ConsumePurchase(callback_data);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_DEFFERED");
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_CONSUMED");
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
BIAP_FinishTransaction(callback_data);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_CONSUMED_FAILED");
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
const char* error_string = BIAP_GetTransactionErrorString(callback_data);
SDL_Log("error string: %s", error_string);
BIAP_FinishTransaction(callback_data);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORED");
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
BIAP_FinishTransaction(callback_data);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORE_FINISHED");
BIAP_FinishTransaction(callback_data);
break;
}
{
SDL_Log("BIAP_EVENT_TRANSACTION_RESTORE_FAILED");
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
BIAP_FinishTransaction(callback_data);
break;
}
{
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
const char* error_string = BIAP_GetTransactionErrorString(callback_data);
SDL_Log("BIAP_EVENT_REQUEST_FAILED: %s", error_string);
break;
}
{
const char* error_string = BIAP_GetTransactionErrorString(callback_data);
SDL_Log("BIAP_EVENT_TRANSACTION_FAILED: %s", error_string);
SDL_Log("BIAP_FailedTransactionType: %d", fail_type);
{
SDL_Log("failed because user cancelled");
}
const char* product_identifier = BIAP_GetTransactionProductIdentifier(callback_data);
SDL_Log("In MyTransactionResponseCallback product identifier: %s", product_identifier);
BIAP_FinishTransaction(callback_data);
break;
}
{
BIAP_AppStore* app_store = BIAP_GetTransactionAppStore(callback_data);
const char* receipt_path = BIAP_GetReceiptPath(app_store);
SDL_Log("BIAP_EVENT_TRANSACTION_RECEIPT_REQUEST_FINISHED receipt path: %s", receipt_path);
break;
}
{
const char* error_string = BIAP_GetTransactionErrorString(callback_data);
SDL_Log("BIAP_EVENT_TRANSACTION_RECEIPT_REQUEST_FAILED: %s", error_string);
break;
}
default:
{
SDL_Log("unexpected EventType in MyProductResponseCallback: %d", callback_type);
break;
}
}
// Always remember to free the callback data when you are done with it.
}
static bool s_appDone = false;
void main_loop()
{
SDL_Event the_event;
int the_result;
bool got_quit = false;
// Pump event loop
do
{
the_result = SDL_PollEvent(&the_event);
if(the_result > 0)
{
switch(the_event.type)
{
case SDL_KEYDOWN:
// Android back key
if(the_event == SDLK_AC_BACK)
{
got_quit = true;
}
break;
case SDL_QUIT:
case SDL_APP_TERMINATING:
got_quit = 1;
break;
default:
{
// Because SDL_INAPP_PRODUCT_EVENT and SDL_INAPP_TRANSACTION_EVENT are not compile-time constants, we must use if-checks
if(the_event.type == SDL_INAPP_PRODUCT_EVENT)
{
MySDLThreadProductResponseCallback((BIAP_EventType)event.user.code, (BIAP_ProductResponseData*)event.user.data1, event.user.data2);
}
else if(the_event.type == SDL_INAPP_TRANSACTION_EVENT)
{
MySDLThreadTransactionResponseCallback((BIAP_EventType)event.user.code, (BIAP_TransactionResponseData*)event.user.data1, event.user.data2);
}
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
BIAP_AppStore* app_store = BIAP_CreateAppStore(BIAP_STORE_DEFAULT, native_platform_context, MyMainThreadProductResponseCallback, MyMainThreadTransactionResponseCallback, NULL);
BIAP_StartAppStore(app_store);
s_appDone = false; // reset global/static for to avoid Android NDK relaunch problems
while(! s_appDone)
{
main_loop();
}
BIAP_FreeAppStore(app_store);
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