29 Commits
2.0.1 ... 3.3.0

Author SHA1 Message Date
JCash
92d90bb4de Fixed nul pointer deferencing 2021-10-06 09:55:49 +02:00
Björn Ritzl
e6e2f2de63 Update iap.script_api 2021-09-09 13:26:08 +02:00
Björn Ritzl
3468b52658 Merge pull request #41 from BigButtonCo/master
Add original_json to transaction for validating purchases on android
2021-09-09 13:23:13 +02:00
Mathias Westerdahl
bda785ef08 Merge pull request #44 from defold/buffer-threading-fix
Fixed threading issue of checking if command buffer is empty
2021-09-07 14:34:35 +02:00
JCash
b3b79006d7 Fixed threading issue of checking if command buffer is empty 2021-08-02 10:10:00 +02:00
Björn Ritzl
8b2c7b0bca Removed old Amazon SDK version 2021-07-22 19:58:18 +02:00
Björn Ritzl
78d1c80c19 Merge pull request #42 from SalavatR/master
Brought stores to a common view,  Amazon iap_lib update
2021-07-22 14:32:30 +02:00
Salavat
beb89d507f Brought stores to a common view
Amazon iap_lib update
2021-07-20 02:32:13 +06:00
BigButtonCo
1a6d2f01e5 Merge branch 'defold:master' into master 2021-06-03 20:05:02 +02:00
Björn Ritzl
1fe29ad24a Autobuilder 2021-05-28 11:25:20 +02:00
Björn Ritzl
112744c6bb Merge pull request #39 from defold/dev-remove-init-count
Do not track init count for multiple Lua states
2021-04-28 10:08:40 +02:00
Björn Ritzl
6e975b67de Merge branch 'master' into dev-remove-init-count 2021-04-28 10:08:12 +02:00
Björn Ritzl
f3ee2361ac Merge pull request #38 from defold/issue-35-crash-on-iap-oncommand
Get the Lua context from the callback and not the listener
2021-04-28 10:06:24 +02:00
Björn Ritzl
db2c7f0edb Merge pull request #40 from defold/issue-37-crash-when-receiving-products
Check that the extension hasn't been rebooted while fetching products
2021-04-28 10:05:58 +02:00
Björn Ritzl
c21a8ea984 Update iap_ios.mm 2021-04-28 09:46:23 +02:00
Björn Ritzl
1c829abbfb Check that the extension hasn't been recreated while fetching products 2021-04-28 08:59:16 +02:00
Björn Ritzl
80ba15bf22 Do not track init count for multiple Lua states
None of our other extensions support this
2021-04-28 08:54:38 +02:00
Björn Ritzl
5843f631af Get the Lua context from the callback and not the listener 2021-04-27 19:52:14 +02:00
BigButtonCo
276437b981 Update IapGooglePlay.java 2021-04-12 19:26:56 +02:00
BigButtonCo
246fcd3179 Added original_json 2021-04-12 19:22:33 +02:00
Björn Ritzl
257f95f1d3 Merge pull request #32 from defold/Issue-32-android-crash-on-restore-buy-finish
Make sure skuDetails exists and isn't empty
2021-01-25 23:20:24 +01:00
Björn Ritzl
1a16fcc795 Make sure skuDetails exists and isn't empty 2021-01-25 10:37:09 +01:00
Björn Ritzl
412a609738 Fixed incorrect script api doc for iap-buy() 2021-01-25 10:36:08 +01:00
Björn Ritzl
401e23562e Merge pull request #30 from defold/fix-for-new-emscripten
Method calls update
2020-12-29 19:43:01 +01:00
Alexey Gulev
0b455dd9da Method calls update 2020-12-29 19:09:32 +01:00
Björn Ritzl
aabfc49c1d Updated docs with iap.acknowledge() 2020-11-14 21:00:18 +01:00
Björn Ritzl
5c806a990d Merge pull request #28 from SkaterDad/Amazon-Fix-NPE
Fix crashes when using Amazon provider
2020-10-20 11:12:15 +02:00
Mike
ca33678486 Amazon: Stub out acknowledgeTransaction 2020-10-17 13:15:17 -05:00
Mike
faf62f2314 Amazon: Initialize listProductsCommandPtrs 2020-10-17 11:42:47 -05:00
12 changed files with 185 additions and 98 deletions

