32 Commits
1.0 ... 1.4.0

Author SHA1 Message Date
Mathias Westerdahl
e79b5cfb36 Merge pull request #22 from dyukorev/master
Pending transactions fix
2020-04-01 10:10:41 +02:00
Denis Dyukorev
9a40a8ea73 Add 'process_pending_transactions' function documentation 2020-03-31 17:22:07 +07:00
Denis Dyukorev
564df1e279 Rename function argument to match naming requirements. 2020-03-31 16:27:22 +07:00
Björn Ritzl
f1d53948e9 Merge pull request #20 from defold/Issue-18-crash-on-empty-product
Check if title or description is null
2020-03-09 12:33:48 +01:00
Björn Ritzl
607b4a117e Update iap_ios.mm 2020-03-09 11:57:14 +01:00
Björn Ritzl
38656e973c Check if title or description is null
Fixes #18
2020-03-09 11:46:01 +01:00
Björn Ritzl
4efe0c2a25 Merge pull request #19 from defold/Issue-3-handle-missing-google-play-store
Handle missing google play store
2020-03-09 11:43:28 +01:00
Björn Ritzl
3fb09c3b95 Missing import 2020-03-09 11:22:54 +01:00
Björn Ritzl
2843080690 Check that the Google Play Store exists on device
Fixes #3
2020-03-09 11:22:41 +01:00
Björn Ritzl
1c017cf4ed Merge pull request #17 from Filazapovich/master
iOS: Fix localized numbers in transaction date for some Arab countries
2020-03-05 06:29:00 +01:00
Denis Dyukorev
cbc1f659c1 Add transactions handler to init method according ios documentation it's important to add handler on application:didFinishLaunchingWithOptions: (https://developer.apple.com/documentation/storekit/in-app_purchase/setting_up_the_transaction_observer_for_the_payment_queue?language=objc), move observable commands to separate queue so it can be processed separately and after listener setup, add IAP_ProcessPendingTransactions function to resolve pending transactions on iOS. 2020-03-04 17:26:52 +03:00
Filazopovich Gennady
eda78dda4f Fix incorrect numbers in transaction date for some Arab countries 2020-03-04 15:39:30 +03:00
Denis Dyukorev
d6cc6f55f9 IAP command pointer to products list function to avoid crash on multiple calls products list 2020-02-15 15:00:15 +03:00
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
Mathias Westerdahl
eedfb80a6b Merge pull request #10 from defold/ios-thread-queue
Added thread safe command queue for iOS
2019-11-15 10:16:05 +01:00
mathiaswking
f784c187b2 Added thread safe command queue for iOS 2019-11-14 18:26:55 +01:00
mathiaswking
bbab40a315 Made iOS functions static
Removed old documentation
2019-11-14 10:50:07 +01:00
mathiaswking
bca28de29a Moved command queue code to iap_private.h/cpp 2019-11-14 10:48:19 +01:00
Björn Ritzl
33c5709eaf Fixed docs 2019-11-12 13:55:46 +01:00
Björn Ritzl
daf85dbba5 Updated docs 2019-11-12 13:31:00 +01:00
Björn Ritzl
056618f6ba Merge pull request #9 from dev-masih/patch-1
fixed wrong words
2019-11-03 22:34:31 +01:00
Masih
3994291854 fixed wrong words 2019-10-30 11:18:47 +03:30
Björn Ritzl
23a694bbb5 Update api.yml 2019-10-24 08:44:11 +02:00
52 changed files with 1568 additions and 1478 deletions

View File

