10 Commits
1.1 ... 1.2.2

Author SHA1 Message Date
Björn Ritzl
8d9ea79d7d Fixed crash if buying when no listener was set 2020-02-10 08:25:48 +01:00
Björn Ritzl
c8d2d4e0d8 Merge branch 'master' of https://github.com/defold/extension-iap 2020-02-10 08:00:59 +01:00
Björn Ritzl
d2e2a640df Updated the example 2020-02-10 08:00:56 +01:00
Björn Ritzl
aa9448b5e9 Update index.md 2020-02-06 07:35:00 +01:00
Björn Ritzl
ce26b8fdd9 Merge pull request #15 from defold/crash_issue_fix
Fix crash related to lua stack
2020-01-17 06:20:18 +01:00
Alexey Gulev
1a778d0e98 fix crash 2020-01-16 23:16:21 +01:00
Mathias Westerdahl
6056ff8eeb Merge pull request #13 from defold/refactor-lua-platform-code
Updated html5 to use the new Lua callback api from dmScript
2019-11-21 18:20:43 +01:00
mathiaswking
0e76cd4f24 Updated html5 to use the new Lua callback api from dmScript 2019-11-21 18:06:39 +01:00
Mathias Westerdahl
4221a3ba27 Merge pull request #11 from defold/sdk-lua-callback
Use Lua callback handling functions from DefoldSDK
2019-11-19 08:59:10 +01:00
mathiaswking
8eebb889e3 Use Lua callback handling functions from DefoldSDK 2019-11-15 19:04:23 +01:00
12 changed files with 955 additions and 571 deletions

View File