76
.github/workflows/bob.yml vendored Normal file
View File

@@ -0,0 +1,76 @@
name: Build with bob
on:
push:
pull_request_target:
schedule:
# nightly at 05:00 on the 1st and 15th
- cron: 0 5 1,15 * *
env:
VERSION_FILENAME: 'info.json'
BUILD_SERVER: 'https://build.defold.com'
jobs:
build_with_bob:
strategy:
matrix:
platform: [armv7-android, x86_64-linux, x86_64-win32, x86-win32, js-web]
runs-on: ubuntu-latest
name: Build
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: '11.0.2'
- name: Get Defold version
run: |
TMPVAR=`curl -s http://d.defold.com/stable/${{env.VERSION_FILENAME}} | jq -r '.sha1'`
echo "DEFOLD_VERSION=${TMPVAR}" >> $GITHUB_ENV
echo "Found version ${TMPVAR}"
- name: Download bob.jar
run: |
wget -q http://d.defold.com/archive/stable/${{env.DEFOLD_VERSION}}/bob/bob.jar
java -jar bob.jar --version
- name: Resolve libraries
run: java -jar bob.jar resolve --email a@b.com --auth 123456
- name: Build
run: java -jar bob.jar --platform=${{ matrix.platform }} build --archive --build-server=${{env.BUILD_SERVER}}
- name: Bundle
run: java -jar bob.jar --platform=${{ matrix.platform }} bundle
# macOS is not technically needed for building, but we want to test bundling as well, since we're also testing the manifest merging
build_with_bob_macos:
strategy:
matrix:
platform: [armv7-darwin, x86_64-darwin]
runs-on: macOS-latest
name: Build
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: '11.0.2'
- name: Get Defold version
run: |
TMPVAR=`curl -s http://d.defold.com/stable/${{env.VERSION_FILENAME}} | jq -r '.sha1'`
echo "DEFOLD_VERSION=${TMPVAR}" >> $GITHUB_ENV
echo "Found version ${TMPVAR}"
- name: Download bob.jar
run: |
wget -q http://d.defold.com/archive/stable/${{env.DEFOLD_VERSION}}/bob/bob.jar
java -jar bob.jar --version
- name: Resolve libraries
run: java -jar bob.jar resolve --email a@b.com --auth 123456
- name: Build
run: java -jar bob.jar --platform=${{ matrix.platform }} build --archive --build-server=${{env.BUILD_SERVER}}
- name: Bundle
run: java -jar bob.jar --platform=${{ matrix.platform }} bundle

View File