@@ -1,11 +1,9 @@
# In-app purchase extension for Defold
This is a 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.
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.
To learn more please visit the [documentation page](https://defold.github.io/extension-iap/) for this extension.
## API and installation
[API and setup instructions](https://defold.github.io/extension-iap).
[The manual](https://defold.com/manuals/iap/) is available on the official Defold site.
---
If you have any issues, questions or suggestions please [create an issue](https://github.com/defold/extension-iap/issues).
## Manual
[The manual](https://www.defold.com/manuals/iap/) is available on the official Defold site.

View File

@@ -1,2 +0,0 @@
source 'https://rubygems.org'
gem 'github-pages', group: :jekyll_plugins

View File

@@ -9,7 +9,7 @@
- name: buy
type: function
desc: Sets the listener function for inter-app communication events.
desc: Sets the listener function for In-app purchase events.
parameters:
- name: id
type: string
@@ -161,6 +161,13 @@
desc: value is `true` if current store supports handling
restored transactions, otherwise `false`.
#*****************************************************************************************************
- name: process_pending_transactions
type: function
desc: Process transactions still unprocessed from previous session if any. Transactions will be
processed with callback function set with `set_listener` function
#*****************************************************************************************************
- name: set_listener
@@ -296,4 +303,4 @@
type: number
desc: transaction unverified state, requires verification of purchase

112
docs/_includes/api_ref.md Normal file
View File

@@ -0,0 +1,112 @@
## Modules
{% for item in site.data.api %}
### <code>{{ item.name }}</code>
{{ item.desc }}
{% endfor %}
## Enums
<table>
<tbody>
{% for module in site.data.api %}
{% for item in module.members %}
{% if item.type contains 'number' %}
<tr>
<td><strong>{{ module.name }}.{{ item.name }}</strong></td>
<td>{{ item.desc | markdownify | replace: "[icon:attention]","<br><br>⚠️"}}</td>
</tr>
{% endif %}
{% endfor %}
{% endfor %}
</tbody>
</table>
<hr>
## Functions
<table>
<tbody>
{% for module in site.data.api %}
{% for item in module.members %}
{% if item.type contains 'function' %}
<tr>
<td><a href="#{{ item.name | url_encode }}"><strong>{{ module.name }}.{{ item.name }}()</strong></a></td>
<td>{{ item.desc | truncate: 80 }}</td>
</tr>
{% endif %}
{% endfor %}
{% endfor %}
</tbody>
</table>
{% for module in site.data.api %}
{% for function in module.members %}
{% if function.type contains 'function' %}
<div class="function-wrap">
<h3 class="function-header"><a href="#{{ function.name | url_encode }}" id="{{ function.name | url_encode }}"><code>{{ module.name }}.{{ function.name }}({% for param in function.parameters %}{{param.name}}{% unless forloop.last %}, {% endunless %}{% endfor %})</code></a></h3>
{% if function.parameters %}
<table>
<thead>
<tr>
<th>Parameter</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% for param in function.parameters %}
<tr>
<td style="text-align: right;">
<strong>{{ param.name }}</strong>
{% if param.optional %}
(optional)
{% endif %}
</td>
<td><code>{{ param.type }}</code></td>
<td>{{ param.desc | markdownify }}
{% if param.type == "function" %}
{% include type-function.md params=param.parameters %}
{% endif %}
{% if param.type == "table" %}
{% include type-table.md fields=param.members %}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if function.returns %}
<table>
<thead>
<tr>
<th>Return value</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<h4>Returns</h4>
{% for return in function.returns %}
<tr>
<td>{{ return.name }}</td>
<td><code class="inline-code-block">{{ return.type }}</code></td>
<td>{{ return.desc | markdownify }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{{ function.desc | markdownify | replace: "[icon:attention]","<br><br>⚠️" | replace: "[type:string]","<code class='inline-code-block'>string</code>" | replace: "[type:number]","<code class='inline-code-block'>number</code>" | replace: "[type:table]","<code class='inline-code-block'>table</code>" | markdownify}}
{% if function.examples %}
<h4>Examples</h4>
{% for example in function.examples %}
{{ example.desc | markdownify }}
{% endfor %}
{% endif %}
</div>
{% endif %}
{% endfor %}
{% endfor %}

View File

@@ -1,38 +0,0 @@
{% assign truncate = 10000 %}
{% if include.truncate %}
{% assign truncate = include.truncate %}
{% endif %}
{{ include.desc
| truncate: truncate
| replace: "[type:string]","<code class='inline-code-block'>string</code>"
| replace: "[type:number]","<code class='inline-code-block'>number</code>"
| replace: "[type:table]","<code class='inline-code-block'>table</code>"
| replace: "[icon:attention]","<br><br><span class='icon-attention'></span>"
| replace: "[icon:android]", "<span class='icon-android'></span>"
| replace: "[icon:gameroom]", "<span class='icon-gameroom'></span>"
| replace: "[icon:apple]", "<span class='icon-apple'></span>"
| replace: "[icon:clipboard]", "<span class='icon-clipboard'></span>"
| replace: "[icon:king]", "<span class='icon-king'></span>"
| replace: "[icon:defold]", "<span class='icon-defold'></span>"
| replace: "[icon:search]", "<span class='icon-search'></span>"
| replace: "[icon:link-ext]", "<span class='icon-link-ext'></span>"
| replace: "[icon:link]", "<span class='icon-link'></span>"
| replace: "[icon:amazon]", "<span class='icon-amazon'></span>"
| replace: "[icon:html5]", "<span class='icon-html5'></span>"
| replace: "[icon:ios]", "<span class='icon-ios'></span>"
| replace: "[icon:linux]", "<span class='icon-linux'></span>"
| replace: "[icon:windows]", "<span class='icon-windows'></span>"
| replace: "[icon:macos]", "<span class='icon-macos'></span>"
| replace: "[icon:clock]", "<span class='icon-clock'></span>"
| replace: "[icon:star]", "<span class='icon-star'></span>"
| replace: "[icon:googleplay]", "<span class='icon-googleplay'></span>"
| replace: "[icon:dropbox]", "<span class='icon-dropbox'></span>"
| replace: "[icon:twitter]", "<span class='icon-twitter'></span>"
| replace: "[icon:slack]", "<span class='icon-slack'></span>"
| replace: "[icon:instagram]", "<span class='icon-instagram'></span>"
| replace: "[icon:steam]", "<span class='icon-steam'></span>"
| replace: "[icon:github]", "<span class='icon-github'></span>"
| replace: "[icon:facebook]", "<span class='icon-facebook'></span>"
| markdownify
}}

View File

@@ -11,7 +11,7 @@
<tr>
<td><strong>{{ param.name }}</strong></td>
<td><code>{{ param.type }}</code></td>
<td>{% include description.md desc=param.desc %}
<td>{{ param.desc | markdownify }}
{% if param.type == "table" %}
{% include type-table.md fields=param.members %}

View File

@@ -5,7 +5,7 @@
{% if field.optional %}
(optional)
{% endif %}
<code>{{ field.type }}</code> - {% include description.md desc=field.desc %}
<code>{{ field.type }}</code> - {{ field.desc | markdownify }}
</li>
{% endfor %}
</ul>

View File

@@ -4,9 +4,9 @@
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="rouge.css">
<link rel="stylesheet" href="fonts.css">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/rouge.css">
<link rel="stylesheet" href="css/fonts.css">
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
<![endif]-->
@@ -14,24 +14,8 @@
</head>
<body>
<div class="wrapper">
<header>
<h1>Defold In-app purchase extension API documentation</h1>
<p>Functions and constants for doing in-app purchase. Supported on iOS, Android (Google Play and Amazon) and Facebook Canvas.</p>
<p>To use this library in your Defold project, add the following URL to your <code class="inline-code-block">game.project</code> dependencies:
<pre>https://github.com/defold/extension-iap/archive/master.zip</pre>
</p>
<p>We recommend using a link to a <code class="inline-code-block">zip</code> file of a <a href="https://github.com/defold/extension-iap/releases">specific release</a>.</p>
<p>The source code can be viewed at: <a href="https://github.com/defold/extension-iap">https://github.com/defold/extension-iap</a></p>
</header>
<hr>
<section>
{{ content }}
</section>
</div>
</body>

View File

@@ -53,15 +53,3 @@
url('fonts/Noto-Sans-700italic/Noto-Sans-700italic.ttf') format('truetype'),
url('fonts/Noto-Sans-700italic/Noto-Sans-700italic.svg#NotoSans') format('svg');
}
@font-face {
font-family: fontello;
font-weight: 400;
font-style: normal;
src: url(fonts/fontello/fontello_ef8859ce.eot);
src: url(fonts/fontello/fontello_ef8859ce.eot#iefix) format("embedded-opentype"),
url(fonts/fontello/fontello_248ab5dd.woff2) format("woff2"),
url(fonts/fontello/fontello_b6287553.woff) format("woff"),
url(fonts/fontello/fontello_df5eac8f.ttf) format("truetype"),
url(fonts/fontello/fontello_793be834.svg#fontello) format("svg");
}

View File

@@ -207,136 +207,3 @@
.highlight {
background-color: #f8f8f8;
}
.icon-amazon:before, .icon-android:before, .icon-apple:before,
.icon-attention:before, .icon-clipboard:before, .icon-clock:before,
.icon-defold:before, .icon-dropbox:before, .icon-facebook:before,
.icon-gameroom:before, .icon-github:before, .icon-googleplay:before,
.icon-html5:before, .icon-instagram:before, .icon-ios:before, .icon-king:before,
.icon-link-ext:before, .icon-link:before, .icon-linux:before, .icon-macos:before,
.icon-search:before, .icon-slack:before, .icon-star:before, .icon-steam:before,
.icon-twitter:before, .icon-windows:before {
font-family: fontello;
font-style: normal;
font-weight: 400;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
font-variant: normal;
text-transform: none;
line-height: 1em;
margin-left: .2em;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale
}
.icon-gameroom:before {
content: "\E800"
}
.icon-apple:before {
content: "\E809"
}
.icon-clipboard:before {
content: "\E819"
}
.icon-king:before {
content: "\E858"
}
.icon-defold:before {
content: "\E859"
}
.icon-search:before {
content: "\E86A"
}
.icon-link-ext:before {
content: "\E86F"
}
.icon-link:before {
content: "\E870"
}
.icon-attention:before {
content: "\E871";
color:#ffa500;
}
.icon-amazon:before {
content: "\E872"
}
.icon-android:before {
content: "\E873";
color: #A4C639;
}
.icon-html5:before {
content: "\E875";
color:#f16529;
}
.icon-ios:before {
content: "\E876";
color:#147efb;
}
.icon-linux:before {
content: "\E877"
}
.icon-windows:before {
content: "\E878"
}
.icon-macos:before {
content: "\E87A"
}
.icon-clock:before {
content: "\E87B"
}
.icon-star:before {
content: "\E87C"
}
.icon-googleplay:before {
content: "\E87D"
}
.icon-dropbox:before {
content: "\E87F"
}
.icon-twitter:before {
content: "\E881"
}
.icon-slack:before {
content: "\E883"
}
.icon-instagram:before {
content: "\E884"
}
.icon-steam:before {
content: "\E885"
}
.icon-github:before {
content: "\E886"
}
.icon-facebook:before {
content: "\E887"
}

0
docs/fonts/Noto-Sans-700/Noto-Sans-700.eot Executable file → Normal file
View File

0
docs/fonts/Noto-Sans-700/Noto-Sans-700.svg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

0
docs/fonts/Noto-Sans-700/Noto-Sans-700.ttf Executable file → Normal file
View File

0
docs/fonts/Noto-Sans-700/Noto-Sans-700.woff Executable file → Normal file
View File

0
docs/fonts/Noto-Sans-700/Noto-Sans-700.woff2 Executable file → Normal file
View File

0
docs/fonts/Noto-Sans-700italic/Noto-Sans-700italic.eot Executable file → Normal file
View File

0
docs/fonts/Noto-Sans-700italic/Noto-Sans-700italic.svg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

0
docs/fonts/Noto-Sans-700italic/Noto-Sans-700italic.ttf Executable file → Normal file
View File

View File

View File

0
docs/fonts/Noto-Sans-italic/Noto-Sans-italic.eot Executable file → Normal file
View File

0
docs/fonts/Noto-Sans-italic/Noto-Sans-italic.svg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

0
docs/fonts/Noto-Sans-italic/Noto-Sans-italic.ttf Executable file → Normal file
View File

0
docs/fonts/Noto-Sans-italic/Noto-Sans-italic.woff Executable file → Normal file
View File

0
docs/fonts/Noto-Sans-italic/Noto-Sans-italic.woff2 Executable file → Normal file
View File

0
docs/fonts/Noto-Sans-regular/Noto-Sans-regular.eot Executable file → Normal file
View File

0
docs/fonts/Noto-Sans-regular/Noto-Sans-regular.svg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

0
docs/fonts/Noto-Sans-regular/Noto-Sans-regular.ttf Executable file → Normal file
View File

0
docs/fonts/Noto-Sans-regular/Noto-Sans-regular.woff Executable file → Normal file
View File

0
docs/fonts/Noto-Sans-regular/Noto-Sans-regular.woff2 Executable file → Normal file
View File

View File

@@ -1,62 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Copyright (C) 2018 by original authors @ fontello.com</metadata>
<defs>
<font id="fontello" horiz-adv-x="1000" >
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="gameroom" unicode="&#xe800;" d="M939 850c34 0 61-27 61-61l0-878c0-34-27-61-61-61l-878 0c-34 0-61 27-61 61l0 878c0 34 27 61 61 61l878 0z m-548-787l-129 0 0-129 129 0 0 129z m-176 0l-129 0 0-129 129 0 0 129z m703 0l-129 0 0-129 129 0 0 129z m-352 0l-128 0 0-129 128 0 0 129z m176 0l-129 0 0-129 129 0 0 129z m176 176l-129 0 0-129 129 0 0 129z m-352 0l-128 0 0-129 128 0 0 129z m-175 0l-129 0 0-129 129 0 0 129z m351 176l-129 0 0-129 129 0 0 129z m-527 0l-129 0 0-129 129 0 0 129z m703 175l-129 0 0-129 129 0 0 129z m-352 176l-128 0 0-129 128 0 0 129z" horiz-adv-x="1000" />
<glyph glyph-name="apple" unicode="&#xe809;" d="M657 783a168 168 0 0 0 0-41 191 191 0 0 0-70-123 143 143 0 0 0-102-36 122 122 0 0 0 0 45 193 193 0 0 0 86 124 196 196 0 0 0 76 31z m67-472a191 191 0 0 0 92 177l-4 6a189 189 0 0 1-71 58 214 214 0 0 1-122 20 353 353 0 0 1-86-27 90 90 0 0 0-73 0c-15 6-31 12-47 18a173 173 0 0 1-57 12 204 204 0 0 1-166-82 252 252 0 0 1-57-138 467 467 0 0 1 27-203 506 506 0 0 1 57-117 408 408 0 0 1 76-93 96 96 0 0 1 74-25 185 185 0 0 1 58 17 172 172 0 0 0 145 3c10-4 19-8 29-12a109 109 0 0 1 95 6 174 174 0 0 1 47 43 551 551 0 0 1 67 105c11 22 19 44 29 66a190 190 0 0 0-114 166z" horiz-adv-x="1000" />
<glyph glyph-name="clipboard" unicode="&#xe819;" d="M125 100h250v-62h-250v62z m313 375h-313v-62h313v62z m125-187v125l-188-188 188-187v125h312v125h-312z m-282 62h-156v-62h156v62z m-156-187h156v62h-156v-62z m563-63h62v-125c-1-18-7-32-19-44s-26-18-43-19h-625c-35 0-63 29-63 63v688c0 34 28 62 63 62h187c0 69 56 125 125 125s125-56 125-125h188c34 0 62-28 62-62v-313h-62v188h-625v-563h625v125z m-563 500h500c0 34-28 63-62 63h-63c-34 0-62 28-62 62s-29 63-63 63-62-29-62-63-29-62-63-62h-62c-35 0-63-29-63-63z" horiz-adv-x="875" />
<glyph glyph-name="king" unicode="&#xe858;" d="M1000 378a81 81 0 0 1-29 60 79 79 0 0 1-49 19 91 91 0 0 1-76-48 88 88 0 0 1-51 18 93 93 0 0 1-61-25 129 129 0 0 1-31-41 103 103 0 0 1-24 44 66 66 0 0 1-48 21h0a77 77 0 0 1-62-35 49 49 0 0 1-22 6h-4a238 238 0 0 1 54 140c0 2 0 4 0 6a181 181 0 0 1-4 36c-13 57-54 96-101 96h0l-12 0a95 95 0 0 1-82-93 125 125 0 0 1 0-24 97 97 0 0 1 76-81c-45-56-157-113-279-140h0a212 212 0 0 1-24 54 107 107 0 0 1-88 57 81 81 0 0 1-36-8 84 84 0 0 1-47-71v-7a86 86 0 0 1 83-88h3a395 395 0 0 0 14-77c0-7 0-15 2-23 2-22 3-43 6-63 5-39 17-132 123-132a239 239 0 0 1 41 4 30 30 0 0 1 24 25 33 33 0 0 1-10 36l0 0-2 0a86 86 0 0 0-30 44c25-37 51-57 82-57h4a41 41 0 0 1 4-16l11-24 25 10a400 400 0 0 0 127 16 860 860 0 0 0 214-29 276 276 0 0 1 69-10c105 0 115 91 118 126a765 765 0 0 1 5 79c0 18 0 35 3 52a345 345 0 0 0 8 54 81 81 0 0 1 76 87v0z m-731-357c2 0 0-9 0-9a210 210 0 0 0-36-3c-69 0-86 46-94 108-4 26-6 54-8 85a357 357 0 0 1-26 105 51 51 0 0 0-22-4 57 57 0 0 0-54 59v2h0v0h0a55 55 0 0 0 28 48 53 53 0 0 0 23 5 83 83 0 0 0 65-44 182 182 0 0 0 20-45h0l8-28c4-16 14-61 19-94l0-4 1-3v0c17-78 34-155 76-178z m702 355a53 53 0 0 0-52-59 47 47 0 0 0-16 3 325 325 0 0 1-18-83c-3-45-1-92-7-130-6-57-24-100-89-100a250 250 0 0 0-62 9 884 884 0 0 1-222 31 413 413 0 0 1-137-18s-2 6 0 8c17 27 99 55 211 55h19c64-3 115-17 151-17s64 16 74 81l0 3a79 79 0 0 0-61-36 43 43 0 0 0-39 23 127 127 0 0 0-12 36c-14-29-36-60-70-60h-3c-22 0-28 16-28 49q0 5 0 9c0 24 1 68 2 91v2c0 22-7 31-15 31s-22-16-25-40c0-3 0-11 0-20 0-35 0-88 0-111 0-5 0-9 0-12a176 176 0 0 0-27-2c-36 0-45 17-45 48-18-49-43-70-64-70s-34 16-41 43c-10-40-36-79-61-79-33 0-65 40-112 148l-3 15c-5 30-11 59-18 86 160 36 308 121 324 200a67 67 0 0 0-23-8l-9 0a68 68 0 0 0-66 59 94 94 0 0 0 0 18h0a67 67 0 0 0 57 66l7 0a84 84 0 0 0 74-73 152 152 0 0 0 3-31q0-2 0-4v0c-2-86-70-194-279-278 34-62 60-108 80-108a14 14 0 0 1 6 0c12 5 13 30 9 124a422 422 0 0 0 75 41c0-97 7-139 21-139a11 11 0 0 1 6 2c15 10 15 43 15 94 0 40-2 64-2 64a86 86 0 0 0 49 30c14 0 23-14 23-42 14 50 38 72 61 72 28 0 51-34 48-91-5-101-2-121 7-121s21 22 20 44h0c-2 57 12 122 46 150a65 65 0 0 0 42 18 77 77 0 0 0 59-37l3 4c21 47 46 63 67 63a50 50 0 0 0 31-12 52 52 0 0 0 16-37v0z m-279-267a109 109 0 0 1 14 12 73 73 0 0 1 18-17l-31 5z m-335 88c-8 13-18 29-28 48l28 12c0-21 0-43 0-60z m456 143a29 29 0 0 1-19-9 135 135 0 0 1-25-84 109 109 0 0 1 6-36 25 25 0 0 1 24-15 35 35 0 0 1 32 23 886 886 0 0 0 11 100 37 37 0 0 1-29 20z m-9-107v-6a7 7 0 0 0-3-2 109 109 0 0 0-2 23 127 127 0 0 0 12 59c-4-25-6-51-8-76z" horiz-adv-x="1000" />
<glyph glyph-name="defold" unicode="&#xe859;" d="M836 561v190l-172 99-164-95-164 95-171-99v-190l-165-94v-200l164-95v-189l171-99 164 94 164-94 171 99v190l166 95v200z m-189-99l-147-86-147 86 147 86z m-302-15l147-85v-169l-147 86z m138 299l-138-80v160z m-458-473l139 80v-160z m639-369l-164 95-164-95-147 86 164 94-164 94 147 86 164-95 163 95 147-86-163-95 163-94z m155 99l-138 80 138 81v-160z m17 190v160l139-80z m147 94l-164 95v-189l-146 86v188l-164 94v170l163-94v189l147-86v-189l164-95v-169z" horiz-adv-x="1000" />
<glyph glyph-name="search" unicode="&#xe86a;" d="M643 386q0 103-74 176t-176 74-177-74-73-176 73-177 177-73 176 73 74 177z m286-465q0-29-22-50t-50-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 152-31 126-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
<glyph glyph-name="link-ext" unicode="&#xe86f;" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-26t-25-10-25 10l-98 99-364-364q-5-6-13-6t-13 6l-63 63q-6 6-6 13t6 13l364 364-99 98q-10 11-10 25t10 25 26 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
<glyph glyph-name="link" unicode="&#xe870;" d="M779 400l-57-100 59-100-116-200h-231l-115 200 173 300h-115l-174-300 174-300h346l173 300z m-632-100l58 100-59 100 115 200h231l116-200-174-300h116l174 300-174 300h-346l-174-300z" horiz-adv-x="1000" />
<glyph glyph-name="attention" unicode="&#xe871;" d="M571 83v106q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-106q0-8 5-13t12-6h108q7 0 12 6t5 13z m-1 208l10 257q0 6-5 10-7 6-14 6h-122q-6 0-14-6-5-4-5-12l9-255q0-5 6-9t13-3h103q8 0 14 3t5 9z m-7 522l428-786q20-35-1-70-9-17-26-26t-35-10h-858q-18 0-35 10t-26 26q-21 35-1 70l429 786q9 17 26 27t36 10 36-10 27-27z" horiz-adv-x="1000" />
<glyph glyph-name="amazon" unicode="&#xe872;" d="M598 219c-7-7-14-13-20-19a217 217 0 0 0-173-57 160 160 0 0 0-96 37 149 149 0 0 0-51 103c-7 68 11 128 67 172a220 220 0 0 0 101 44c47 7 93 13 140 19 7 1 10 3 10 11 0 11 0 23 0 35-2 50-36 78-86 71-39-6-65-25-74-64a21 21 0 0 0-27-18l-94 10a20 20 0 0 0-20 27 176 176 0 0 0 107 132 319 319 0 0 0 249 5 142 142 0 0 0 97-136c2-78 2-155 1-233a146 146 0 0 1 33-97c21-26 20-29-6-50-21-19-43-37-65-56a25 25 0 0 0-38 0 338 338 0 0 0-30 30c-9 10-16 22-25 34z m-22 174c0 10 0 21 0 31 0 5-2 8-8 8a273 273 0 0 1-86-12c-62-22-74-77-62-121a61 61 0 0 1 75-47c37 8 57 34 70 66a193 193 0 0 1 11 75z m-84-443a712 712 0 0 0-467 174c-5 5-13 10-8 17s13 2 19-1a972 972 0 0 1 393-118 945 945 0 0 1 437 66 46 46 0 0 0 18 5 16 16 0 0 0 12-7 18 18 0 0 0-2-14 31 31 0 0 0-10-10 673 673 0 0 0-250-99 760 760 0 0 0-142-13z m389 177c-25-2-45-4-64-6a41 41 0 0 0-13 4c3 4 5 9 8 11a163 163 0 0 0 67 24 218 218 0 0 0 85-2c17-5 21-9 20-26a197 197 0 0 0-57-134 56 56 0 0 0-18-8 109 109 0 0 0 3 18c8 27 17 54 26 82a34 34 0 0 1 1 8c1 16-3 23-19 25s-29 3-39 4z" horiz-adv-x="1000" />
<glyph glyph-name="android" unicode="&#xe873;" d="M386-134c-3 1-7 1-11 3a53 53 0 0 0-36 51q0 64 0 128c0 6 0 6-6 6h-47a54 54 0 0 0-54 46 64 64 0 0 0 0 11q0 197 0 395c0 6 0 6 6 6h525c6 0 6 0 6-6 0-132 0-264 0-396a55 55 0 0 0-43-55 76 76 0 0 0-13-1h-46c-6 0-6 0-6-5 0-42 0-85 0-127a55 55 0 0 0-46-55l-2 0h-13l-1 0a55 55 0 0 0-47 57c1 41 0 82 0 123 0 7 0 7-7 7h-92c-8 0-7 1-7-7 0-42 0-84 0-126a52 52 0 0 0-32-51 110 110 0 0 0-16-4z m280 968a64 64 0 0 0 13-6 27 27 0 0 0 4-37c-10-14-19-28-28-43l-3-4 4-3a227 227 0 0 0 88-91 222 222 0 0 0 24-81c0-3 0-5-4-4h-526c-7 0-7 0-6 7a218 218 0 0 0 52 120 230 230 0 0 0 59 49c4 2 4 2 1 7-9 15-18 29-28 44a27 27 0 0 0 9 38 58 58 0 0 0 10 4h7a33 33 0 0 0 23-17c10-16 20-31 31-48 1-2 2-3 5-3a320 320 0 0 0 75 14 349 349 0 0 0 122-13 5 5 0 0 1 6 2c11 16 21 32 32 47a38 38 0 0 0 22 18z m-58-150a27 27 0 1 1 26-27 27 27 0 0 1-26 27z m-216-54a27 27 0 1 1-26 27 27 27 0 0 1 26-27z m-322-293c0 40 0 80 0 120a54 54 0 0 0 107 2c0-18 0-37 0-56q0-93 0-187a54 54 0 0 0-107-3c0 41 0 82 0 123z m861 0c0-41 0-81 0-121a53 53 0 0 0-46-53 54 54 0 0 0-62 50c0 3 0 6 0 9q0 117 0 234a54 54 0 0 0 107 3c0-41 0-82 0-123z" horiz-adv-x="1000" />
<glyph glyph-name="html5" unicode="&#xe875;" d="M502-134h-5a29 29 0 0 1-3 1l-339 96-3 1a4 4 0 0 0 0 1l-4 43-4 47-5 48-3 37-4 47-4 38-4 47-3 38-5 46-3 38-4 47-3 38-5 47-4 46-4 47-3 38-4 46-4 39-4 48c-1 12-2 24-3 36a22 22 0 0 1-1 5v3h853v-3c0-2 0-3 0-4s0-6 0-9q-1-14-3-29l-4-37-4-47-5-46-4-47-3-37-4-47-4-46-5-47-4-46q-2-23-4-47c-1-16-2-31-4-47-1-18-3-37-5-55s-3-37-5-55c-2-21-4-42-6-63s-3-43-5-64q-4-42-8-84c0-3 0-6-1-8l-3-2-333-93z m-269 790c0-1 0-2 0-3 1-12 1-23 2-35 2-18 4-36 5-54 2-16 3-31 4-47 2-18 4-36 5-55s4-36 5-54q4-37 7-73c0-1 0-2 0-3h371l-13-138-4-1-113-31a11 11 0 0 0-6 0l-112 32-4 1-7 85h-106l13-168 4-1 210-58a20 20 0 0 1 11 0l190 52 24 6c0 2 0 4 0 5q1 10 2 20 2 24 4 48c1 15 3 31 4 47s3 31 4 47 3 32 4 47c2 19 4 38 5 56 2 16 3 33 5 49 0 3 0 6 1 9h-389c-1 9-9 107-8 110h407l1 9c2 18 4 35 5 52 2 13 3 27 4 41a26 26 0 0 1 0 3z" horiz-adv-x="1000" />
<glyph glyph-name="ios" unicode="&#xe876;" d="M669 821a182 182 0 0 0 0-45 206 206 0 0 0-76-133 155 155 0 0 0-110-38 132 132 0 0 0 1 48 209 209 0 0 0 91 135 212 212 0 0 0 82 31 98 98 0 0 0 12 2z m197-690c-11-24-20-49-31-73a596 596 0 0 0-74-113 188 188 0 0 0-52-47 118 118 0 0 0-102-6c-11 4-22 8-31 13a186 186 0 0 1-157-4 200 200 0 0 0-63-18 104 104 0 0 0-78 27 442 442 0 0 0-83 100 547 547 0 0 0-62 128 503 503 0 0 0-29 220 273 273 0 0 0 59 148 221 221 0 0 0 179 85 188 188 0 0 0 61-13c17-6 34-12 51-20a98 98 0 0 1 79 4 382 382 0 0 0 94 30 231 231 0 0 0 132-22 205 205 0 0 0 77-62c1-2 2-4 4-7-67-47-106-108-100-192s51-140 126-178z" horiz-adv-x="1000" />
<glyph glyph-name="linux" unicode="&#xe877;" d="M674-134a72 72 0 0 0-48 38 13 13 0 0 1-8 5 465 465 0 0 1-166 5c-14-2-29-4-42-7a19 19 0 0 1-11-8 56 56 0 0 0-56-26 455 455 0 0 0-59 14c-34 10-68 21-102 31-20 5-41 8-63 12a57 57 0 0 0-13 6 27 27 0 0 0-16 38c3 10 6 20 9 30a67 67 0 0 1 0 41 106 106 0 0 0-5 21 31 31 0 0 0 26 36c13 4 26 7 39 11a66 66 0 0 1 38 33 206 206 0 0 0 19 24 11 11 0 0 1 3 8 94 94 0 0 0 8 63c14 31 26 62 37 95a339 339 0 0 0 54 97c11 14 21 30 33 43a90 90 0 0 1 21 65c-1 44-5 87-5 130a363 363 0 0 0 6 72 98 98 0 0 0 58 72 163 163 0 0 0 142 6 133 133 0 0 0 68-73 323 323 0 0 0 22-115c1-21 2-42 4-62a157 157 0 0 1 29-76q52-78 103-156a215 215 0 0 0 27-177 14 14 0 0 1 3-11 73 73 0 0 0 18-49 53 53 0 0 1 27-49 156 156 0 0 1 15-8c29-13 32-42 6-61a226 226 0 0 0-20-12l-113-68a38 38 0 0 1-7-5 107 107 0 0 0-57-33z m-300 152c12-14 24-28 36-41a19 19 0 0 1 11-6c25 0 49 0 74 0a189 189 0 0 1 141 73 24 24 0 0 1 5 14c2 28 3 57 4 86a16 16 0 0 0 16 17 214 214 0 0 0 23 0 44 44 0 0 1 2 5 269 269 0 0 1-4 139 1586 1586 0 0 1-91 219c-11 22-13 22-34 11s-47-25-70-39a49 49 0 0 0-60 5 168 168 0 0 0-17 14c-5 6-10 12-15 18a163 163 0 0 0-30-94 231 231 0 0 1-21-38c-12-29-22-60-36-88a242 242 0 0 1-27-86c-3-33 2-64 30-86 4-4 7-8 12-11 19-16 39-31 57-47a71 71 0 0 0 19-24c7-17-2-31-25-41z m6-66a109 109 0 0 1-9 23c-12 18-26 35-38 53-16 25-31 51-47 76a223 223 0 0 1-37 40c-9 8-18 5-25-6-5-8-9-17-14-25a38 38 0 0 0-26-22c-13-3-26-5-38-9-18-5-21-10-18-29a37 37 0 0 1 0-3 88 88 0 0 0-3-52 139 139 0 0 1-5-23c-2-13 1-17 13-21a162 162 0 0 1 19-4 468 468 0 0 0 119-31 281 281 0 0 1 58-18c24-4 51 20 52 51z m311 179c-14 2-18-1-18-12a473 473 0 0 0 2-53c-3-33-8-65-13-98a73 73 0 0 1 6-52 31 31 0 0 1 42-14 140 140 0 0 1 36 22 283 283 0 0 0 62 45c16 8 30 16 45 23a213 213 0 0 1 21 13c8 6 9 12 0 19a114 114 0 0 1-21 14 52 52 0 0 0-29 36 223 223 0 0 0-3 26 26 26 0 0 1-10 20c-1 0-1 0-2-1a31 31 0 0 1-2-4 77 77 0 0 0-59-42c-34-7-55 9-57 44 0 4 0 9 0 14z m-219 479a44 44 0 0 1-12-4c-15-11-31-23-47-35-11-9-12-14-2-24a422 422 0 0 1 34-27 31 31 0 0 1 39-2c20 12 41 23 62 36a97 97 0 0 1 16 14l-16 15-1 0-64 24a73 73 0 0 1-9 3z m43 10a183 183 0 0 0 2 30 22 22 0 0 0 22 18 24 24 0 0 0 21-18 38 38 0 0 0-11-44c8-7 13-5 20 1a67 67 0 0 1 3 85 41 41 0 0 1-68-3 63 63 0 0 1-11-46c2-18 5-21 22-24z m-80-1c7 4 12 7 17 10a7 7 0 0 1 3 4 64 64 0 0 1-13 54 28 28 0 0 1-46 0 71 71 0 0 1 0-83c11-13 13-13 24 1-18 3-26 30-21 45 2 8 6 16 14 15a28 28 0 0 0 17-10 42 42 0 0 0 5-37z" horiz-adv-x="1000" />
<glyph glyph-name="windows" unicode="&#xe878;" d="M17 695l394 57v-379h-394z m0-690l394-57v379h-394z m441 754v-386h525v461z m0-818l525-75v461h-525z" horiz-adv-x="1000" />
<glyph glyph-name="macos" unicode="&#xe87a;" d="M287 459a23 23 0 0 1 23 24v67a23 23 0 0 1-47 0v-67a23 23 0 0 1 24-24z m-49-230a23 23 0 1 1-35-31c65-75 176-120 297-120a487 487 0 0 1 68 5c-2 15-3 31-4 47a413 413 0 0 0-64-5c-108 1-206 39-262 104z m681 578h-838a67 67 0 0 1-66-66v-782a67 67 0 0 1 66-67h838a67 67 0 0 1 67 67v782a67 67 0 0 1-67 66z m-838-890a43 43 0 0 0-43 42v782a43 43 0 0 0 43 43h435a1383 1383 0 0 1-90-495 8 8 0 0 1 9-8l115 0a11 11 0 0 0 12-12c0-13 0-27 0-41q0-50 3-98c81 12 152 47 196 99a23 23 0 1 0 36-31c-52-61-135-102-228-116a1395 1395 0 0 1 25-167z m651 633v-67a23 23 0 0 0-47 0v67a23 23 0 0 0 47 0z" horiz-adv-x="1000" />
<glyph glyph-name="clock" unicode="&#xe87b;" d="M616 260a31 31 0 0 0-44-44l-88 88c-5-2-10-4-15-4-35 0-63 29-63 63 0 23 13 42 32 53v197a31 31 0 1 0 62 0v-197c18-11 31-30 31-53 0-5-1-10-3-15l88-88z m-147-303a407 407 0 0 0-406 406c0 224 182 406 406 406 224 0 406-182 406-406 0-224-182-406-406-406z m0 875c-259 0-469-211-469-469 0-258 210-469 469-469 258 0 469 211 469 469 0 258-211 469-469 469z" horiz-adv-x="1000" />
<glyph glyph-name="star" unicode="&#xe87c;" d="M727 224l7-242-7-4-227 81-227-81-7 4 7 242-147 191-25-19 27 26 232 68 136 199-26 18 34-18 136-199 232-68 2-7-147-191z m207 214a67 67 0 0 1-45 44l-210 62-124 181c-25 36-85 36-110 0l-124-181-210-62a67 67 0 0 1-34-105l133-174-6-219a67 67 0 0 1 90-65l206 73 206-73a67 67 0 0 1 90 65l-6 219 134 174c13 17 17 40 10 61z" horiz-adv-x="1000" />
<glyph glyph-name="googleplay" unicode="&#xe87d;" d="M247-53l383 217-82 91-301-308z m-140 797c-4-8-7-17-7-27v-757c0-14 5-26 13-35l382 390-388 429z m767-368l-134 75-136-139 98-108 172 97c16 8 24 23 26 38-2 15-10 29-26 37z m-206 116l-461 260 344-380 117 120z" horiz-adv-x="1000" />
<glyph glyph-name="dropbox" unicode="&#xe87f;" d="M305 805l-285-178 196-164 284 185-195 157z m368-755c-5 0-10 2-15 6l-158 131-158-131c-4-4-10-6-15-6-4 0-8 2-12 4l-117 77v-46l302-190 302 191v45l-117-77c-4-2-8-4-12-4z m307 577l-285 178-195-157 284-185 196 164z m-480-342l176-146 283 184-175 140-284-178z m-176-146l176 146-284 178-174-139 282-185z" horiz-adv-x="1000" />
<glyph glyph-name="twitter" unicode="&#xe881;" d="M866 538c0-8 0-17 0-25 0-249-189-537-537-537-106 0-205 32-289 85 15-2 30-3 45-3 89 0 170 31 234 81-82 2-152 56-176 131 12-2 23-3 36-3 17 0 34 2 49 6-86 18-151 94-151 186 0 0 0 1 0 2 25-14 55-23 85-24-50 34-84 92-84 157 0 35 10 67 26 95 93-114 232-189 389-197-3 14-5 28-5 43 0 104 85 189 189 189 54 0 103-23 138-60 43 9 83 25 120 46-14-44-44-81-83-104 38 4 74 14 108 29-25-38-57-71-94-97z" horiz-adv-x="1000" />
<glyph glyph-name="slack" unicode="&#xe883;" d="M412 392l43-129 133 45-43 128-133-45v1z m372-128l-64-21 22-67c9-27-6-57-33-66-6-2-12-3-18-3-21 1-40 15-48 36l-22 67-134-45 22-67c9-27-5-57-33-66-6-2-12-2-18-2-21 0-41 14-48 35l-23 68-65-22c-6-2-11-3-18-3-21 1-40 15-48 36-9 27 6 57 33 66l65 21-41 129-65-22c-6-2-12-3-18-2-21 0-41 14-47 35-9 27 5 56 33 65l65 22-23 67c-9 27 6 57 33 66 28 9 57-6 66-33l22-67 134 45-23 67c-9 26 6 56 33 65 27 9 57-5 66-33l22-67 65 21c27 9 56-6 65-33 10-27-5-56-32-65l-65-22 43-129 65 22c27 9 56-6 65-33 9-28-5-57-32-66l-1 1z m174 224c-103 343-252 423-595 320-344-103-424-252-321-595 103-344 252-424 596-321 343 103 423 252 320 596z" horiz-adv-x="1000" />
<glyph glyph-name="instagram" unicode="&#xe884;" d="M500 850c-136 0-153-1-206-3-53-2-90-11-121-23-33-13-61-30-89-58s-45-56-58-88c-12-32-20-69-23-122-2-53-3-70-3-206s1-153 3-206c3-53 11-90 23-121 13-33 30-61 58-89 28-28 56-45 89-58 31-12 68-21 121-23 53-3 70-3 206-3s153 1 206 3c53 2 90 11 122 23 32 13 60 30 88 58 28 28 45 55 58 89 12 31 21 68 23 121 3 53 3 70 3 206s-1 153-3 206c-2 53-11 90-23 122-13 32-30 60-58 88-28 28-55 45-88 58-32 12-69 21-122 23-53 3-70 3-206 3z m0-90c134 0 149-1 202-3 49-2 75-10 93-17 23-9 40-20 58-38 17-17 28-34 37-57 7-18 15-44 17-93 2-53 3-68 3-202s-1-149-3-202c-3-49-11-75-18-93-9-23-20-40-37-58-18-17-34-28-58-37-17-7-44-15-93-17-53-2-68-3-202-3-134 0-150 1-203 3-48 3-75 11-93 18-24 9-40 20-57 37-18 18-29 35-38 58-7 17-15 44-17 93-2 52-3 69-3 202 0 133 1 149 3 202 2 49 10 76 17 93 9 24 20 40 38 58 17 17 33 29 57 37 18 7 44 15 93 18 53 2 68 2 202 2l2-1z m0-153c-142 0-257-115-257-257 0-142 115-257 257-257 142 0 257 115 257 257 0 142-115 257-257 257z m0-424c-92 0-167 75-167 167s75 167 167 167 167-75 167-167-75-167-167-167z m327 434c0-33-27-60-60-60-33 0-60 27-60 60 0 33 27 60 60 60 33 0 60-27 60-60z" horiz-adv-x="1000" />
<glyph glyph-name="steam" unicode="&#xe885;" d="M499 850c-262 0-478-202-498-460l268-111c23 16 50 25 80 25 2 0 5 0 7 0l120 172v3c0 104 84 188 188 188 104 0 189-84 189-188s-85-189-189-189h-4l-170-121c0-2 0-5 0-7 0-78-63-141-141-141-68 0-126 49-139 113l-192 80c60-210 252-364 481-364 276 0 500 224 500 500s-224 500-500 500z m-185-759l-61 26c11-23 30-42 55-52 54-23 116 3 138 57 11 26 11 55 1 81s-32 47-58 58c-26 11-53 10-78 1l63-26c40-17 59-63 43-103-17-39-63-58-103-42h0z m476 388c0 69-57 126-126 126-69 0-125-57-125-126 0-69 56-126 125-126 70 0 126 57 126 126z m-220 0c0 52 42 95 95 95 52 0 94-43 94-95 0-52-42-94-94-94-53 0-95 42-95 94z" horiz-adv-x="1000" />
<glyph glyph-name="github" unicode="&#xe886;" d="M500 838c-276 0-500-224-500-500 0-221 143-409 342-475 25-5 34 11 34 24 0 12 0 44-1 85-139-30-168 67-168 67-23 58-56 74-56 74-45 31 4 30 4 30 50-4 77-52 77-52 44-76 117-54 145-41 5 32 18 54 32 67-111 12-228 55-228 247 0 54 19 99 52 134-6 13-23 63 4 132 0 0 42 14 137-51 40 11 83 17 125 17 43 0 85-6 125-17 95 65 137 51 137 51 27-69 10-119 5-132 32-35 52-80 52-134 0-192-117-235-229-247 18-15 34-46 34-92 0-67 0-121 0-137 0-14 8-29 34-24 200 65 343 253 343 474 0 276-224 500-500 500" horiz-adv-x="1000" />
<glyph glyph-name="facebook" unicode="&#xe887;" d="M945 850h-890c-30 0-55-25-55-55v-890c0-30 25-55 55-55h479v387h-130v151h130v112c0 129 79 199 194 199 55 0 103-4 117-6v-135h-80c-63 0-75-30-75-74v-96h149l-19-151h-130v-387h255c30 0 55 25 55 55v890c0 30-25 55-55 55" horiz-adv-x="1000" />
</font>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -1,121 +1,23 @@
---
layout: default
---
## Modules
{% for item in site.data.api %}
### <code>{{ item.name }}</code>
{% include description.md desc=item.desc %}
{% endfor %}
<hr>
# Defold In-app purchase extension API documentation
## Enums
<table>
<tbody>
{% for module in site.data.api %}
{% for item in module.members %}
{% if item.type contains 'number' %}
<tr>
<td><strong>{{ module.name }}.{{ item.name }}</strong></td>
<td>{{ item.desc | markdownify | replace: "[icon:attention]","<br><br>⚠️"}}</td>
</tr>
This extension provides functions for making in-app purchases. Supported on iOS, Android (Google Play and Amazon) and Facebook Canvas.
{% endif %}
{% endfor %}
{% endfor %}
</tbody>
</table>
# Usage
To use this library in your Defold project, add the following URL to your <code class="inline-code-block">game.project</code> dependencies:
<hr>
https://github.com/defold/extension-iap/archive/master.zip
## Functions
<table>
<tbody>
{% for module in site.data.api %}
{% for item in module.members %}
{% if item.type contains 'function' %}
<tr>
<td><a href="#{{ item.name | url_encode }}"><strong>{{ module.name }}.{{ item.name }}()</strong></a></td>
<td>{% include description.md desc=item.desc %}</td>
</tr>
{% endif %}
{% endfor %}
{% endfor %}
</tbody>
</table>
We recommend using a link to a zip file of a [specific release](https://github.com/defold/extension-iap/releases).
{% for module in site.data.api %}
{% for function in module.members %}
{% if function.type contains 'function' %}
<div class="function-wrap">
<h3 class="function-header"><a href="#{{ function.name | url_encode }}" id="{{ function.name | url_encode }}"><code>{{ module.name }}.{{ function.name }}({% for param in function.parameters %}{{param.name}}{% unless forloop.last %}, {% endunless %}{% endfor %})</code></a></h3>
{% include description.md desc=function.desc %}
{% if function.parameters %}
<table>
<thead>
<tr>
<th>Parameter</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% for param in function.parameters %}
<tr>
<td style="text-align: right;">
<strong>{{ param.name }}</strong>
{% if param.optional %}
(optional)
{% endif %}
</td>
<td><code>{{ param.type }}</code></td>
<td>{% include description.md desc=param.desc %}
{% if param.type == "function" %}
{% include type-function.md params=param.parameters %}
{% endif %}
{% if param.type == "table" %}
{% include type-table.md fields=param.members %}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if function.returns %}
<table>
<thead>
<tr>
<th>Return value</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<h4>Returns</h4>
{% for return in function.returns %}
<tr>
<td>{{ return.name }}</td>
<td><code class="inline-code-block">{{ return.type }}</code></td>
<td>{% include description.md desc=return.desc %}
{% if return.type == "table" %}
{% include type-table.md fields=return.members %}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
## Source code
{% if function.examples %}
<h4>Examples</h4>
{% for example in function.examples %}
{{ example.desc | markdownify }}
{% endfor %}
{% endif %}
</div>
The source code is available on [GitHub](https://github.com/defold/extension-iap)
{% endif %}
{% endfor %}
{% endfor %}
# API reference
{% include api_ref.md %}

View File

@@ -9,7 +9,7 @@
- name: buy
type: function
desc: Sets the listener function for inter-app communication events.
desc: Sets the listener function for In-app purchase events.
parameters:
- name: id
type: string
@@ -296,4 +296,4 @@
type: number
desc: transaction unverified state, requires verification of purchase

View File

@@ -31,7 +31,7 @@ var LibraryFacebookIAP = {
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.status == 200) {
var xmlDoc = document.createElement( 'html' );
@@ -72,11 +72,11 @@ 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_state, res_buf]);
Runtime.dynCall('vii', callback, [lua_callback, res_buf]);
} else {
var xmlhttp = new XMLHttpRequest();
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.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_count = product_ids.length;
if(product_count == 0) {
@@ -96,7 +96,7 @@ var LibraryFacebookIAP = {
products = {};
var xmlhttp = new XMLHttpRequest();
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
xmlhttp.open("GET", product_ids[0], true);
@@ -104,7 +104,7 @@ var LibraryFacebookIAP = {
},
// 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 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_state, res_buf, 0]);
Runtime.dynCall('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_state, 0, reason]);
Runtime.dynCall('viii', callback, [lua_callback, 0, reason]);
}
}
);

View File

@@ -9,22 +9,6 @@
#define LIB_NAME "iap"
struct IAP;
#define CMD_PRODUCT_RESULT (0)
#define CMD_PURCHASE_RESULT (1)
struct DM_ALIGNED(16) IAPCommand
{
IAPCommand()
{
memset(this, 0, sizeof(IAPCommand));
}
uint32_t m_Command;
int32_t m_ResponseCode;
void* m_Data1;
};
static JNIEnv* Attach()
{
JNIEnv* env;
@@ -43,64 +27,38 @@ struct IAP
IAP()
{
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_ProviderId = PROVIDER_ID_GOOGLE;
}
int m_InitCount;
int m_Callback;
int m_Self;
bool m_autoFinishTransactions;
int m_ProviderId;
lua_State* m_L;
IAPListener m_Listener;
int m_InitCount;
bool m_autoFinishTransactions;
int m_ProviderId;
jobject m_IAP;
jobject m_IAPJNI;
jmethodID m_List;
jmethodID m_Stop;
jmethodID m_Buy;
jmethodID m_Restore;
jmethodID m_ProcessPendingConsumables;
jmethodID m_FinishTransaction;
dmScript::LuaCallbackInfo* m_Listener;
dmArray<IAPCommand> m_CommandsQueue;
dmMutex::HMutex m_Mutex;
jobject m_IAP;
jobject m_IAPJNI;
jmethodID m_List;
jmethodID m_Stop;
jmethodID m_Buy;
jmethodID m_Restore;
jmethodID m_ProcessPendingConsumables;
jmethodID m_FinishTransaction;
IAPCommandQueue m_CommandQueue;
};
static IAP g_IAP;
static void QueueCommand(IAPCommand* cmd)
static int IAP_ProcessPendingTransactions(lua_State* L)
{
DM_MUTEX_SCOPED_LOCK(g_IAP.m_Mutex);
if(g_IAP.m_CommandsQueue.Full())
{
g_IAP.m_CommandsQueue.OffsetCapacity(2);
}
g_IAP.m_CommandsQueue.Push(*cmd);
}
static void VerifyCallback(lua_State* L)
{
if (g_IAP.m_Callback != LUA_NOREF) {
dmLogError("Unexpected callback set");
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Callback);
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Self);
g_IAP.m_Callback = LUA_NOREF;
g_IAP.m_Self = LUA_NOREF;
g_IAP.m_L = 0;
}
//todo handle pending transactions if there is such thing on Android
return 0;
}
static int IAP_List(lua_State* L)
{
int top = lua_gettop(L);
VerifyCallback(L);
char* buf = IAP_List_CreateBuffer(L);
if( buf == 0 )
{
@@ -108,18 +66,13 @@ static int IAP_List(lua_State* L)
return 0;
}
luaL_checktype(L, 2, LUA_TFUNCTION);
lua_pushvalue(L, 2);
g_IAP.m_Callback = dmScript::Ref(L, LUA_REGISTRYINDEX);
dmScript::GetInstance(L);
g_IAP.m_Self = dmScript::Ref(L, LUA_REGISTRYINDEX);
g_IAP.m_L = dmScript::GetMainThread(L);
JNIEnv* env = Attach();
IAPCommand* cmd = new IAPCommand;
cmd->m_Callback = dmScript::CreateCallback(L, 2);
cmd->m_Command = IAP_PRODUCT_RESULT;
jstring products = env->NewStringUTF(buf);
env->CallVoidMethod(g_IAP.m_IAP, g_IAP.m_List, products, g_IAP.m_IAPJNI);
env->CallVoidMethod(g_IAP.m_IAP, g_IAP.m_List, products, g_IAP.m_IAPJNI, (jlong)cmd);
env->DeleteLocalRef(products);
Detach();
@@ -209,22 +162,13 @@ static int IAP_Restore(lua_State* L)
static int IAP_SetListener(lua_State* L)
{
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;
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;
}
bool had_previous = iap->m_Listener != 0;
iap->m_Listener.m_L = dmScript::GetMainThread(L);
iap->m_Listener.m_Callback = cb;
if (iap->m_Listener)
dmScript::DestroyCallback(iap->m_Listener);
dmScript::GetInstance(L);
iap->m_Listener.m_Self = dmScript::Ref(L, LUA_REGISTRYINDEX);
iap->m_Listener = dmScript::CreateCallback(L, 1);
// On first set listener, trigger process old ones.
if (!had_previous) {
@@ -249,6 +193,7 @@ static const luaL_reg IAP_methods[] =
{"restore", IAP_Restore},
{"set_listener", IAP_SetListener},
{"get_provider_id", IAP_GetProviderId},
{"process_pending_transactions", IAP_ProcessPendingTransactions},
{0, 0}
};
@@ -258,7 +203,7 @@ extern "C" {
#endif
JNIEXPORT void JNICALL Java_com_defold_iap_IapJNI_onProductsResult__ILjava_lang_String_2(JNIEnv* env, jobject, jint responseCode, jstring productList)
JNIEXPORT void JNICALL Java_com_defold_iap_IapJNI_onProductsResult(JNIEnv* env, jobject, jint responseCode, jstring productList, jlong cmdHandle)
{
const char* pl = 0;
if (productList)
@@ -266,15 +211,14 @@ JNIEXPORT void JNICALL Java_com_defold_iap_IapJNI_onProductsResult__ILjava_lang_
pl = env->GetStringUTFChars(productList, 0);
}
IAPCommand cmd;
cmd.m_Command = CMD_PRODUCT_RESULT;
cmd.m_ResponseCode = responseCode;
IAPCommand* cmd = (IAPCommand*)cmdHandle;
cmd->m_ResponseCode = responseCode;
if (pl)
{
cmd.m_Data1 = strdup(pl);
cmd->m_Data = strdup(pl);
env->ReleaseStringUTFChars(productList, pl);
}
QueueCommand(&cmd);
IAP_Queue_Push(&g_IAP.m_CommandQueue, cmd);
}
JNIEXPORT void JNICALL Java_com_defold_iap_IapJNI_onPurchaseResult__ILjava_lang_String_2(JNIEnv* env, jobject, jint responseCode, jstring purchaseData)
@@ -286,14 +230,15 @@ JNIEXPORT void JNICALL Java_com_defold_iap_IapJNI_onPurchaseResult__ILjava_lang_
}
IAPCommand cmd;
cmd.m_Command = CMD_PURCHASE_RESULT;
cmd.m_Callback = g_IAP.m_Listener;
cmd.m_Command = IAP_PURCHASE_RESULT;
cmd.m_ResponseCode = responseCode;
if (pd)
{
cmd.m_Data1 = strdup(pd);
cmd.m_Data = strdup(pd);
env->ReleaseStringUTFChars(purchaseData, pd);
}
QueueCommand(&cmd);
IAP_Queue_Push(&g_IAP.m_CommandQueue, &cmd);
}
#ifdef __cplusplus
@@ -302,32 +247,24 @@ JNIEXPORT void JNICALL Java_com_defold_iap_IapJNI_onPurchaseResult__ILjava_lang_
static void HandleProductResult(const IAPCommand* cmd)
{
lua_State* L = g_IAP.m_L;
int top = lua_gettop(L);
if (g_IAP.m_Callback == LUA_NOREF) {
dmLogError("No callback set");
if (cmd->m_Callback == 0)
{
dmLogWarning("Received product list but no listener was set!");
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
lua_rawgeti(L, LUA_REGISTRYINDEX, g_IAP.m_Self);
lua_pushvalue(L, -1);
dmScript::SetInstance(L);
if (!dmScript::IsInstanceValid(L))
if (!dmScript::SetupCallback(cmd->m_Callback))
{
dmLogError("Could not run IAP callback because the instance has been deleted.");
lua_pop(L, 2);
assert(top == lua_gettop(L));
return;
}
if (cmd->m_ResponseCode == BILLING_RESPONSE_RESULT_OK) {
dmJson::Document doc;
dmJson::Result r = dmJson::Parse((const char*) cmd->m_Data1, &doc);
dmJson::Result r = dmJson::Parse((const char*) cmd->m_Data, &doc);
if (r == dmJson::RESULT_OK && doc.m_NodeCount > 0) {
char err_str[128];
if (dmScript::JsonToLua(L, &doc, 0, err_str, sizeof(err_str)) < 0) {
@@ -349,50 +286,35 @@ static void HandleProductResult(const IAPCommand* cmd)
IAP_PushError(L, "failed to fetch product", REASON_UNSPECIFIED);
}
int ret = lua_pcall(L, 3, 0, 0);
if (ret != 0) {
dmLogError("Error running callback: %s", lua_tostring(L, -1));
lua_pop(L, 1);
}
dmScript::PCall(L, 3, 0);
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;
dmScript::TeardownCallback(cmd->m_Callback);
dmScript::DestroyCallback(cmd->m_Callback);
assert(top == lua_gettop(L));
}
static void HandlePurchaseResult(const IAPCommand* cmd)
{
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");
if (cmd->m_Callback == 0)
{
dmLogWarning("Received purchase result but no listener was set!");
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);
// Setup self
lua_rawgeti(L, LUA_REGISTRYINDEX, g_IAP.m_Listener.m_Self);
lua_pushvalue(L, -1);
dmScript::SetInstance(L);
if (!dmScript::IsInstanceValid(L))
if (!dmScript::SetupCallback(cmd->m_Callback))
{
dmLogError("Could not run IAP callback because the instance has been deleted.");
lua_pop(L, 2);
assert(top == lua_gettop(L));
return;
}
if (cmd->m_ResponseCode == BILLING_RESPONSE_RESULT_OK) {
if (cmd->m_Data1 != 0) {
if (cmd->m_Data != 0) {
dmJson::Document doc;
dmJson::Result r = dmJson::Parse((const char*) cmd->m_Data1, &doc);
dmJson::Result r = dmJson::Parse((const char*) cmd->m_Data, &doc);
if (r == dmJson::RESULT_OK && doc.m_NodeCount > 0) {
char err_str[128];
if (dmScript::JsonToLua(L, &doc, 0, err_str, sizeof(err_str)) < 0) {
@@ -422,11 +344,9 @@ static void HandlePurchaseResult(const IAPCommand* cmd)
IAP_PushError(L, "failed to buy product", REASON_UNSPECIFIED);
}
int ret = lua_pcall(L, 3, 0, 0);
if (ret != 0) {
dmLogError("Error running callback: %s", lua_tostring(L, -1));
lua_pop(L, 1);
}
dmScript::PCall(L, 3, 0);
dmScript::TeardownCallback(cmd->m_Callback);
assert(top == lua_gettop(L));
}
@@ -436,8 +356,7 @@ 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_CommandsQueue.SetCapacity(2);
g_IAP.m_Mutex = dmMutex::New();
IAP_Queue_Create(&g_IAP.m_CommandQueue);
g_IAP.m_autoFinishTransactions = dmConfigFile::GetInt(params->m_ConfigFile, "iap.auto_finish_transactions", 1) == 1;
@@ -470,7 +389,7 @@ static dmExtension::Result InitializeIAP(dmExtension::Params* params)
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;)V");
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");
@@ -499,50 +418,40 @@ static dmExtension::Result InitializeIAP(dmExtension::Params* params)
return dmExtension::RESULT_OK;
}
static void IAP_OnCommand(IAPCommand* cmd, void*)
{
switch (cmd->m_Command)
{
case IAP_PRODUCT_RESULT:
HandleProductResult(cmd);
break;
case IAP_PURCHASE_RESULT:
HandlePurchaseResult(cmd);
break;
default:
assert(false);
}
if (cmd->m_Data) {
free(cmd->m_Data);
}
}
static dmExtension::Result UpdateIAP(dmExtension::Params* params)
{
if (g_IAP.m_CommandsQueue.Empty())
{
return dmExtension::RESULT_OK;
}
DM_MUTEX_SCOPED_LOCK(g_IAP.m_Mutex);
for(uint32_t i = 0; i != g_IAP.m_CommandsQueue.Size(); ++i)
{
IAPCommand& cmd = g_IAP.m_CommandsQueue[i];
switch (cmd.m_Command)
{
case CMD_PRODUCT_RESULT:
HandleProductResult(&cmd);
break;
case CMD_PURCHASE_RESULT:
HandlePurchaseResult(&cmd);
break;
default:
assert(false);
}
if (cmd.m_Data1) {
free(cmd.m_Data1);
}
}
g_IAP.m_CommandsQueue.SetSize(0);
IAP_Queue_Flush(&g_IAP.m_CommandQueue, IAP_OnCommand, 0);
return dmExtension::RESULT_OK;
}
static dmExtension::Result FinalizeIAP(dmExtension::Params* params)
{
dmMutex::Delete(g_IAP.m_Mutex);
IAP_Queue_Destroy(&g_IAP.m_CommandQueue);
--g_IAP.m_InitCount;
if (params->m_L == g_IAP.m_Listener.m_L && g_IAP.m_Listener.m_Callback != LUA_NOREF) {
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);
g_IAP.m_Listener.m_L = 0;
g_IAP.m_Listener.m_Callback = LUA_NOREF;
g_IAP.m_Listener.m_Self = LUA_NOREF;
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) {

View File

@@ -15,152 +15,102 @@ struct IAP
IAP()
{
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;
}
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;
typedef void (*OnIAPFBList)(void *L, const char* json);
typedef void (*OnIAPFBListenerCallback)(void *L, const char* json, int error_code);
typedef void (*OnIAPFBList)(void* luacallback, const char* json);
typedef void (*OnIAPFBListenerCallback)(void* luacallback, const char* json, int error_code);
extern "C" {
// Implementation in library_facebook_iap.js
void dmIAPFBList(const char* item_ids, OnIAPFBList callback, lua_State* L);
void dmIAPFBBuy(const char* item_id, const char* request_id, OnIAPFBListenerCallback 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, dmScript::LuaCallbackInfo* luacallback);
}
static void VerifyCallback(lua_State* L)
static void IAPList_Callback(void* luacallback, const char* result_json)
{
if (g_IAP.m_Callback != LUA_NOREF) {
dmLogError("Unexpected callback set");
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Callback);
dmScript::Unref(L, LUA_REGISTRYINDEX, g_IAP.m_Self);
g_IAP.m_Callback = LUA_NOREF;
g_IAP.m_Self = LUA_NOREF;
g_IAP.m_L = 0;
dmScript::LuaCallbackInfo* callback = (dmScript::LuaCallbackInfo*)luacallback;
lua_State* L = dmScript::GetCallbackLuaContext(callback);
DM_LUA_STACK_CHECK(L, 0);
if (!dmScript::SetupCallback(callback))
{
dmScript::DestroyCallback(callback);
return;
}
}
void IAPList_Callback(void* Lv, const char* result_json)
{
lua_State* L = (lua_State*) Lv;
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;
}
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);
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);
IAP_PushError(L, "Failed to parse list result JSON", REASON_UNSPECIFIED);
}
dmJson::Free(&doc);
}
else
{
dmLogError("Got empty list result.");
} else {
dmLogError("Failed to parse list result JSON (%d)", r);
lua_pushnil(L);
IAP_PushError(L, "Got empty list result.", REASON_UNSPECIFIED);
IAP_PushError(L, "Failed to parse list result JSON", REASON_UNSPECIFIED);
}
int ret = lua_pcall(L, 3, 0, 0);
if (ret != 0) {
dmLogError("Error running callback: %s", lua_tostring(L, -1));
lua_pop(L, 1);
}
assert(top == lua_gettop(L));
dmScript::Unref(L, LUA_REGISTRYINDEX, callback);
g_IAP.m_Callback = LUA_NOREF;
} else {
dmLogError("No callback set");
dmJson::Free(&doc);
}
else
{
dmLogError("Got empty list result.");
lua_pushnil(L);
IAP_PushError(L, "Got empty list result.", REASON_UNSPECIFIED);
}
dmScript::PCall(L, 3, 0);
dmScript::DestroyCallback(callback);
dmScript::TeardownCallback(callback);
}
int IAP_List(lua_State* L)
static int IAP_ProcessPendingTransactions(lua_State* L)
{
int top = lua_gettop(L);
VerifyCallback(L);
return 0;
}
static int IAP_List(lua_State* L)
{
DM_LUA_STACK_CHECK(L, 0);
char* buf = IAP_List_CreateBuffer(L);
if( buf == 0 )
{
assert(top == lua_gettop(L));
return 0;
}
luaL_checktype(L, 2, LUA_TFUNCTION);
lua_pushvalue(L, 2);
g_IAP.m_Callback = dmScript::Ref(L, LUA_REGISTRYINDEX);
dmScript::GetInstance(L);
g_IAP.m_Self = dmScript::Ref(L, LUA_REGISTRYINDEX);
g_IAP.m_L = dmScript::GetMainThread(L);
dmIAPFBList(buf, (OnIAPFBList)IAPList_Callback, g_IAP.m_L);
dmScript::LuaCallbackInfo* callback = dmScript::CreateCallback(L, 2);
dmIAPFBList(buf, (OnIAPFBList)IAPList_Callback, callback);
free(buf);
assert(top == lua_gettop(L));
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)
{
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;
}
lua_rawgeti(L, LUA_REGISTRYINDEX, g_IAP.m_Listener.m_Callback);
dmScript::LuaCallbackInfo* callback = (dmScript::LuaCallbackInfo*)luacallback;
lua_State* L = dmScript::GetCallbackLuaContext(callback);
DM_LUA_STACK_CHECK(L, 0);
// Setup self
lua_rawgeti(L, LUA_REGISTRYINDEX, g_IAP.m_Listener.m_Self);
lua_pushvalue(L, -1);
dmScript::SetInstance(L);
if (!dmScript::IsInstanceValid(L))
if (!dmScript::SetupCallback(callback))
{
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) {
dmJson::Document doc;
dmJson::Result r = dmJson::Parse(result_json, &doc);
@@ -197,21 +147,22 @@ void IAPListener_Callback(void* Lv, const char* result_json, int error_code)
break;
}
}
int ret = lua_pcall(L, 3, 0, 0);
if (ret != 0) {
dmLogError("Error running callback: %s", lua_tostring(L, -1));
lua_pop(L, 1);
}
assert(top == lua_gettop(L));
dmScript::PCall(L, 3, 0);
dmScript::TeardownCallback(callback);
}
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");
return 0;
}
int top = lua_gettop(L);
const char* id = luaL_checkstring(L, 1);
const char* request_id = 0x0;
@@ -224,43 +175,34 @@ int IAP_Buy(lua_State* L)
lua_pop(L, 2);
}
dmIAPFBBuy(id, request_id, (OnIAPFBListenerCallback)IAPListener_Callback, L);
assert(top == lua_gettop(L));
dmIAPFBBuy(id, request_id, (OnIAPFBListenerCallback)IAPListener_Callback, g_IAP.m_Listener);
return 0;
}
int IAP_SetListener(lua_State* L)
static int IAP_SetListener(lua_State* L)
{
IAP* iap = &g_IAP;
luaL_checktype(L, 1, LUA_TFUNCTION);
lua_pushvalue(L, 1);
int cb = dmScript::Ref(L, LUA_REGISTRYINDEX);
if (iap->m_Listener.m_Callback != LUA_NOREF) {
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);
DM_LUA_STACK_CHECK(L, 0);
if (g_IAP.m_Listener)
dmScript::DestroyCallback(g_IAP.m_Listener);
g_IAP.m_Listener = dmScript::CreateCallback(L, 1);
return 0;
}
int IAP_Finish(lua_State* L)
static int IAP_Finish(lua_State* L)
{
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);
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);
return 1;
}
@@ -273,16 +215,17 @@ static const luaL_reg IAP_methods[] =
{"restore", IAP_Restore},
{"set_listener", IAP_SetListener},
{"get_provider_id", IAP_GetProviderId},
{"process_pending_transactions", IAP_ProcessPendingTransactions},
{0, 0}
};
dmExtension::Result InitializeIAP(dmExtension::Params* params)
static dmExtension::Result InitializeIAP(dmExtension::Params* params)
{
if (g_IAP.m_InitCount == 0) {
g_IAP.m_autoFinishTransactions = dmConfigFile::GetInt(params->m_ConfigFile, "iap.auto_finish_transactions", 1) == 1;
}
g_IAP.m_InitCount++;
lua_State*L = params->m_L;
lua_State* L = params->m_L;
int top = lua_gettop(L);
luaL_register(L, LIB_NAME, IAP_methods);
@@ -293,15 +236,13 @@ dmExtension::Result InitializeIAP(dmExtension::Params* params)
return dmExtension::RESULT_OK;
}
dmExtension::Result FinalizeIAP(dmExtension::Params* params)
static dmExtension::Result FinalizeIAP(dmExtension::Params* params)
{
--g_IAP.m_InitCount;
if (params->m_L == g_IAP.m_Listener.m_L && g_IAP.m_Listener.m_Callback != LUA_NOREF) {
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);
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_Listener && g_IAP.m_InitCount == 0)
{
dmScript::DestroyCallback(g_IAP.m_Listener);
g_IAP.m_Listener = 0;
}
return dmExtension::RESULT_OK;
}

