mirror of
https://github.com/defold/extension-iap
synced 2025-09-27 09:02:18 +02:00
Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
fb3d6b78aa | ||
|
c4b177a24d | ||
|
09544baf1c | ||
|
6d3ca89b65 | ||
|
0df5a8e968 | ||
|
88028fd29f | ||
|
c0e35039f4 | ||
|
2e17b2f413 | ||
|
f398677d7b | ||
|
d1c4e88562 | ||
|
6f06d7f08f | ||
|
0852e42977 | ||
|
33174f25ea | ||
|
ba0e1b645a | ||
|
801179288e | ||
|
1c27130eef | ||
|
195ef400b5 | ||
|
68ef7f4615 | ||
|
1a34582603 | ||
|
09f5060d44 | ||
|
649a8a1ebf | ||
|
5c09447e37 | ||
|
ad06de7b9c | ||
|
d74c97d5c7 | ||
|
f97a7ee6b6 | ||
|
5f3f43fb2e | ||
|
caff4397d8 | ||
|
501af9c90d | ||
|
961a43f732 | ||
|
47f03108ab | ||
|
c0e1a9aef1 | ||
|
f4294a9eb5 | ||
|
8799860198 |
75
.github/workflows/bob.yml
vendored
75
.github/workflows/bob.yml
vendored
@@ -1,76 +1,7 @@
|
||||
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'
|
||||
on: [push, pull_request]
|
||||
|
||||
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
|
||||
build:
|
||||
uses: defold/github-actions-common/.github/workflows/bob.yml@master
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,3 +9,5 @@ Thumbs.db
|
||||
.cproject
|
||||
builtins
|
||||
_site
|
||||
manifest.private.der
|
||||
manifest.public.der
|
||||
|
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Defold In-app purchase extension API documentation
|
||||
brief: This manual covers how to setup and use Google Play Game Services in Defold.
|
||||
brief: This manual covers how to setup and use In-App Purchases in Defold.
|
||||
---
|
||||
|
||||
# Defold In-app purchase extension API documentation
|
||||
@@ -8,7 +8,7 @@ brief: This manual covers how to setup and use Google Play Game Services in Defo
|
||||
This extension provides a unified, simple to use interface to several different stores for in-app purchase:
|
||||
|
||||
* Apple’s iOS Appstore - StoreKit
|
||||
* Google Play Billing 3.0
|
||||
* Google Play Billing 5.0
|
||||
* Amazon 'in-app billing' 2.0.61
|
||||
* Facebook Canvas 'game payments'
|
||||
|
||||
@@ -31,7 +31,7 @@ Detailed documentation from Apple, Google, Amazon and Facebook can be found here
|
||||
## Installation
|
||||
To use this library in your Defold project, add the following URL to your `game.project` dependencies:
|
||||
|
||||
https://github.com/defold/extension-iap/archive/master.zip
|
||||
[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 [specific release](https://github.com/defold/extension-iap/releases).
|
||||
|
||||
@@ -186,7 +186,11 @@ IAP supports auto-completion, where fulfilment is automatically signalled to the
|
||||
|
||||
### 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 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.
|
||||
#### Google Play
|
||||
|
||||
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.
|
||||
|
||||
#### App Store
|
||||
|
||||
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()`.
|
||||
|
||||
@@ -234,6 +238,3 @@ On iOS, the "price_string" field contains '~' characters
|
||||
## Source code
|
||||
|
||||
The source code is available on [GitHub](https://github.com/defold/extension-iap)
|
||||
|
||||
|
||||
## API reference
|
||||
|
@@ -21,11 +21,11 @@
|
||||
members:
|
||||
- name: request_id
|
||||
type: string
|
||||
desc: Facebook only. [icon:facebook] Optional custom unique request id to
|
||||
desc: Facebook only [icon:facebook]. Optional custom unique request id to
|
||||
set for this transaction. The id becomes attached to the payment within the Graph API.
|
||||
- name: token
|
||||
type: string
|
||||
desc: [icon:googleplay] Which subscription offer to use when buying a subscription. The token can be retrieved from
|
||||
desc: Google Play only [icon:googleplay]. Which subscription offer to use when buying a subscription. The token can be retrieved from
|
||||
the subscriptions table returned when calling iap.list()
|
||||
|
||||
examples:
|
||||
@@ -132,22 +132,22 @@
|
||||
- name: price
|
||||
type: number
|
||||
desc: The price of the product.
|
||||
[icon:googleplay]: Used only for in-app products
|
||||
For Google Play [icon:googleplay] this is used only for in-app products
|
||||
|
||||
- name: price_string
|
||||
type: string
|
||||
desc: The price of the product, as a formatted string (amount and currency symbol).
|
||||
[icon:googleplay]: Used only for in-app products
|
||||
For Google Play [icon:googleplay] this is used only for in-app products
|
||||
|
||||
- name: currency_code
|
||||
type: string
|
||||
desc: [icon:ios] [icon:googleplay] [icon:facebook] The currency code.
|
||||
[icon:googleplay]: The merchant's locale, instead of the user's
|
||||
[icon:googleplay]: Used only for in-app products
|
||||
desc: The currency code.
|
||||
For Google Play [icon:googleplay] this is the merchant's locale, instead of the user's.
|
||||
For Google Play [icon:googleplay] this is used only for in-app products
|
||||
|
||||
- name: subscriptions
|
||||
type: table
|
||||
desc: [icon:googleplay] List of subscription offers.
|
||||
desc: Only available for Google Play [icon:googleplay]. List of subscription offers.
|
||||
Each offer contains a token and a list of price and billing options.
|
||||
See https://developer.android.com/reference/com/android/billingclient/api/ProductDetails.PricingPhase
|
||||
members:
|
||||
@@ -269,36 +269,36 @@
|
||||
|
||||
- name: original_trans
|
||||
type: string
|
||||
desc: Apple only[icon:apple]. The original transaction. This field is only set when `state` is `TRANS_STATE_RESTORED`.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
||||
- name: request_id
|
||||
type: string
|
||||
desc: Facebook only[icon:facebook]. This field is set to the optional custom unique request id `request_id` if set in the `iap.buy()` call parameters.
|
||||
desc: Facebook only [icon:facebook]. This field is set to the optional custom unique request id `request_id` if set in the `iap.buy()` call parameters.
|
||||
|
||||
- name: user_id
|
||||
type: string
|
||||
desc: Amazon Pay only[icon:amazon]. The user ID.
|
||||
desc: Amazon Pay only [icon:amazon]. The user ID.
|
||||
|
||||
- name: is_sandbox_mode
|
||||
type: boolean
|
||||
desc: Amazon Pay only[icon:amazon]. If `true`, the SDK is running in Sandbox mode.
|
||||
desc: Amazon Pay only [icon:amazon]. If `true`, the SDK is running in Sandbox mode.
|
||||
This only allows interactions with the Amazon AppTester. Use this mode only for testing locally.
|
||||
|
||||
- name: cancel_date
|
||||
type: string
|
||||
desc: Amazon Pay only[icon:amazon]. The cancel date for the purchase. This field is only set if the purchase is canceled.
|
||||
desc: Amazon Pay only [icon:amazon]. The cancel date for the purchase. This field is only set if the purchase is canceled.
|
||||
|
||||
- name: canceled
|
||||
type: string
|
||||
desc: Amazon Pay only[icon:amazon]. Is set to `true` if the receipt was canceled or has expired; otherwise `false`.
|
||||
desc: Amazon Pay only [icon:amazon]. Is set to `true` if the receipt was canceled or has expired; otherwise `false`.
|
||||
|
||||
- name: error
|
||||
type: table
|
||||
|
@@ -1,10 +1,6 @@
|
||||
name: IAPExt
|
||||
|
||||
platforms:
|
||||
armv7-ios:
|
||||
context:
|
||||
weakFrameworks: ['StoreKit', 'UIKit', 'Foundation']
|
||||
|
||||
arm64-ios:
|
||||
context:
|
||||
weakFrameworks: ['StoreKit', 'UIKit', 'Foundation']
|
||||
|
11
extension-iap/ext.properties
Normal file
11
extension-iap/ext.properties
Normal file
@@ -0,0 +1,11 @@
|
||||
[iap]
|
||||
title = IAP
|
||||
help = Settings for In-App Purchases extension
|
||||
group = Runtime
|
||||
|
||||
iap_provider.type = string
|
||||
iap_provider.default = GooglePlay
|
||||
iap_provider.options = GooglePlay, Amazon
|
||||
|
||||
auto_finish_transactions.type = bool
|
||||
auto_finish_transactions.default = 1
|
@@ -71,7 +71,7 @@ var LibraryFacebookIAP = {
|
||||
|
||||
if(url_index == product_count-1) {
|
||||
var productsJSON = JSON.stringify(products);
|
||||
var res_buf = allocate(intArrayFromString(productsJSON), 'i8', ALLOC_STACK);
|
||||
var res_buf = stringToUTF8OnStack(productsJSON);
|
||||
{{{ makeDynCall('vii', 'callback')}}}(lua_callback, res_buf);
|
||||
} else {
|
||||
var xmlhttp = new XMLHttpRequest();
|
||||
@@ -143,7 +143,7 @@ var LibraryFacebookIAP = {
|
||||
}
|
||||
|
||||
var productsJSON = JSON.stringify(result)
|
||||
var res_buf = allocate(intArrayFromString(productsJSON), 'i8', ALLOC_STACK);
|
||||
var res_buf = stringToUTF8OnStack(productsJSON);
|
||||
{{{ makeDynCall('viii', 'callback')}}}(lua_callback, res_buf, 0);
|
||||
|
||||
} else {
|
||||
@@ -166,4 +166,4 @@ var LibraryFacebookIAP = {
|
||||
}
|
||||
|
||||
autoAddDeps(LibraryFacebookIAP, '$FBinner');
|
||||
mergeInto(LibraryManager.library, LibraryFacebookIAP);
|
||||
addToLibrary(LibraryFacebookIAP);
|
||||
|
@@ -1,3 +1,7 @@
|
||||
dependencies {
|
||||
implementation 'com.android.billingclient:billing:5.0.0'
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.android.billingclient:billing:7.0.0'
|
||||
}
|
||||
|
@@ -31,6 +31,7 @@ enum BillingResponse
|
||||
BILLING_RESPONSE_RESULT_ERROR = 6,
|
||||
BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7,
|
||||
BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8,
|
||||
BILLING_RESPONSE_RESULT_NETWORK_ERROR = 9,
|
||||
};
|
||||
|
||||
enum ProviderId
|
||||
|
@@ -306,23 +306,9 @@ static void HandleProductResult(const IAPCommand* cmd)
|
||||
}
|
||||
|
||||
if (cmd->m_ResponseCode == BILLING_RESPONSE_RESULT_OK) {
|
||||
dmJson::Document doc;
|
||||
dmJson::Result r = dmJson::Parse((const char*) cmd->m_Data, &doc);
|
||||
if (r == dmJson::RESULT_OK && doc.m_NodeCount > 0) {
|
||||
char err_str[128];
|
||||
if (dmScript::JsonToLua(L, &doc, 0, err_str, sizeof(err_str)) < 0) {
|
||||
dmLogError("Failed converting product result JSON to Lua; %s", err_str);
|
||||
lua_pushnil(L);
|
||||
IAP_PushError(L, "failed to convert JSON to Lua for product response", REASON_UNSPECIFIED);
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
} else {
|
||||
dmLogError("Failed to parse product response (%d)", r);
|
||||
lua_pushnil(L);
|
||||
IAP_PushError(L, "failed to parse product response", REASON_UNSPECIFIED);
|
||||
}
|
||||
dmJson::Free(&doc);
|
||||
const char* json = (const char*)cmd->m_Data;
|
||||
dmScript::JsonToLua(L, json, strlen(json)); // throws lua error if it fails
|
||||
lua_pushnil(L);
|
||||
} else {
|
||||
dmLogError("IAP error %d", cmd->m_ResponseCode);
|
||||
lua_pushnil(L);
|
||||
@@ -356,23 +342,9 @@ static void HandlePurchaseResult(const IAPCommand* cmd)
|
||||
|
||||
if (cmd->m_ResponseCode == BILLING_RESPONSE_RESULT_OK) {
|
||||
if (cmd->m_Data != 0) {
|
||||
dmJson::Document doc;
|
||||
dmJson::Result r = dmJson::Parse((const char*) cmd->m_Data, &doc);
|
||||
if (r == dmJson::RESULT_OK && doc.m_NodeCount > 0) {
|
||||
char err_str[128];
|
||||
if (dmScript::JsonToLua(L, &doc, 0, err_str, sizeof(err_str)) < 0) {
|
||||
dmLogError("Failed converting purchase JSON result to Lua; %s", err_str);
|
||||
lua_pushnil(L);
|
||||
IAP_PushError(L, "failed to convert purchase response JSON to Lua", REASON_UNSPECIFIED);
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
} else {
|
||||
dmLogError("Failed to parse purchase response (%d)", r);
|
||||
lua_pushnil(L);
|
||||
IAP_PushError(L, "failed to parse purchase response", REASON_UNSPECIFIED);
|
||||
}
|
||||
dmJson::Free(&doc);
|
||||
const char* json = (const char*)cmd->m_Data;
|
||||
dmScript::JsonToLua(L, json, strlen(json)); // throws lua error if it fails
|
||||
lua_pushnil(L);
|
||||
} else {
|
||||
dmLogError("IAP error, purchase response was null");
|
||||
lua_pushnil(L);
|
||||
|
@@ -46,23 +46,8 @@ static void IAPList_Callback(void* luacallback, const char* result_json)
|
||||
|
||||
if(result_json != 0)
|
||||
{
|
||||
dmJson::Document doc;
|
||||
dmJson::Result r = dmJson::Parse(result_json, &doc);
|
||||
if (r == dmJson::RESULT_OK && doc.m_NodeCount > 0) {
|
||||
char err_str[128];
|
||||
if (dmScript::JsonToLua(L, &doc, 0, err_str, sizeof(err_str)) < 0) {
|
||||
dmLogError("Failed converting list result JSON to Lua; %s", err_str);
|
||||
lua_pushnil(L);
|
||||
IAP_PushError(L, "Failed converting list result JSON to Lua", REASON_UNSPECIFIED);
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
} else {
|
||||
dmLogError("Failed to parse list result JSON (%d)", r);
|
||||
lua_pushnil(L);
|
||||
IAP_PushError(L, "Failed to parse list result JSON", REASON_UNSPECIFIED);
|
||||
}
|
||||
dmJson::Free(&doc);
|
||||
dmScript::JsonToLua(L, result_json, strlen(result_json)); // throws lua error if it fails
|
||||
lua_pushnil(L);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -112,23 +97,8 @@ static void IAPListener_Callback(void* luacallback, const char* result_json, int
|
||||
}
|
||||
|
||||
if (result_json) {
|
||||
dmJson::Document doc;
|
||||
dmJson::Result r = dmJson::Parse(result_json, &doc);
|
||||
if (r == dmJson::RESULT_OK && doc.m_NodeCount > 0) {
|
||||
char err_str[128];
|
||||
if (dmScript::JsonToLua(L, &doc, 0, err_str, sizeof(err_str)) < 0) {
|
||||
dmLogError("Failed converting purchase result JSON to Lua; %s", err_str);
|
||||
lua_pushnil(L);
|
||||
IAP_PushError(L, "failed converting purchase result JSON to Lua", REASON_UNSPECIFIED);
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
} else {
|
||||
dmLogError("Failed to parse purchase response (%d)", r);
|
||||
lua_pushnil(L);
|
||||
IAP_PushError(L, "failed to parse purchase response", REASON_UNSPECIFIED);
|
||||
}
|
||||
dmJson::Free(&doc);
|
||||
dmScript::JsonToLua(L, result_json, strlen(result_json)); // throws lua error if it fails
|
||||
lua_pushnil(L);
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
switch(error_code)
|
||||
|
@@ -21,6 +21,7 @@ import com.android.billingclient.api.BillingClient;
|
||||
import com.android.billingclient.api.BillingClient.BillingResponseCode;
|
||||
import com.android.billingclient.api.BillingClient.ProductType;
|
||||
import com.android.billingclient.api.BillingResult;
|
||||
import com.android.billingclient.api.PendingPurchasesParams;
|
||||
import com.android.billingclient.api.Purchase;
|
||||
import com.android.billingclient.api.Purchase.PurchaseState;
|
||||
import com.android.billingclient.api.ProductDetails;
|
||||
@@ -56,7 +57,8 @@ public class IapGooglePlay implements PurchasesUpdatedListener {
|
||||
this.activity = activity;
|
||||
this.autoFinishTransactions = autoFinishTransactions;
|
||||
|
||||
billingClient = BillingClient.newBuilder(activity).setListener(this).enablePendingPurchases().build();
|
||||
PendingPurchasesParams pendingPurchasesParams = PendingPurchasesParams.newBuilder().enableOneTimeProducts().build();
|
||||
billingClient = BillingClient.newBuilder(activity).setListener(this).enablePendingPurchases(pendingPurchasesParams).build();
|
||||
billingClient.startConnection(new BillingClientStateListener() {
|
||||
@Override
|
||||
public void onBillingSetupFinished(BillingResult billingResult) {
|
||||
@@ -221,7 +223,6 @@ public class IapGooglePlay implements PurchasesUpdatedListener {
|
||||
case BillingResponseCode.OK:
|
||||
defoldResponse = IapJNI.BILLING_RESPONSE_RESULT_OK;
|
||||
break;
|
||||
case BillingResponseCode.SERVICE_TIMEOUT:
|
||||
case BillingResponseCode.SERVICE_UNAVAILABLE:
|
||||
case BillingResponseCode.SERVICE_DISCONNECTED:
|
||||
defoldResponse = IapJNI.BILLING_RESPONSE_RESULT_SERVICE_UNAVAILABLE;
|
||||
@@ -229,6 +230,9 @@ public class IapGooglePlay implements PurchasesUpdatedListener {
|
||||
case BillingResponseCode.USER_CANCELED:
|
||||
defoldResponse = IapJNI.BILLING_RESPONSE_RESULT_USER_CANCELED;
|
||||
break;
|
||||
case BillingResponseCode.NETWORK_ERROR: // new in Play Billing Library 6.0.0
|
||||
defoldResponse = IapJNI.BILLING_RESPONSE_RESULT_NETWORK_ERROR;
|
||||
break;
|
||||
case BillingResponseCode.FEATURE_NOT_SUPPORTED:
|
||||
case BillingResponseCode.ERROR:
|
||||
default:
|
||||
@@ -243,6 +247,20 @@ public class IapGooglePlay implements PurchasesUpdatedListener {
|
||||
return billingResponseCodeToDefoldResponse(result.getResponseCode());
|
||||
}
|
||||
|
||||
private void invokeOnPurchaseResultListener(IPurchaseListener purchaseListener, int billingResultCode, String purchaseData) {
|
||||
if (purchaseListener == null) {
|
||||
Log.w(TAG, "Received billing result but no listener has been set");
|
||||
return;
|
||||
}
|
||||
purchaseListener.onPurchaseResult(billingResultCode, purchaseData);
|
||||
}
|
||||
private void invokeOnPurchaseResultListener(IPurchaseListener purchaseListener, BillingResult billingResult, Purchase purchase) {
|
||||
invokeOnPurchaseResultListener(purchaseListener, billingResultToDefoldResponse(billingResult), convertPurchase(purchase));
|
||||
}
|
||||
private void invokeOnPurchaseResultListener(IPurchaseListener purchaseListener, BillingResult billingResult) {
|
||||
invokeOnPurchaseResultListener(purchaseListener, billingResultToDefoldResponse(billingResult), "");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called either explicitly from Lua or from extension code
|
||||
* when "set_listener()" is called from Lua.
|
||||
@@ -306,7 +324,7 @@ public class IapGooglePlay implements PurchasesUpdatedListener {
|
||||
// note: we only call the purchase listener if an error happens
|
||||
if (billingResult.getResponseCode() != BillingResponseCode.OK) {
|
||||
Log.e(TAG, "Unable to consume purchase: " + billingResult.getDebugMessage());
|
||||
purchaseListener.onPurchaseResult(billingResultToDefoldResponse(billingResult), "");
|
||||
invokeOnPurchaseResultListener(purchaseListener, billingResult);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -329,7 +347,7 @@ public class IapGooglePlay implements PurchasesUpdatedListener {
|
||||
// note: we only call the purchase listener if an error happens
|
||||
if (billingResult.getResponseCode() != BillingResponseCode.OK) {
|
||||
Log.e(TAG, "Unable to acknowledge purchase: " + billingResult.getDebugMessage());
|
||||
purchaseListener.onPurchaseResult(billingResultToDefoldResponse(billingResult), "");
|
||||
invokeOnPurchaseResultListener(purchaseListener, billingResult);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -348,12 +366,12 @@ public class IapGooglePlay implements PurchasesUpdatedListener {
|
||||
@Override
|
||||
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
|
||||
Log.d(TAG, "handlePurchase() response code " + billingResult.getResponseCode() + " purchaseToken: " + purchaseToken);
|
||||
purchaseListener.onPurchaseResult(billingResultToDefoldResponse(billingResult), convertPurchase(purchase));
|
||||
invokeOnPurchaseResultListener(purchaseListener, billingResult, purchase);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
purchaseListener.onPurchaseResult(billingResponseCodeToDefoldResponse(BillingResponseCode.OK), convertPurchase(purchase));
|
||||
invokeOnPurchaseResultListener(purchaseListener, billingResponseCodeToDefoldResponse(BillingResponseCode.OK), convertPurchase(purchase));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,13 +380,17 @@ public class IapGooglePlay implements PurchasesUpdatedListener {
|
||||
*/
|
||||
@Override
|
||||
public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
|
||||
if (billingResult.getResponseCode() == BillingResponseCode.OK && purchases != null) {
|
||||
for (Purchase purchase : purchases) {
|
||||
handlePurchase(purchase, this.purchaseListener);
|
||||
if (billingResult.getResponseCode() == BillingResponseCode.OK) {
|
||||
if (purchases != null && !purchases.isEmpty()) {
|
||||
for (Purchase purchase : purchases) {
|
||||
if (purchase != null) {
|
||||
handlePurchase(purchase, this.purchaseListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.purchaseListener.onPurchaseResult(billingResultToDefoldResponse(billingResult), "");
|
||||
invokeOnPurchaseResultListener(this.purchaseListener, billingResult);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,7 +414,7 @@ public class IapGooglePlay implements PurchasesUpdatedListener {
|
||||
BillingResult billingResult = billingClient.launchBillingFlow(this.activity, billingFlowParams);
|
||||
if (billingResult.getResponseCode() != BillingResponseCode.OK) {
|
||||
Log.e(TAG, "Purchase failed: " + billingResult.getDebugMessage());
|
||||
purchaseListener.onPurchaseResult(billingResultToDefoldResponse(billingResult), "");
|
||||
invokeOnPurchaseResultListener(purchaseListener, billingResult);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,11 +435,16 @@ public class IapGooglePlay implements PurchasesUpdatedListener {
|
||||
@Override
|
||||
public void onProductDetailsResponse(BillingResult billingResult, List<ProductDetails> productDetailsList) {
|
||||
if (billingResult.getResponseCode() == BillingResponseCode.OK && (productDetailsList != null) && !productDetailsList.isEmpty()) {
|
||||
buyProduct(productDetailsList.get(0), token, purchaseListener);
|
||||
for (ProductDetails productDetails : productDetailsList) {
|
||||
if (productDetails != null) {
|
||||
buyProduct(productDetails, token, purchaseListener);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Log.e(TAG, "Unable to get product details before buying: " + billingResult.getDebugMessage());
|
||||
purchaseListener.onPurchaseResult(billingResultToDefoldResponse(billingResult), "");
|
||||
invokeOnPurchaseResultListener(purchaseListener, billingResult);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -435,10 +462,12 @@ public class IapGooglePlay implements PurchasesUpdatedListener {
|
||||
|
||||
@Override
|
||||
public void onProductDetailsResponse(BillingResult billingResult, List<ProductDetails> productDetails) {
|
||||
if (productDetails != null) {
|
||||
if (productDetails != null && !productDetails.isEmpty()) {
|
||||
// cache products (cache will be used to speed up buying)
|
||||
for (ProductDetails pd : productDetails) {
|
||||
IapGooglePlay.this.products.put(pd.getProductId(), pd);
|
||||
if (pd != null) {
|
||||
IapGooglePlay.this.products.put(pd.getProductId(), pd);
|
||||
}
|
||||
}
|
||||
// add to list of all product details
|
||||
allProductDetails.addAll(productDetails);
|
||||
@@ -485,9 +514,11 @@ public class IapGooglePlay implements PurchasesUpdatedListener {
|
||||
@Override
|
||||
public void onProductDetailsResponse(BillingResult billingResult, List<ProductDetails> productDetails) {
|
||||
JSONArray a = new JSONArray();
|
||||
if (billingResult.getResponseCode() == BillingResponseCode.OK) {
|
||||
if ((billingResult.getResponseCode() == BillingResponseCode.OK) && (productDetails != null) && !productDetails.isEmpty()) {
|
||||
for (ProductDetails pd : productDetails) {
|
||||
a.put(convertProductDetails(pd));
|
||||
if (pd != null) {
|
||||
a.put(convertProductDetails(pd));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@@ -18,6 +18,7 @@ public class IapJNI implements IListProductsListener, IPurchaseListener {
|
||||
public static final int BILLING_RESPONSE_RESULT_ERROR = 6;
|
||||
public static final int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7;
|
||||
public static final int BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8;
|
||||
public static final int BILLING_RESPONSE_RESULT_NETWORK_ERROR = 9;
|
||||
|
||||
public IapJNI() {
|
||||
}
|
||||
|
@@ -11,12 +11,12 @@ height = 1136
|
||||
[android]
|
||||
input_method = HiddenInputField
|
||||
package = com.defold.extension.iap
|
||||
version_code = 7
|
||||
target_sdk_version = 29
|
||||
version_code = 9
|
||||
minimum_sdk_version = 21
|
||||
|
||||
[project]
|
||||
title = extension-iap
|
||||
dependencies = https://github.com/andsve/dirtylarry/archive/master.zip
|
||||
dependencies#0 = https://github.com/andsve/dirtylarry/archive/master.zip
|
||||
|
||||
[library]
|
||||
include_dirs = extension-iap
|
||||
|
100
main/main.gui
100
main/main.gui
@@ -1,7 +1,7 @@
|
||||
script: "/main/main.gui_script"
|
||||
fonts {
|
||||
name: "system_font"
|
||||
font: "/builtins/fonts/system_font.font"
|
||||
name: "default"
|
||||
font: "/builtins/fonts/default.font"
|
||||
}
|
||||
background_color {
|
||||
x: 0.0
|
||||
@@ -47,6 +47,8 @@ nodes {
|
||||
alpha: 1.0
|
||||
template: "/dirtylarry/button.gui"
|
||||
template_node_child: false
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -102,6 +104,10 @@ nodes {
|
||||
alpha: 1.0
|
||||
template_node_child: true
|
||||
size_mode: SIZE_MODE_MANUAL
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -166,6 +172,10 @@ nodes {
|
||||
template_node_child: true
|
||||
text_leading: 1.0
|
||||
text_tracking: 0.0
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -205,6 +215,8 @@ nodes {
|
||||
alpha: 1.0
|
||||
template: "/dirtylarry/button.gui"
|
||||
template_node_child: false
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -261,6 +273,10 @@ nodes {
|
||||
overridden_fields: 4
|
||||
template_node_child: true
|
||||
size_mode: SIZE_MODE_MANUAL
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -325,6 +341,10 @@ nodes {
|
||||
template_node_child: true
|
||||
text_leading: 1.0
|
||||
text_tracking: 0.0
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -360,7 +380,7 @@ nodes {
|
||||
type: TYPE_TEXT
|
||||
blend_mode: BLEND_MODE_ALPHA
|
||||
text: "<text>"
|
||||
font: "system_font"
|
||||
font: "default"
|
||||
id: "log"
|
||||
xanchor: XANCHOR_NONE
|
||||
yanchor: YANCHOR_NONE
|
||||
@@ -387,6 +407,10 @@ nodes {
|
||||
template_node_child: false
|
||||
text_leading: 1.0
|
||||
text_tracking: 0.0
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -426,6 +450,8 @@ nodes {
|
||||
alpha: 1.0
|
||||
template: "/dirtylarry/button.gui"
|
||||
template_node_child: false
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -481,6 +507,10 @@ nodes {
|
||||
alpha: 1.0
|
||||
template_node_child: true
|
||||
size_mode: SIZE_MODE_MANUAL
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -545,6 +575,10 @@ nodes {
|
||||
template_node_child: true
|
||||
text_leading: 1.0
|
||||
text_tracking: 0.0
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -584,6 +618,8 @@ nodes {
|
||||
alpha: 1.0
|
||||
template: "/dirtylarry/button.gui"
|
||||
template_node_child: false
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -639,6 +675,10 @@ nodes {
|
||||
alpha: 1.0
|
||||
template_node_child: true
|
||||
size_mode: SIZE_MODE_MANUAL
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -703,6 +743,10 @@ nodes {
|
||||
template_node_child: true
|
||||
text_leading: 1.0
|
||||
text_tracking: 0.0
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -742,6 +786,8 @@ nodes {
|
||||
alpha: 1.0
|
||||
template: "/dirtylarry/button.gui"
|
||||
template_node_child: false
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -798,6 +844,10 @@ nodes {
|
||||
overridden_fields: 4
|
||||
template_node_child: true
|
||||
size_mode: SIZE_MODE_MANUAL
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -862,6 +912,10 @@ nodes {
|
||||
template_node_child: true
|
||||
text_leading: 1.0
|
||||
text_tracking: 0.0
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -901,6 +955,8 @@ nodes {
|
||||
alpha: 1.0
|
||||
template: "/dirtylarry/button.gui"
|
||||
template_node_child: false
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -956,6 +1012,10 @@ nodes {
|
||||
alpha: 1.0
|
||||
template_node_child: true
|
||||
size_mode: SIZE_MODE_MANUAL
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -1020,6 +1080,10 @@ nodes {
|
||||
template_node_child: true
|
||||
text_leading: 1.0
|
||||
text_tracking: 0.0
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -1059,6 +1123,8 @@ nodes {
|
||||
alpha: 1.0
|
||||
template: "/dirtylarry/button.gui"
|
||||
template_node_child: false
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -1115,6 +1181,10 @@ nodes {
|
||||
overridden_fields: 4
|
||||
template_node_child: true
|
||||
size_mode: SIZE_MODE_MANUAL
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -1179,6 +1249,10 @@ nodes {
|
||||
template_node_child: true
|
||||
text_leading: 1.0
|
||||
text_tracking: 0.0
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -1218,6 +1292,8 @@ nodes {
|
||||
alpha: 1.0
|
||||
template: "/dirtylarry/checkbox_label.gui"
|
||||
template_node_child: false
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -1273,6 +1349,10 @@ nodes {
|
||||
alpha: 1.0
|
||||
template_node_child: true
|
||||
size_mode: SIZE_MODE_MANUAL
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -1337,6 +1417,10 @@ nodes {
|
||||
template_node_child: true
|
||||
text_leading: 1.0
|
||||
text_tracking: 0.0
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -1376,6 +1460,8 @@ nodes {
|
||||
alpha: 1.0
|
||||
template: "/dirtylarry/checkbox_label.gui"
|
||||
template_node_child: false
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -1431,6 +1517,10 @@ nodes {
|
||||
alpha: 1.0
|
||||
template_node_child: true
|
||||
size_mode: SIZE_MODE_MANUAL
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
@@ -1495,6 +1585,10 @@ nodes {
|
||||
template_node_child: true
|
||||
text_leading: 1.0
|
||||
text_tracking: 0.0
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
material: "/builtins/materials/gui.material"
|
||||
adjust_reference: ADJUST_REFERENCE_PARENT
|
||||
|
@@ -44,7 +44,13 @@ end
|
||||
|
||||
local function buy(id)
|
||||
log("iap.buy() " .. id)
|
||||
iap.buy(id)
|
||||
local options = {}
|
||||
local item = available_items[id]
|
||||
if item.subscriptions then
|
||||
local subscription = item.subscriptions[1]
|
||||
options.token = subscription.token
|
||||
end
|
||||
iap.buy(id, options)
|
||||
end
|
||||
|
||||
local function restore()
|
||||
|
Reference in New Issue
Block a user