@@ -1,3 +1,5 @@
[![Actions Status Alpha](https://github.com/defold/extension-iap/actions/workflows/bob.yml/badge.svg)](https://github.com/defold/extension-iap/actions)
# In-app purchase extension for Defold
Defold [native extension](https://www.defold.com/manuals/extensions/) which provides access to In-app purchase functionality on iOS, Android (Google Play and Amazon) and Facebook Canvas platforms.

View File

@@ -173,17 +173,20 @@ Most payment providers only supports synchronous payments. This means that the c
## Asynchronous payments
Some payment providers require supporting asynchronous payments. This means that the client (your application) will only receive a notification when the payment is initiated. In order to verify completion of payment, further communication needs to be done between the developer server (or client) and the payment provider in order to verify.
In the case of an initiated asynchronous payment the IAP listener will receive the state TRANS_STATE_UNVERIFIED to indicate this (as opposed to TRANS_STATE_PURCHASED). This is the final state of the payment, meaning no more callbacks will be done on this transaction.
## Purchase fulfilment
In order to complete a purchase from a payment provider, the application needs to signal a purchase fulfilment to the provider telling the provider the purchase has gone through (for example by developer server-side verification).
IAP supports auto-completion, where fulfilment is automatically signalled to the provider when a purchase is complete (this is the default behavior). You can also disable auto-completion in the game project settings. You are then required to call `iap.finish()` when the transaction is complete, which will signal purchase fulfilment to the provider.
### Consumable vs non-consumable products
The Google Play store does only support consumable products. If you need non-consumable products it is recommended to use manual fulfillment of purchases and never finish purchases for products that should be non-consumable. As long as a purchase isn't finished it will be returned as an active purchase when `iap.set_listener()` is called.
The Google Play store does only support consumable products. If you need non-consumable products it is recommended to use manual fulfilment of purchases and never finish purchases for products that should be non-consumable. As long as a purchase isn't finished it will be returned as an active purchase when `iap.set_listener()` is called. If you do not call `iap.finish()` on a purchase you still need to indicate to Google Play that the purchase has been handled. You can do this by calling `iap.acknowledge()`. If you do not call `iap.acknowledge()` the purchase will be automatically refunded by Google after a few days.
The Apple App Store supports non-consumable products which means that you need to finish all purchases when you provide products to your users. You can do it automatically by keeping the default behavior in the game project settings or manually (if you want to do that after server validation, for example) using `iap.finish()`.
@@ -212,7 +215,7 @@ Android `iap.list()` returns "failed to fetch product"
: You need to upload and publish an *.apk* on the alpha or beta channels on the Google Play Developer Console. Also make sure that the _time and date_ on your device is correct.
Android (Google Play) `iap.list()` never returns more than 20 products
: Google has an [limit of 20 products per request](https://github.com/android/play-billing-samples/blob/7a94c6905a9c125518354c216b5c3094fde47ce1/TrivialDrive/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl#L62). The solution is to make multiple calls to `iap.list()` and combine the results if the number of products exceeds 20.
: Google has a [limit of 20 products per request](https://github.com/android/play-billing-samples/blob/7a94c6905a9c125518354c216b5c3094fde47ce1/TrivialDrive/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl#L62). The solution is to make multiple calls to `iap.list()` and combine the results if the number of products exceeds 20.
iOS `iap.list()` returns nothing
: Make sure that youve requested an iOS Paid Applications account, and all proper documentation has been filed. Without proper authorization, your iOS app purchasing (even test purchases) will not work.

View File

@@ -9,7 +9,7 @@
- name: buy
type: function
desc: Sets the listener function for In-app purchase events.
desc: Purchase a product.
parameters:
- name: id
type: string
@@ -226,6 +226,10 @@
type: string
desc: Apple only[icon:apple]. The original transaction. This field is only set when `state` is `TRANS_STATE_RESTORED`.
- name: original_json
type: string
desc: Android only[icon:android]. The purchase order details in JSON format.
- name: signature
type: string
desc: Google Play only[icon:googleplay]. A string containing the signature of the purchase data that was signed with the private key of the developer.

View File

@@ -72,7 +72,7 @@ var LibraryFacebookIAP = {
if(url_index == product_count-1) {
var productsJSON = JSON.stringify(products);
var res_buf = allocate(intArrayFromString(productsJSON), 'i8', ALLOC_STACK);
Runtime.dynCall('vii', callback, [lua_callback, res_buf]);
{{{ makeDynCall('vii', 'callback')}}}(lua_callback, res_buf);
} else {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
@@ -87,7 +87,7 @@ var LibraryFacebookIAP = {
},
dmIAPFBList: function(params, callback, lua_callback) {
var product_ids = Pointer_stringify(params).trim().split(',');
var product_ids = UTF8ToString(params).trim().split(',');
var product_count = product_ids.length;
if(product_count == 0) {
console.log("Calling iap.list with no item id's. Ignored.");
@@ -105,7 +105,7 @@ var LibraryFacebookIAP = {
// https://developers.facebook.com/docs/javascript/reference/FB.ui
dmIAPFBBuy: function(param_product_id, param_request_id, callback, lua_callback) {
var product_id = Pointer_stringify(param_product_id);
var product_id = UTF8ToString(param_product_id);
var buy_params = {
method: 'pay',
@@ -113,7 +113,7 @@ var LibraryFacebookIAP = {
product: product_id,
};
if(param_request_id != 0) {
buy_params.request_id = Pointer_stringify(param_request_id);
buy_params.request_id = UTF8ToString(param_request_id);
}
FB.ui(buy_params,
@@ -144,7 +144,7 @@ var LibraryFacebookIAP = {
var productsJSON = JSON.stringify(result)
var res_buf = allocate(intArrayFromString(productsJSON), 'i8', ALLOC_STACK);
Runtime.dynCall('viii', callback, [lua_callback, res_buf, 0]);
{{{ makeDynCall('viii', 'callback')}}}(lua_callback, res_buf, 0);
} else {
@@ -157,7 +157,7 @@ var LibraryFacebookIAP = {
reason = FBinner.BillingResponse.BILLING_RESPONSE_RESULT_ERROR;
console.log("Unknown response: ", response);
}
Runtime.dynCall('viii', callback, [lua_callback, 0, reason]);
{{{ makeDynCall('viii', 'callback')}}}(lua_callback, 0, reason);
}
}
);

View File

@@ -4,9 +4,9 @@
<uses-sdk android:minSdkVersion="{{android.minimum_sdk_version}}" android:targetSdkVersion="{{android.target_sdk_version}}" />
<application>
<!-- For Amazon IAP -->
<receiver android:name="com.amazon.device.iap.ResponseReceiver" >
<receiver android:name = "com.amazon.device.iap.ResponseReceiver" android:permission = "com.amazon.inapp.purchasing.Permission.NOTIFY" >
<intent-filter>
<action android:name="com.amazon.inapp.purchasing.NOTIFY" android:permission="com.amazon.inapp.purchasing.Permission.NOTIFY" />
<action android:name = "com.amazon.inapp.purchasing.NOTIFY" />
</intent-filter>
</receiver>
</application>

View File

@@ -30,7 +30,6 @@ struct IAP
m_autoFinishTransactions = true;
m_ProviderId = PROVIDER_ID_GOOGLE;
}
int m_InitCount;
bool m_autoFinishTransactions;
int m_ProviderId;
@@ -399,59 +398,54 @@ static void HandlePurchaseResult(const IAPCommand* cmd)
static dmExtension::Result InitializeIAP(dmExtension::Params* params)
{
// TODO: Life-cycle managaemnt is *budget*. No notion of "static initalization"
// Extend extension functionality with per system initalization?
if (g_IAP.m_InitCount == 0) {
IAP_Queue_Create(&g_IAP.m_CommandQueue);
IAP_Queue_Create(&g_IAP.m_CommandQueue);
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;
JNIEnv* env = Attach();
JNIEnv* env = Attach();
jclass activity_class = env->FindClass("android/app/NativeActivity");
jmethodID get_class_loader = env->GetMethodID(activity_class,"getClassLoader", "()Ljava/lang/ClassLoader;");
jobject cls = env->CallObjectMethod(dmGraphics::GetNativeAndroidActivity(), get_class_loader);
jclass class_loader = env->FindClass("java/lang/ClassLoader");
jmethodID find_class = env->GetMethodID(class_loader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
jclass activity_class = env->FindClass("android/app/NativeActivity");
jmethodID get_class_loader = env->GetMethodID(activity_class,"getClassLoader", "()Ljava/lang/ClassLoader;");
jobject cls = env->CallObjectMethod(dmGraphics::GetNativeAndroidActivity(), get_class_loader);
jclass class_loader = env->FindClass("java/lang/ClassLoader");
jmethodID find_class = env->GetMethodID(class_loader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
const char* provider = dmConfigFile::GetString(params->m_ConfigFile, "android.iap_provider", "GooglePlay");
const char* class_name = "com.defold.iap.IapGooglePlay";
const char* provider = dmConfigFile::GetString(params->m_ConfigFile, "android.iap_provider", "GooglePlay");
const char* class_name = "com.defold.iap.IapGooglePlay";
g_IAP.m_ProviderId = PROVIDER_ID_GOOGLE;
if (!strcmp(provider, "Amazon")) {
g_IAP.m_ProviderId = PROVIDER_ID_AMAZON;
class_name = "com.defold.iap.IapAmazon";
}
else if (strcmp(provider, "GooglePlay")) {
dmLogWarning("Unknown IAP provider name [%s], defaulting to GooglePlay", provider);
}
jstring str_class_name = env->NewStringUTF(class_name);
jclass iap_class = (jclass)env->CallObjectMethod(cls, find_class, str_class_name);
env->DeleteLocalRef(str_class_name);
str_class_name = env->NewStringUTF("com.defold.iap.IapJNI");
jclass iap_jni_class = (jclass)env->CallObjectMethod(cls, find_class, str_class_name);
env->DeleteLocalRef(str_class_name);
g_IAP.m_List = env->GetMethodID(iap_class, "listItems", "(Ljava/lang/String;Lcom/defold/iap/IListProductsListener;J)V");
g_IAP.m_Buy = env->GetMethodID(iap_class, "buy", "(Ljava/lang/String;Lcom/defold/iap/IPurchaseListener;)V");
g_IAP.m_Restore = env->GetMethodID(iap_class, "restore", "(Lcom/defold/iap/IPurchaseListener;)V");
g_IAP.m_Stop = env->GetMethodID(iap_class, "stop", "()V");
g_IAP.m_ProcessPendingConsumables = env->GetMethodID(iap_class, "processPendingConsumables", "(Lcom/defold/iap/IPurchaseListener;)V");
g_IAP.m_FinishTransaction = env->GetMethodID(iap_class, "finishTransaction", "(Ljava/lang/String;Lcom/defold/iap/IPurchaseListener;)V");
g_IAP.m_AcknowledgeTransaction = env->GetMethodID(iap_class, "acknowledgeTransaction", "(Ljava/lang/String;Lcom/defold/iap/IPurchaseListener;)V");
jmethodID jni_constructor = env->GetMethodID(iap_class, "<init>", "(Landroid/app/Activity;Z)V");
g_IAP.m_IAP = env->NewGlobalRef(env->NewObject(iap_class, jni_constructor, dmGraphics::GetNativeAndroidActivity(), g_IAP.m_autoFinishTransactions));
jni_constructor = env->GetMethodID(iap_jni_class, "<init>", "()V");
g_IAP.m_IAPJNI = env->NewGlobalRef(env->NewObject(iap_jni_class, jni_constructor));
Detach();
g_IAP.m_ProviderId = PROVIDER_ID_GOOGLE;
if (!strcmp(provider, "Amazon")) {
g_IAP.m_ProviderId = PROVIDER_ID_AMAZON;
class_name = "com.defold.iap.IapAmazon";
}
g_IAP.m_InitCount++;
else if (strcmp(provider, "GooglePlay")) {
dmLogWarning("Unknown IAP provider name [%s], defaulting to GooglePlay", provider);
}
jstring str_class_name = env->NewStringUTF(class_name);
jclass iap_class = (jclass)env->CallObjectMethod(cls, find_class, str_class_name);
env->DeleteLocalRef(str_class_name);
str_class_name = env->NewStringUTF("com.defold.iap.IapJNI");
jclass iap_jni_class = (jclass)env->CallObjectMethod(cls, find_class, str_class_name);
env->DeleteLocalRef(str_class_name);
g_IAP.m_List = env->GetMethodID(iap_class, "listItems", "(Ljava/lang/String;Lcom/defold/iap/IListProductsListener;J)V");
g_IAP.m_Buy = env->GetMethodID(iap_class, "buy", "(Ljava/lang/String;Lcom/defold/iap/IPurchaseListener;)V");
g_IAP.m_Restore = env->GetMethodID(iap_class, "restore", "(Lcom/defold/iap/IPurchaseListener;)V");
g_IAP.m_Stop = env->GetMethodID(iap_class, "stop", "()V");
g_IAP.m_ProcessPendingConsumables = env->GetMethodID(iap_class, "processPendingConsumables", "(Lcom/defold/iap/IPurchaseListener;)V");
g_IAP.m_FinishTransaction = env->GetMethodID(iap_class, "finishTransaction", "(Ljava/lang/String;Lcom/defold/iap/IPurchaseListener;)V");
g_IAP.m_AcknowledgeTransaction = env->GetMethodID(iap_class, "acknowledgeTransaction", "(Ljava/lang/String;Lcom/defold/iap/IPurchaseListener;)V");
jmethodID jni_constructor = env->GetMethodID(iap_class, "<init>", "(Landroid/app/Activity;Z)V");
g_IAP.m_IAP = env->NewGlobalRef(env->NewObject(iap_class, jni_constructor, dmGraphics::GetNativeAndroidActivity(), g_IAP.m_autoFinishTransactions));
jni_constructor = env->GetMethodID(iap_jni_class, "<init>", "()V");
g_IAP.m_IAPJNI = env->NewGlobalRef(env->NewObject(iap_jni_class, jni_constructor));
Detach();
lua_State*L = params->m_L;
int top = lua_gettop(L);
@@ -494,21 +488,18 @@ static dmExtension::Result UpdateIAP(dmExtension::Params* params)
static dmExtension::Result FinalizeIAP(dmExtension::Params* params)
{
IAP_Queue_Destroy(&g_IAP.m_CommandQueue);
--g_IAP.m_InitCount;
if (params->m_L == dmScript::GetCallbackLuaContext(g_IAP.m_Listener)) {
dmScript::DestroyCallback(g_IAP.m_Listener);
g_IAP.m_Listener = 0;
}
if (g_IAP.m_InitCount == 0) {
JNIEnv* env = Attach();
env->CallVoidMethod(g_IAP.m_IAP, g_IAP.m_Stop);
env->DeleteGlobalRef(g_IAP.m_IAP);
env->DeleteGlobalRef(g_IAP.m_IAPJNI);
Detach();
g_IAP.m_IAP = NULL;
}
JNIEnv* env = Attach();
env->CallVoidMethod(g_IAP.m_IAP, g_IAP.m_Stop);
env->DeleteGlobalRef(g_IAP.m_IAP);
env->DeleteGlobalRef(g_IAP.m_IAPJNI);
Detach();
g_IAP.m_IAP = NULL;
return dmExtension::RESULT_OK;
}

View File

@@ -24,7 +24,7 @@ struct IAP
memset(this, 0, sizeof(*this));
m_AutoFinishTransactions = true;
}
int m_InitCount;
int m_Version;
bool m_AutoFinishTransactions;
NSMutableDictionary* m_PendingTransactions;
dmScript::LuaCallbackInfo* m_Listener;
@@ -103,11 +103,17 @@ static void IAP_FreeTransaction(IAPTransaction* transaction)
@interface SKProductsRequestDelegate : NSObject<SKProductsRequestDelegate>
@property dmScript::LuaCallbackInfo* m_Callback;
@property (assign) SKProductsRequest* m_Request;
@property int m_Version;
@end
@implementation SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
if (self.m_Version != g_IAP.m_Version) {
dmLogWarning("Received products but the extension has been restarted")
return;
}
if (!dmScript::IsCallbackValid(self.m_Callback)) {
dmLogError("No callback set");
return;
@@ -162,7 +168,7 @@ static void HandleProductResult(IAPCommand* cmd)
IAPResponse* response = (IAPResponse*)cmd->m_Data;
lua_State* L = dmScript::GetCallbackLuaContext(g_IAP.m_Listener);
lua_State* L = dmScript::GetCallbackLuaContext(cmd->m_Callback);
int top = lua_gettop(L);
if (!dmScript::SetupCallback(cmd->m_Callback))
@@ -409,6 +415,7 @@ static int IAP_List(lua_State* L)
delegate.m_Callback = dmScript::CreateCallback(L, 2);
delegate.m_Request = products_request;
delegate.m_Version = g_IAP.m_Version;
products_request.delegate = delegate;
[products_request start];
@@ -529,13 +536,9 @@ static const luaL_reg IAP_methods[] =
static dmExtension::Result InitializeIAP(dmExtension::Params* params)
{
// TODO: Life-cycle managaemnt is *budget*. No notion of "static initalization"
// Extend extension functionality with per system initalization?
if (g_IAP.m_InitCount == 0) {
g_IAP.m_AutoFinishTransactions = dmConfigFile::GetInt(params->m_ConfigFile, "iap.auto_finish_transactions", 1) == 1;
g_IAP.m_PendingTransactions = [[NSMutableDictionary alloc]initWithCapacity:2];
}
g_IAP.m_InitCount++;
g_IAP.m_AutoFinishTransactions = dmConfigFile::GetInt(params->m_ConfigFile, "iap.auto_finish_transactions", 1) == 1;
g_IAP.m_PendingTransactions = [[NSMutableDictionary alloc]initWithCapacity:2];
g_IAP.m_Version++;
IAP_Queue_Create(&g_IAP.m_CommandQueue);
IAP_Queue_Create(&g_IAP.m_ObservableQueue);
@@ -590,26 +593,22 @@ static dmExtension::Result UpdateIAP(dmExtension::Params* params)
static dmExtension::Result FinalizeIAP(dmExtension::Params* params)
{
--g_IAP.m_InitCount;
// TODO: Should we support one listener per lua-state?
// Or just use a single lua-state...?
if (params->m_L == dmScript::GetCallbackLuaContext(g_IAP.m_Listener)) {
if (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_PendingTransactions) {
[g_IAP.m_PendingTransactions release];
g_IAP.m_PendingTransactions = 0;
}
g_IAP.m_Listener = 0;
if (g_IAP.m_Observer) {
[[SKPaymentQueue defaultQueue] removeTransactionObserver: g_IAP.m_Observer];
[g_IAP.m_Observer release];
g_IAP.m_Observer = 0;
}
if (g_IAP.m_PendingTransactions) {
[g_IAP.m_PendingTransactions release];
g_IAP.m_PendingTransactions = 0;
}
if (g_IAP.m_Observer) {
[[SKPaymentQueue defaultQueue] removeTransactionObserver: g_IAP.m_Observer];
[g_IAP.m_Observer release];
g_IAP.m_Observer = 0;
}
IAP_Queue_Destroy(&g_IAP.m_CommandQueue);

View File

@@ -121,13 +121,14 @@ void IAP_Queue_Push(IAPCommandQueue* queue, IAPCommand* cmd)
void IAP_Queue_Flush(IAPCommandQueue* queue, IAPCommandFn fn, void* ctx)
{
assert(fn != 0);
DM_MUTEX_SCOPED_LOCK(queue->m_Mutex);
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);

View File

@@ -11,6 +11,7 @@ import java.util.concurrent.BlockingQueue;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONArray;
import android.app.Activity;
import android.os.Bundle;
@@ -45,6 +46,7 @@ public class IapAmazon implements PurchasingListener {
this.activity = activity;
this.autoFinishTransactions = autoFinishTransactions;
this.listProductsListeners = new HashMap<RequestId, IListProductsListener>();
this.listProductsCommandPtrs = new HashMap<RequestId, Long>();
this.purchaseListeners = new HashMap<RequestId, IPurchaseListener>();
PurchasingService.registerListener(activity, this);
}
@@ -97,6 +99,10 @@ public class IapAmazon implements PurchasingListener {
PurchasingService.notifyFulfillment(receipt, FulfillmentResult.FULFILLED);
}
public void acknowledgeTransaction(final String purchaseToken, final IPurchaseListener purchaseListener) {
// Stub to prevent errors.
}
private void doGetPurchaseUpdates(final IPurchaseListener listener, final boolean reset) {
synchronized (purchaseListeners) {
RequestId req = PurchasingService.getPurchaseUpdates(reset);
@@ -168,9 +174,13 @@ public class IapAmazon implements PurchasingListener {
if (productDataResponse.getRequestStatus() != ProductDataResponse.RequestStatus.SUCCESSFUL) {
listener.onProductsResult(IapJNI.BILLING_RESPONSE_RESULT_ERROR, null, commadPtr);
} else {
for (final String s : productDataResponse.getUnavailableSkus()) {
Log.v(TAG, "Unavailable SKU: " + s);
}
Map<String, Product> products = productDataResponse.getProductData();
try {
JSONObject data = new JSONObject();
JSONArray data = new JSONArray();
for (Map.Entry<String, Product> entry : products.entrySet()) {
String key = entry.getKey();
Product product = entry.getValue();
@@ -184,7 +194,7 @@ public class IapAmazon implements PurchasingListener {
// Based on return values from getPrice: https://developer.amazon.com/public/binaries/content/assets/javadoc/in-app-purchasing-api/com/amazon/inapp/purchasing/item.html
item.put("price", priceString.replaceAll("[^0-9.,]", ""));
}
data.put(key, item);
data.put(item);
}
listener.onProductsResult(IapJNI.BILLING_RESPONSE_RESULT_OK, data.toString(), commadPtr);
} catch (JSONException e) {

View File

@@ -102,6 +102,7 @@ public class IapGooglePlay implements PurchasesUpdatedListener {
p.put("date", toISO8601(new Date(purchase.getPurchaseTime())));
p.put("receipt", purchase.getPurchaseToken());
p.put("signature", purchase.getSignature());
p.put("original_json", purchase.getOriginalJson());
}
catch (JSONException e) {
Log.wtf(TAG, "Failed to convert purchase", e);
@@ -339,7 +340,7 @@ public class IapGooglePlay implements PurchasesUpdatedListener {
querySkuDetailsAsync(skuList, new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
if (billingResult.getResponseCode() == BillingResponseCode.OK) {
if (billingResult.getResponseCode() == BillingResponseCode.OK && (skuDetailsList != null) && !skuDetailsList.isEmpty()) {
buyProduct(skuDetailsList.get(0), purchaseListener);
}
else {