File diff suppressed because it is too large Load Diff

View File

@@ -96,4 +96,43 @@ void IAP_PushConstants(lua_State* L)
#undef SETCONSTANT
}
void IAP_Queue_Create(IAPCommandQueue* queue)
{
queue->m_Mutex = dmMutex::New();
}
void IAP_Queue_Destroy(IAPCommandQueue* queue)
{
dmMutex::Delete(queue->m_Mutex);
}
void IAP_Queue_Push(IAPCommandQueue* queue, IAPCommand* cmd)
{
DM_MUTEX_SCOPED_LOCK(queue->m_Mutex);
if(queue->m_Commands.Full())
{
queue->m_Commands.OffsetCapacity(2);
}
queue->m_Commands.Push(*cmd);
}
void IAP_Queue_Flush(IAPCommandQueue* queue, IAPCommandFn fn, void* ctx)
{
assert(fn != 0);
if (queue->m_Commands.Empty())
{
return;
}
DM_MUTEX_SCOPED_LOCK(queue->m_Mutex);
for(uint32_t i = 0; i != queue->m_Commands.Size(); ++i)
{
fn(&queue->m_Commands[i], ctx);
}
queue->m_Commands.SetSize(0);
}
#endif // DM_PLATFORM_HTML5 || DM_PLATFORM_ANDROID || DM_PLATFORM_IOS

