mirror of
https://github.com/defold/extension-iap
synced 2025-06-27 10:27:46 +02:00
Merge pull request #10 from defold/ios-thread-queue
Added thread safe command queue for iOS
This commit is contained in:
commit
eedfb80a6b
@ -9,22 +9,6 @@
|
|||||||
|
|
||||||
#define LIB_NAME "iap"
|
#define LIB_NAME "iap"
|
||||||
|
|
||||||
struct IAP;
|
|
||||||
|
|
||||||
#define CMD_PRODUCT_RESULT (0)
|
|
||||||
#define CMD_PURCHASE_RESULT (1)
|
|
||||||
|
|
||||||
struct DM_ALIGNED(16) IAPCommand
|
|
||||||
{
|
|
||||||
IAPCommand()
|
|
||||||
{
|
|
||||||
memset(this, 0, sizeof(IAPCommand));
|
|
||||||
}
|
|
||||||
uint32_t m_Command;
|
|
||||||
int32_t m_ResponseCode;
|
|
||||||
void* m_Data1;
|
|
||||||
};
|
|
||||||
|
|
||||||
static JNIEnv* Attach()
|
static JNIEnv* Attach()
|
||||||
{
|
{
|
||||||
JNIEnv* env;
|
JNIEnv* env;
|
||||||
@ -50,44 +34,31 @@ struct IAP
|
|||||||
m_autoFinishTransactions = true;
|
m_autoFinishTransactions = true;
|
||||||
m_ProviderId = PROVIDER_ID_GOOGLE;
|
m_ProviderId = PROVIDER_ID_GOOGLE;
|
||||||
}
|
}
|
||||||
int m_InitCount;
|
int m_InitCount;
|
||||||
int m_Callback;
|
int m_Callback;
|
||||||
int m_Self;
|
int m_Self;
|
||||||
bool m_autoFinishTransactions;
|
bool m_autoFinishTransactions;
|
||||||
int m_ProviderId;
|
int m_ProviderId;
|
||||||
lua_State* m_L;
|
lua_State* m_L;
|
||||||
IAPListener m_Listener;
|
IAPListener m_Listener;
|
||||||
|
|
||||||
jobject m_IAP;
|
jobject m_IAP;
|
||||||
jobject m_IAPJNI;
|
jobject m_IAPJNI;
|
||||||
jmethodID m_List;
|
jmethodID m_List;
|
||||||
jmethodID m_Stop;
|
jmethodID m_Stop;
|
||||||
jmethodID m_Buy;
|
jmethodID m_Buy;
|
||||||
jmethodID m_Restore;
|
jmethodID m_Restore;
|
||||||
jmethodID m_ProcessPendingConsumables;
|
jmethodID m_ProcessPendingConsumables;
|
||||||
jmethodID m_FinishTransaction;
|
jmethodID m_FinishTransaction;
|
||||||
|
|
||||||
dmArray<IAPCommand> m_CommandsQueue;
|
IAPCommandQueue m_CommandQueue;
|
||||||
dmMutex::HMutex m_Mutex;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static IAP g_IAP;
|
static IAP g_IAP;
|
||||||
|
|
||||||
static void QueueCommand(IAPCommand* cmd)
|
static void ResetCallback(lua_State* L)
|
||||||
{
|
|
||||||
DM_MUTEX_SCOPED_LOCK(g_IAP.m_Mutex);
|
|
||||||
|
|
||||||
if(g_IAP.m_CommandsQueue.Full())
|
|
||||||
{
|
|
||||||
g_IAP.m_CommandsQueue.OffsetCapacity(2);
|
|
||||||
}
|
|
||||||
g_IAP.m_CommandsQueue.Push(*cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void VerifyCallback(lua_State* L)
|
|
||||||
{
|
{
|
||||||
if (g_IAP.m_Callback != LUA_NOREF) {
|
if (g_IAP.m_Callback != LUA_NOREF) {
|
||||||
dmLogError("Unexpected callback set");
|
|
||||||
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Callback);
|
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Callback);
|
||||||
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Self);
|
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Self);
|
||||||
g_IAP.m_Callback = LUA_NOREF;
|
g_IAP.m_Callback = LUA_NOREF;
|
||||||
@ -99,7 +70,7 @@ static void VerifyCallback(lua_State* L)
|
|||||||
static int IAP_List(lua_State* L)
|
static int IAP_List(lua_State* L)
|
||||||
{
|
{
|
||||||
int top = lua_gettop(L);
|
int top = lua_gettop(L);
|
||||||
VerifyCallback(L);
|
ResetCallback(L);
|
||||||
|
|
||||||
char* buf = IAP_List_CreateBuffer(L);
|
char* buf = IAP_List_CreateBuffer(L);
|
||||||
if( buf == 0 )
|
if( buf == 0 )
|
||||||
@ -267,14 +238,14 @@ JNIEXPORT void JNICALL Java_com_defold_iap_IapJNI_onProductsResult__ILjava_lang_
|
|||||||
}
|
}
|
||||||
|
|
||||||
IAPCommand cmd;
|
IAPCommand cmd;
|
||||||
cmd.m_Command = CMD_PRODUCT_RESULT;
|
cmd.m_Command = IAP_PRODUCT_RESULT;
|
||||||
cmd.m_ResponseCode = responseCode;
|
cmd.m_ResponseCode = responseCode;
|
||||||
if (pl)
|
if (pl)
|
||||||
{
|
{
|
||||||
cmd.m_Data1 = strdup(pl);
|
cmd.m_Data = strdup(pl);
|
||||||
env->ReleaseStringUTFChars(productList, pl);
|
env->ReleaseStringUTFChars(productList, pl);
|
||||||
}
|
}
|
||||||
QueueCommand(&cmd);
|
IAP_Queue_Push(&g_IAP.m_CommandQueue, &cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_com_defold_iap_IapJNI_onPurchaseResult__ILjava_lang_String_2(JNIEnv* env, jobject, jint responseCode, jstring purchaseData)
|
JNIEXPORT void JNICALL Java_com_defold_iap_IapJNI_onPurchaseResult__ILjava_lang_String_2(JNIEnv* env, jobject, jint responseCode, jstring purchaseData)
|
||||||
@ -286,14 +257,14 @@ JNIEXPORT void JNICALL Java_com_defold_iap_IapJNI_onPurchaseResult__ILjava_lang_
|
|||||||
}
|
}
|
||||||
|
|
||||||
IAPCommand cmd;
|
IAPCommand cmd;
|
||||||
cmd.m_Command = CMD_PURCHASE_RESULT;
|
cmd.m_Command = IAP_PURCHASE_RESULT;
|
||||||
cmd.m_ResponseCode = responseCode;
|
cmd.m_ResponseCode = responseCode;
|
||||||
if (pd)
|
if (pd)
|
||||||
{
|
{
|
||||||
cmd.m_Data1 = strdup(pd);
|
cmd.m_Data = strdup(pd);
|
||||||
env->ReleaseStringUTFChars(purchaseData, pd);
|
env->ReleaseStringUTFChars(purchaseData, pd);
|
||||||
}
|
}
|
||||||
QueueCommand(&cmd);
|
IAP_Queue_Push(&g_IAP.m_CommandQueue, &cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
@ -327,7 +298,7 @@ static void HandleProductResult(const IAPCommand* cmd)
|
|||||||
|
|
||||||
if (cmd->m_ResponseCode == BILLING_RESPONSE_RESULT_OK) {
|
if (cmd->m_ResponseCode == BILLING_RESPONSE_RESULT_OK) {
|
||||||
dmJson::Document doc;
|
dmJson::Document doc;
|
||||||
dmJson::Result r = dmJson::Parse((const char*) cmd->m_Data1, &doc);
|
dmJson::Result r = dmJson::Parse((const char*) cmd->m_Data, &doc);
|
||||||
if (r == dmJson::RESULT_OK && doc.m_NodeCount > 0) {
|
if (r == dmJson::RESULT_OK && doc.m_NodeCount > 0) {
|
||||||
char err_str[128];
|
char err_str[128];
|
||||||
if (dmScript::JsonToLua(L, &doc, 0, err_str, sizeof(err_str)) < 0) {
|
if (dmScript::JsonToLua(L, &doc, 0, err_str, sizeof(err_str)) < 0) {
|
||||||
@ -390,9 +361,9 @@ static void HandlePurchaseResult(const IAPCommand* cmd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cmd->m_ResponseCode == BILLING_RESPONSE_RESULT_OK) {
|
if (cmd->m_ResponseCode == BILLING_RESPONSE_RESULT_OK) {
|
||||||
if (cmd->m_Data1 != 0) {
|
if (cmd->m_Data != 0) {
|
||||||
dmJson::Document doc;
|
dmJson::Document doc;
|
||||||
dmJson::Result r = dmJson::Parse((const char*) cmd->m_Data1, &doc);
|
dmJson::Result r = dmJson::Parse((const char*) cmd->m_Data, &doc);
|
||||||
if (r == dmJson::RESULT_OK && doc.m_NodeCount > 0) {
|
if (r == dmJson::RESULT_OK && doc.m_NodeCount > 0) {
|
||||||
char err_str[128];
|
char err_str[128];
|
||||||
if (dmScript::JsonToLua(L, &doc, 0, err_str, sizeof(err_str)) < 0) {
|
if (dmScript::JsonToLua(L, &doc, 0, err_str, sizeof(err_str)) < 0) {
|
||||||
@ -436,8 +407,7 @@ static dmExtension::Result InitializeIAP(dmExtension::Params* params)
|
|||||||
// TODO: Life-cycle managaemnt is *budget*. No notion of "static initalization"
|
// TODO: Life-cycle managaemnt is *budget*. No notion of "static initalization"
|
||||||
// Extend extension functionality with per system initalization?
|
// Extend extension functionality with per system initalization?
|
||||||
if (g_IAP.m_InitCount == 0) {
|
if (g_IAP.m_InitCount == 0) {
|
||||||
g_IAP.m_CommandsQueue.SetCapacity(2);
|
IAP_Queue_Create(&g_IAP.m_CommandQueue);
|
||||||
g_IAP.m_Mutex = dmMutex::New();
|
|
||||||
|
|
||||||
g_IAP.m_autoFinishTransactions = dmConfigFile::GetInt(params->m_ConfigFile, "iap.auto_finish_transactions", 1) == 1;
|
g_IAP.m_autoFinishTransactions = dmConfigFile::GetInt(params->m_ConfigFile, "iap.auto_finish_transactions", 1) == 1;
|
||||||
|
|
||||||
@ -499,42 +469,35 @@ static dmExtension::Result InitializeIAP(dmExtension::Params* params)
|
|||||||
return dmExtension::RESULT_OK;
|
return dmExtension::RESULT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void IAP_OnCommand(IAPCommand* cmd, void*)
|
||||||
|
{
|
||||||
|
switch (cmd->m_Command)
|
||||||
|
{
|
||||||
|
case IAP_PRODUCT_RESULT:
|
||||||
|
HandleProductResult(cmd);
|
||||||
|
break;
|
||||||
|
case IAP_PURCHASE_RESULT:
|
||||||
|
HandlePurchaseResult(cmd);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd->m_Data) {
|
||||||
|
free(cmd->m_Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static dmExtension::Result UpdateIAP(dmExtension::Params* params)
|
static dmExtension::Result UpdateIAP(dmExtension::Params* params)
|
||||||
{
|
{
|
||||||
if (g_IAP.m_CommandsQueue.Empty())
|
IAP_Queue_Flush(&g_IAP.m_CommandQueue, IAP_OnCommand, 0);
|
||||||
{
|
|
||||||
return dmExtension::RESULT_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
DM_MUTEX_SCOPED_LOCK(g_IAP.m_Mutex);
|
|
||||||
|
|
||||||
for(uint32_t i = 0; i != g_IAP.m_CommandsQueue.Size(); ++i)
|
|
||||||
{
|
|
||||||
IAPCommand& cmd = g_IAP.m_CommandsQueue[i];
|
|
||||||
switch (cmd.m_Command)
|
|
||||||
{
|
|
||||||
case CMD_PRODUCT_RESULT:
|
|
||||||
HandleProductResult(&cmd);
|
|
||||||
break;
|
|
||||||
case CMD_PURCHASE_RESULT:
|
|
||||||
HandlePurchaseResult(&cmd);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
assert(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cmd.m_Data1) {
|
|
||||||
free(cmd.m_Data1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_IAP.m_CommandsQueue.SetSize(0);
|
|
||||||
return dmExtension::RESULT_OK;
|
return dmExtension::RESULT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static dmExtension::Result FinalizeIAP(dmExtension::Params* params)
|
static dmExtension::Result FinalizeIAP(dmExtension::Params* params)
|
||||||
{
|
{
|
||||||
dmMutex::Delete(g_IAP.m_Mutex);
|
IAP_Queue_Destroy(&g_IAP.m_CommandQueue);
|
||||||
--g_IAP.m_InitCount;
|
--g_IAP.m_InitCount;
|
||||||
|
|
||||||
if (params->m_L == g_IAP.m_Listener.m_L && g_IAP.m_Listener.m_Callback != LUA_NOREF) {
|
if (params->m_L == g_IAP.m_Listener.m_L && g_IAP.m_Listener.m_Callback != LUA_NOREF) {
|
||||||
|
@ -17,165 +17,208 @@ struct IAP;
|
|||||||
@property IAP* m_IAP;
|
@property IAP* m_IAP;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
/*# In-app purchases API documentation
|
|
||||||
*
|
|
||||||
* Functions and constants for interacting with Apple's In-app purchases
|
|
||||||
* and Google's In-app billing.
|
|
||||||
*
|
|
||||||
* @document
|
|
||||||
* @name In-app purchases
|
|
||||||
* @namespace iap
|
|
||||||
*/
|
|
||||||
|
|
||||||
struct IAP
|
struct IAP
|
||||||
{
|
{
|
||||||
IAP()
|
IAP()
|
||||||
{
|
{
|
||||||
m_Callback = LUA_NOREF;
|
memset(this, 0, sizeof(*this));
|
||||||
m_Self = LUA_NOREF;
|
|
||||||
m_InitCount = 0;
|
|
||||||
m_AutoFinishTransactions = true;
|
m_AutoFinishTransactions = true;
|
||||||
m_PendingTransactions = 0;
|
IAP_ClearCallback(&m_Listener);
|
||||||
}
|
}
|
||||||
int m_InitCount;
|
int m_InitCount;
|
||||||
int m_Callback;
|
bool m_AutoFinishTransactions;
|
||||||
int m_Self;
|
NSMutableDictionary* m_PendingTransactions;
|
||||||
bool m_AutoFinishTransactions;
|
IAPListener m_Listener;
|
||||||
NSMutableDictionary* m_PendingTransactions;
|
IAPCommandQueue m_CommandQueue;
|
||||||
IAPListener m_Listener;
|
SKPaymentTransactionObserver* m_Observer;
|
||||||
SKPaymentTransactionObserver* m_Observer;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
IAP g_IAP;
|
IAP g_IAP;
|
||||||
|
|
||||||
|
|
||||||
|
// The payload of the purchasing commands
|
||||||
|
struct IAPProduct
|
||||||
|
{
|
||||||
|
const char* ident;
|
||||||
|
const char* title;
|
||||||
|
const char* description;
|
||||||
|
float price;
|
||||||
|
const char* price_string;
|
||||||
|
const char* currency_code;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IAPResponse
|
||||||
|
{
|
||||||
|
const char* error; // optional
|
||||||
|
int32_t error_code; // only valid if error is set
|
||||||
|
dmArray<IAPProduct> m_Products;
|
||||||
|
|
||||||
|
IAPResponse() {
|
||||||
|
memset(this, 0, sizeof(*this));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IAPTransaction
|
||||||
|
{
|
||||||
|
const char* ident;
|
||||||
|
int32_t state;
|
||||||
|
const char* date;
|
||||||
|
const char* trans_ident; // optional
|
||||||
|
const char* receipt; // optional
|
||||||
|
uint32_t receipt_length;
|
||||||
|
const char* original_trans; // optional
|
||||||
|
const char* error; // optional
|
||||||
|
int32_t error_code; // only valid if error is set
|
||||||
|
IAPTransaction* m_OriginalTransaction;
|
||||||
|
|
||||||
|
IAPTransaction() {
|
||||||
|
memset(this, 0, sizeof(*this));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static void IAP_FreeProduct(IAPProduct* product)
|
||||||
|
{
|
||||||
|
free((void*)product->ident);
|
||||||
|
free((void*)product->title);
|
||||||
|
free((void*)product->description);
|
||||||
|
free((void*)product->price_string);
|
||||||
|
free((void*)product->currency_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void IAP_FreeTransaction(IAPTransaction* transaction)
|
||||||
|
{
|
||||||
|
if (transaction->m_OriginalTransaction) {
|
||||||
|
IAP_FreeTransaction(transaction->m_OriginalTransaction);
|
||||||
|
}
|
||||||
|
delete transaction->m_OriginalTransaction;
|
||||||
|
free((void*)transaction->ident);
|
||||||
|
free((void*)transaction->date);
|
||||||
|
free((void*)transaction->trans_ident);
|
||||||
|
free((void*)transaction->receipt);
|
||||||
|
free((void*)transaction->original_trans);
|
||||||
|
free((void*)transaction->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@interface SKProductsRequestDelegate : NSObject<SKProductsRequestDelegate>
|
@interface SKProductsRequestDelegate : NSObject<SKProductsRequestDelegate>
|
||||||
@property lua_State* m_LuaState;
|
@property IAPListener m_Callback;
|
||||||
@property (assign) SKProductsRequest* m_Request;
|
@property (assign) SKProductsRequest* m_Request;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation SKProductsRequestDelegate
|
@implementation SKProductsRequestDelegate
|
||||||
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
|
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
|
||||||
|
|
||||||
lua_State* L = self.m_LuaState;
|
IAPListener callback = self.m_Callback;
|
||||||
if (g_IAP.m_Callback == LUA_NOREF) {
|
if (!IAP_CallbackIsValid(&callback)) {
|
||||||
dmLogError("No callback set");
|
dmLogError("No callback set");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSArray * skProducts = response.products;
|
NSArray* skProducts = response.products;
|
||||||
int top = lua_gettop(L);
|
|
||||||
|
|
||||||
lua_rawgeti(L, LUA_REGISTRYINDEX, g_IAP.m_Callback);
|
IAPResponse* iap_response = new IAPResponse;
|
||||||
|
|
||||||
// Setup self
|
|
||||||
lua_rawgeti(L, LUA_REGISTRYINDEX, g_IAP.m_Self);
|
|
||||||
lua_pushvalue(L, -1);
|
|
||||||
dmScript::SetInstance(L);
|
|
||||||
|
|
||||||
if (!dmScript::IsInstanceValid(L))
|
|
||||||
{
|
|
||||||
dmLogError("Could not run facebook callback because the instance has been deleted.");
|
|
||||||
lua_pop(L, 2);
|
|
||||||
assert(top == lua_gettop(L));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_newtable(L);
|
|
||||||
for (SKProduct * p in skProducts) {
|
for (SKProduct * p in skProducts) {
|
||||||
|
|
||||||
lua_pushstring(L, [p.productIdentifier UTF8String]);
|
IAPProduct product = {0};
|
||||||
lua_newtable(L);
|
product.ident = strdup([p.productIdentifier UTF8String]);
|
||||||
|
product.title = strdup([p.localizedTitle UTF8String]);
|
||||||
lua_pushstring(L, "ident");
|
product.description = strdup([p.localizedDescription UTF8String]);
|
||||||
lua_pushstring(L, [p.productIdentifier UTF8String]);
|
product.currency_code = strdup([[p.priceLocale objectForKey:NSLocaleCurrencyCode] UTF8String]);
|
||||||
lua_rawset(L, -3);
|
product.price = p.price.floatValue;
|
||||||
|
|
||||||
lua_pushstring(L, "title");
|
|
||||||
lua_pushstring(L, [p.localizedTitle UTF8String]);
|
|
||||||
lua_rawset(L, -3);
|
|
||||||
|
|
||||||
lua_pushstring(L, "description");
|
|
||||||
lua_pushstring(L, [p.localizedDescription UTF8String]);
|
|
||||||
lua_rawset(L, -3);
|
|
||||||
|
|
||||||
lua_pushstring(L, "price");
|
|
||||||
lua_pushnumber(L, p.price.floatValue);
|
|
||||||
lua_rawset(L, -3);
|
|
||||||
|
|
||||||
NSNumberFormatter *formatter = [[[NSNumberFormatter alloc] init] autorelease];
|
NSNumberFormatter *formatter = [[[NSNumberFormatter alloc] init] autorelease];
|
||||||
[formatter setNumberStyle: NSNumberFormatterCurrencyStyle];
|
[formatter setNumberStyle: NSNumberFormatterCurrencyStyle];
|
||||||
[formatter setLocale: p.priceLocale];
|
[formatter setLocale: p.priceLocale];
|
||||||
NSString *price_string = [formatter stringFromNumber: p.price];
|
NSString* price_string = [formatter stringFromNumber: p.price];
|
||||||
|
product.price_string = strdup([price_string UTF8String]);
|
||||||
|
|
||||||
lua_pushstring(L, "price_string");
|
if (iap_response->m_Products.Full()) {
|
||||||
lua_pushstring(L, [price_string UTF8String]);
|
iap_response->m_Products.OffsetCapacity(2);
|
||||||
lua_rawset(L, -3);
|
}
|
||||||
|
iap_response->m_Products.Push(product);
|
||||||
|
}
|
||||||
|
|
||||||
lua_pushstring(L, "currency_code");
|
|
||||||
lua_pushstring(L, [[p.priceLocale objectForKey:NSLocaleCurrencyCode] UTF8String]);
|
IAPCommand cmd;
|
||||||
lua_rawset(L, -3);
|
cmd.m_Command = IAP_PRODUCT_RESULT;
|
||||||
|
cmd.m_Callback = callback;
|
||||||
|
cmd.m_Data = iap_response;
|
||||||
|
IAP_Queue_Push(&g_IAP.m_CommandQueue, &cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HandleProductResult(IAPCommand* cmd)
|
||||||
|
{
|
||||||
|
|
||||||
|
IAPResponse* response = (IAPResponse*)cmd->m_Data;
|
||||||
|
|
||||||
|
lua_State* L = cmd->m_Callback.m_L;
|
||||||
|
int top = lua_gettop(L);
|
||||||
|
|
||||||
|
if (!IAP_SetupCallback(&cmd->m_Callback))
|
||||||
|
{
|
||||||
|
assert(top == lua_gettop(L));
|
||||||
|
delete response;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_newtable(L);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < response->m_Products.Size(); ++i) {
|
||||||
|
IAPProduct* product = &response->m_Products[i];
|
||||||
|
|
||||||
|
lua_pushstring(L, product->ident);
|
||||||
|
lua_newtable(L);
|
||||||
|
|
||||||
|
lua_pushstring(L, product->ident);
|
||||||
|
lua_setfield(L, -2, "ident");
|
||||||
|
lua_pushstring(L, product->title);
|
||||||
|
lua_setfield(L, -2, "title");
|
||||||
|
lua_pushstring(L, product->description);
|
||||||
|
lua_setfield(L, -2, "description");
|
||||||
|
lua_pushnumber(L, product->price);
|
||||||
|
lua_setfield(L, -2, "price");
|
||||||
|
lua_pushstring(L, product->price_string);
|
||||||
|
lua_setfield(L, -2, "price_string");
|
||||||
|
lua_pushstring(L, product->currency_code);
|
||||||
|
lua_setfield(L, -2, "currency_code");
|
||||||
|
|
||||||
lua_rawset(L, -3);
|
lua_rawset(L, -3);
|
||||||
|
|
||||||
|
IAP_FreeProduct(product);
|
||||||
}
|
}
|
||||||
lua_pushnil(L);
|
lua_pushnil(L);
|
||||||
|
|
||||||
int ret = lua_pcall(L, 3, 0, 0);
|
int ret = lua_pcall(L, 3, 0, 0);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
dmLogError("Error running callback: %s", lua_tostring(L, -1));
|
dmLogError("%d: Error running callback: %s", __LINE__, lua_tostring(L, -1));
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Callback);
|
IAP_UnregisterCallback(&cmd->m_Callback);
|
||||||
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Self);
|
|
||||||
g_IAP.m_Callback = LUA_NOREF;
|
|
||||||
g_IAP.m_Self = LUA_NOREF;
|
|
||||||
|
|
||||||
|
delete response;
|
||||||
assert(top == lua_gettop(L));
|
assert(top == lua_gettop(L));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
|
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
|
||||||
dmLogWarning("SKProductsRequest failed: %s", [error.localizedDescription UTF8String]);
|
dmLogWarning("SKProductsRequest failed: %s", [error.localizedDescription UTF8String]);
|
||||||
|
|
||||||
lua_State* L = self.m_LuaState;
|
IAPListener callback = self.m_Callback;
|
||||||
int top = lua_gettop(L);
|
if (!IAP_CallbackIsValid(&callback)) {
|
||||||
|
|
||||||
if (g_IAP.m_Callback == LUA_NOREF) {
|
|
||||||
dmLogError("No callback set");
|
dmLogError("No callback set");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lua_rawgeti(L, LUA_REGISTRYINDEX, g_IAP.m_Callback);
|
IAPResponse* response = new IAPResponse;
|
||||||
|
response->error = strdup([error.localizedDescription UTF8String]);
|
||||||
|
response->error_code = REASON_UNSPECIFIED;
|
||||||
|
|
||||||
// Setup self
|
IAPCommand cmd;
|
||||||
lua_rawgeti(L, LUA_REGISTRYINDEX, g_IAP.m_Self);
|
cmd.m_Command = IAP_PRODUCT_RESULT;
|
||||||
lua_pushvalue(L, -1);
|
cmd.m_Callback = callback;
|
||||||
dmScript::SetInstance(L);
|
cmd.m_Data = response;
|
||||||
|
|
||||||
if (!dmScript::IsInstanceValid(L))
|
IAP_Queue_Push(&g_IAP.m_CommandQueue, &cmd);
|
||||||
{
|
|
||||||
dmLogError("Could not run iap callback because the instance has been deleted.");
|
|
||||||
lua_pop(L, 2);
|
|
||||||
assert(top == lua_gettop(L));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_pushnil(L);
|
|
||||||
IAP_PushError(L, [error.localizedDescription UTF8String], REASON_UNSPECIFIED);
|
|
||||||
|
|
||||||
int ret = lua_pcall(L, 3, 0, 0);
|
|
||||||
if (ret != 0) {
|
|
||||||
dmLogError("Error running callback: %s", lua_tostring(L, -1));
|
|
||||||
lua_pop(L, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Callback);
|
|
||||||
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Self);
|
|
||||||
g_IAP.m_Callback = LUA_NOREF;
|
|
||||||
g_IAP.m_Self = LUA_NOREF;
|
|
||||||
|
|
||||||
assert(top == lua_gettop(L));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)requestDidFinish:(SKRequest *)request
|
- (void)requestDidFinish:(SKRequest *)request
|
||||||
@ -186,202 +229,157 @@ IAP g_IAP;
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
static void PushTransaction(lua_State* L, SKPaymentTransaction* transaction)
|
static void CopyTransaction(SKPaymentTransaction* transaction, IAPTransaction* out)
|
||||||
{
|
{
|
||||||
lua_newtable(L);
|
if (transaction.transactionState == SKPaymentTransactionStateFailed) {
|
||||||
|
out->error = strdup([transaction.error.localizedDescription UTF8String]);
|
||||||
lua_pushstring(L, "ident");
|
out->error_code = transaction.error.code == SKErrorPaymentCancelled ? REASON_USER_CANCELED : REASON_UNSPECIFIED;
|
||||||
lua_pushstring(L, [transaction.payment.productIdentifier UTF8String]);
|
} else {
|
||||||
lua_rawset(L, -3);
|
out->error = 0;
|
||||||
|
|
||||||
lua_pushstring(L, "state");
|
|
||||||
lua_pushnumber(L, transaction.transactionState);
|
|
||||||
lua_rawset(L, -3);
|
|
||||||
|
|
||||||
if (transaction.transactionState == SKPaymentTransactionStatePurchased || transaction.transactionState == SKPaymentTransactionStateRestored) {
|
|
||||||
lua_pushstring(L, "trans_ident");
|
|
||||||
lua_pushstring(L, [transaction.transactionIdentifier UTF8String]);
|
|
||||||
lua_rawset(L, -3);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
|
|
||||||
lua_pushstring(L, "receipt");
|
|
||||||
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
|
|
||||||
lua_pushlstring(L, (const char*) transaction.transactionReceipt.bytes, transaction.transactionReceipt.length);
|
|
||||||
} else {
|
|
||||||
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
|
|
||||||
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
|
|
||||||
lua_pushlstring(L, (const char*) receiptData.bytes, receiptData.length);
|
|
||||||
}
|
|
||||||
lua_rawset(L, -3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
|
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
|
||||||
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZ"];
|
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZ"];
|
||||||
lua_pushstring(L, "date");
|
|
||||||
lua_pushstring(L, [[dateFormatter stringFromDate: transaction.transactionDate] UTF8String]);
|
out->ident = strdup([transaction.payment.productIdentifier UTF8String]);
|
||||||
lua_rawset(L, -3);
|
|
||||||
|
|
||||||
|
if (transaction.transactionDate)
|
||||||
|
out->date = strdup([[dateFormatter stringFromDate: transaction.transactionDate] UTF8String]);
|
||||||
|
out->state = transaction.transactionState;
|
||||||
|
|
||||||
|
if (transaction.transactionState == SKPaymentTransactionStatePurchased ||
|
||||||
|
transaction.transactionState == SKPaymentTransactionStateRestored) {
|
||||||
|
out->trans_ident = strdup([transaction.transactionIdentifier UTF8String]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
|
||||||
|
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
|
||||||
|
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
|
||||||
|
out->receipt_length = receiptData.length;
|
||||||
|
out->receipt = (const char*)malloc(out->receipt_length);
|
||||||
|
memcpy((void*)out->receipt, receiptData.bytes, out->receipt_length);
|
||||||
|
}
|
||||||
|
|
||||||
if (transaction.transactionState == SKPaymentTransactionStateRestored && transaction.originalTransaction) {
|
if (transaction.transactionState == SKPaymentTransactionStateRestored && transaction.originalTransaction) {
|
||||||
|
out->m_OriginalTransaction = new IAPTransaction;
|
||||||
|
CopyTransaction(transaction.originalTransaction, out->m_OriginalTransaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PushTransaction(lua_State* L, IAPTransaction* transaction)
|
||||||
|
{
|
||||||
|
// first argument to the callback
|
||||||
|
lua_newtable(L);
|
||||||
|
|
||||||
|
lua_pushstring(L, transaction->ident);
|
||||||
|
lua_setfield(L, -2, "ident");
|
||||||
|
lua_pushnumber(L, transaction->state);
|
||||||
|
lua_setfield(L, -2, "state");
|
||||||
|
if (transaction->date) {
|
||||||
|
lua_pushstring(L, transaction->date);
|
||||||
|
lua_setfield(L, -2, "date");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transaction->trans_ident) {
|
||||||
|
lua_pushstring(L, transaction->trans_ident);
|
||||||
|
lua_setfield(L, -2, "trans_ident");
|
||||||
|
}
|
||||||
|
if (transaction->receipt) {
|
||||||
|
lua_pushlstring(L, transaction->receipt, transaction->receipt_length);
|
||||||
|
lua_setfield(L, -2, "receipt");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transaction->m_OriginalTransaction) {
|
||||||
lua_pushstring(L, "original_trans");
|
lua_pushstring(L, "original_trans");
|
||||||
PushTransaction(L, transaction.originalTransaction);
|
PushTransaction(L, transaction->m_OriginalTransaction);
|
||||||
lua_rawset(L, -3);
|
lua_rawset(L, -3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RunTransactionCallback(lua_State* L, int cb, int self, SKPaymentTransaction* transaction)
|
|
||||||
|
static void HandlePurchaseResult(IAPCommand* cmd)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
IAPTransaction* transaction = (IAPTransaction*)cmd->m_Data;
|
||||||
|
|
||||||
|
lua_State* L = cmd->m_Callback.m_L;
|
||||||
int top = lua_gettop(L);
|
int top = lua_gettop(L);
|
||||||
|
|
||||||
lua_rawgeti(L, LUA_REGISTRYINDEX, cb);
|
if (!IAP_SetupCallback(&cmd->m_Callback))
|
||||||
|
|
||||||
// Setup self
|
|
||||||
lua_rawgeti(L, LUA_REGISTRYINDEX, self);
|
|
||||||
lua_pushvalue(L, -1);
|
|
||||||
dmScript::SetInstance(L);
|
|
||||||
|
|
||||||
if (!dmScript::IsInstanceValid(L))
|
|
||||||
{
|
{
|
||||||
dmLogError("Could not run iap callback because the instance has been deleted.");
|
|
||||||
lua_pop(L, 2);
|
|
||||||
assert(top == lua_gettop(L));
|
assert(top == lua_gettop(L));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PushTransaction(L, transaction);
|
PushTransaction(L, transaction);
|
||||||
|
|
||||||
if (transaction.transactionState == SKPaymentTransactionStateFailed) {
|
// Second argument to callback
|
||||||
if (transaction.error.code == SKErrorPaymentCancelled) {
|
if (transaction->error) {
|
||||||
IAP_PushError(L, [transaction.error.localizedDescription UTF8String], REASON_USER_CANCELED);
|
IAP_PushError(L, transaction->error, transaction->error_code);
|
||||||
} else {
|
|
||||||
IAP_PushError(L, [transaction.error.localizedDescription UTF8String], REASON_UNSPECIFIED);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
lua_pushnil(L);
|
lua_pushnil(L);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ret = lua_pcall(L, 3, 0, 0);
|
int ret = lua_pcall(L, 3, 0, 0);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
dmLogError("Error running callback: %s", lua_tostring(L, -1));
|
dmLogError("%d: Error running callback: %s", __LINE__, lua_tostring(L, -1));
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IAP_FreeTransaction(transaction);
|
||||||
|
|
||||||
|
delete transaction;
|
||||||
assert(top == lua_gettop(L));
|
assert(top == lua_gettop(L));
|
||||||
}
|
}
|
||||||
|
|
||||||
@implementation SKPaymentTransactionObserver
|
@implementation SKPaymentTransactionObserver
|
||||||
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
|
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
|
||||||
{
|
{
|
||||||
for (SKPaymentTransaction * transaction in transactions) {
|
for (SKPaymentTransaction* transaction in transactions) {
|
||||||
|
|
||||||
if ((!g_IAP.m_AutoFinishTransactions) && (transaction.transactionState == SKPaymentTransactionStatePurchased)) {
|
if ((!self.m_IAP->m_AutoFinishTransactions) && (transaction.transactionState == SKPaymentTransactionStatePurchased)) {
|
||||||
NSData *data = [transaction.transactionIdentifier dataUsingEncoding:NSUTF8StringEncoding];
|
NSData *data = [transaction.transactionIdentifier dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
uint64_t trans_id_hash = dmHashBuffer64((const char*) [data bytes], [data length]);
|
uint64_t trans_id_hash = dmHashBuffer64((const char*) [data bytes], [data length]);
|
||||||
[g_IAP.m_PendingTransactions setObject:transaction forKey:[NSNumber numberWithInteger:trans_id_hash] ];
|
[self.m_IAP->m_PendingTransactions setObject:transaction forKey:[NSNumber numberWithInteger:trans_id_hash] ];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool has_listener = false;
|
bool has_listener = self.m_IAP->m_Listener.m_Callback != LUA_NOREF;
|
||||||
if (self.m_IAP->m_Listener.m_Callback != LUA_NOREF) {
|
if (!has_listener)
|
||||||
const IAPListener& l = self.m_IAP->m_Listener;
|
continue;
|
||||||
RunTransactionCallback(l.m_L, l.m_Callback, l.m_Self, transaction);
|
|
||||||
has_listener = true;
|
IAPTransaction* iap_transaction = new IAPTransaction;
|
||||||
}
|
CopyTransaction(transaction, iap_transaction);
|
||||||
|
|
||||||
|
IAPCommand cmd;
|
||||||
|
cmd.m_Callback = self.m_IAP->m_Listener;
|
||||||
|
cmd.m_Command = IAP_PURCHASE_RESULT;
|
||||||
|
cmd.m_Data = iap_transaction;
|
||||||
|
IAP_Queue_Push(&self.m_IAP->m_CommandQueue, &cmd);
|
||||||
|
|
||||||
switch (transaction.transactionState)
|
switch (transaction.transactionState)
|
||||||
{
|
{
|
||||||
case SKPaymentTransactionStatePurchasing:
|
|
||||||
break;
|
|
||||||
case SKPaymentTransactionStatePurchased:
|
case SKPaymentTransactionStatePurchased:
|
||||||
if (has_listener > 0 && g_IAP.m_AutoFinishTransactions) {
|
if (g_IAP.m_AutoFinishTransactions) {
|
||||||
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SKPaymentTransactionStateFailed:
|
case SKPaymentTransactionStateFailed:
|
||||||
if (has_listener > 0) {
|
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
||||||
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case SKPaymentTransactionStateRestored:
|
case SKPaymentTransactionStateRestored:
|
||||||
if (has_listener > 0) {
|
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
||||||
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
break;
|
||||||
}
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
/*# list in-app products
|
static int IAP_List(lua_State* L)
|
||||||
*
|
|
||||||
* Get a list of all avaliable iap products. Products are described as a [type:table]
|
|
||||||
* with the following fields:
|
|
||||||
*
|
|
||||||
* `ident`
|
|
||||||
* : The product identifier.
|
|
||||||
*
|
|
||||||
* `title`
|
|
||||||
* : The product title.
|
|
||||||
*
|
|
||||||
* `description`
|
|
||||||
* : The product description.
|
|
||||||
*
|
|
||||||
* `price`
|
|
||||||
* : The price of the product.
|
|
||||||
*
|
|
||||||
* `price_string`
|
|
||||||
* : The price of the product, as a formatted string (amount and currency symbol).
|
|
||||||
*
|
|
||||||
* `currency_code` [icon:ios] [icon:googleplay] [icon:facebook]
|
|
||||||
* : The currency code. On Google Play, this reflects the merchant's locale, instead of the user's.
|
|
||||||
*
|
|
||||||
* [icon:attention] Nested calls, that is calling `iap.list()` from within the callback is
|
|
||||||
* not supported. Doing so will result in call being ignored with the engine reporting
|
|
||||||
* "Unexpected callback set".
|
|
||||||
*
|
|
||||||
* @name iap.list
|
|
||||||
* @param ids [type:table] table (array) of identifiers to get products from
|
|
||||||
* @param callback [type:function(self, products, error)] result callback
|
|
||||||
*
|
|
||||||
* `self`
|
|
||||||
* : [type:object] The current object.
|
|
||||||
*
|
|
||||||
* `products`
|
|
||||||
* : [type:table] Table describing the available iap products. See above for details.
|
|
||||||
*
|
|
||||||
* `error`
|
|
||||||
* : [type:table] a table containing error information. `nil` if there is no error.
|
|
||||||
* - `error` (the error message)
|
|
||||||
*
|
|
||||||
* @examples
|
|
||||||
*
|
|
||||||
* ```lua
|
|
||||||
* local function iap_callback(self, products, error)
|
|
||||||
* if error == nil then
|
|
||||||
* for k,p in pairs(products) do
|
|
||||||
* -- present the product
|
|
||||||
* print(p.title)
|
|
||||||
* print(p.description)
|
|
||||||
* end
|
|
||||||
* else
|
|
||||||
* print(error.error)
|
|
||||||
* end
|
|
||||||
* end
|
|
||||||
*
|
|
||||||
* function init(self)
|
|
||||||
* iap.list({"my_iap"}, iap_callback)
|
|
||||||
* end
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
int IAP_List(lua_State* L)
|
|
||||||
{
|
{
|
||||||
int top = lua_gettop(L);
|
int top = lua_gettop(L);
|
||||||
if (g_IAP.m_Callback != LUA_NOREF) {
|
|
||||||
dmLogError("Unexpected callback set");
|
|
||||||
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Callback);
|
|
||||||
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Self);
|
|
||||||
g_IAP.m_Callback = LUA_NOREF;
|
|
||||||
g_IAP.m_Self = LUA_NOREF;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSCountedSet* product_identifiers = [[[NSCountedSet alloc] init] autorelease];
|
NSCountedSet* product_identifiers = [[[NSCountedSet alloc] init] autorelease];
|
||||||
|
|
||||||
@ -393,16 +391,13 @@ int IAP_List(lua_State* L)
|
|||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
luaL_checktype(L, 2, LUA_TFUNCTION);
|
|
||||||
lua_pushvalue(L, 2);
|
|
||||||
g_IAP.m_Callback = dmScript::Ref(L, LUA_REGISTRYINDEX);
|
|
||||||
|
|
||||||
dmScript::GetInstance(L);
|
|
||||||
g_IAP.m_Self = dmScript::Ref(L, LUA_REGISTRYINDEX);
|
|
||||||
|
|
||||||
SKProductsRequest* products_request = [[SKProductsRequest alloc] initWithProductIdentifiers: product_identifiers];
|
SKProductsRequest* products_request = [[SKProductsRequest alloc] initWithProductIdentifiers: product_identifiers];
|
||||||
SKProductsRequestDelegate* delegate = [SKProductsRequestDelegate alloc];
|
SKProductsRequestDelegate* delegate = [SKProductsRequestDelegate alloc];
|
||||||
delegate.m_LuaState = dmScript::GetMainThread(L);
|
|
||||||
|
IAPListener callback;
|
||||||
|
IAP_RegisterCallback(L, 2, &callback);
|
||||||
|
delegate.m_Callback = callback;
|
||||||
|
|
||||||
delegate.m_Request = products_request;
|
delegate.m_Request = products_request;
|
||||||
products_request.delegate = delegate;
|
products_request.delegate = delegate;
|
||||||
[products_request start];
|
[products_request start];
|
||||||
@ -411,44 +406,7 @@ int IAP_List(lua_State* L)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*# buy product
|
static int IAP_Buy(lua_State* L)
|
||||||
*
|
|
||||||
* Perform a product purchase.
|
|
||||||
*
|
|
||||||
* [icon:attention] Calling `iap.finish()` is required on a successful transaction if
|
|
||||||
* `auto_finish_transactions` is disabled in project settings.
|
|
||||||
*
|
|
||||||
* @name iap.buy
|
|
||||||
* @param id [type:string] product to buy
|
|
||||||
* @param [options] [type:table] optional parameters as properties. The following parameters can be set:
|
|
||||||
*
|
|
||||||
* - `request_id` ([icon:facebook] Facebook only. Optional custom unique request id to
|
|
||||||
* set for this transaction. The id becomes attached to the payment within the Graph API.)
|
|
||||||
*
|
|
||||||
* @examples
|
|
||||||
*
|
|
||||||
* ```lua
|
|
||||||
* local function iap_listener(self, transaction, error)
|
|
||||||
* if error == nil then
|
|
||||||
* -- purchase is successful.
|
|
||||||
* print(transaction.date)
|
|
||||||
* -- required if auto finish transactions is disabled in project settings
|
|
||||||
* if (transaction.state == iap.TRANS_STATE_PURCHASED) then
|
|
||||||
* -- do server-side verification of purchase here..
|
|
||||||
* iap.finish(transaction)
|
|
||||||
* end
|
|
||||||
* else
|
|
||||||
* print(error.error, error.reason)
|
|
||||||
* end
|
|
||||||
* end
|
|
||||||
*
|
|
||||||
* function init(self)
|
|
||||||
* iap.set_listener(iap_listener)
|
|
||||||
* iap.buy("my_iap")
|
|
||||||
* end
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
int IAP_Buy(lua_State* L)
|
|
||||||
{
|
{
|
||||||
int top = lua_gettop(L);
|
int top = lua_gettop(L);
|
||||||
|
|
||||||
@ -464,20 +422,7 @@ int IAP_Buy(lua_State* L)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*# finish buying product
|
static int IAP_Finish(lua_State* L)
|
||||||
*
|
|
||||||
* Explicitly finish a product transaction.
|
|
||||||
*
|
|
||||||
* [icon:attention] Calling iap.finish is required on a successful transaction
|
|
||||||
* if `auto_finish_transactions` is disabled in project settings. Calling this function
|
|
||||||
* with `auto_finish_transactions` set will be ignored and a warning is printed.
|
|
||||||
* The `transaction.state` field must equal `iap.TRANS_STATE_PURCHASED`.
|
|
||||||
*
|
|
||||||
* @name iap.finish
|
|
||||||
* @param transaction [type:table] transaction table parameter as supplied in listener callback
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
int IAP_Finish(lua_State* L)
|
|
||||||
{
|
{
|
||||||
if(g_IAP.m_AutoFinishTransactions)
|
if(g_IAP.m_AutoFinishTransactions)
|
||||||
{
|
{
|
||||||
@ -525,15 +470,7 @@ int IAP_Finish(lua_State* L)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*# restore products (non-consumable)
|
static int IAP_Restore(lua_State* L)
|
||||||
*
|
|
||||||
* Restore previously purchased products.
|
|
||||||
*
|
|
||||||
* @name iap.restore
|
|
||||||
* @return success [type:boolean] `true` if current store supports handling
|
|
||||||
* restored transactions, otherwise `false`.
|
|
||||||
*/
|
|
||||||
int IAP_Restore(lua_State* L)
|
|
||||||
{
|
{
|
||||||
// TODO: Missing callback here for completion/error
|
// TODO: Missing callback here for completion/error
|
||||||
// See callback under "Handling Restored Transactions"
|
// See callback under "Handling Restored Transactions"
|
||||||
@ -545,88 +482,12 @@ int IAP_Restore(lua_State* L)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*# set purchase transaction listener
|
static int IAP_SetListener(lua_State* L)
|
||||||
*
|
|
||||||
* Set the callback function to receive purchase transaction events. Transactions are
|
|
||||||
* described as a [type:table] with the following fields:
|
|
||||||
*
|
|
||||||
* `ident`
|
|
||||||
* : The product identifier.
|
|
||||||
*
|
|
||||||
* `state`
|
|
||||||
* : The transaction state. See `iap.TRANS_STATE_*`.
|
|
||||||
*
|
|
||||||
* `date`
|
|
||||||
* : The date and time for the transaction.
|
|
||||||
*
|
|
||||||
* `trans_ident`
|
|
||||||
* : The transaction identifier. This field is only set when `state` is TRANS_STATE_RESTORED,
|
|
||||||
* TRANS_STATE_UNVERIFIED or TRANS_STATE_PURCHASED.
|
|
||||||
*
|
|
||||||
* `receipt`
|
|
||||||
* : The transaction receipt. This field is only set when `state` is TRANS_STATE_PURCHASED
|
|
||||||
* or TRANS_STATE_UNVERIFIED.
|
|
||||||
*
|
|
||||||
* `original_trans` [icon:apple]
|
|
||||||
* : Apple only. The original transaction. This field is only set when `state` is
|
|
||||||
* TRANS_STATE_RESTORED.
|
|
||||||
*
|
|
||||||
* `signature` [icon:googleplay]
|
|
||||||
* : Google Play only. A string containing the signature of the purchase data that was signed
|
|
||||||
* with the private key of the developer.
|
|
||||||
*
|
|
||||||
* `request_id` [icon:facebook]
|
|
||||||
* : Facebook only. This field is set to the optional custom unique request id `request_id`
|
|
||||||
* if set in the `iap.buy()` call parameters.
|
|
||||||
*
|
|
||||||
* `user_id` [icon:amazon]
|
|
||||||
* : Amazon Pay only. The user ID.
|
|
||||||
*
|
|
||||||
* `is_sandbox_mode` [icon:amazon]
|
|
||||||
* : Amazon Pay only. If `true`, the SDK is running in Sandbox mode. This only allows
|
|
||||||
* interactions with the Amazon AppTester. Use this mode only for testing locally.
|
|
||||||
*
|
|
||||||
* `cancel_date` [icon:amazon]
|
|
||||||
* : Amazon Pay only. The cancel date for the purchase. This field is only set if the
|
|
||||||
* purchase is canceled.
|
|
||||||
*
|
|
||||||
* `canceled` [icon:amazon]
|
|
||||||
* : Amazon Pay only. Is set to `true` if the receipt was canceled or has expired;
|
|
||||||
* otherwise `false`.
|
|
||||||
*
|
|
||||||
* @name iap.set_listener
|
|
||||||
* @param listener [type:function(self, transaction, error)] listener callback function.
|
|
||||||
* Pass an empty function if you no longer wish to receive callbacks.
|
|
||||||
*
|
|
||||||
* `self`
|
|
||||||
* : [type:object] The current object.
|
|
||||||
*
|
|
||||||
* `transaction`
|
|
||||||
* : [type:table] a table describing the transaction. See above for details.
|
|
||||||
*
|
|
||||||
* `error`
|
|
||||||
* : [type:table] a table containing error information. `nil` if there is no error.
|
|
||||||
* - `error` (the error message)
|
|
||||||
* - `reason` (the reason for the error, see `iap.REASON_*`)
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
int IAP_SetListener(lua_State* L)
|
|
||||||
{
|
{
|
||||||
IAP* iap = &g_IAP;
|
IAP* iap = &g_IAP;
|
||||||
luaL_checktype(L, 1, LUA_TFUNCTION);
|
|
||||||
lua_pushvalue(L, 1);
|
|
||||||
int cb = dmScript::Ref(L, LUA_REGISTRYINDEX);
|
|
||||||
|
|
||||||
if (iap->m_Listener.m_Callback != LUA_NOREF) {
|
IAP_UnregisterCallback(&iap->m_Listener);
|
||||||
dmScript::Unref(iap->m_Listener.m_L, LUA_REGISTRYINDEX, iap->m_Listener.m_Callback);
|
IAP_RegisterCallback(L, 1, &iap->m_Listener);
|
||||||
dmScript::Unref(iap->m_Listener.m_L, LUA_REGISTRYINDEX, iap->m_Listener.m_Self);
|
|
||||||
}
|
|
||||||
|
|
||||||
iap->m_Listener.m_L = dmScript::GetMainThread(L);
|
|
||||||
iap->m_Listener.m_Callback = cb;
|
|
||||||
|
|
||||||
dmScript::GetInstance(L);
|
|
||||||
iap->m_Listener.m_Self = dmScript::Ref(L, LUA_REGISTRYINDEX);
|
|
||||||
|
|
||||||
if (g_IAP.m_Observer == 0) {
|
if (g_IAP.m_Observer == 0) {
|
||||||
SKPaymentTransactionObserver* observer = [[SKPaymentTransactionObserver alloc] init];
|
SKPaymentTransactionObserver* observer = [[SKPaymentTransactionObserver alloc] init];
|
||||||
@ -642,18 +503,7 @@ int IAP_SetListener(lua_State* L)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*# get current provider id
|
static int IAP_GetProviderId(lua_State* L)
|
||||||
*
|
|
||||||
* @name iap.get_provider_id
|
|
||||||
* @return id [type:constant] provider id.
|
|
||||||
*
|
|
||||||
* - `iap.PROVIDER_ID_GOOGLE`
|
|
||||||
* - `iap.PROVIDER_ID_AMAZON`
|
|
||||||
* - `iap.PROVIDER_ID_APPLE`
|
|
||||||
* - `iap.PROVIDER_ID_FACEBOOK`
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
int IAP_GetProviderId(lua_State* L)
|
|
||||||
{
|
{
|
||||||
lua_pushinteger(L, PROVIDER_ID_APPLE);
|
lua_pushinteger(L, PROVIDER_ID_APPLE);
|
||||||
return 1;
|
return 1;
|
||||||
@ -670,79 +520,7 @@ static const luaL_reg IAP_methods[] =
|
|||||||
{0, 0}
|
{0, 0}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*# transaction purchasing state
|
static dmExtension::Result InitializeIAP(dmExtension::Params* params)
|
||||||
*
|
|
||||||
* This is an intermediate mode followed by TRANS_STATE_PURCHASED.
|
|
||||||
* Store provider support dependent.
|
|
||||||
*
|
|
||||||
* @name iap.TRANS_STATE_PURCHASING
|
|
||||||
* @variable
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*# transaction purchased state
|
|
||||||
*
|
|
||||||
* @name iap.TRANS_STATE_PURCHASED
|
|
||||||
* @variable
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*# transaction unverified state, requires verification of purchase
|
|
||||||
*
|
|
||||||
* @name iap.TRANS_STATE_UNVERIFIED
|
|
||||||
* @variable
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*# transaction failed state
|
|
||||||
*
|
|
||||||
* @name iap.TRANS_STATE_FAILED
|
|
||||||
* @variable
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*# transaction restored state
|
|
||||||
*
|
|
||||||
* This is only available on store providers supporting restoring purchases.
|
|
||||||
*
|
|
||||||
* @name iap.TRANS_STATE_RESTORED
|
|
||||||
* @variable
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*# unspecified error reason
|
|
||||||
*
|
|
||||||
* @name iap.REASON_UNSPECIFIED
|
|
||||||
* @variable
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*# user canceled reason
|
|
||||||
*
|
|
||||||
* @name iap.REASON_USER_CANCELED
|
|
||||||
* @variable
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/*# iap provider id for Google
|
|
||||||
*
|
|
||||||
* @name iap.PROVIDER_ID_GOOGLE
|
|
||||||
* @variable
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*# provider id for Amazon
|
|
||||||
*
|
|
||||||
* @name iap.PROVIDER_ID_AMAZON
|
|
||||||
* @variable
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*# provider id for Apple
|
|
||||||
*
|
|
||||||
* @name iap.PROVIDER_ID_APPLE
|
|
||||||
* @variable
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*# provider id for Facebook
|
|
||||||
*
|
|
||||||
* @name iap.PROVIDER_ID_FACEBOOK
|
|
||||||
* @variable
|
|
||||||
*/
|
|
||||||
|
|
||||||
dmExtension::Result InitializeIAP(dmExtension::Params* params)
|
|
||||||
{
|
{
|
||||||
// TODO: Life-cycle managaemnt is *budget*. No notion of "static initalization"
|
// TODO: Life-cycle managaemnt is *budget*. No notion of "static initalization"
|
||||||
// Extend extension functionality with per system initalization?
|
// Extend extension functionality with per system initalization?
|
||||||
@ -752,37 +530,55 @@ dmExtension::Result InitializeIAP(dmExtension::Params* params)
|
|||||||
}
|
}
|
||||||
g_IAP.m_InitCount++;
|
g_IAP.m_InitCount++;
|
||||||
|
|
||||||
|
IAP_Queue_Create(&g_IAP.m_CommandQueue);
|
||||||
|
|
||||||
lua_State*L = params->m_L;
|
lua_State*L = params->m_L;
|
||||||
int top = lua_gettop(L);
|
int top = lua_gettop(L);
|
||||||
luaL_register(L, LIB_NAME, IAP_methods);
|
luaL_register(L, LIB_NAME, IAP_methods);
|
||||||
|
|
||||||
// ensure ios payment constants values corresponds to iap constants.
|
// ensure ios payment constants values corresponds to iap constants.
|
||||||
assert(TRANS_STATE_PURCHASING == SKPaymentTransactionStatePurchasing);
|
assert((int)TRANS_STATE_PURCHASING == (int)SKPaymentTransactionStatePurchasing);
|
||||||
assert(TRANS_STATE_PURCHASED == SKPaymentTransactionStatePurchased);
|
assert((int)TRANS_STATE_PURCHASED == (int)SKPaymentTransactionStatePurchased);
|
||||||
assert(TRANS_STATE_FAILED == SKPaymentTransactionStateFailed);
|
assert((int)TRANS_STATE_FAILED == (int)SKPaymentTransactionStateFailed);
|
||||||
assert(TRANS_STATE_RESTORED == SKPaymentTransactionStateRestored);
|
assert((int)TRANS_STATE_RESTORED == (int)SKPaymentTransactionStateRestored);
|
||||||
|
|
||||||
IAP_PushConstants(L);
|
IAP_PushConstants(L);
|
||||||
|
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
assert(top == lua_gettop(L));
|
assert(top == lua_gettop(L));
|
||||||
|
|
||||||
return dmExtension::RESULT_OK;
|
return dmExtension::RESULT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
dmExtension::Result FinalizeIAP(dmExtension::Params* params)
|
static void IAP_OnCommand(IAPCommand* cmd, void*)
|
||||||
|
{
|
||||||
|
switch (cmd->m_Command)
|
||||||
|
{
|
||||||
|
case IAP_PRODUCT_RESULT:
|
||||||
|
HandleProductResult(cmd);
|
||||||
|
break;
|
||||||
|
case IAP_PURCHASE_RESULT:
|
||||||
|
HandlePurchaseResult(cmd);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static dmExtension::Result UpdateIAP(dmExtension::Params* params)
|
||||||
|
{
|
||||||
|
IAP_Queue_Flush(&g_IAP.m_CommandQueue, IAP_OnCommand, 0);
|
||||||
|
return dmExtension::RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static dmExtension::Result FinalizeIAP(dmExtension::Params* params)
|
||||||
{
|
{
|
||||||
--g_IAP.m_InitCount;
|
--g_IAP.m_InitCount;
|
||||||
|
|
||||||
// TODO: Should we support one listener per lua-state?
|
// TODO: Should we support one listener per lua-state?
|
||||||
// Or just use a single lua-state...?
|
// Or just use a single lua-state...?
|
||||||
if (params->m_L == g_IAP.m_Listener.m_L && g_IAP.m_Listener.m_Callback != LUA_NOREF) {
|
if (params->m_L == g_IAP.m_Listener.m_L) {
|
||||||
dmScript::Unref(g_IAP.m_Listener.m_L, LUA_REGISTRYINDEX, g_IAP.m_Listener.m_Callback);
|
IAP_UnregisterCallback(&g_IAP.m_Listener);
|
||||||
dmScript::Unref(g_IAP.m_Listener.m_L, LUA_REGISTRYINDEX, g_IAP.m_Listener.m_Self);
|
|
||||||
g_IAP.m_Listener.m_L = 0;
|
|
||||||
g_IAP.m_Listener.m_Callback = LUA_NOREF;
|
|
||||||
g_IAP.m_Listener.m_Self = LUA_NOREF;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_IAP.m_InitCount == 0) {
|
if (g_IAP.m_InitCount == 0) {
|
||||||
@ -797,10 +593,13 @@ dmExtension::Result FinalizeIAP(dmExtension::Params* params)
|
|||||||
g_IAP.m_Observer = 0;
|
g_IAP.m_Observer = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IAP_Queue_Destroy(&g_IAP.m_CommandQueue);
|
||||||
|
|
||||||
return dmExtension::RESULT_OK;
|
return dmExtension::RESULT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DM_DECLARE_EXTENSION(IAPExt, "IAP", 0, 0, InitializeIAP, 0, 0, FinalizeIAP)
|
DM_DECLARE_EXTENSION(IAPExt, "IAP", 0, 0, InitializeIAP, UpdateIAP, 0, FinalizeIAP)
|
||||||
|
|
||||||
#endif // DM_PLATFORM_IOS
|
#endif // DM_PLATFORM_IOS
|
||||||
|
@ -7,6 +7,65 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
|
||||||
|
IAPListener::IAPListener() {
|
||||||
|
IAP_ClearCallback(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IAP_ClearCallback(IAPListener* callback)
|
||||||
|
{
|
||||||
|
callback->m_L = 0;
|
||||||
|
callback->m_Callback = LUA_NOREF;
|
||||||
|
callback->m_Self = LUA_NOREF;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IAP_RegisterCallback(lua_State* L, int index, IAPListener* callback)
|
||||||
|
{
|
||||||
|
luaL_checktype(L, index, LUA_TFUNCTION);
|
||||||
|
lua_pushvalue(L, index);
|
||||||
|
callback->m_Callback = dmScript::Ref(L, LUA_REGISTRYINDEX);
|
||||||
|
|
||||||
|
dmScript::GetInstance(L);
|
||||||
|
callback->m_Self = dmScript::Ref(L, LUA_REGISTRYINDEX);
|
||||||
|
|
||||||
|
callback->m_L = L;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IAP_UnregisterCallback(IAPListener* callback)
|
||||||
|
{
|
||||||
|
if (LUA_NOREF != callback->m_Callback)
|
||||||
|
dmScript::Unref(callback->m_L, LUA_REGISTRYINDEX, callback->m_Callback);
|
||||||
|
if (LUA_NOREF != callback->m_Self)
|
||||||
|
dmScript::Unref(callback->m_L, LUA_REGISTRYINDEX, callback->m_Self);
|
||||||
|
callback->m_Callback = LUA_NOREF;
|
||||||
|
callback->m_Self = LUA_NOREF;
|
||||||
|
callback->m_L = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IAP_SetupCallback(IAPListener* callback)
|
||||||
|
{
|
||||||
|
lua_State* L = callback->m_L;
|
||||||
|
lua_rawgeti(L, LUA_REGISTRYINDEX, callback->m_Callback);
|
||||||
|
|
||||||
|
// Setup self
|
||||||
|
lua_rawgeti(L, LUA_REGISTRYINDEX, callback->m_Self);
|
||||||
|
lua_pushvalue(L, -1);
|
||||||
|
dmScript::SetInstance(L);
|
||||||
|
|
||||||
|
if (!dmScript::IsInstanceValid(L))
|
||||||
|
{
|
||||||
|
dmLogError("Could not run callback because the instance has been deleted.");
|
||||||
|
lua_pop(L, 2);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IAP_CallbackIsValid(IAPListener* callback)
|
||||||
|
{
|
||||||
|
return callback != 0 && callback->m_L != 0 && callback->m_Callback != LUA_NOREF && callback->m_Self != LUA_NOREF;
|
||||||
|
}
|
||||||
|
|
||||||
// Creates a comma separated string, given a table where all values are strings (or numbers)
|
// Creates a comma separated string, given a table where all values are strings (or numbers)
|
||||||
// Returns a malloc'ed string, which the caller must free
|
// Returns a malloc'ed string, which the caller must free
|
||||||
char* IAP_List_CreateBuffer(lua_State* L)
|
char* IAP_List_CreateBuffer(lua_State* L)
|
||||||
@ -96,4 +155,43 @@ void IAP_PushConstants(lua_State* L)
|
|||||||
#undef SETCONSTANT
|
#undef SETCONSTANT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void IAP_Queue_Create(IAPCommandQueue* queue)
|
||||||
|
{
|
||||||
|
queue->m_Mutex = dmMutex::New();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IAP_Queue_Destroy(IAPCommandQueue* queue)
|
||||||
|
{
|
||||||
|
dmMutex::Delete(queue->m_Mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IAP_Queue_Push(IAPCommandQueue* queue, IAPCommand* cmd)
|
||||||
|
{
|
||||||
|
DM_MUTEX_SCOPED_LOCK(queue->m_Mutex);
|
||||||
|
|
||||||
|
if(queue->m_Commands.Full())
|
||||||
|
{
|
||||||
|
queue->m_Commands.OffsetCapacity(2);
|
||||||
|
}
|
||||||
|
queue->m_Commands.Push(*cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IAP_Queue_Flush(IAPCommandQueue* queue, IAPCommandFn fn, void* ctx)
|
||||||
|
{
|
||||||
|
assert(fn != 0);
|
||||||
|
if (queue->m_Commands.Empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DM_MUTEX_SCOPED_LOCK(queue->m_Mutex);
|
||||||
|
|
||||||
|
for(uint32_t i = 0; i != queue->m_Commands.Size(); ++i)
|
||||||
|
{
|
||||||
|
fn(&queue->m_Commands[i], ctx);
|
||||||
|
}
|
||||||
|
queue->m_Commands.SetSize(0);
|
||||||
|
}
|
||||||
|
|
||||||
#endif // DM_PLATFORM_HTML5 || DM_PLATFORM_ANDROID || DM_PLATFORM_IOS
|
#endif // DM_PLATFORM_HTML5 || DM_PLATFORM_ANDROID || DM_PLATFORM_IOS
|
||||||
|
@ -5,24 +5,62 @@
|
|||||||
|
|
||||||
#include <dmsdk/sdk.h>
|
#include <dmsdk/sdk.h>
|
||||||
|
|
||||||
|
// TODO: Rename Callback
|
||||||
struct IAPListener
|
struct IAPListener
|
||||||
{
|
{
|
||||||
IAPListener()
|
IAPListener();
|
||||||
{
|
|
||||||
m_L = 0;
|
|
||||||
m_Callback = LUA_NOREF;
|
|
||||||
m_Self = LUA_NOREF;
|
|
||||||
}
|
|
||||||
lua_State* m_L;
|
lua_State* m_L;
|
||||||
int m_Callback;
|
int m_Callback;
|
||||||
int m_Self;
|
int m_Self;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum EIAPCommand
|
||||||
|
{
|
||||||
|
IAP_PRODUCT_RESULT,
|
||||||
|
IAP_PURCHASE_RESULT,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DM_ALIGNED(16) IAPCommand
|
||||||
|
{
|
||||||
|
IAPCommand()
|
||||||
|
{
|
||||||
|
memset(this, 0, sizeof(IAPCommand));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for storing eventual callback info (if needed)
|
||||||
|
IAPListener m_Callback;
|
||||||
|
|
||||||
|
// THe actual command payload
|
||||||
|
int32_t m_Command;
|
||||||
|
int32_t m_ResponseCode;
|
||||||
|
void* m_Data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IAPCommandQueue
|
||||||
|
{
|
||||||
|
dmArray<IAPCommand> m_Commands;
|
||||||
|
dmMutex::HMutex m_Mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
void IAP_ClearCallback(IAPListener* callback);
|
||||||
|
void IAP_RegisterCallback(lua_State* L, int index, IAPListener* callback);
|
||||||
|
void IAP_UnregisterCallback(IAPListener* callback);
|
||||||
|
bool IAP_SetupCallback(IAPListener* callback);
|
||||||
|
bool IAP_CallbackIsValid(IAPListener* callback);
|
||||||
|
|
||||||
char* IAP_List_CreateBuffer(lua_State* L);
|
char* IAP_List_CreateBuffer(lua_State* L);
|
||||||
void IAP_PushError(lua_State* L, const char* error, int reason);
|
void IAP_PushError(lua_State* L, const char* error, int reason);
|
||||||
void IAP_PushConstants(lua_State* L);
|
void IAP_PushConstants(lua_State* L);
|
||||||
|
|
||||||
|
typedef void (*IAPCommandFn)(IAPCommand* cmd, void* ctx);
|
||||||
|
|
||||||
|
void IAP_Queue_Create(IAPCommandQueue* queue);
|
||||||
|
void IAP_Queue_Destroy(IAPCommandQueue* queue);
|
||||||
|
// The command is copied by value into the queue
|
||||||
|
void IAP_Queue_Push(IAPCommandQueue* queue, IAPCommand* cmd);
|
||||||
|
void IAP_Queue_Flush(IAPCommandQueue* queue, IAPCommandFn fn, void* ctx);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif // DM_PLATFORM_HTML5 || DM_PLATFORM_ANDROID || DM_PLATFORM_IOS
|
#endif // DM_PLATFORM_HTML5 || DM_PLATFORM_ANDROID || DM_PLATFORM_IOS
|
||||||
|
Loading…
x
Reference in New Issue
Block a user