@@ -11,7 +11,7 @@ To use this library in your Defold project, add the following URL to your <code
https://github.com/defold/extension-iap/archive/master.zip https://github.com/defold/extension-iap/archive/master.zip
We recommend using a link to a zip file of a [https://github.com/defold/extension-iap/releases](specific release). We recommend using a link to a zip file of a [specific release](https://github.com/defold/extension-iap/releases).
## Source code ## Source code

View File

@@ -31,7 +31,7 @@ var LibraryFacebookIAP = {
FB_PAYMENT_RESPONSE_APPINVALIDITEMPARAM : 1383051 FB_PAYMENT_RESPONSE_APPINVALIDITEMPARAM : 1383051
}, },
http_callback: function(xmlhttp, callback, lua_state, products, product_ids, product_count, url_index, url_count) { http_callback: function(xmlhttp, callback, lua_callback, products, product_ids, product_count, url_index, url_count) {
if (xmlhttp.readyState == 4) { if (xmlhttp.readyState == 4) {
if(xmlhttp.status == 200) { if(xmlhttp.status == 200) {
var xmlDoc = document.createElement( 'html' ); var xmlDoc = document.createElement( 'html' );
@@ -72,11 +72,11 @@ var LibraryFacebookIAP = {
if(url_index == product_count-1) { if(url_index == product_count-1) {
var productsJSON = JSON.stringify(products); var productsJSON = JSON.stringify(products);
var res_buf = allocate(intArrayFromString(productsJSON), 'i8', ALLOC_STACK); var res_buf = allocate(intArrayFromString(productsJSON), 'i8', ALLOC_STACK);
Runtime.dynCall('vii', callback, [lua_state, res_buf]); Runtime.dynCall('vii', callback, [lua_callback, res_buf]);
} else { } else {
var xmlhttp = new XMLHttpRequest(); var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() { xmlhttp.onreadystatechange = function() {
FBinner.http_callback(xmlhttp, callback, lua_state, products, product_ids, product_count, url_index+1); FBinner.http_callback(xmlhttp, callback, lua_callback, products, product_ids, product_count, url_index+1);
}; };
xmlhttp.open("GET", product_ids[url_index+1], true); xmlhttp.open("GET", product_ids[url_index+1], true);
xmlhttp.send(); xmlhttp.send();
@@ -86,7 +86,7 @@ var LibraryFacebookIAP = {
}, },
dmIAPFBList: function(params, callback, lua_state) { dmIAPFBList: function(params, callback, lua_callback) {
var product_ids = Pointer_stringify(params).trim().split(','); var product_ids = Pointer_stringify(params).trim().split(',');
var product_count = product_ids.length; var product_count = product_ids.length;
if(product_count == 0) { if(product_count == 0) {
@@ -96,7 +96,7 @@ var LibraryFacebookIAP = {
products = {}; products = {};
var xmlhttp = new XMLHttpRequest(); var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() { xmlhttp.onreadystatechange = function() {
FBinner.http_callback(xmlhttp, callback, lua_state, products, product_ids, product_count, 0); FBinner.http_callback(xmlhttp, callback, lua_callback, products, product_ids, product_count, 0);
}; };
// chain of async http read of product files // chain of async http read of product files
xmlhttp.open("GET", product_ids[0], true); xmlhttp.open("GET", product_ids[0], true);
@@ -104,7 +104,7 @@ var LibraryFacebookIAP = {
}, },
// https://developers.facebook.com/docs/javascript/reference/FB.ui // https://developers.facebook.com/docs/javascript/reference/FB.ui
dmIAPFBBuy: function(param_product_id, param_request_id, callback, lua_state) { dmIAPFBBuy: function(param_product_id, param_request_id, callback, lua_callback) {
var product_id = Pointer_stringify(param_product_id); var product_id = Pointer_stringify(param_product_id);
var buy_params = { var buy_params = {
@@ -144,7 +144,7 @@ var LibraryFacebookIAP = {
var productsJSON = JSON.stringify(result) var productsJSON = JSON.stringify(result)
var res_buf = allocate(intArrayFromString(productsJSON), 'i8', ALLOC_STACK); var res_buf = allocate(intArrayFromString(productsJSON), 'i8', ALLOC_STACK);
Runtime.dynCall('viii', callback, [lua_state, res_buf, 0]); Runtime.dynCall('viii', callback, [lua_callback, res_buf, 0]);
} else { } else {
@@ -157,7 +157,7 @@ var LibraryFacebookIAP = {
reason = FBinner.BillingResponse.BILLING_RESPONSE_RESULT_ERROR; reason = FBinner.BillingResponse.BILLING_RESPONSE_RESULT_ERROR;
console.log("Unknown response: ", response); console.log("Unknown response: ", response);
} }
Runtime.dynCall('viii', callback, [lua_state, 0, reason]); Runtime.dynCall('viii', callback, [lua_callback, 0, reason]);
} }
} }
); );

View File

@@ -27,20 +27,15 @@ struct IAP
IAP() IAP()
{ {
memset(this, 0, sizeof(*this)); memset(this, 0, sizeof(*this));
m_Callback = LUA_NOREF;
m_Self = LUA_NOREF;
m_Listener.m_Callback = LUA_NOREF;
m_Listener.m_Self = LUA_NOREF;
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_Self;
bool m_autoFinishTransactions; bool m_autoFinishTransactions;
int m_ProviderId; int m_ProviderId;
lua_State* m_L;
IAPListener m_Listener; dmScript::LuaCallbackInfo* m_ProductCallback;
dmScript::LuaCallbackInfo* m_Listener;
jobject m_IAP; jobject m_IAP;
jobject m_IAPJNI; jobject m_IAPJNI;
@@ -56,22 +51,9 @@ struct IAP
static IAP g_IAP; static IAP g_IAP;
static void ResetCallback(lua_State* L)
{
if (g_IAP.m_Callback != LUA_NOREF) {
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;
g_IAP.m_L = 0;
}
}
static int IAP_List(lua_State* L) static int IAP_List(lua_State* L)
{ {
int top = lua_gettop(L); int top = lua_gettop(L);
ResetCallback(L);
char* buf = IAP_List_CreateBuffer(L); char* buf = IAP_List_CreateBuffer(L);
if( buf == 0 ) if( buf == 0 )
{ {
@@ -79,14 +61,10 @@ static int IAP_List(lua_State* L)
return 0; return 0;
} }
luaL_checktype(L, 2, LUA_TFUNCTION); if (g_IAP.m_ProductCallback)
lua_pushvalue(L, 2); dmScript::DestroyCallback(g_IAP.m_ProductCallback);
g_IAP.m_Callback = dmScript::Ref(L, LUA_REGISTRYINDEX);
dmScript::GetInstance(L); g_IAP.m_ProductCallback = dmScript::CreateCallback(L, 2);
g_IAP.m_Self = dmScript::Ref(L, LUA_REGISTRYINDEX);
g_IAP.m_L = dmScript::GetMainThread(L);
JNIEnv* env = Attach(); JNIEnv* env = Attach();
jstring products = env->NewStringUTF(buf); jstring products = env->NewStringUTF(buf);
@@ -180,22 +158,13 @@ static int IAP_Restore(lua_State* L)
static int IAP_SetListener(lua_State* L) static 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);
bool had_previous = false; bool had_previous = iap->m_Listener != 0;
if (iap->m_Listener.m_Callback != LUA_NOREF) {
dmScript::Unref(iap->m_Listener.m_L, LUA_REGISTRYINDEX, iap->m_Listener.m_Callback);
dmScript::Unref(iap->m_Listener.m_L, LUA_REGISTRYINDEX, iap->m_Listener.m_Self);
had_previous = true;
}
iap->m_Listener.m_L = dmScript::GetMainThread(L); if (iap->m_Listener)
iap->m_Listener.m_Callback = cb; dmScript::DestroyCallback(iap->m_Listener);
dmScript::GetInstance(L); iap->m_Listener = dmScript::CreateCallback(L, 1);
iap->m_Listener.m_Self = dmScript::Ref(L, LUA_REGISTRYINDEX);
// On first set listener, trigger process old ones. // On first set listener, trigger process old ones.
if (!had_previous) { if (!had_previous) {
@@ -238,6 +207,7 @@ JNIEXPORT void JNICALL Java_com_defold_iap_IapJNI_onProductsResult__ILjava_lang_
} }
IAPCommand cmd; IAPCommand cmd;
cmd.m_Callback = g_IAP.m_ProductCallback;
cmd.m_Command = IAP_PRODUCT_RESULT; cmd.m_Command = IAP_PRODUCT_RESULT;
cmd.m_ResponseCode = responseCode; cmd.m_ResponseCode = responseCode;
if (pl) if (pl)
@@ -257,6 +227,7 @@ JNIEXPORT void JNICALL Java_com_defold_iap_IapJNI_onPurchaseResult__ILjava_lang_
} }
IAPCommand cmd; IAPCommand cmd;
cmd.m_Callback = g_IAP.m_Listener;
cmd.m_Command = IAP_PURCHASE_RESULT; cmd.m_Command = IAP_PURCHASE_RESULT;
cmd.m_ResponseCode = responseCode; cmd.m_ResponseCode = responseCode;
if (pd) if (pd)
@@ -273,25 +244,17 @@ JNIEXPORT void JNICALL Java_com_defold_iap_IapJNI_onPurchaseResult__ILjava_lang_
static void HandleProductResult(const IAPCommand* cmd) static void HandleProductResult(const IAPCommand* cmd)
{ {
lua_State* L = g_IAP.m_L; if (cmd->m_Callback == 0)
int top = lua_gettop(L); {
dmLogWarning("Received product list but no listener was set!");
if (g_IAP.m_Callback == LUA_NOREF) {
dmLogError("No callback set");
return; return;
} }
lua_rawgeti(L, LUA_REGISTRYINDEX, g_IAP.m_Callback); lua_State* L = dmScript::GetCallbackLuaContext(cmd->m_Callback);
int top = lua_gettop(L);
// Setup self if (!dmScript::SetupCallback(cmd->m_Callback))
lua_rawgeti(L, LUA_REGISTRYINDEX, g_IAP.m_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;
} }
@@ -320,42 +283,29 @@ static void HandleProductResult(const IAPCommand* cmd)
IAP_PushError(L, "failed to fetch product", REASON_UNSPECIFIED); IAP_PushError(L, "failed to fetch product", REASON_UNSPECIFIED);
} }
int ret = lua_pcall(L, 3, 0, 0); dmScript::PCall(L, 3, 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::TeardownCallback(cmd->m_Callback);
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Self); dmScript::DestroyCallback(cmd->m_Callback);
g_IAP.m_Callback = LUA_NOREF; assert(g_IAP.m_ProductCallback == cmd->m_Callback);
g_IAP.m_Self = LUA_NOREF; g_IAP.m_ProductCallback = 0;
assert(top == lua_gettop(L)); assert(top == lua_gettop(L));
} }
static void HandlePurchaseResult(const IAPCommand* cmd) static void HandlePurchaseResult(const IAPCommand* cmd)
{ {
lua_State* L = g_IAP.m_Listener.m_L; if (cmd->m_Callback == 0)
int top = lua_gettop(L); {
dmLogWarning("Received purchase result but no listener was set!");
if (g_IAP.m_Listener.m_Callback == LUA_NOREF) {
dmLogError("No callback set");
return; return;
} }
lua_State* L = dmScript::GetCallbackLuaContext(cmd->m_Callback);
int top = lua_gettop(L);
lua_rawgeti(L, LUA_REGISTRYINDEX, g_IAP.m_Listener.m_Callback); if (!dmScript::SetupCallback(cmd->m_Callback))
// Setup self
lua_rawgeti(L, LUA_REGISTRYINDEX, g_IAP.m_Listener.m_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;
} }
@@ -393,11 +343,9 @@ static void HandlePurchaseResult(const IAPCommand* cmd)
IAP_PushError(L, "failed to buy product", REASON_UNSPECIFIED); IAP_PushError(L, "failed to buy product", REASON_UNSPECIFIED);
} }
int ret = lua_pcall(L, 3, 0, 0); dmScript::PCall(L, 3, 0);
if (ret != 0) {
dmLogError("Error running callback: %s", lua_tostring(L, -1)); dmScript::TeardownCallback(cmd->m_Callback);
lua_pop(L, 1);
}
assert(top == lua_gettop(L)); assert(top == lua_gettop(L));
} }
@@ -500,12 +448,9 @@ static dmExtension::Result FinalizeIAP(dmExtension::Params* params)
IAP_Queue_Destroy(&g_IAP.m_CommandQueue); 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 == dmScript::GetCallbackLuaContext(g_IAP.m_Listener)) {
dmScript::Unref(g_IAP.m_Listener.m_L, LUA_REGISTRYINDEX, g_IAP.m_Listener.m_Callback); dmScript::DestroyCallback(g_IAP.m_Listener);
dmScript::Unref(g_IAP.m_Listener.m_L, LUA_REGISTRYINDEX, g_IAP.m_Listener.m_Self); g_IAP.m_Listener = 0;
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) {

View File

@@ -15,63 +15,35 @@ struct IAP
IAP() IAP()
{ {
memset(this, 0, sizeof(*this)); memset(this, 0, sizeof(*this));
m_Callback = LUA_NOREF;
m_Self = LUA_NOREF;
m_Listener.m_Callback = LUA_NOREF;
m_Listener.m_Self = LUA_NOREF;
m_autoFinishTransactions = true; m_autoFinishTransactions = true;
} }
int m_InitCount;
int m_Callback;
int m_Self;
bool m_autoFinishTransactions;
lua_State* m_L;
IAPListener m_Listener;
dmScript::LuaCallbackInfo* m_Listener;
int m_InitCount;
bool m_autoFinishTransactions;
} g_IAP; } g_IAP;
typedef void (*OnIAPFBList)(void *L, const char* json); typedef void (*OnIAPFBList)(void* luacallback, const char* json);
typedef void (*OnIAPFBListenerCallback)(void *L, const char* json, int error_code); typedef void (*OnIAPFBListenerCallback)(void* luacallback, const char* json, int error_code);
extern "C" { extern "C" {
// Implementation in library_facebook_iap.js // Implementation in library_facebook_iap.js
void dmIAPFBList(const char* item_ids, OnIAPFBList callback, lua_State* L); void dmIAPFBList(const char* item_ids, OnIAPFBList callback, dmScript::LuaCallbackInfo* luacallback);
void dmIAPFBBuy(const char* item_id, const char* request_id, OnIAPFBListenerCallback callback, lua_State* L); void dmIAPFBBuy(const char* item_id, const char* request_id, OnIAPFBListenerCallback callback, dmScript::LuaCallbackInfo* luacallback);
} }
static void IAPList_Callback(void* luacallback, const char* result_json)
static void VerifyCallback(lua_State* L)
{ {
if (g_IAP.m_Callback != LUA_NOREF) { dmScript::LuaCallbackInfo* callback = (dmScript::LuaCallbackInfo*)luacallback;
dmLogError("Unexpected callback set"); lua_State* L = dmScript::GetCallbackLuaContext(callback);
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Callback); DM_LUA_STACK_CHECK(L, 0);
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Self);
g_IAP.m_Callback = LUA_NOREF;
g_IAP.m_Self = LUA_NOREF;
g_IAP.m_L = 0;
}
}
void IAPList_Callback(void* Lv, const char* result_json) if (!dmScript::SetupCallback(callback))
{ {
lua_State* L = (lua_State*) Lv; dmScript::DestroyCallback(callback);
if (g_IAP.m_Callback != LUA_NOREF) {
int top = lua_gettop(L);
int callback = g_IAP.m_Callback;
lua_rawgeti(L, LUA_REGISTRYINDEX, callback);
// 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 iap facebook callback because the instance has been deleted.");
lua_pop(L, 2);
assert(top == lua_gettop(L));
return; return;
} }
if(result_json != 0) if(result_json != 0)
{ {
dmJson::Document doc; dmJson::Document doc;
@@ -99,68 +71,41 @@ void IAPList_Callback(void* Lv, const char* result_json)
IAP_PushError(L, "Got empty list result.", REASON_UNSPECIFIED); IAP_PushError(L, "Got empty list result.", REASON_UNSPECIFIED);
} }
int ret = lua_pcall(L, 3, 0, 0); dmScript::PCall(L, 3, 0);
if (ret != 0) {
dmLogError("Error running callback: %s", lua_tostring(L, -1));
lua_pop(L, 1);
}
assert(top == lua_gettop(L));
dmScript::Unref(L, LUA_REGISTRYINDEX, callback);
g_IAP.m_Callback = LUA_NOREF; dmScript::DestroyCallback(callback);
} else { dmScript::TeardownCallback(callback);
dmLogError("No callback set");
} }
} static int IAP_List(lua_State* L)
int IAP_List(lua_State* L)
{ {
int top = lua_gettop(L); DM_LUA_STACK_CHECK(L, 0);
VerifyCallback(L);
char* buf = IAP_List_CreateBuffer(L); char* buf = IAP_List_CreateBuffer(L);
if( buf == 0 ) if( buf == 0 )
{ {
assert(top == lua_gettop(L));
return 0; return 0;
} }
luaL_checktype(L, 2, LUA_TFUNCTION); dmScript::LuaCallbackInfo* callback = dmScript::CreateCallback(L, 2);
lua_pushvalue(L, 2);
g_IAP.m_Callback = dmScript::Ref(L, LUA_REGISTRYINDEX); dmIAPFBList(buf, (OnIAPFBList)IAPList_Callback, callback);
dmScript::GetInstance(L);
g_IAP.m_Self = dmScript::Ref(L, LUA_REGISTRYINDEX);
g_IAP.m_L = dmScript::GetMainThread(L);
dmIAPFBList(buf, (OnIAPFBList)IAPList_Callback, g_IAP.m_L);
free(buf); free(buf);
assert(top == lua_gettop(L));
return 0; return 0;
} }
void IAPListener_Callback(void* Lv, const char* result_json, int error_code) static void IAPListener_Callback(void* luacallback, const char* result_json, int error_code)
{
dmScript::LuaCallbackInfo* callback = (dmScript::LuaCallbackInfo*)luacallback;
lua_State* L = dmScript::GetCallbackLuaContext(callback);
DM_LUA_STACK_CHECK(L, 0);
if (!dmScript::SetupCallback(callback))
{ {
lua_State* L = g_IAP.m_Listener.m_L;
int top = lua_gettop(L);
if (g_IAP.m_Listener.m_Callback == LUA_NOREF) {
dmLogError("No callback set");
return; return;
} }
lua_rawgeti(L, LUA_REGISTRYINDEX, g_IAP.m_Listener.m_Callback);
// Setup self
lua_rawgeti(L, LUA_REGISTRYINDEX, g_IAP.m_Listener.m_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));
return;
}
if (result_json) { if (result_json) {
dmJson::Document doc; dmJson::Document doc;
dmJson::Result r = dmJson::Parse(result_json, &doc); dmJson::Result r = dmJson::Parse(result_json, &doc);
@@ -197,21 +142,22 @@ void IAPListener_Callback(void* Lv, const char* result_json, int error_code)
break; break;
} }
} }
int ret = lua_pcall(L, 3, 0, 0);
if (ret != 0) { dmScript::PCall(L, 3, 0);
dmLogError("Error running callback: %s", lua_tostring(L, -1));
lua_pop(L, 1); dmScript::TeardownCallback(callback);
}
assert(top == lua_gettop(L));
} }
int IAP_Buy(lua_State* L) static int IAP_Buy(lua_State* L)
{ {
if (g_IAP.m_Listener.m_Callback == LUA_NOREF) { DM_LUA_STACK_CHECK(L, 0);
if (!g_IAP.m_Listener) {
dmLogError("No callback set"); dmLogError("No callback set");
return 0; return 0;
} }
int top = lua_gettop(L); int top = lua_gettop(L);
const char* id = luaL_checkstring(L, 1); const char* id = luaL_checkstring(L, 1);
const char* request_id = 0x0; const char* request_id = 0x0;
@@ -224,43 +170,34 @@ int IAP_Buy(lua_State* L)
lua_pop(L, 2); lua_pop(L, 2);
} }
dmIAPFBBuy(id, request_id, (OnIAPFBListenerCallback)IAPListener_Callback, L); dmIAPFBBuy(id, request_id, (OnIAPFBListenerCallback)IAPListener_Callback, g_IAP.m_Listener);
assert(top == lua_gettop(L));
return 0; return 0;
} }
int IAP_SetListener(lua_State* L) static int IAP_SetListener(lua_State* L)
{ {
IAP* iap = &g_IAP; DM_LUA_STACK_CHECK(L, 0);
luaL_checktype(L, 1, LUA_TFUNCTION); if (g_IAP.m_Listener)
lua_pushvalue(L, 1); dmScript::DestroyCallback(g_IAP.m_Listener);
int cb = dmScript::Ref(L, LUA_REGISTRYINDEX); g_IAP.m_Listener = dmScript::CreateCallback(L, 1);
if (iap->m_Listener.m_Callback != LUA_NOREF) {
dmScript::Unref(iap->m_Listener.m_L, LUA_REGISTRYINDEX, iap->m_Listener.m_Callback);
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);
return 0; return 0;
} }
int IAP_Finish(lua_State* L) static int IAP_Finish(lua_State* L)
{ {
return 0; return 0;
} }
int IAP_Restore(lua_State* L) static int IAP_Restore(lua_State* L)
{ {
DM_LUA_STACK_CHECK(L, 1);
lua_pushboolean(L, 0); lua_pushboolean(L, 0);
return 1; return 1;
} }
int IAP_GetProviderId(lua_State* L) static int IAP_GetProviderId(lua_State* L)
{ {
DM_LUA_STACK_CHECK(L, 1);
lua_pushinteger(L, PROVIDER_ID_FACEBOOK); lua_pushinteger(L, PROVIDER_ID_FACEBOOK);
return 1; return 1;
} }
@@ -276,7 +213,7 @@ static const luaL_reg IAP_methods[] =
{0, 0} {0, 0}
}; };
dmExtension::Result InitializeIAP(dmExtension::Params* params) static dmExtension::Result InitializeIAP(dmExtension::Params* params)
{ {
if (g_IAP.m_InitCount == 0) { if (g_IAP.m_InitCount == 0) {
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;
@@ -293,15 +230,13 @@ dmExtension::Result InitializeIAP(dmExtension::Params* params)
return dmExtension::RESULT_OK; return dmExtension::RESULT_OK;
} }
dmExtension::Result FinalizeIAP(dmExtension::Params* params) static dmExtension::Result FinalizeIAP(dmExtension::Params* params)
{ {
--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 (g_IAP.m_Listener && g_IAP.m_InitCount == 0)
dmScript::Unref(g_IAP.m_Listener.m_L, LUA_REGISTRYINDEX, g_IAP.m_Listener.m_Callback); {
dmScript::Unref(g_IAP.m_Listener.m_L, LUA_REGISTRYINDEX, g_IAP.m_Listener.m_Self); dmScript::DestroyCallback(g_IAP.m_Listener);
g_IAP.m_Listener.m_L = 0; g_IAP.m_Listener = 0;
g_IAP.m_Listener.m_Callback = LUA_NOREF;
g_IAP.m_Listener.m_Self = LUA_NOREF;
} }
return dmExtension::RESULT_OK; return dmExtension::RESULT_OK;
} }

View File

@@ -23,12 +23,11 @@ struct IAP
{ {
memset(this, 0, sizeof(*this)); memset(this, 0, sizeof(*this));
m_AutoFinishTransactions = true; m_AutoFinishTransactions = true;
IAP_ClearCallback(&m_Listener);
} }
int m_InitCount; int m_InitCount;
bool m_AutoFinishTransactions; bool m_AutoFinishTransactions;
NSMutableDictionary* m_PendingTransactions; NSMutableDictionary* m_PendingTransactions;
IAPListener m_Listener; dmScript::LuaCallbackInfo* m_Listener;
IAPCommandQueue m_CommandQueue; IAPCommandQueue m_CommandQueue;
SKPaymentTransactionObserver* m_Observer; SKPaymentTransactionObserver* m_Observer;
}; };
@@ -101,15 +100,14 @@ static void IAP_FreeTransaction(IAPTransaction* transaction)
@interface SKProductsRequestDelegate : NSObject<SKProductsRequestDelegate> @interface SKProductsRequestDelegate : NSObject<SKProductsRequestDelegate>
@property IAPListener m_Callback; @property dmScript::LuaCallbackInfo* 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{
IAPListener callback = self.m_Callback; if (!dmScript::IsCallbackValid(self.m_Callback)) {
if (!IAP_CallbackIsValid(&callback)) {
dmLogError("No callback set"); dmLogError("No callback set");
return; return;
} }
@@ -141,7 +139,7 @@ static void IAP_FreeTransaction(IAPTransaction* transaction)
IAPCommand cmd; IAPCommand cmd;
cmd.m_Command = IAP_PRODUCT_RESULT; cmd.m_Command = IAP_PRODUCT_RESULT;
cmd.m_Callback = callback; cmd.m_Callback = self.m_Callback;
cmd.m_Data = iap_response; cmd.m_Data = iap_response;
IAP_Queue_Push(&g_IAP.m_CommandQueue, &cmd); IAP_Queue_Push(&g_IAP.m_CommandQueue, &cmd);
} }
@@ -151,10 +149,10 @@ static void HandleProductResult(IAPCommand* cmd)
IAPResponse* response = (IAPResponse*)cmd->m_Data; IAPResponse* response = (IAPResponse*)cmd->m_Data;
lua_State* L = cmd->m_Callback.m_L; lua_State* L = dmScript::GetCallbackLuaContext(g_IAP.m_Listener);
int top = lua_gettop(L); int top = lua_gettop(L);
if (!IAP_SetupCallback(&cmd->m_Callback)) if (!dmScript::SetupCallback(cmd->m_Callback))
{ {
assert(top == lua_gettop(L)); assert(top == lua_gettop(L));
delete response; delete response;
@@ -188,13 +186,10 @@ static void HandleProductResult(IAPCommand* cmd)
} }
lua_pushnil(L); lua_pushnil(L);
int ret = lua_pcall(L, 3, 0, 0); dmScript::PCall(L, 3, 0);
if (ret != 0) {
dmLogError("%d: Error running callback: %s", __LINE__, lua_tostring(L, -1));
lua_pop(L, 1);
}
IAP_UnregisterCallback(&cmd->m_Callback); dmScript::TeardownCallback(cmd->m_Callback);
dmScript::DestroyCallback(cmd->m_Callback);
delete response; delete response;
assert(top == lua_gettop(L)); assert(top == lua_gettop(L));
@@ -203,8 +198,7 @@ static void HandleProductResult(IAPCommand* cmd)
- (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]);
IAPListener callback = self.m_Callback; if (!dmScript::IsCallbackValid(self.m_Callback)) {
if (!IAP_CallbackIsValid(&callback)) {
dmLogError("No callback set"); dmLogError("No callback set");
return; return;
} }
@@ -215,7 +209,7 @@ static void HandleProductResult(IAPCommand* cmd)
IAPCommand cmd; IAPCommand cmd;
cmd.m_Command = IAP_PRODUCT_RESULT; cmd.m_Command = IAP_PRODUCT_RESULT;
cmd.m_Callback = callback; cmd.m_Callback = self.m_Callback;
cmd.m_Data = response; cmd.m_Data = response;
IAP_Queue_Push(&g_IAP.m_CommandQueue, &cmd); IAP_Queue_Push(&g_IAP.m_CommandQueue, &cmd);
@@ -300,13 +294,12 @@ static void PushTransaction(lua_State* L, IAPTransaction* transaction)
static void HandlePurchaseResult(IAPCommand* cmd) static void HandlePurchaseResult(IAPCommand* cmd)
{ {
IAPTransaction* transaction = (IAPTransaction*)cmd->m_Data; IAPTransaction* transaction = (IAPTransaction*)cmd->m_Data;
lua_State* L = cmd->m_Callback.m_L; lua_State* L = dmScript::GetCallbackLuaContext(cmd->m_Callback);
int top = lua_gettop(L); int top = lua_gettop(L);
if (!IAP_SetupCallback(&cmd->m_Callback)) if (!dmScript::SetupCallback(cmd->m_Callback))
{ {
assert(top == lua_gettop(L)); assert(top == lua_gettop(L));
return; return;
@@ -321,11 +314,9 @@ static void HandlePurchaseResult(IAPCommand* cmd)
lua_pushnil(L); lua_pushnil(L);
} }
int ret = lua_pcall(L, 3, 0, 0); dmScript::PCall(L, 3, 0);
if (ret != 0) {
dmLogError("%d: Error running callback: %s", __LINE__, lua_tostring(L, -1)); dmScript::TeardownCallback(cmd->m_Callback);
lua_pop(L, 1);
}
IAP_FreeTransaction(transaction); IAP_FreeTransaction(transaction);
@@ -344,8 +335,7 @@ static void HandlePurchaseResult(IAPCommand* cmd)
[self.m_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 = self.m_IAP->m_Listener.m_Callback != LUA_NOREF; if (!self.m_IAP->m_Listener)
if (!has_listener)
continue; continue;
IAPTransaction* iap_transaction = new IAPTransaction; IAPTransaction* iap_transaction = new IAPTransaction;
@@ -394,10 +384,7 @@ static int IAP_List(lua_State* L)
SKProductsRequest* products_request = [[SKProductsRequest alloc] initWithProductIdentifiers: product_identifiers]; SKProductsRequest* products_request = [[SKProductsRequest alloc] initWithProductIdentifiers: product_identifiers];
SKProductsRequestDelegate* delegate = [SKProductsRequestDelegate alloc]; SKProductsRequestDelegate* delegate = [SKProductsRequestDelegate alloc];
IAPListener callback; delegate.m_Callback = dmScript::CreateCallback(L, 2);
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];
@@ -486,8 +473,10 @@ static int IAP_SetListener(lua_State* L)
{ {
IAP* iap = &g_IAP; IAP* iap = &g_IAP;
IAP_UnregisterCallback(&iap->m_Listener); if (iap->m_Listener)
IAP_RegisterCallback(L, 1, &iap->m_Listener); dmScript::DestroyCallback(iap->m_Listener);
iap->m_Listener = dmScript::CreateCallback(L, 1);
if (g_IAP.m_Observer == 0) { if (g_IAP.m_Observer == 0) {
SKPaymentTransactionObserver* observer = [[SKPaymentTransactionObserver alloc] init]; SKPaymentTransactionObserver* observer = [[SKPaymentTransactionObserver alloc] init];
@@ -577,8 +566,9 @@ static dmExtension::Result FinalizeIAP(dmExtension::Params* params)
// 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) { if (params->m_L == dmScript::GetCallbackLuaContext(g_IAP.m_Listener)) {
IAP_UnregisterCallback(&g_IAP.m_Listener); dmScript::DestroyCallback(g_IAP.m_Listener);
g_IAP.m_Listener = 0;
} }
if (g_IAP.m_InitCount == 0) { if (g_IAP.m_InitCount == 0) {

View File

@@ -7,65 +7,6 @@
#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)

View File

@@ -5,16 +5,6 @@
#include <dmsdk/sdk.h> #include <dmsdk/sdk.h>
// TODO: Rename Callback
struct IAPListener
{
IAPListener();
lua_State* m_L;
int m_Callback;
int m_Self;
};
enum EIAPCommand enum EIAPCommand
{ {
IAP_PRODUCT_RESULT, IAP_PRODUCT_RESULT,
@@ -29,7 +19,7 @@ struct DM_ALIGNED(16) IAPCommand
} }
// Used for storing eventual callback info (if needed) // Used for storing eventual callback info (if needed)
IAPListener m_Callback; dmScript::LuaCallbackInfo* m_Callback;
// THe actual command payload // THe actual command payload
int32_t m_Command; int32_t m_Command;
@@ -43,12 +33,6 @@ struct IAPCommandQueue
dmMutex::HMutex m_Mutex; 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);

View File

@@ -5,12 +5,12 @@ main_collection = /main/main.collectionc
shared_state = 1 shared_state = 1
[display] [display]
width = 960 width = 640
height = 640 height = 1136
[android] [android]
input_method = HiddenInputField input_method = HiddenInputField
package = com.defold.iaprtestapp package = com.defold.extension.iap
version_code = 3 version_code = 3
[project] [project]
@@ -21,8 +21,11 @@ dependencies = https://github.com/andsve/dirtylarry/archive/master.zip
include_dirs = extension-iap include_dirs = extension-iap
[ios] [ios]
bundle_identifier = com.defoldextension.push bundle_identifier = com.defold.extension.iap
[iap] [iap]
auto_finish_transactions = 0 auto_finish_transactions = 0
[osx]
bundle_identifier = com.defold.extension.iap

View File

@@ -41,7 +41,7 @@ nodes {
w: 1.0 w: 1.0
} }
type: TYPE_TEMPLATE type: TYPE_TEMPLATE
id: "consumable" id: "goldbars_small"
layer: "" layer: ""
inherit_alpha: true inherit_alpha: true
alpha: 1.0 alpha: 1.0
@@ -82,12 +82,12 @@ nodes {
type: TYPE_BOX type: TYPE_BOX
blend_mode: BLEND_MODE_ALPHA blend_mode: BLEND_MODE_ALPHA
texture: "button/button_normal" texture: "button/button_normal"
id: "consumable/larrybutton" id: "goldbars_small/larrybutton"
xanchor: XANCHOR_NONE xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT adjust_mode: ADJUST_MODE_FIT
parent: "consumable" parent: "goldbars_small"
layer: "" layer: ""
inherit_alpha: true inherit_alpha: true
slice9 { slice9 {
@@ -129,16 +129,16 @@ nodes {
w: 1.0 w: 1.0
} }
color { color {
x: 0.6 x: 1.0
y: 0.0 y: 1.0
z: 0.0 z: 1.0
w: 1.0 w: 1.0
} }
type: TYPE_TEXT type: TYPE_TEXT
blend_mode: BLEND_MODE_ALPHA blend_mode: BLEND_MODE_ALPHA
text: "consumable" text: "Goldbars - S"
font: "larryfont" font: "larryfont"
id: "consumable/larrylabel" id: "goldbars_small/larrylabel"
xanchor: XANCHOR_NONE xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER pivot: PIVOT_CENTER
@@ -156,172 +156,12 @@ nodes {
} }
adjust_mode: ADJUST_MODE_FIT adjust_mode: ADJUST_MODE_FIT
line_break: false line_break: false
parent: "consumable/larrybutton" parent: "goldbars_small/larrybutton"
layer: "" layer: ""
inherit_alpha: true inherit_alpha: true
alpha: 1.0 alpha: 1.0
outline_alpha: 1.0 outline_alpha: 1.0
shadow_alpha: 1.0 shadow_alpha: 1.0
overridden_fields: 5
overridden_fields: 8
template_node_child: true
text_leading: 1.0
text_tracking: 0.0
}
nodes {
position {
x: 171.0
y: 449.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 200.0
y: 100.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_TEMPLATE
id: "nonconsumable"
layer: ""
inherit_alpha: true
alpha: 1.0
template: "/dirtylarry/button.gui"
template_node_child: false
}
nodes {
position {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 300.0
y: 88.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_BOX
blend_mode: BLEND_MODE_ALPHA
texture: "button/button_normal"
id: "nonconsumable/larrybutton"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "nonconsumable"
layer: ""
inherit_alpha: true
slice9 {
x: 32.0
y: 32.0
z: 32.0
w: 32.0
}
clipping_mode: CLIPPING_MODE_NONE
clipping_visible: true
clipping_inverted: false
alpha: 1.0
template_node_child: true
size_mode: SIZE_MODE_MANUAL
}
nodes {
position {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 200.0
y: 100.0
z: 0.0
w: 1.0
}
color {
x: 0.6
y: 0.0
z: 0.0
w: 1.0
}
type: TYPE_TEXT
blend_mode: BLEND_MODE_ALPHA
text: "non-consumable"
font: "larryfont"
id: "nonconsumable/larrylabel"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
outline {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
shadow {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "nonconsumable/larrybutton"
layer: ""
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
shadow_alpha: 1.0
overridden_fields: 5
overridden_fields: 8 overridden_fields: 8
template_node_child: true template_node_child: true
text_leading: 1.0 text_leading: 1.0
@@ -330,7 +170,7 @@ nodes {
nodes { nodes {
position { position {
x: 485.0 x: 485.0
y: 449.0 y: 56.0
z: 0.0 z: 0.0
w: 1.0 w: 1.0
} }
@@ -447,14 +287,14 @@ nodes {
w: 1.0 w: 1.0
} }
color { color {
x: 0.6 x: 1.0
y: 0.0 y: 1.0
z: 0.0 z: 1.0
w: 1.0 w: 1.0
} }
type: TYPE_TEXT type: TYPE_TEXT
blend_mode: BLEND_MODE_ALPHA blend_mode: BLEND_MODE_ALPHA
text: "<- reset" text: "Reset"
font: "larryfont" font: "larryfont"
id: "reset/larrylabel" id: "reset/larrylabel"
xanchor: XANCHOR_NONE xanchor: XANCHOR_NONE
@@ -480,7 +320,700 @@ nodes {
alpha: 1.0 alpha: 1.0
outline_alpha: 1.0 outline_alpha: 1.0
shadow_alpha: 1.0 shadow_alpha: 1.0
overridden_fields: 5 overridden_fields: 8
template_node_child: true
text_leading: 1.0
text_tracking: 0.0
}
nodes {
position {
x: 20.0
y: 1124.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 600.0
y: 450.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_TEXT
blend_mode: BLEND_MODE_ALPHA
text: "<text>"
font: "system_font"
id: "log"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_NW
outline {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
shadow {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
adjust_mode: ADJUST_MODE_FIT
line_break: true
layer: ""
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
shadow_alpha: 1.0
template_node_child: false
text_leading: 1.0
text_tracking: 0.0
}
nodes {
position {
x: 171.0
y: 465.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 200.0
y: 100.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_TEMPLATE
id: "goldbars_medium"
layer: ""
inherit_alpha: true
alpha: 1.0
template: "/dirtylarry/button.gui"
template_node_child: false
}
nodes {
position {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 300.0
y: 88.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_BOX
blend_mode: BLEND_MODE_ALPHA
texture: "button/button_normal"
id: "goldbars_medium/larrybutton"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "goldbars_medium"
layer: ""
inherit_alpha: true
slice9 {
x: 32.0
y: 32.0
z: 32.0
w: 32.0
}
clipping_mode: CLIPPING_MODE_NONE
clipping_visible: true
clipping_inverted: false
alpha: 1.0
template_node_child: true
size_mode: SIZE_MODE_MANUAL
}
nodes {
position {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 200.0
y: 100.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_TEXT
blend_mode: BLEND_MODE_ALPHA
text: "Goldbars - M"
font: "larryfont"
id: "goldbars_medium/larrylabel"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
outline {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
shadow {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "goldbars_medium/larrybutton"
layer: ""
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
shadow_alpha: 1.0
overridden_fields: 8
template_node_child: true
text_leading: 1.0
text_tracking: 0.0
}
nodes {
position {
x: 171.0
y: 357.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 200.0
y: 100.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_TEMPLATE
id: "goldbars_large"
layer: ""
inherit_alpha: true
alpha: 1.0
template: "/dirtylarry/button.gui"
template_node_child: false
}
nodes {
position {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 300.0
y: 88.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_BOX
blend_mode: BLEND_MODE_ALPHA
texture: "button/button_normal"
id: "goldbars_large/larrybutton"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "goldbars_large"
layer: ""
inherit_alpha: true
slice9 {
x: 32.0
y: 32.0
z: 32.0
w: 32.0
}
clipping_mode: CLIPPING_MODE_NONE
clipping_visible: true
clipping_inverted: false
alpha: 1.0
template_node_child: true
size_mode: SIZE_MODE_MANUAL
}
nodes {
position {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 200.0
y: 100.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_TEXT
blend_mode: BLEND_MODE_ALPHA
text: "Goldbars - L"
font: "larryfont"
id: "goldbars_large/larrylabel"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
outline {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
shadow {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "goldbars_large/larrybutton"
layer: ""
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
shadow_alpha: 1.0
overridden_fields: 8
template_node_child: true
text_leading: 1.0
text_tracking: 0.0
}
nodes {
position {
x: 159.0
y: 56.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 200.0
y: 100.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_TEMPLATE
id: "list"
layer: ""
inherit_alpha: true
alpha: 1.0
template: "/dirtylarry/button.gui"
template_node_child: false
}
nodes {
position {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 300.0
y: 88.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_BOX
blend_mode: BLEND_MODE_ALPHA
texture: "button/button_normal"
id: "list/larrybutton"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "list"
layer: ""
inherit_alpha: true
slice9 {
x: 32.0
y: 32.0
z: 32.0
w: 32.0
}
clipping_mode: CLIPPING_MODE_NONE
clipping_visible: true
clipping_inverted: false
alpha: 1.0
template_node_child: true
size_mode: SIZE_MODE_MANUAL
}
nodes {
position {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 200.0
y: 100.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_TEXT
blend_mode: BLEND_MODE_ALPHA
text: "List"
font: "larryfont"
id: "list/larrylabel"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
outline {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
shadow {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "list/larrybutton"
layer: ""
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
shadow_alpha: 1.0
overridden_fields: 8
template_node_child: true
text_leading: 1.0
text_tracking: 0.0
}
nodes {
position {
x: 171.0
y: 251.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 200.0
y: 100.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_TEMPLATE
id: "subscription"
layer: ""
inherit_alpha: true
alpha: 1.0
template: "/dirtylarry/button.gui"
template_node_child: false
}
nodes {
position {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 300.0
y: 88.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_BOX
blend_mode: BLEND_MODE_ALPHA
texture: "button/button_normal"
id: "subscription/larrybutton"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "subscription"
layer: ""
inherit_alpha: true
slice9 {
x: 32.0
y: 32.0
z: 32.0
w: 32.0
}
clipping_mode: CLIPPING_MODE_NONE
clipping_visible: true
clipping_inverted: false
alpha: 1.0
template_node_child: true
size_mode: SIZE_MODE_MANUAL
}
nodes {
position {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
size {
x: 200.0
y: 100.0
z: 0.0
w: 1.0
}
color {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_TEXT
blend_mode: BLEND_MODE_ALPHA
text: "Subscription"
font: "larryfont"
id: "subscription/larrylabel"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
outline {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
shadow {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "subscription/larrybutton"
layer: ""
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
shadow_alpha: 1.0
overridden_fields: 8 overridden_fields: 8
template_node_child: true template_node_child: true
text_leading: 1.0 text_leading: 1.0

View File

@@ -1,59 +1,112 @@
local dirtylarry = require "dirtylarry/dirtylarry" local dirtylarry = require "dirtylarry/dirtylarry"
local GOLDBARS_SMALL = "com.defold.iap.goldbar.small"
local GOLDBARS_MEDIUM = "com.defold.iap.goldbar.medium"
local GOLDBARS_LARGE = "com.defold.iap.goldbar.large"
local SUBSCRIPTION = "com.defold.iap.subscription"
local items = { local items = {
"consumable", GOLDBARS_SMALL,
"nonconsumable" GOLDBARS_MEDIUM,
GOLDBARS_LARGE,
SUBSCRIPTION,
} }
local product_items = {} local item_buttons = {
[GOLDBARS_SMALL] = "goldbars_small",
[GOLDBARS_MEDIUM] = "goldbars_medium",
[GOLDBARS_LARGE] = "goldbars_large",
[SUBSCRIPTION] = "subscription",
}
local available_items = {}
local LOG = {}
local function log(fmt, ...)
if not fmt then return end
local line = fmt:format(...)
print(line)
table.insert(LOG, line)
if #LOG > 10 then
table.remove(LOG, 1)
end
local s = table.concat(LOG, "\n")
gui.set_text(gui.get_node("log"), s)
end
local function buy(id)
log("iap.buy() " .. id)
iap.buy(id)
end
local function list()
log("iap.list()")
for item, button in pairs(item_buttons) do
gui.set_color(gui.get_node(button.."/larrylabel"), vmath.vector4(1,1,1,0.5))
end
iap.list(items, function(self, products, error)
if error then
log(error.error)
return
end
local function list_callback(self, products, error)
if error == nil then
for k,p in pairs(products) do for k,p in pairs(products) do
product_items[p.ident] = p available_items[p.ident] = p
local name = p.ident.."/larrylabel" log("Item %s", p.ident)
gui.set_color(gui.get_node(name), vmath.vector4(1,1,1,1)) local button = item_buttons[p.ident]
end if button then
gui.set_color(gui.get_node(button.."/larrylabel"), vmath.vector4(1,1,1,1))
else else
print(error.error) log("Unable to find button for %s", tostring(p.ident))
end end
end end
end)
end
local function buy_listener(self, transaction, error) local function buy_listener(self, transaction, error)
pprint(transaction, error) pprint(transaction, error)
if not error and iap.get_provider_id() == iap.PROVIDER_ID_GOOGLE and transaction.ident == "nonconsumable" then if error then
local name = "reset/larrylabel" log("iap.buy() error %s - %s", tostring(error.error), tostring(error.reason))
gui.set_color(gui.get_node(name), vmath.vector4(1,1,1,1)) return
end
if iap.get_provider_id() == iap.PROVIDER_ID_GOOGLE and transaction.ident == NON_CONSUMABLE then
log("iap.buy() ok - google")
gui.set_color(gui.get_node("reset/larrylabel"), vmath.vector4(1,1,1,1))
product_items["reset"] = transaction product_items["reset"] = transaction
elseif not error then else
log("iap.buy() ok")
log("iap.finish()")
iap.finish(transaction) iap.finish(transaction)
end end
end end
function init(self) function init(self)
self.log = {}
log("init()")
msg.post(".", "acquire_input_focus") msg.post(".", "acquire_input_focus")
iap.list(items, list_callback) if not iap then
log("In-App Purchases not supported")
return
end
list()
iap.set_listener(buy_listener) iap.set_listener(buy_listener)
end end
function on_input(self, action_id, action) function on_input(self, action_id, action)
if product_items["consumable"] then if action_id then
dirtylarry:button("consumable", action_id, action, function() for item, button in pairs(item_buttons) do
iap.buy("consumable") if available_items[item] then
end) dirtylarry:button(button, action_id, action, function()
end buy(item)
end)
if product_items["nonconsumable"] then end
dirtylarry:button("nonconsumable", action_id, action, function() end
iap.buy("nonconsumable") dirtylarry:button("list", action_id, action, function()
end) list()
end
if product_items["reset"] then
dirtylarry:button("reset", action_id, action, function()
iap.finish(product_items["reset"])
product_items["reset"] = nil
gui.set_color(gui.get_node("reset/larrylabel"), vmath.vector4(1,0,0,1))
end) end)
end end
end end