View File

@@ -5,24 +5,46 @@
#include <dmsdk/sdk.h>
struct IAPListener
enum EIAPCommand
{
IAPListener()
{
m_L = 0;
m_Callback = LUA_NOREF;
m_Self = LUA_NOREF;
}
lua_State* m_L;
int m_Callback;
int m_Self;
IAP_PRODUCT_RESULT,
IAP_PURCHASE_RESULT,
};
struct DM_ALIGNED(16) IAPCommand
{
IAPCommand()
{
memset(this, 0, sizeof(IAPCommand));
}
// Used for storing eventual callback info (if needed)
dmScript::LuaCallbackInfo* m_Callback;
// THe actual command payload
int32_t m_Command;
int32_t m_ResponseCode;
void* m_Data;
};
struct IAPCommandQueue
{
dmArray<IAPCommand> m_Commands;
dmMutex::HMutex m_Mutex;
};
char* IAP_List_CreateBuffer(lua_State* L);
void IAP_PushError(lua_State* L, const char* error, int reason);
void IAP_PushConstants(lua_State* L);
typedef void (*IAPCommandFn)(IAPCommand* cmd, void* ctx);
void IAP_Queue_Create(IAPCommandQueue* queue);
void IAP_Queue_Destroy(IAPCommandQueue* queue);
// The command is copied by value into the queue
void IAP_Queue_Push(IAPCommandQueue* queue, IAPCommand* cmd);
void IAP_Queue_Flush(IAPCommandQueue* queue, IAPCommandFn fn, void* ctx);
#endif
#endif // DM_PLATFORM_HTML5 || DM_PLATFORM_ANDROID || DM_PLATFORM_IOS

View File

@@ -1,5 +1,5 @@
package com.defold.iap;
public interface IListProductsListener {
public void onProductsResult(int resultCode, String productList);
public void onProductsResult(int resultCode, String productList, long cmdHandle);
}

View File

@@ -35,6 +35,7 @@ public class IapAmazon implements PurchasingListener {
public static final String TAG = "iap";
private HashMap<RequestId, IListProductsListener> listProductsListeners;
private HashMap<RequestId, Long> listProductsCommandPtrs;
private HashMap<RequestId, IPurchaseListener> purchaseListeners;
private Activity activity;
@@ -54,7 +55,7 @@ public class IapAmazon implements PurchasingListener {
public void stop() {
}
public void listItems(final String skus, final IListProductsListener listener) {
public void listItems(final String skus, final IListProductsListener listener, final long commandPtr) {
final Set<String> skuSet = new HashSet<String>();
for (String x : skus.split(",")) {
if (x.trim().length() > 0) {
@@ -71,6 +72,7 @@ public class IapAmazon implements PurchasingListener {
RequestId req = PurchasingService.getProductData(skuSet);
if (req != null) {
listProductsListeners.put(req, listener);
listProductsCommandPtrs.put(req, commandPtr);
} else {
Log.e(TAG, "Did not expect a null requestId");
}
@@ -150,17 +152,21 @@ public class IapAmazon implements PurchasingListener {
public void onProductDataResponse(ProductDataResponse productDataResponse) {
RequestId reqId = productDataResponse.getRequestId();
IListProductsListener listener;
long commadPtr = 0;
synchronized (this.listProductsListeners) {
listener = this.listProductsListeners.get(reqId);
commadPtr = this.listProductsCommandPtrs.get(reqId);
this.listProductsListeners.remove(reqId);
this.listProductsCommandPtrs.remove(reqId);
if (listener == null) {
Log.e(TAG, "No listener found for request " + reqId.toString());
return;
}
this.listProductsListeners.remove(reqId);
}
if (productDataResponse.getRequestStatus() != ProductDataResponse.RequestStatus.SUCCESSFUL) {
listener.onProductsResult(IapJNI.BILLING_RESPONSE_RESULT_ERROR, null);
listener.onProductsResult(IapJNI.BILLING_RESPONSE_RESULT_ERROR, null, commadPtr);
} else {
Map<String, Product> products = productDataResponse.getProductData();
try {
@@ -180,9 +186,9 @@ public class IapAmazon implements PurchasingListener {
}
data.put(key, item);
}
listener.onProductsResult(IapJNI.BILLING_RESPONSE_RESULT_OK, data.toString());
listener.onProductsResult(IapJNI.BILLING_RESPONSE_RESULT_OK, data.toString(), commadPtr);
} catch (JSONException e) {
listener.onProductsResult(IapJNI.BILLING_RESPONSE_RESULT_ERROR, null);
listener.onProductsResult(IapJNI.BILLING_RESPONSE_RESULT_ERROR, null, commadPtr);
}
}
}

View File

@@ -17,6 +17,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -67,7 +68,7 @@ public class IapGooglePlay implements Handler.Callback {
private boolean autoFinishTransactions;
private static interface ISkuRequestListener {
public void onProducts(int resultCode, JSONObject products);
public void onProducts(int resultCode, JSONObject products);
}
private static class SkuRequest {
@@ -155,10 +156,24 @@ public class IapGooglePlay implements Handler.Callback {
this.autoFinishTransactions = autoFinishTransactions;
}
private static boolean isPlayStoreInstalled(Context context){
try {
context.getPackageManager().getPackageInfo("com.android.vending", 0);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
private void init() {
// NOTE: We must create Handler lazily as construction of
// handlers must be in the context of a "looper" on Android
if (!isPlayStoreInstalled(activity)) {
Log.e(TAG, "Unable to find Google Play Store (com.android.vending)");
return;
}
if (this.initialized)
return;
@@ -238,7 +253,7 @@ public class IapGooglePlay implements Handler.Callback {
});
}
public void listItems(final String skus, final IListProductsListener listener) {
public void listItems(final String skus, final IListProductsListener listener, final long commandPtr) {
ArrayList<String> skuList = new ArrayList<String>();
for (String x : skus.split(",")) {
if (x.trim().length() > 0) {
@@ -261,15 +276,15 @@ public class IapGooglePlay implements Handler.Callback {
products.put(key, convertProduct(product));
}
}
listener.onProductsResult(resultCode, products.toString());
listener.onProductsResult(resultCode, products.toString(), commandPtr);
}
catch(JSONException e) {
Log.wtf(TAG, "Failed to convert products", e);
listener.onProductsResult(resultCode, null);
listener.onProductsResult(resultCode, null, commandPtr);
}
}
else {
listener.onProductsResult(resultCode, null);
listener.onProductsResult(resultCode, null, commandPtr);
}
}
}));

View File

@@ -69,8 +69,8 @@ public class IapGooglePlayActivity extends Activity {
}
private void buy(String product, String productType) {
// Flush any pending items, in order to be able to buy the same (new) product again
processPendingConsumables();
// Flush any pending items, in order to be able to buy the same (new) product again
processPendingConsumables();
try {
Bundle buyIntentBundle = service.getBuyIntent(3, getPackageName(), product, productType, "");

View File

@@ -23,7 +23,7 @@ public class IapJNI implements IListProductsListener, IPurchaseListener {
}
@Override
public native void onProductsResult(int responseCode, String productList);
public native void onProductsResult(int responseCode, String productList, long cmdHandle);
@Override
public native void onPurchaseResult(int responseCode, String purchaseData);

View File

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

View File

@@ -41,7 +41,7 @@ nodes {
w: 1.0
}
type: TYPE_TEMPLATE
id: "consumable"
id: "goldbars_small"
layer: ""
inherit_alpha: true
alpha: 1.0
@@ -82,12 +82,12 @@ nodes {
type: TYPE_BOX
blend_mode: BLEND_MODE_ALPHA
texture: "button/button_normal"
id: "consumable/larrybutton"
id: "goldbars_small/larrybutton"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
adjust_mode: ADJUST_MODE_FIT
parent: "consumable"
parent: "goldbars_small"
layer: ""
inherit_alpha: true
slice9 {
@@ -129,16 +129,16 @@ nodes {
w: 1.0
}
color {
x: 0.6
y: 0.0
z: 0.0
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_TEXT
blend_mode: BLEND_MODE_ALPHA
text: "consumable"
text: "Goldbars - S"
font: "larryfont"
id: "consumable/larrylabel"
id: "goldbars_small/larrylabel"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_CENTER
@@ -156,172 +156,12 @@ nodes {
}
adjust_mode: ADJUST_MODE_FIT
line_break: false
parent: "consumable/larrybutton"
parent: "goldbars_small/larrybutton"
layer: ""
inherit_alpha: true
alpha: 1.0
outline_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
template_node_child: true
text_leading: 1.0
@@ -330,7 +170,7 @@ nodes {
nodes {
position {
x: 485.0
y: 449.0
y: 56.0
z: 0.0
w: 1.0
}
@@ -447,14 +287,14 @@ nodes {
w: 1.0
}
color {
x: 0.6
y: 0.0
z: 0.0
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
type: TYPE_TEXT
blend_mode: BLEND_MODE_ALPHA
text: "<- reset"
text: "Reset"
font: "larryfont"
id: "reset/larrylabel"
xanchor: XANCHOR_NONE
@@ -480,7 +320,700 @@ nodes {
alpha: 1.0
outline_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
template_node_child: true
text_leading: 1.0

View File

@@ -1,59 +1,113 @@
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 = {
"consumable",
"nonconsumable"
GOLDBARS_SMALL,
GOLDBARS_MEDIUM,
GOLDBARS_LARGE,
SUBSCRIPTION,
}
local product_items = {}
-- mapping between product id and button name
local item_buttons = {
[GOLDBARS_SMALL] = "goldbars_small",
[GOLDBARS_MEDIUM] = "goldbars_medium",
[GOLDBARS_LARGE] = "goldbars_large",
[SUBSCRIPTION] = "subscription",
}
local function list_callback(self, products, error)
if error == nil then
for k,p in pairs(products) do
product_items[p.ident] = p
local name = p.ident.."/larrylabel"
gui.set_color(gui.get_node(name), vmath.vector4(1,1,1,1))
end
else
print(error.error)
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
for k,p in pairs(products) do
available_items[p.ident] = p
log("Item %s", p.ident)
local button = item_buttons[p.ident]
if button then
gui.set_color(gui.get_node(button.."/larrylabel"), vmath.vector4(1,1,1,1))
else
log("Unable to find button for %s", tostring(p.ident))
end
end
end)
end
local function buy_listener(self, transaction, error)
pprint(transaction, error)
if not error and iap.get_provider_id() == iap.PROVIDER_ID_GOOGLE and transaction.ident == "nonconsumable" then
local name = "reset/larrylabel"
gui.set_color(gui.get_node(name), vmath.vector4(1,1,1,1))
if error then
log("iap.buy() error %s - %s", tostring(error.error), tostring(error.reason))
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
elseif not error then
else
log("iap.buy() ok")
log("iap.finish()")
iap.finish(transaction)
end
end
function init(self)
self.log = {}
log("init()")
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)
end
function on_input(self, action_id, action)
if product_items["consumable"] then
dirtylarry:button("consumable", action_id, action, function()
iap.buy("consumable")
if action_id then
for item, button in pairs(item_buttons) do
if available_items[item] then
dirtylarry:button(button, action_id, action, function()
buy(item)
end)
end
end
dirtylarry:button("list", action_id, action, function()
list()
end)
end
if product_items["nonconsumable"] then
dirtylarry:button("nonconsumable", action_id, action, function()
iap.buy("nonconsumable")
end)
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