├── .github
└── workflows
│ ├── ci.yml
│ └── stale.yml
├── .gitignore
├── CHANGELOG.md
├── KINDLE.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── ancillary
└── amazon.sdktester.json
├── android
├── .gitignore
├── .project
├── .settings
│ └── org.eclipse.buildship.core.prefs
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jars
│ └── in-app-purchasing-2.0.76.jar
├── settings.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── com
│ └── dooboolab
│ └── flutterinapppurchase
│ ├── AmazonInappPurchasePlugin.kt
│ ├── AndroidInappPurchasePlugin.kt
│ ├── BillingError.kt
│ ├── FlutterInappPurchasePlugin.kt
│ └── MethodResultWrapper.kt
├── example
├── .gitignore
├── .metadata
├── README.md
├── android
│ ├── .gitignore
│ ├── .project
│ ├── .settings
│ │ └── org.eclipse.buildship.core.prefs
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ │ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java
│ │ │ └── com
│ │ │ │ └── dooboolab
│ │ │ │ ├── iapexample
│ │ │ │ └── MainActivity.java
│ │ │ │ └── test
│ │ │ │ └── MainActivity.java
│ │ │ └── res
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ └── values
│ │ │ └── styles.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle
├── flutter_inapp_purchase_example.iml
├── flutter_inapp_purchase_example_android.iml
├── ios
│ ├── .gitignore
│ ├── Flutter
│ │ ├── .last_build_id
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ ├── Flutter.podspec
│ │ └── Release.xcconfig
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── Runner
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ │ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── main.m
├── lib
│ └── main.dart
├── pubspec.yaml
└── test
│ └── widget_test.dart
├── flutter_inapp_purchase_android.iml
├── ios
├── .gitignore
├── Assets
│ └── .gitkeep
├── Classes
│ ├── FlutterInappPurchasePlugin.h
│ ├── FlutterInappPurchasePlugin.m
│ ├── IAPPromotionObserver.h
│ └── IAPPromotionObserver.m
└── flutter_inapp_purchase.podspec
├── issue_template.md
├── lib
├── Store.dart
├── flutter_inapp_purchase.dart
├── modules.dart
└── utils.dart
├── pubspec.yaml
└── test
├── flutter_inapp_purchase_test.dart
└── utils_test.dart
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Flutter CI
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: actions/setup-java@v3
15 | with:
16 | distribution: 'adopt'
17 | java-version: "14.x"
18 |
19 | - uses: subosito/flutter-action@v2
20 | with:
21 | channel: "stable"
22 | flutter-version: "3.x"
23 |
24 | - run: flutter pub get
25 |
26 | - run: dart format --set-exit-if-changed .
27 |
28 | # - run: flutter analyze .
29 |
30 | - run: flutter test --coverage
31 |
32 | - name: Upload coverage to Codecov
33 | uses: codecov/codecov-action@v3
34 | with:
35 | token: ${{ secrets.CODECOV_TOKEN }}
36 | file: coverage/lcov.info
37 |
38 | # - run: flutter build apk
39 |
40 | # Upload generated apk to the artifacts.
41 | # - uses: actions/upload-artifact@v1
42 | # with:
43 | # name: release-apk
44 | # path: build/app/outputs/apk/release/app-release.apk
45 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: Mark stale issues and pull requests
2 |
3 | on:
4 | schedule:
5 | - cron: "0 0 * * *"
6 |
7 | jobs:
8 | stale:
9 |
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/stale@v8
14 | with:
15 | repo-token: ${{ secrets.GITHUB_TOKEN }}
16 | days-before-stale: 90
17 | days-before-close: 7
18 | stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. Leave a comment or this will be closed in 7 days.'
19 | stale-pr-message: 'This PR is stale because it has been open 90 days with no activity. Leave a comment or this will be closed in 7 days'
20 | stale-issue-label: 'no-issue-activity'
21 | stale-pr-label: 'no-pr-activity'
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .dart_tool/
3 |
4 | .packages
5 | .pub/
6 | pubspec.lock
7 |
8 | build/
9 |
10 | # dooboolab
11 | .idea/
12 | flutter_inapp.iml
13 | flutter_inapp_android.iml
14 | flutter_export_environment*
15 | launch.json
16 | .flutter-plugins-*
17 |
18 | coverage
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 5.6.2
2 |
3 | - fix: removed references to deprecated v1 Android embedding by @moodstubos in https://github.com/hyochan/flutter_inapp_purchase/pull/497
4 |
5 | ## 5.6.1
6 |
7 | - Erroneous duplicate item by @deakjahn in https://github.com/hyochan/flutter_inapp_purchase/pull/441
8 | - Fixed consumable products reading on Android by @33-Elephants in https://github.com/hyochan/flutter_inapp_purchase/pull/439
9 | - fix: Support AGP8 namespace by @dev-yakuza in https://github.com/hyochan/flutter_inapp_purchase/pull/467
10 |
11 | ## 5.6.0
12 |
13 | - refactor: android init connection
14 | ```
15 | Used Kotlin apply for cleaner initialization of billingClient.
16 | Introduced context ?: return for null-safety with context.
17 | Merged repetitive code into the updateConnectionStatus method to avoid duplication.
18 | Improved the handling of the alreadyFinished flag to ensure it is only set once and at the appropriate time.
19 | Streamlined the error and success handling for clarity.
20 | ```
21 | - Migrate android billingClient to 6.0.1
22 | - https://developer.android.com/google/play/billing/release-notes#6-0-1
23 |
24 | ## 5.5.0
25 |
26 | - Erroneous duplicate item (#441) - Remove extra `introductoryPricePaymentModeIOS`
27 | - Fixed consumable products reading on Android (#439)
28 | - chore(deps): migrate internal packages to recent
29 | ```
30 | http: ^1.1.0
31 | meta: ^1.10.0
32 | platform: ^3.1.3
33 | ```
34 | - chore: migrate example project to recent flutter version, 3.16.0-0.3.pre
35 |
36 | ## 5.4.2
37 |
38 | ## What's Changed
39 |
40 | - Update actions/stale action to v8 by @renovate in https://github.com/hyochan/flutter_inapp_purchase/pull/414
41 | - Fix - wrong casting by @BrunoFSimon in https://github.com/hyochan/flutter_inapp_purchase/pull/427
42 | - Fixed consumable product purchase on Android by @33-Elephants in https://github.com/hyochan/flutter_inapp_purchase/pull/420
43 |
44 | ## New Contributors
45 |
46 | - @BrunoFSimon made their first contribution in https://github.com/hyochan/flutter_inapp_purchase/pull/427
47 | - @33-Elephants made their first contribution in https://github.com/hyochan/flutter_inapp_purchase/pull/420
48 |
49 | **Full Changelog**: https://github.com/hyochan/flutter_inapp_purchase/compare/5.4.1...5.4.2
50 |
51 | ## 5.4.1
52 |
53 | - Fixed concurrency issue on iOS. by @OctavianLfrd in https://github.com/dooboolab/flutter_inapp_purchase/pull/413
54 |
55 | ## 5.4.0
56 |
57 | - Fixed wrong casting in checkSubscribed method by @kleeb in https://github.com/dooboolab/flutter_inapp_purchase/pull/368
58 | - Upgrade to billing 5.1 (reverse compatible) by @SamBergeron in https://github.com/dooboolab/flutter_inapp_purchase/pull/392
59 |
60 | ## 5.3.0
61 |
62 | ## What's Changed
63 |
64 | - Refactor java to kotlin, add showInAppMessageAndroid by @offline-first in https://github.com/dooboolab/flutter_inapp_purchase/pull/365
65 |
66 | ## New Contributors
67 |
68 | - @offline-first made their first contribution in https://github.com/dooboolab/flutter_inapp_purchase/pull/365
69 |
70 | **Full Changelog**: https://github.com/dooboolab/flutter_inapp_purchase/compare/5.2.0...5.3.0
71 |
72 | ## 5.2.0
73 |
74 | Bugfix #356
75 |
76 | ## 5.1.1
77 |
78 | Run on UiThread and few others (#328)
79 |
80 | - Related #272
81 |
82 | - The main difference is a new MethodResultWrapper class that wraps both the result and the channel. onMethodCall() now immediately saves this wrapped result-channel to a field and only uses that later to set both the result and to send back info on the channel. I did this in both Google and Amazon but I can't test the Amazon one.
83 |
84 | - Included the plugin registration differences.
85 |
86 | - Midified suggested in one of the issues that initConnection, endConnection and consumeAllItems shouldn't be accessors. This is very much so, property accessors are not supposed to do work and have side effects, just return a value. Now three new functions are suggested and marked the old ones deprecated.
87 |
88 | Fourth, EnumUtil.getValueString() is not really necessary, we have describeEnum() in the Flutter engine just for this purpose.
89 |
90 | ## 5.1.0
91 |
92 | Upgrade android billing client to `4.0.0` (#326)
93 |
94 | Remove `orderId` in `Purchase`
95 |
96 | - This is duplicate of `transactionId`.
97 |
98 | Support for Amazon devices with Google Play sideloaded (#313)
99 |
100 | ## 5.0.4
101 |
102 | - Add iOS promo codes (#325)
103 | - Use http client in validateReceiptIos (#322)
104 | - Amazon `getPrice` directly withoiut formatting (#316)
105 |
106 | ## 5.0.3
107 |
108 | - Fix plugin exception for `requestProductWithQuantityIOS` [#306](https://github.com/dooboolab/flutter_inapp_purchase/pull/306)
109 |
110 | ## 5.0.2
111 |
112 | - Replaced obfuscatedAccountIdAndroid with obfuscatedAccountId in request purchase method [#299](https://github.com/dooboolab/flutter_inapp_purchase/pull/299)
113 |
114 | ## 5.0.1
115 |
116 | - Add AndroidProrationMode values [#273](https://github.com/dooboolab/flutter_inapp_purchase/pull/273)
117 |
118 | ## 5.0.0
119 |
120 | - Support null safety [#275](https://github.com/dooboolab/flutter_inapp_purchase/pull/275)
121 |
122 | ## 4.0.2
123 |
124 | - The dart side requires "introductoryPriceCyclesAndroid" to be a int [#268](https://github.com/dooboolab/flutter_inapp_purchase/pull/268)
125 |
126 | ## 4.0.1
127 |
128 | - `platform` dep version `>=2.0.0 <4.0.0`
129 |
130 | ## 4.0.0
131 |
132 | - Support flutter v2 [#265](https://github.com/dooboolab/flutter_inapp_purchase/pull/265)
133 |
134 | ## 3.0.1
135 |
136 | - Migrate to flutter embedding v2 [#240](https://github.com/dooboolab/flutter_inapp_purchase/pull/240)
137 | - Expose android purchase state as enum [#243](https://github.com/dooboolab/flutter_inapp_purchase/pull/243)
138 |
139 | ## 3.0.0
140 |
141 | - Upgrade android billing client to `2.1.0` from `3.0.0`.
142 | - Removed `deveoperId` and `accountId` when requesting `purchase` or `subscription` in `android`.
143 | - Added `obfuscatedAccountIdAndroid` and `obfuscatedProfileIdAndroid` when requesting `purchase` or `subscription` in `android`.
144 | - Removed `developerPayload` in `android`.
145 | - Added `purchaseTokenAndroid` as an optional parameter to `requestPurchase` and `requestSubscription`.
146 |
147 | ## 2.3.1
148 |
149 | Republishing since sourcode seems not merged correctly.
150 |
151 | ## 2.3.0
152 |
153 | - Bugfix IAPItem deserialization [#212](https://github.com/dooboolab/flutter_inapp_purchase/pull/212)
154 | - Add introductoryPriceNumberIOS [#214](https://github.com/dooboolab/flutter_inapp_purchase/pull/214)
155 | - Fix iOS promotional offers [#220](https://github.com/dooboolab/flutter_inapp_purchase/pull/220)
156 |
157 | ## 2.2.0
158 |
159 | - Implement `endConnection` method to declaratively finish observer in iOS.
160 | - Remove `addTransactionObserver` in IAPPromotionObserver.m for dup observer problems.
161 | - Automatically startPromotionObserver in `initConnection` for iOS.
162 |
163 | ## 2.1.5
164 |
165 | - Fix ios failed purchase handling problem in 11.4+ [#176](https://github.com/dooboolab/flutter_inapp_purchase/pull/176)
166 |
167 | ## 2.1.4
168 |
169 | - Fix dart side expression warning [#169](https://github.com/dooboolab/flutter_inapp_purchase/pull/169).
170 |
171 | ## 2.1.3
172 |
173 | - Fix wrong introductory price number of periods [#164](https://github.com/dooboolab/flutter_inapp_purchase/pull/164).
174 |
175 | ## 2.1.2
176 |
177 | - Trigger purchaseUpdated callback when iap purchased [#165](https://github.com/dooboolab/flutter_inapp_purchase/pull/165).
178 |
179 | ## 2.1.1
180 |
181 | - Renamed `finishTransactionIOS` argument `purchaseToken` to `transactionId`.
182 |
183 | ## 2.1.0
184 |
185 | - `finishTransaction` parameter changes to `purchasedItem` from `purchaseToken`.
186 | - Update android billing client to `2.1.0` from `2.0.3`.
187 |
188 | ## 2.0.5
189 |
190 | - [bugfix] Fix double call of result reply on connection init [#126](https://github.com/dooboolab/flutter_inapp_purchase/pull/126)
191 |
192 | ## 2.0.4
193 |
194 | - [bugfix] Fix plugin throws exceptions with flutter v1.10.7 beta [#117](https://github.com/dooboolab/flutter_inapp_purchase/pull/117)
195 |
196 | ## 2.0.3
197 |
198 | - [bugfix] Decode response code for connection updates stream [#114](https://github.com/dooboolab/flutter_inapp_purchase/pull/114)
199 | - [bugfix] Fix typo in `consumePurchase` [#115](https://github.com/dooboolab/flutter_inapp_purchase/pull/115)
200 |
201 | ## 2.0.2
202 |
203 | - use ConnectionResult as type for connection stream, fix controller creation [#112](https://github.com/dooboolab/flutter_inapp_purchase/pull/112)
204 |
205 | ## 2.0.0+16
206 |
207 | - Resolve [#106](https://github.com/dooboolab/flutter_inapp_purchase/issues/106) by not sending `result.error` to the listener. Created use `_conectionSubscription`.
208 |
209 | ## 2.0.0+15
210 |
211 | - Fixed minor typo when generating string with `toString`. Resolve [#110](https://github.com/dooboolab/flutter_inapp_purchase/issues/110).
212 |
213 | ## 2.0.0+14
214 |
215 | - Pass android exception to flutter side.
216 |
217 | ## 2.0.0+13
218 |
219 | - Android receipt validation api upgrade to `v3`.
220 |
221 | ## 2.0.0+12
222 |
223 | - Resolve [#102](https://github.com/dooboolab/flutter_inapp_purchase/issues/102). Fluter seems to only sends strings between platforms.
224 |
225 | ## 2.0.0+9
226 |
227 | - Resolve [#101](https://github.com/dooboolab/flutter_inapp_purchase/issues/101).
228 |
229 | ## 2.0.0+8
230 |
231 | - Resolve [#100](https://github.com/dooboolab/flutter_inapp_purchase/issues/100).
232 |
233 | ## 2.0.0+7
234 |
235 | - Resolve [#99](https://github.com/dooboolab/flutter_inapp_purchase/issues/99).
236 |
237 | ## 2.0.0+6
238 |
239 | - Send `purchase-error` with purchases returns null.
240 |
241 | ## 2.0.0+5
242 |
243 | - Renamed invoked parameters non-platform specific.
244 |
245 | ## 2.0.0+4
246 |
247 | - Add `deveoperId` and `accountId` when requesting `purchase` or `subscription` in `android`. Find out more in `requestPurchase` and `requestSubscription`.
248 |
249 | ## 2.0.0+3
250 |
251 | - Correctly mock invoke method and return results [#94](https://github.com/dooboolab/flutter_inapp_purchase/pull/96)
252 |
253 | ## 2.0.0+2
254 |
255 | - Seperate long `example` code to `example` readme.
256 |
257 | ## 2.0.0+1
258 |
259 | - Properly set return type `PurchaseResult` of when finishing transaction.
260 |
261 | ## 2.0.0 :tada:
262 |
263 | - Removed deprecated note in the `readme`.
264 | - Make the previous tests work in `travis`.
265 | - Documentation on `readme` for breaking features.
266 | - Abstracts `finishTransaction`.
267 | - `acknowledgePurchaseAndroid`, `consumePurchaseAndroid`, `finishTransactionIOS`.
268 |
269 | [Android]
270 |
271 | - Completely remove prepare.
272 | - Upgrade billingclient to 2.0.3 which is currently recent in Sep 15 2019.
273 | - Remove [IInAppBillingService] binding since billingClient has its own functionalities.
274 | - Add [DoobooUtils] and add `getBillingResponseData` that visualizes erorr codes better.
275 | - `buyProduct` no more return asyn result. It rather relies on the `purchaseUpdatedListener`.
276 | - Add feature method `acknowledgePurchaseAndroid`
277 | - Implement `acknowledgePurchaseAndroid`.
278 | - Renamed `consumePurchase` to `consumePurchaseAndroid` in dart side.
279 | - Update test codes.
280 | - Renamed methods
281 | - `buyProduct` to `requestPurchase`.
282 | - `buySubscription` to `requestSubscription`.
283 |
284 | [iOS]
285 |
286 | - Implment features in new releases.
287 | - enforce to `finishTransaction` after purchases.
288 | - Work with `purchaseUpdated` and `purchaseError` listener as in android.
289 | - Feature set from `react-native-iap v3`.
290 | - Should call finish transaction in every purchase request.
291 | - Add `IAPPromotionObserver` cocoa touch file
292 | - Convert dic to json string before invoking purchase-updated
293 | - Add `getPromotedProductIOS` and `requestPromotedProductIOS` methods
294 | - Implement clearTransaction for ios
295 | - Include `purchasePromoted` stream that listens to `iap-promoted-product`.
296 |
297 | ## 1.0.0
298 |
299 | - Add `DEPRECATION` note. Please use [in_app_purchase](https://pub.dev/packages/in_app_purchase).
300 |
301 | ## 0.9.+
302 |
303 | - Breaking change. Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to also migrate to Android X if they're using the original support library. [Android's Migrating to Android X Guide](https://developer.android.com/jetpack/androidx/migrate).
304 |
305 | * Improved getPurchaseHistory's speed 44% faster [#68](https://github.com/dooboolab/flutter_inapp_purchase/pull/68).
306 |
307 | ## 0.8.+
308 |
309 | - Fixed receipt validation param for `android`.
310 | - Updated `http` package.
311 | - Implemented new method `getAppStoreInitiatedProducts`.
312 | - Handling of iOS method `paymentQueue:shouldAddStorePayment:forProduct:`
313 | - Has no effect on Android.
314 | - Fixed issue with method `buyProductWithoutFinishTransaction` for iOS, was not getting the productId.
315 | - Fixed issue with `toString` method of class `IAPItem`, was printing incorrect values.
316 | - Fixes for #44. Unsafe getting `originalJson` when restoring item and `Android`.
317 | - Use dictionaryWithObjectsAndKeys in NSDictionary to fetch product values. This will prevent from NSInvalidArgumentException in ios which rarely occurs.
318 | - Fixed wrong npe in `android` when `getAvailablePurchases`.
319 |
320 | * Only parse `orderId` when exists in `Android` to prevent crashing.
321 | * Add additional success purchase listener in `iOS`. Related [#54](https://github.com/dooboolab/flutter_inapp_purchase/issues/54)
322 |
323 | ## 0.7.1
324 |
325 | - Implemented receiptValidation for both android and ios.
326 | - In Android, you need own backend to get your `accessToken`.
327 |
328 | ## 0.7.0
329 |
330 | - Addition of Amazon In-App Purchases.
331 |
332 | ## 0.6.9
333 |
334 | - Prevent nil element exception when getting products.
335 |
336 | ## 0.6.8
337 |
338 | - Prevent nil exception in ios when fetching products.
339 |
340 | ## 0.6.7
341 |
342 | - Fix broken images on pub.
343 |
344 | ## 0.6.6
345 |
346 | - Added missing introductory fields in ios.
347 |
348 | ## 0.6.5
349 |
350 | - convert dynamic objects to PurchasedItems.
351 | - Fix return type for getAvailablePurchases().
352 | - Fix ios null value if optional operator.
353 |
354 | ## 0.6.3
355 |
356 | - Update readme.
357 |
358 | ## 0.6.2
359 |
360 | - Fixed failing when there is no introductory price in ios.
361 |
362 | ## 0.6.1
363 |
364 | - Fixed `checkSubscribed` that can interrupt billing lifecycle.
365 |
366 | ## 0.6.0
367 |
368 | - Major code refactoring by lukepighetti. Unify PlatformException, cleanup new, DateTime instead of string.
369 |
370 | ## 0.5.9
371 |
372 | - Fix getSubscription json encoding problem in `ios`.
373 |
374 | ## 0.5.8
375 |
376 | - Avoid crashing on android caused by IllegalStateException.
377 |
378 | ## 0.5.7
379 |
380 | - Avoid possible memory leak in android by deleting static declaration of activity and context.
381 |
382 | ## 0.5.6
383 |
384 | - Few types fixed.
385 |
386 | ## 0.5.4
387 |
388 | - Fixed error parsing IAPItem.
389 |
390 | ## 0.5.3
391 |
392 | - Fixed error parsing purchaseHistory.
393 |
394 | ## 0.5.2
395 |
396 | - Fix crashing on error.
397 |
398 | ## 0.5.1
399 |
400 | - Give better error message on ios.
401 |
402 | ## 0.5.0
403 |
404 | - Code migration.
405 | - Support subscription period.
406 | - There was parameter renaming in `0.5.0` to identify different parameters sent from the device. Please check the readme.
407 |
408 | ## 0.4.3
409 |
410 | - Fixed subscription return types.
411 |
412 | ## 0.4.0
413 |
414 | - Well formatted code.
415 |
416 | ## 0.3.3
417 |
418 | - Code formatted
419 | - Updated missing data types
420 |
421 | ## 0.3.1
422 |
423 | - Upgraded readme for ease of usage.
424 | - Enabled strong mode.
425 |
426 | ## 0.3.0
427 |
428 | - Moved dynamic return type away and instead give `PurchasedItem`.
429 |
430 | ## 0.2.3
431 |
432 | - Quickly fixed purchase bug out there in [issue](https://github.com/dooboolab/flutter_inapp_purchase/issues/2). Need much more improvement currently.
433 |
434 | ## 0.2.2
435 |
436 | - Migrated packages from FlutterInApp to FlutterInAppPurchase because pub won't get it.
437 |
438 | ## 0.1.0
439 |
440 | - Initial release of beta
441 | - Moved code from [react-native-iap](https://github.com/dooboolab/react-native-iap)
442 |
--------------------------------------------------------------------------------
/KINDLE.md:
--------------------------------------------------------------------------------
1 | # Amazon Kindle Fire / Fire TV In-App Purchases Guide
2 |
3 | The plugin will automatically detect Amazon devices during runtime.
4 |
5 | ## Testing In-App Purchases
6 |
7 | To test your purchases, you do not need to create an Amazon developer account.
8 |
9 | Install the Amazon App Tester (AAT) :
10 | [Amazon App Tester](https://www.amazon.com/Amazon-App-Tester/dp/B00BN3YZM2)
11 |
12 | You need to create an amazon.sdktester.json file.
13 |
14 | Example : [amazon.sdktester.json](https://github.com/dooboolab/flutter_inapp_purchase/blob/main/ancillary/amazon.sdktester.json)
15 | Edit this to add your own Product Ids.
16 |
17 | Put this file into the kindle sdcard with :
18 |
19 | adb push amazon.sdktester.json /sdcard/
20 |
21 | You can verify if the file is valid in the AAT and view the purchases.
22 |
23 | Add android.permission.INTERNET and com.amazon.device.iap.ResponseReceiver to your AndroidManifest like in the example https://github.com/dooboolab/flutter_inapp_purchase/blob/main/example/android/app/src/main/AndroidManifest.xml.
24 |
25 | Now, when you make a purchase the AAT will intercept, show the purchases screen and allow you to make a purchase. Your app will think a real purchase has been made and you can test the full purchase flow.
26 |
27 | ## Testing Live Purchases
28 | Add your apk into the "Live App Testing" tab. Add your IAP into the "In-App Items" tab. You must fill in your bank details first and submit your IAP so that the status is "live".
29 |
30 | Also with Gradle 3.4.0 or higher you need to take care of the obfuscating. To do so, create a Proguard file named "proguard-rules.pro" in the android/app folder. Into that file put the following content:
31 |
32 | -dontwarn com.amazon.**
33 | -keep class com.amazon.** {*;}
34 | -keepattributes *Annotation*
35 |
36 | Then edit your build.gradle in app level like:
37 |
38 | build.gradle:
39 |
40 | buildTypes {
41 | release {
42 | shrinkResources false
43 | signingConfig signingConfigs.release
44 | minifyEnabled false
45 | useProguard true
46 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
47 | }
48 | }
49 |
50 |
51 | If you need more help, check out https://stackoverflow.com/questions/62833628/how-to-disable-code-obfuscation-in-flutter-release-apk and the amazon part https://developer.amazon.com/de/docs/in-app-purchasing/iap-obfuscate-the-code.html.
52 |
53 | Now your testers will be sent a link to the test version of your app. They can make purchases at no cost to test your app.
54 |
55 | ## Submitting to the Amazon store
56 | Amazon developer accounts are free. I found the Amazon store the easiest to submit to (compared with Googles play & Apple store). Required screenshots are the same size as a Nexus 7 so that is what I used.
57 |
58 | I found the staff who checked my app very helpful (such as providing logcat output on request for example). Text is the same as other stores, except there is an additional up-to 10 bullet point summary of your app you can add.
59 |
60 | When you submit your app to the store there will be a warning that google billing is detected in your code. When you submit your app for approval you can mention in the testing instructions for the Amazon reviewer that you are using a cross-platform tool and the google IAP code is not used. I dont know if this is necessary but my app was approved anyway.
61 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 dooboolab
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # flutter_inapp_purchase
2 |
3 | [](https://pub.dartlang.org/packages/flutter_inapp_purchase)
4 | [](https://github.com/dooboolab/flutter_inapp_purchase/actions/workflows/ci.yml)
5 | [](https://codecov.io/gh/dooboolab/flutter_inapp_purchase)
6 | 
7 |
8 | ## Flutter V2
9 |
10 | This packages is compatible with flutter v2 from `4.0.0`. For those who use older version please use `< 4.0.0`.
11 |
12 | ## Sun Rise :sunrise:
13 |
14 | Since many one of you wanted me to keep working on this plugin in [#93](https://github.com/dooboolab/flutter_inapp_purchase/issues/93), I've decided to keep working on current project. I hope many one of you can help me maintain this. Thank you for all your supports in advance :tada:.
15 |
16 | ~~## Deprecated
17 | I've been maintaining this plugin since there wasn't an official plugin out when I implemented it. I saw in `flutter` github [issue #9591](https://github.com/flutter/flutter/issues/9591) that many people have been waiting for this plugin for more than a year before I've thought of building one. However, there has been an official `Google` plugin rised today which is [in_app_purchase](https://pub.dev/packages/in_app_purchase). Please try to use an official one because you might want to get much prompt support from giant `Google`.
18 | Also, thanks for all your supports that made me stubborn to work hard on this plugin. I've had great experience with all of you and hope we can meet someday with other projects.
19 | I'll leave this project as live for those who need time. I'll also try to merge the new `PR`'s and publish to `pub` if there's any further work given to this repo.~~
20 |
21 | ## What this plugin do
22 |
23 | This is an `In App Purchase` plugin for flutter. This project has been `forked` from [react-native-iap](https://github.com/dooboolab/react-native-iap). We are trying to share same experience of `in-app-purchase` in `flutter` as in `react-native`.
24 | We will keep working on it as time goes by just like we did in `react-native-iap`.
25 |
26 | `PR` is always welcomed.
27 |
28 | ## Breaking Changes
29 |
30 | - Sunrise in `2.0.0` for highly requests from customers on discomfort in what's called an `official` plugin [in_app_purchase](https://pub.dev/packages/in_app_purchase).
31 | - Migrated to Android X in `0.9.0`. Please check the [Migration Guide](#migration-guide).
32 | - There was parameter renaming in `0.5.0` to identify different parameters sent from the device. Please check the readme.
33 |
34 | ## Migration Guide
35 |
36 | To migrate to `0.9.0` you must migrate your Android app to Android X by following the [Migrating to AndroidX Guide](https://developer.android.com/jetpack/androidx/migrate).
37 |
38 | ## Getting Started
39 |
40 | Follow the [Medium Blog](https://medium.com/@dooboolab/flutter-in-app-purchase-7a3fb9345e2a) for the configuration.
41 |
42 | Follow the [Medium Blog](https://medium.com/bosc-tech-labs-private-limited/how-to-implement-subscriptions-in-app-purchase-in-flutter-7ce8906e608a) to add **subscriptions** in app purchase.
43 |
44 | For help getting started with Flutter, view our online
45 | [documentation](https://flutter.io/).
46 |
47 | For help on editing plugin code, view the [documentation](https://flutter.io/developing-packages/#edit-plugin-package).
48 |
49 | ## Methods
50 |
51 | | Func | Param | Return | Description |
52 | | :--------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
53 | | initConnection | | `String` | Prepare IAP module. Must be called on Android before any other purchase flow methods. In ios, it will simply call `canMakePayments` method and return value. |
54 | | getProducts | `List` Product IDs/skus | `List` | Get a list of products (consumable and non-consumable items, but not subscriptions). Note: On iOS versions earlier than 11.2 this method _will_ return subscriptions if they are included in your list of SKUs. This is because we cannot differentiate between IAP products and subscriptions prior to 11.2. |
55 | | getSubscriptions | `List` Subscription IDs/skus | `List` | Get a list of subscriptions. Note: On iOS this method has the same output as `getProducts`. Because iOS does not differentiate between IAP products and subscriptions. |
56 | | getPurchaseHistory | | `List` | Gets an invetory of purchases made by the user regardless of consumption status (where possible) |
57 | | getAvailablePurchases | | `List` | (aka restore purchase) Get all purchases made by the user (either non-consumable, or haven't been consumed yet) |
58 | | getAppStoreInitiatedProducts | | `List` | If the user has initiated a purchase directly on the App Store, the products that the user is attempting to purchase will be returned here. (iOS only) Note: On iOS versions earlier than 11.0 this method will always return an empty list, as the functionality was introduced in v11.0. [See Apple Docs for more info](https://developer.apple.com/documentation/storekit/skpaymenttransactionobserver/2877502-paymentqueue) Always returns an empty list on Android. |
59 | | requestSubscription | `String` sku, `String` oldSkuAndroid?, `int` prorationModeAndroid?, `String` obfuscatedAccountIdAndroid?, `String` obfuscatedProfileIdAndroid?, `String` purchaseTokenAndroid? | Null | Create (request) a subscription to a sku. For upgrading/downgrading subscription on Android pass second parameter with current subscription ID, on iOS this is handled automatically by store. `purchaseUpdatedListener` will receive the result. |
60 | | requestPurchase | `String` sku, `String` obfuscatedAccountIdAndroid?, `String` obfuscatedProfileIdAndroid?, `String` purchaseToken? | Null | Request a purchase. `purchaseUpdatedListener` will receive the result. |
61 | | finishTransactionIOS | `String` purchaseTokenAndroid | `PurchaseResult` | Send finishTransaction call to Apple IAP server. Call this function after receipt validation process |
62 | | acknowledgePurchaseAndroid | `String` purchaseToken | `PurchaseResult` | Acknowledge a product (on Android) for `non-consumable` and `subscription` purchase. No-op on iOS. |
63 | | consumePurchaseAndroid | `String` purchaseToken | `PurchaseResult` | Consume a product (on Android) for `consumable` purchase. No-op on iOS. |
64 | | finishTransaction | `String` purchaseToken, `bool` isConsumable? } | `PurchaseResult` | Send finishTransaction call that abstracts all `acknowledgePurchaseAndroid`, `finishTransactionIOS`, `consumePurchaseAndroid` methods. |
65 | | endConnection | | `String` | End billing connection. |
66 | | consumeAllItems | | `String` | Manually consume all items in android. Do NOT call if you have any non-consumables (one time purchase items). No-op on iOS. |
67 | | validateReceiptIos | `Map` receiptBody, `bool` isTest | `http.Response` | Validate receipt for ios. |
68 | | validateReceiptAndroid | `String` packageName, `String` productId, `String` productToken, `String` accessToken, `bool` isSubscription | `http.Response` | Validate receipt for android. |
69 | | showPromoCodesIOS | | | Show redeem codes in iOS. |
70 | | showInAppMessageAndroid | | | Google Play will show users messaging during grace period and account hold once per day and provide them an opportunity to fix their payment without leaving the app |
71 |
72 | ## Purchase flow in `flutter_inapp_purchase@2.0.0+
73 |
74 | 
75 |
76 | > When you've successfully received result from `purchaseUpdated` listener, you'll have to `verify` the purchase either by `acknowledgePurchaseAndroid`, `consumePurchaseAndroid`, `finishTransactionIOS` depending on the purchase types or platforms. You'll have to use `consumePurchaseAndroid` for `consumable` products and `android` and `acknowledgePurchaseAndroid` for `non-consumable` products either `subscription`. For `ios`, there is no differences in `verifying` purchases. You can just call `finishTransaction`. If you do not verify the purchase, it will be refunded within 3 days to users. We recommend you to `verifyReceipt` first before actually finishing transaction. Lastly, if you want to abstract three different methods into one, consider using `finishTransaction` method.
77 |
78 | ## Data Types
79 |
80 | - IAPItem
81 |
82 | ```dart
83 | final String productId;
84 | final String price;
85 | final String currency;
86 | final String localizedPrice;
87 | final String title;
88 | final String description;
89 | final String introductoryPrice;
90 |
91 | /// ios only
92 | final String subscriptionPeriodNumberIOS;
93 | final String subscriptionPeriodUnitIOS;
94 | final String introductoryPricePaymentModeIOS;
95 | final String introductoryPriceNumberOfPeriodsIOS;
96 | final String introductoryPriceSubscriptionPeriodIOS;
97 |
98 | /// android only
99 | final String subscriptionPeriodAndroid;
100 | final String introductoryPriceCyclesAndroid;
101 | final String introductoryPricePeriodAndroid;
102 | final String freeTrialPeriodAndroid;
103 | final String signatureAndroid;
104 |
105 | final String iconUrl;
106 | final String originalJson;
107 | final String originalPrice;
108 | ```
109 |
110 | - PurchasedItem
111 |
112 | ```dart
113 | final String productId;
114 | final String transactionId;
115 | final DateTime transactionDate;
116 | final String transactionReceipt;
117 | final String purchaseToken;
118 |
119 | // Android only
120 | final String dataAndroid;
121 | final String signatureAndroid;
122 | final bool autoRenewingAndroid;
123 | final bool isAcknowledgedAndroid;
124 | final int purchaseStateAndroid;
125 |
126 | // iOS only
127 | final DateTime originalTransactionDateIOS;
128 | final String originalTransactionIdentifierIOS;
129 | ```
130 |
131 | ## Install
132 |
133 | Add `flutter_inapp_purchase` as a dependency in pubspec.yaml
134 |
135 | For help on adding as a dependency, view the [documentation](https://flutter.io/using-packages/).
136 |
137 | ## Configuring in app purchase
138 |
139 | - Please refer to [Blog](https://medium.com/@dooboolab/react-native-in-app-purchase-121622d26b67).
140 | - [Amazon Kindle Fire](KINDLE.md)
141 |
142 | ## Usage Guide
143 |
144 | #### Android `connect` and `endConnection`
145 |
146 | - You should start the billing service in android to use its funtionalities. We recommend you to use `initConnection` getter method in `initState()`. Note that this step is necessary in `ios` also from `flutter_inapp_purchase@2.0.0+` which will also register the `purchaseUpdated` and `purchaseError` `Stream`.
147 |
148 | ```dart
149 | /// start connection for android
150 | @override
151 | void initState() {
152 | super.initState();
153 | asyncInitState(); // async is not allowed on initState() directly
154 | }
155 |
156 | void asyncInitState() async {
157 | await FlutterInappPurchase.instance.initConnection;
158 | }
159 | ```
160 |
161 | - You should end the billing service in android when you are done with it. Otherwise it will be keep running in background. We recommend to use this feature in `dispose()`.
162 |
163 | - Additionally, we've added `connectionUpdated` stream just in case if you'd like to monitor the connection more thoroughly form `2.0.1`.
164 |
165 | ```
166 | _conectionSubscription = FlutterInappPurchase.connectionUpdated.listen((connected) {
167 | print('connected: $connected');
168 | });
169 | ```
170 |
171 | > You can see how you can use this in detail in `example` project.
172 |
173 | ```dart
174 | /// start connection for android
175 | @override
176 | void dispose() async{
177 | super.dispose();
178 | await FlutterInappPurchase.instance.endConnection;
179 | }
180 | ```
181 |
182 | #### Get IAP items
183 |
184 | ```dart
185 | void getItems () async {
186 | List items = await FlutterInappPurchase.instance.getProducts(_productLists);
187 | for (var item in items) {
188 | print('${item.toString()}');
189 | this._items.add(item);
190 | }
191 | }
192 | ```
193 |
194 | #### Purchase Item
195 |
196 | ```dart
197 | void purchase() {
198 | FlutterInappPurchase.instance.requestPurchase(item.productId);
199 | }
200 | ```
201 |
202 | #### Register listeners to receive purchase
203 |
204 | ```dart
205 | StreamSubscription _purchaseUpdatedSubscription = FlutterInappPurchase.purchaseUpdated.listen((productItem) {
206 | print('purchase-updated: $productItem');
207 | });
208 |
209 | StreamSubscription _purchaseErrorSubscription = FlutterInappPurchase.purchaseError.listen((purchaseError) {
210 | print('purchase-error: $purchaseError');
211 | });
212 | ```
213 |
214 | #### Remove listeners when ending connection
215 |
216 | ```dart
217 | _purchaseUpdatedSubscription.cancel();
218 | _purchaseUpdatedSubscription = null;
219 | _purchaseErrorSubscription.cancel();
220 | _purchaseErrorSubscription = null;
221 | ```
222 |
223 | #### Receipt validation
224 |
225 | From `0.7.1`, we support receipt validation. For Android, you need separate json file from the service account to get the `access_token` from `google-apis`, therefore it is impossible to implement serverless. You should have your own backend and get `access_token`. With `access_token` you can simply call `validateReceiptAndroid` method we implemented. Further reading is [here](https://stackoverflow.com/questions/35127086/android-inapp-purchase-receipt-validation-google-play?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa).
226 | Currently, serverless receipt validation is possible using `validateReceiptIos` method. The first parameter, you should pass `transactionReceipt` which returns after `requestPurchase`. The second parameter, you should pass whether this is `test` environment. If `true`, it will request to `sandbox` and `false` it will request to `production`.
227 |
228 | ```dart
229 | validateReceipt() async {
230 | var receiptBody = {
231 | 'receipt-data': purchased.transactionReceipt,
232 | 'password': '******'
233 | };
234 | const result = await validateReceiptIos(receiptBody, false);
235 | console.log(result);
236 | }
237 | ```
238 |
239 | For further information, please refer to [guide](https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html).
240 |
241 | #### App Store initiated purchases
242 |
243 | When the user starts an in-app purchase in the App Store, the transaction continues in your app, the product will then be added to a list that you can access through the method `getAppStoreInitiatedProducts`. This means you can decide how and when to continue the transaction.
244 | To continue the transaction simple use the standard purchase flow from this plugin.
245 |
246 | ```dart
247 | void checkForAppStoreInitiatedProducts() async {
248 | List appStoreProducts = await FlutterInappPurchase.getAppStoreInitiatedProducts(); // Get list of products
249 | if (appStoreProducts.length > 0) {
250 | _requestPurchase(appStoreProducts.last); // Buy last product in the list
251 | }
252 | }
253 | ```
254 |
255 | ## Example
256 |
257 | Direct to [example readme](example/README.md) which is just a `cp` from example project. You can test this in real example project.
258 |
259 | ## ProGuard
260 |
261 | If you have enabled proguard you will need to add the following rules to your `proguard-rules.pro`
262 |
263 | ```
264 | #In app Purchase
265 | -keep class com.amazon.** {*;}
266 | -keep class com.dooboolab.** { *; }
267 | -keep class com.android.vending.billing.**
268 | -dontwarn com.amazon.**
269 | -keepattributes *Annotation*
270 | ```
271 |
272 | ## Q & A
273 |
274 | #### Can I buy product right away skipping fetching products if I already know productId?
275 |
276 | - You can in `Android` but not in `ios`. In `ios` you should always `fetchProducts` first. You can see more info [here](https://medium.com/ios-development-tips-and-tricks/working-with-ios-in-app-purchases-e4b55491479b).
277 |
278 | #### How do I validate receipt in ios?
279 |
280 | - Official doc is [here](https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html).
281 |
282 | #### How do I validate receipt in android?
283 |
284 | - Offical doc is [here](https://developer.android.com/google/play/billing/billing_library_overview).
285 | - I've developed this feature for other developers to contribute easily who are aware of these things. The doc says you can also get the `accessToken` via play console without any of your backend server. You can get this by following process.
286 | - Select your app > Services & APIs > "YOUR LICENSE KEY FOR THIS APPLICATION Base64-encoded RSA public key to include in your binary". [reference](https://stackoverflow.com/questions/27132443/how-to-find-my-google-play-services-android-base64-public-key).
287 |
288 | #### Invalid productId in ios.
289 |
290 | - Please try below and make sure you've done belows.
291 | - Steps
292 | 1. Completed an effective "Agreements, Tax, and Banking."
293 | 2. Setup sandbox testing account in "Users and Roles."
294 | 3. Signed into iOS device with sandbox account.
295 | 4. Set up three In-App Purchases with the following status:
296 | i. Ready to Submit
297 | ii. Missing Metadata
298 | iii. Waiting for Review
299 | 5. Enable "In-App Purchase" in Xcode "Capabilities" and in Apple Developer -> "App ID" setting. Delete app / Restart device / Quit "store" related processes in Activity Monitor / Xcode Development Provisioning Profile -> Clean -> Build.
300 |
301 | ## Help Maintenance
302 |
303 | I've been maintaining quite many repos these days and burning out slowly. If you could help me cheer up, buying me a cup of coffee will make my life really happy and get much energy out of it.
304 |
305 | [](https://paypal.me/dooboolab)
306 |
307 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | analyzer:
2 | strong-mode:
3 | implicit-casts: true
4 | implicit-dynamic: true
--------------------------------------------------------------------------------
/ancillary/amazon.sdktester.json:
--------------------------------------------------------------------------------
1 | {
2 | "android.test.purchased" : {
3 | "itemType" : "ENTITLED",
4 | "price" : "4.99",
5 | "title": "Test Single Purchase",
6 | "description": "One purchase only",
7 | "smallIconUrl": "./assets/sample.theme.red.jpg"
8 | },
9 | "android.test.canceled" : {
10 | "itemType" : "ENTITLED",
11 | "price" : "0.99",
12 | "title": "Test Single Purchase",
13 | "description": "One purchase only",
14 | "smallIconUrl": "./assets/sample.theme.green.jpg"
15 | },
16 | "point_1000" : {
17 | "itemType" : "SUBSCRIPTION",
18 | "price" : "0.99",
19 | "title": "1000 Points",
20 | "description": "1000 Points to spend",
21 | "smallIconUrl": "./assets/sample.theme.blue.jpg"
22 | },
23 | "5000_point" : {
24 | "itemType": "CONSUMABLE",
25 | "price": "10.00",
26 | "title": "Orange",
27 | "description": "An orange",
28 | "smallIconUrl": "http://www.amazon.com/orange.jpg"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/android/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | android_
4 | Project android_ created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.buildship.core.gradleprojectbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.buildship.core.gradleprojectnature
16 |
17 |
18 |
19 | 1636786187292
20 |
21 | 30
22 |
23 | org.eclipse.core.resources.regexFilterMatcher
24 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/android/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | arguments=
2 | auto.sync=false
3 | build.scans.enabled=false
4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(7.0-rc-1))
5 | connection.project.dir=
6 | eclipse.preferences.version=1
7 | gradle.user.home=
8 | java.home=/Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home
9 | jvm.arguments=
10 | offline.mode=false
11 | override.workspace.settings=true
12 | show.console.view=true
13 | show.executions.view=true
14 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | group 'com.dooboolab.flutterinapppurchase'
2 | version '1.0-SNAPSHOT'
3 |
4 | buildscript {
5 | ext.kotlin_version = '1.8.10'
6 | repositories {
7 | google()
8 | mavenCentral()
9 | }
10 |
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:7.4.2'
13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 | }
15 | }
16 |
17 | rootProject.allprojects {
18 | repositories {
19 | google()
20 | mavenCentral()
21 | }
22 | }
23 |
24 | apply plugin: 'com.android.library'
25 | apply plugin: 'kotlin-android'
26 |
27 | android {
28 | if (project.android.hasProperty('namespace')) {
29 | namespace 'com.dooboolab.flutterinapppurchase'
30 | }
31 | compileSdkVersion 33
32 |
33 | defaultConfig {
34 | minSdkVersion 21
35 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
36 | }
37 | sourceSets {
38 | main.java.srcDirs += 'src/main/kotlin'
39 | }
40 | lintOptions {
41 | disable 'InvalidPackage'
42 | }
43 | compileOptions {
44 | sourceCompatibility JavaVersion.VERSION_1_8
45 | targetCompatibility JavaVersion.VERSION_1_8
46 | }
47 |
48 | kotlinOptions {
49 | jvmTarget = '1.8'
50 | }
51 | }
52 |
53 | dependencies {
54 | def billing_version = "6.0.1"
55 |
56 | implementation "com.android.billingclient:billing-ktx:$billing_version"
57 | implementation files('jars/in-app-purchasing-2.0.76.jar')
58 | implementation 'androidx.annotation:annotation:1.6.0'
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | android.enableJetifier=true
2 | android.useAndroidX=true
3 | org.gradle.jvmargs=-Xmx1536M
4 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
4 | networkTimeout=10000
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
147 | # shellcheck disable=SC3045
148 | MAX_FD=$( ulimit -H -n ) ||
149 | warn "Could not query maximum file descriptor limit"
150 | esac
151 | case $MAX_FD in #(
152 | '' | soft) :;; #(
153 | *)
154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
155 | # shellcheck disable=SC3045
156 | ulimit -n "$MAX_FD" ||
157 | warn "Could not set maximum file descriptor limit to $MAX_FD"
158 | esac
159 | fi
160 |
161 | # Collect all arguments for the java command, stacking in reverse order:
162 | # * args from the command line
163 | # * the main class name
164 | # * -classpath
165 | # * -D...appname settings
166 | # * --module-path (only if needed)
167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
168 |
169 | # For Cygwin or MSYS, switch paths to Windows format before running java
170 | if "$cygwin" || "$msys" ; then
171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
173 |
174 | JAVACMD=$( cygpath --unix "$JAVACMD" )
175 |
176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
177 | for arg do
178 | if
179 | case $arg in #(
180 | -*) false ;; # don't mess with options #(
181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
182 | [ -e "$t" ] ;; #(
183 | *) false ;;
184 | esac
185 | then
186 | arg=$( cygpath --path --ignore --mixed "$arg" )
187 | fi
188 | # Roll the args list around exactly as many times as the number of
189 | # args, so each arg winds up back in the position where it started, but
190 | # possibly modified.
191 | #
192 | # NB: a `for` loop captures its iteration list before it begins, so
193 | # changing the positional parameters here affects neither the number of
194 | # iterations, nor the values presented in `arg`.
195 | shift # remove old arg
196 | set -- "$@" "$arg" # push replacement arg
197 | done
198 | fi
199 |
200 | # Collect all arguments for the java command;
201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
202 | # shell script including quotes and variable substitutions, so put them in
203 | # double quotes to make sure that they get re-expanded; and
204 | # * put everything else in single quotes, so that it's not re-expanded.
205 |
206 | set -- \
207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
208 | -classpath "$CLASSPATH" \
209 | org.gradle.wrapper.GradleWrapperMain \
210 | "$@"
211 |
212 | # Stop when "xargs" is not available.
213 | if ! command -v xargs >/dev/null 2>&1
214 | then
215 | die "xargs is not available"
216 | fi
217 |
218 | # Use "xargs" to parse quoted args.
219 | #
220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
221 | #
222 | # In Bash we could simply go:
223 | #
224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
225 | # set -- "${ARGS[@]}" "$@"
226 | #
227 | # but POSIX shell has neither arrays nor command substitution, so instead we
228 | # post-process each arg (as a line of input to sed) to backslash-escape any
229 | # character that might be a shell metacharacter, then use eval to reverse
230 | # that process (while maintaining the separation between arguments), and wrap
231 | # the whole thing up as a single "set" statement.
232 | #
233 | # This will of course break if any of these variables contains a newline or
234 | # an unmatched quote.
235 | #
236 |
237 | eval "set -- $(
238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
239 | xargs -n1 |
240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
241 | tr '\n' ' '
242 | )" '"$@"'
243 |
244 | exec "$JAVACMD" "$@"
245 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/android/jars/in-app-purchasing-2.0.76.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/android/jars/in-app-purchasing-2.0.76.jar
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'flutter_inapp_purchase'
2 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/dooboolab/flutterinapppurchase/AmazonInappPurchasePlugin.kt:
--------------------------------------------------------------------------------
1 | package com.dooboolab.flutterinapppurchase
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.util.Log
6 | import com.amazon.device.iap.PurchasingListener
7 | import com.amazon.device.iap.PurchasingService
8 | import com.amazon.device.iap.model.*
9 | import io.flutter.plugin.common.MethodCall
10 | import io.flutter.plugin.common.MethodChannel
11 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler
12 | import org.json.JSONArray
13 | import org.json.JSONException
14 | import org.json.JSONObject
15 |
16 | /** AmazonInappPurchasePlugin */
17 | class AmazonInappPurchasePlugin : MethodCallHandler {
18 | private val TAG = "InappPurchasePlugin"
19 | private var safeResult: MethodResultWrapper? = null
20 | private var channel: MethodChannel? = null
21 | private var context: Context? = null
22 | private var activity: Activity? = null
23 | fun setContext(context: Context?) {
24 | this.context = context
25 | }
26 |
27 | fun setActivity(activity: Activity?) {
28 | this.activity = activity
29 | }
30 |
31 | fun setChannel(channel: MethodChannel?) {
32 | this.channel = channel
33 | }
34 |
35 | override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
36 | if(call.method == "getStore"){
37 | result.success(FlutterInappPurchasePlugin.getStore())
38 | return
39 | }
40 |
41 | safeResult = MethodResultWrapper(result, channel!!)
42 |
43 | try {
44 | PurchasingService.registerListener(context, purchasesUpdatedListener)
45 | } catch (e: Exception) {
46 | safeResult!!.error(
47 | call.method,
48 | "Call endConnection method if you want to start over.",
49 | e.message
50 | )
51 | }
52 | when (call.method) {
53 | "initConnection" -> {
54 | PurchasingService.getUserData()
55 | safeResult!!.success("Billing client ready")
56 | }
57 | "endConnection" -> {
58 | safeResult!!.success("Billing client has ended.")
59 | }
60 | "isReady" -> {
61 | safeResult!!.success(true)
62 | }
63 | "showInAppMessages" -> {
64 | safeResult!!.success("in app messages not supported for amazon")
65 | }
66 | "consumeAllItems" -> {
67 | // consumable is a separate type in amazon
68 | safeResult!!.success("no-ops in amazon")
69 | }
70 | "getProducts",
71 | "getSubscriptions" -> {
72 | Log.d(TAG, call.method)
73 | val skus = call.argument>("skus")!!
74 | val productSkus: MutableSet = HashSet()
75 | for (i in skus.indices) {
76 | Log.d(TAG, "Adding " + skus[i])
77 | productSkus.add(skus[i])
78 | }
79 | PurchasingService.getProductData(productSkus)
80 | }
81 | "getAvailableItemsByType" -> {
82 | val type = call.argument("type")
83 | Log.d(TAG, "gaibt=$type")
84 | // NOTE: getPurchaseUpdates doesnt return Consumables which are FULFILLED
85 | if (type == "inapp") {
86 | PurchasingService.getPurchaseUpdates(true)
87 | } else if (type == "subs") {
88 | // Subscriptions are retrieved during inapp, so we just return empty list
89 | safeResult!!.success("[]")
90 | } else {
91 | safeResult!!.notImplemented()
92 | }
93 | }
94 | "getPurchaseHistoryByType" -> {
95 | // No equivalent
96 | safeResult!!.success("[]")
97 | }
98 | "buyItemByType" -> {
99 | val type = call.argument("type")
100 | //val obfuscatedAccountId = call.argument("obfuscatedAccountId")
101 | //val obfuscatedProfileId = call.argument("obfuscatedProfileId")
102 | val sku = call.argument("sku")
103 | val oldSku = call.argument("oldSku")
104 | //val prorationMode = call.argument("prorationMode")!!
105 | Log.d(TAG, "type=$type||sku=$sku||oldsku=$oldSku")
106 | val requestId = PurchasingService.purchase(sku)
107 | Log.d(TAG, "resid=$requestId")
108 | }
109 | "consumeProduct" -> {
110 | // consumable is a separate type in amazon
111 | safeResult!!.success("no-ops in amazon")
112 | }
113 | else -> {
114 | safeResult!!.notImplemented()
115 | }
116 | }
117 | }
118 |
119 | private val purchasesUpdatedListener: PurchasingListener = object : PurchasingListener {
120 | override fun onUserDataResponse(userDataResponse: UserDataResponse) {
121 | Log.d(TAG, "oudr=$userDataResponse")
122 | }
123 |
124 | // getItemsByType
125 | override fun onProductDataResponse(response: ProductDataResponse) {
126 | Log.d(TAG, "opdr=$response")
127 | val status = response.requestStatus
128 | Log.d(TAG, "onProductDataResponse: RequestStatus ($status)")
129 | when (status) {
130 | ProductDataResponse.RequestStatus.SUCCESSFUL -> {
131 | Log.d(
132 | TAG,
133 | "onProductDataResponse: successful. The item data map in this response includes the valid SKUs"
134 | )
135 | val productData = response.productData
136 | //Log.d(TAG, "productData="+productData.toString());
137 | val unavailableSkus = response.unavailableSkus
138 | Log.d(
139 | TAG,
140 | "onProductDataResponse: " + unavailableSkus.size + " unavailable skus"
141 | )
142 | Log.d(TAG, "unavailableSkus=$unavailableSkus")
143 | val items = JSONArray()
144 | try {
145 | for ((_, product) in productData) {
146 | //val format = NumberFormat.getCurrencyInstance()
147 | val item = JSONObject()
148 | item.put("productId", product.sku)
149 | item.put("price", product.price)
150 | item.put("currency", null)
151 | when (product.productType) {
152 | ProductType.ENTITLED, ProductType.CONSUMABLE -> item.put(
153 | "type",
154 | "inapp"
155 | )
156 | ProductType.SUBSCRIPTION -> item.put("type", "subs")
157 | }
158 | item.put("localizedPrice", product.price)
159 | item.put("title", product.title)
160 | item.put("description", product.description)
161 | item.put("introductoryPrice", "")
162 | item.put("subscriptionPeriodAndroid", "")
163 | item.put("freeTrialPeriodAndroid", "")
164 | item.put("introductoryPriceCyclesAndroid", 0)
165 | item.put("introductoryPricePeriodAndroid", "")
166 | Log.d(TAG, "opdr Putting $item")
167 | items.put(item)
168 | }
169 | //System.err.println("Sending "+items.toString());
170 | safeResult!!.success(items.toString())
171 | } catch (e: JSONException) {
172 | safeResult!!.error(TAG, "E_BILLING_RESPONSE_JSON_PARSE_ERROR", e.message)
173 | }
174 | }
175 | ProductDataResponse.RequestStatus.FAILED -> {
176 | safeResult!!.error(TAG, "FAILED", null)
177 | Log.d(TAG, "onProductDataResponse: failed, should retry request")
178 | safeResult!!.error(TAG, "NOT_SUPPORTED", null)
179 | }
180 | ProductDataResponse.RequestStatus.NOT_SUPPORTED -> {
181 | Log.d(TAG, "onProductDataResponse: failed, should retry request")
182 | safeResult!!.error(TAG, "NOT_SUPPORTED", null)
183 | }
184 | }
185 | }
186 |
187 | // buyItemByType
188 | override fun onPurchaseResponse(response: PurchaseResponse) {
189 | Log.d(TAG, "opr=$response")
190 | when (val status = response.requestStatus) {
191 | PurchaseResponse.RequestStatus.SUCCESSFUL -> {
192 | val receipt = response.receipt
193 | PurchasingService.notifyFulfillment(
194 | receipt.receiptId,
195 | FulfillmentResult.FULFILLED
196 | )
197 | val date = receipt.purchaseDate
198 | val transactionDate = date.time
199 | try {
200 | val item = getPurchaseData(
201 | receipt.sku,
202 | receipt.receiptId,
203 | receipt.receiptId,
204 | transactionDate.toDouble()
205 | )
206 | Log.d(TAG, "opr Putting $item")
207 | safeResult!!.success(item.toString())
208 | safeResult!!.invokeMethod("purchase-updated", item.toString())
209 | } catch (e: JSONException) {
210 | safeResult!!.error(TAG, "E_BILLING_RESPONSE_JSON_PARSE_ERROR", e.message)
211 | }
212 | }
213 | PurchaseResponse.RequestStatus.FAILED -> safeResult!!.error(
214 | TAG,
215 | "buyItemByType",
216 | "billingResponse is not ok: $status"
217 | )
218 | else -> {}
219 | }
220 | }
221 |
222 | // getAvailableItemsByType
223 | override fun onPurchaseUpdatesResponse(response: PurchaseUpdatesResponse) {
224 | Log.d(TAG, "opudr=$response")
225 | when (response.requestStatus) {
226 | PurchaseUpdatesResponse.RequestStatus.SUCCESSFUL -> {
227 | val items = JSONArray()
228 | try {
229 | val receipts = response.receipts
230 | for (receipt in receipts) {
231 | val date = receipt.purchaseDate
232 | val transactionDate = date.time
233 | val item = getPurchaseData(
234 | receipt.sku,
235 | receipt.receiptId,
236 | receipt.receiptId,
237 | transactionDate.toDouble()
238 | )
239 | Log.d(TAG, "opudr Putting $item")
240 | items.put(item)
241 | }
242 | safeResult!!.success(items.toString())
243 | } catch (e: JSONException) {
244 | safeResult!!.error(TAG, "E_BILLING_RESPONSE_JSON_PARSE_ERROR", e.message)
245 | }
246 | }
247 | PurchaseUpdatesResponse.RequestStatus.FAILED -> safeResult!!.error(
248 | TAG,
249 | "FAILED",
250 | null
251 | )
252 | PurchaseUpdatesResponse.RequestStatus.NOT_SUPPORTED -> {
253 | Log.d(TAG, "onPurchaseUpdatesResponse: failed, should retry request")
254 | safeResult!!.error(TAG, "NOT_SUPPORTED", null)
255 | }
256 | }
257 | }
258 | }
259 |
260 | @Throws(JSONException::class)
261 | fun getPurchaseData(
262 | productId: String?, transactionId: String?, transactionReceipt: String?,
263 | transactionDate: Double?
264 | ): JSONObject {
265 | val item = JSONObject()
266 | item.put("productId", productId)
267 | item.put("transactionId", transactionId)
268 | item.put("transactionReceipt", transactionReceipt)
269 | item.put("transactionDate", (transactionDate!!).toString())
270 | item.put("dataAndroid", null)
271 | item.put("signatureAndroid", null)
272 | item.put("purchaseToken", null)
273 | return item
274 | }
275 | }
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/dooboolab/flutterinapppurchase/BillingError.kt:
--------------------------------------------------------------------------------
1 | package com.dooboolab.flutterinapppurchase
2 |
3 | import android.util.Log
4 | import com.android.billingclient.api.BillingClient.BillingResponseCode
5 |
6 | class BillingError {
7 | companion object {
8 | private const val TAG = "DoobooUtils"
9 | private const val E_UNKNOWN = "E_UNKNOWN"
10 | const val E_NOT_PREPARED = "E_NOT_PREPARED"
11 | private const val E_NOT_ENDED = "E_NOT_ENDED"
12 | private const val E_USER_CANCELLED = "E_USER_CANCELLED"
13 | private const val E_ITEM_UNAVAILABLE = "E_ITEM_UNAVAILABLE"
14 | private const val E_NETWORK_ERROR = "E_NETWORK_ERROR"
15 | private const val E_SERVICE_ERROR = "E_SERVICE_ERROR"
16 | private const val E_ALREADY_OWNED = "E_ALREADY_OWNED"
17 | private const val E_REMOTE_ERROR = "E_REMOTE_ERROR"
18 | private const val E_USER_ERROR = "E_USER_ERROR"
19 | private const val E_DEVELOPER_ERROR = "E_DEVELOPER_ERROR"
20 | const val E_BILLING_RESPONSE_JSON_PARSE_ERROR = "E_BILLING_RESPONSE_JSON_PARSE_ERROR"
21 |
22 | fun getErrorFromResponseData(responseCode: Int): ErrorData {
23 | Log.e(TAG, "Error Code : $responseCode")
24 | return when (responseCode) {
25 | BillingResponseCode.FEATURE_NOT_SUPPORTED ->
26 | ErrorData(E_SERVICE_ERROR,"This feature is not available on your device.")
27 | BillingResponseCode.SERVICE_DISCONNECTED ->
28 | ErrorData(E_NETWORK_ERROR, "The service is disconnected (check your internet connection.)")
29 | BillingResponseCode.OK -> ErrorData("OK","")
30 | BillingResponseCode.USER_CANCELED ->
31 | ErrorData(E_USER_CANCELLED, "Payment is Cancelled.")
32 | BillingResponseCode.SERVICE_UNAVAILABLE ->
33 | ErrorData(E_SERVICE_ERROR, "The service is unreachable. This may be your internet connection, or the Play Store may be down.")
34 | BillingResponseCode.BILLING_UNAVAILABLE ->
35 | ErrorData(E_SERVICE_ERROR, "Billing is unavailable. This may be a problem with your device, or the Play Store may be down.")
36 | BillingResponseCode.ITEM_UNAVAILABLE ->
37 | ErrorData( E_ITEM_UNAVAILABLE, "That item is unavailable.")
38 | BillingResponseCode.DEVELOPER_ERROR ->
39 | ErrorData(E_DEVELOPER_ERROR, "Google is indicating that we have some issue connecting to payment.")
40 | BillingResponseCode.ERROR ->
41 | ErrorData(E_UNKNOWN,"An unknown or unexpected error has occured. Please try again later.")
42 | BillingResponseCode.ITEM_ALREADY_OWNED ->
43 | ErrorData(E_ALREADY_OWNED, "You already own this item.")
44 | else -> ErrorData(E_UNKNOWN,"Purchase failed with code: $responseCode")
45 | }
46 | }
47 | }
48 | }
49 |
50 | data class ErrorData(val code: String, val message: String)
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/dooboolab/flutterinapppurchase/FlutterInappPurchasePlugin.kt:
--------------------------------------------------------------------------------
1 | package com.dooboolab.flutterinapppurchase
2 |
3 | import io.flutter.embedding.engine.plugins.FlutterPlugin
4 | import io.flutter.embedding.engine.plugins.activity.ActivityAware
5 | import android.content.Context
6 | import io.flutter.plugin.common.MethodChannel
7 | import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
8 | import io.flutter.plugin.common.BinaryMessenger
9 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
10 | import android.content.pm.PackageManager.NameNotFoundException
11 |
12 | /** FlutterInappPurchasePlugin */
13 | class FlutterInappPurchasePlugin : FlutterPlugin, ActivityAware {
14 | private var androidInappPurchasePlugin: AndroidInappPurchasePlugin? = null
15 | private var amazonInappPurchasePlugin: AmazonInappPurchasePlugin? = null
16 | private var channel: MethodChannel? = null
17 | override fun onAttachedToEngine(binding: FlutterPluginBinding) {
18 | onAttached(binding.applicationContext, binding.binaryMessenger)
19 | }
20 |
21 | private fun onAttached(context: Context, messenger: BinaryMessenger) {
22 | isAndroid = isPackageInstalled(context, "com.android.vending")
23 | isAmazon = isPackageInstalled(context, "com.amazon.venezia")
24 |
25 | // In the case of an amazon device which has been side loaded with the Google Play store,
26 | // we should use the store the app was installed from.
27 | if (isAmazon && isAndroid) {
28 | if (isAppInstalledFrom(context, "amazon")) {
29 | isAndroid = false
30 | } else {
31 | isAmazon = false
32 | }
33 | }
34 | channel = MethodChannel(messenger, "flutter_inapp")
35 | if (isAndroid) {
36 | androidInappPurchasePlugin = AndroidInappPurchasePlugin()
37 | androidInappPurchasePlugin!!.setContext(context)
38 | androidInappPurchasePlugin!!.setChannel(channel)
39 | channel!!.setMethodCallHandler(androidInappPurchasePlugin)
40 | } else if (isAmazon) {
41 | amazonInappPurchasePlugin = AmazonInappPurchasePlugin()
42 | amazonInappPurchasePlugin!!.setContext(context)
43 | amazonInappPurchasePlugin!!.setChannel(channel)
44 | channel!!.setMethodCallHandler(amazonInappPurchasePlugin)
45 | }
46 | }
47 |
48 | override fun onDetachedFromEngine(binding: FlutterPluginBinding) {
49 | channel!!.setMethodCallHandler(null)
50 | channel = null
51 | if (isAndroid) {
52 | androidInappPurchasePlugin!!.setChannel(null)
53 | } else if (isAmazon) {
54 | amazonInappPurchasePlugin!!.setChannel(null)
55 | }
56 | }
57 |
58 | override fun onAttachedToActivity(binding: ActivityPluginBinding) {
59 | if (isAndroid) {
60 | androidInappPurchasePlugin!!.setActivity(binding.activity)
61 | } else if (isAmazon) {
62 | amazonInappPurchasePlugin!!.setActivity(binding.activity)
63 | }
64 | }
65 |
66 | override fun onDetachedFromActivity() {
67 | if (isAndroid) {
68 | androidInappPurchasePlugin!!.setActivity(null)
69 | androidInappPurchasePlugin!!.onDetachedFromActivity()
70 | } else if (isAmazon) {
71 | amazonInappPurchasePlugin!!.setActivity(null)
72 | }
73 | }
74 |
75 | override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
76 | onAttachedToActivity(binding)
77 | }
78 |
79 | override fun onDetachedFromActivityForConfigChanges() {
80 | onDetachedFromActivity()
81 | }
82 |
83 | private fun setAndroidInappPurchasePlugin(androidInappPurchasePlugin: AndroidInappPurchasePlugin) {
84 | this.androidInappPurchasePlugin = androidInappPurchasePlugin
85 | }
86 |
87 | private fun setAmazonInappPurchasePlugin(amazonInappPurchasePlugin: AmazonInappPurchasePlugin) {
88 | this.amazonInappPurchasePlugin = amazonInappPurchasePlugin
89 | }
90 |
91 | companion object {
92 | private var isAndroid = false
93 | private var isAmazon = false
94 |
95 | fun getStore(): String {
96 | return if (!isAndroid && !isAmazon) "none" else if (isAndroid) "play_store" else "amazon"
97 | }
98 |
99 | private fun isPackageInstalled(ctx: Context, packageName: String): Boolean {
100 | return try {
101 | ctx.packageManager.getPackageInfo(packageName, 0)
102 | true
103 | } catch (e: NameNotFoundException) {
104 | false
105 | }
106 | }
107 |
108 | fun isAppInstalledFrom(ctx: Context, installer: String?): Boolean {
109 | val installerPackageName = ctx.packageManager.getInstallerPackageName(ctx.packageName)
110 | return installer != null && installerPackageName != null && installerPackageName.contains(
111 | installer
112 | )
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/dooboolab/flutterinapppurchase/MethodResultWrapper.kt:
--------------------------------------------------------------------------------
1 | package com.dooboolab.flutterinapppurchase
2 |
3 | import android.os.Handler
4 | import io.flutter.plugin.common.MethodChannel
5 | import android.os.Looper
6 |
7 | // MethodChannel.Result wrapper that responds on the platform thread.
8 | class MethodResultWrapper internal constructor(
9 | private val safeResult: MethodChannel.Result,
10 | private val safeChannel: MethodChannel
11 | ) : MethodChannel.Result {
12 | private val handler: Handler = Handler(Looper.getMainLooper())
13 | private var exhausted: Boolean = false
14 | override fun success(result: Any?) {
15 | if (!exhausted) {
16 | exhausted = true
17 |
18 | handler.post { safeResult.success(result) }
19 | }
20 | }
21 |
22 | override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
23 | if (!exhausted) {
24 | exhausted = true
25 |
26 | handler.post { safeResult.error(errorCode, errorMessage, errorDetails) }
27 | }
28 | }
29 |
30 | override fun notImplemented() {
31 | if (!exhausted) {
32 | exhausted = true
33 |
34 | handler.post { safeResult.notImplemented() }
35 | }
36 | }
37 |
38 | fun invokeMethod(method: String?, arguments: Any?) {
39 | handler.post { safeChannel.invokeMethod(method!!, arguments, null) }
40 | }
41 |
42 | }
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .dart_tool/
3 |
4 | .packages
5 | .pub/
6 |
7 | build/
8 |
9 | .flutter-plugins
10 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 66091f969653fd3535b265ddcd87436901858a1d
8 | channel: dev
9 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # flutter_inapp_purchase_example
2 |
3 | Demonstrates how to use the flutter_inapp_purchase plugin.
4 |
5 | ## Getting Started
6 |
7 | For help getting started with Flutter, view our online
8 | [documentation](https://flutter.io/).
9 |
10 | ## Example
11 | Below code is just a `cp` from example project. You can test this in real example project.
12 | ```dart
13 | class InApp extends StatefulWidget {
14 | @override
15 | _InAppState createState() => new _InAppState();
16 | }
17 |
18 | class _InAppState extends State {
19 | StreamSubscription _purchaseUpdatedSubscription;
20 | StreamSubscription _purchaseErrorSubscription;
21 | final List _productLists = Platform.isAndroid
22 | ? [
23 | 'android.test.purchased',
24 | 'point_1000',
25 | '5000_point',
26 | 'android.test.canceled',
27 | ]
28 | : ['com.cooni.point1000', 'com.cooni.point5000'];
29 |
30 | String _platformVersion = 'Unknown';
31 | List _items = [];
32 | List _purchases = [];
33 |
34 | @override
35 | void initState() {
36 | super.initState();
37 | initPlatformState();
38 | }
39 |
40 | // Platform messages are asynchronous, so we initialize in an async method.
41 | Future initPlatformState() async {
42 | String platformVersion;
43 | // Platform messages may fail, so we use a try/catch PlatformException.
44 | try {
45 | platformVersion = await FlutterInappPurchase.instance.platformVersion;
46 | } on PlatformException {
47 | platformVersion = 'Failed to get platform version.';
48 | }
49 |
50 | // prepare
51 | var result = await FlutterInappPurchase.instance.initConnection;
52 | print('result: $result');
53 |
54 | // If the widget was removed from the tree while the asynchronous platform
55 | // message was in flight, we want to discard the reply rather than calling
56 | // setState to update our non-existent appearance.
57 | if (!mounted) return;
58 |
59 | setState(() {
60 | _platformVersion = platformVersion;
61 | });
62 |
63 | // refresh items for android
64 | try {
65 | String msg = await FlutterInappPurchase.instance.consumeAllItems;
66 | print('consumeAllItems: $msg');
67 | } catch (err) {
68 | print('consumeAllItems error: $err');
69 | }
70 |
71 | _purchaseUpdatedSubscription = FlutterInappPurchase.purchaseUpdated.listen((productItem) {
72 | print('purchase-updated: $productItem');
73 | });
74 |
75 | _purchaseErrorSubscription = FlutterInappPurchase.purchaseError.listen((purchaseError) {
76 | print('purchase-error: $purchaseError');
77 | });
78 | }
79 |
80 | void _requestPurchase(IAPItem item) {
81 | FlutterInappPurchase.instance.requestPurchase(item.productId);
82 | }
83 |
84 | Future _getProduct() async {
85 | List items = await FlutterInappPurchase.instance.getProducts(_productLists);
86 | for (var item in items) {
87 | print('${item.toString()}');
88 | this._items.add(item);
89 | }
90 |
91 | setState(() {
92 | this._items = items;
93 | this._purchases = [];
94 | });
95 | }
96 |
97 | Future _getPurchases() async {
98 | List items =
99 | await FlutterInappPurchase.instance.getAvailablePurchases();
100 | for (var item in items) {
101 | print('${item.toString()}');
102 | this._purchases.add(item);
103 | }
104 |
105 | setState(() {
106 | this._items = [];
107 | this._purchases = items;
108 | });
109 | }
110 |
111 | Future _getPurchaseHistory() async {
112 | List items = await FlutterInappPurchase.instance.getPurchaseHistory();
113 | for (var item in items) {
114 | print('${item.toString()}');
115 | this._purchases.add(item);
116 | }
117 |
118 | setState(() {
119 | this._items = [];
120 | this._purchases = items;
121 | });
122 | }
123 |
124 | List _renderInApps() {
125 | List widgets = this
126 | ._items
127 | .map((item) => Container(
128 | margin: EdgeInsets.symmetric(vertical: 10.0),
129 | child: Container(
130 | child: Column(
131 | children: [
132 | Container(
133 | margin: EdgeInsets.only(bottom: 5.0),
134 | child: Text(
135 | item.toString(),
136 | style: TextStyle(
137 | fontSize: 18.0,
138 | color: Colors.black,
139 | ),
140 | ),
141 | ),
142 | FlatButton(
143 | color: Colors.orange,
144 | onPressed: () {
145 | print("---------- Buy Item Button Pressed");
146 | this._requestPurchase(item);
147 | },
148 | child: Row(
149 | children: [
150 | Expanded(
151 | child: Container(
152 | height: 48.0,
153 | alignment: Alignment(-1.0, 0.0),
154 | child: Text('Buy Item'),
155 | ),
156 | ),
157 | ],
158 | ),
159 | ),
160 | ],
161 | ),
162 | ),
163 | ))
164 | .toList();
165 | return widgets;
166 | }
167 |
168 | List _renderPurchases() {
169 | List widgets = this
170 | ._purchases
171 | .map((item) => Container(
172 | margin: EdgeInsets.symmetric(vertical: 10.0),
173 | child: Container(
174 | child: Column(
175 | children: [
176 | Container(
177 | margin: EdgeInsets.only(bottom: 5.0),
178 | child: Text(
179 | item.toString(),
180 | style: TextStyle(
181 | fontSize: 18.0,
182 | color: Colors.black,
183 | ),
184 | ),
185 | )
186 | ],
187 | ),
188 | ),
189 | ))
190 | .toList();
191 | return widgets;
192 | }
193 |
194 | @override
195 | Widget build(BuildContext context) {
196 | double screenWidth = MediaQuery.of(context).size.width-20;
197 | double buttonWidth=(screenWidth/3)-20;
198 |
199 | return Container(
200 | padding: EdgeInsets.all(10.0),
201 | child: ListView(
202 | children: [
203 | Column(
204 | crossAxisAlignment: CrossAxisAlignment.start,
205 | mainAxisAlignment: MainAxisAlignment.start,
206 | children: [
207 | Container(
208 | child: Text(
209 | 'Running on: $_platformVersion\n',
210 | style: TextStyle(fontSize: 18.0),
211 | ),
212 | ),
213 | Column(
214 | children: [
215 | Row(
216 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
217 | children: [
218 | Container(
219 | width: buttonWidth,
220 | height: 60.0,
221 | margin: EdgeInsets.all(7.0),
222 | child: FlatButton(
223 | color: Colors.amber,
224 | padding: EdgeInsets.all(0.0),
225 | onPressed: () async {
226 | print("---------- Connect Billing Button Pressed");
227 | await FlutterInappPurchase.instance.initConnection;
228 | },
229 | child: Container(
230 | padding: EdgeInsets.symmetric(horizontal: 20.0),
231 | alignment: Alignment(0.0, 0.0),
232 | child: Text(
233 | 'Connect Billing',
234 | style: TextStyle(
235 | fontSize: 16.0,
236 | ),
237 | ),
238 | ),
239 | ),
240 | ),
241 | Container(
242 | width: buttonWidth,
243 | height: 60.0,
244 | margin: EdgeInsets.all(7.0),
245 | child: FlatButton(
246 | color: Colors.amber,
247 | padding: EdgeInsets.all(0.0),
248 | onPressed: () async {
249 | print("---------- End Connection Button Pressed");
250 | await FlutterInappPurchase.instance.endConnection;
251 | _purchaseUpdatedSubscription.cancel();
252 | _purchaseUpdatedSubscription = null;
253 | _purchaseErrorSubscription.cancel();
254 | _purchaseErrorSubscription = null;
255 | setState(() {
256 | this._items = [];
257 | this._purchases = [];
258 | });
259 | },
260 | child: Container(
261 | padding: EdgeInsets.symmetric(horizontal: 20.0),
262 | alignment: Alignment(0.0, 0.0),
263 | child: Text(
264 | 'End Connection',
265 | style: TextStyle(
266 | fontSize: 16.0,
267 | ),
268 | ),
269 | ),
270 | ),
271 | ),
272 | ],
273 | ),
274 | Row(
275 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
276 | children: [
277 | Container(
278 | width: buttonWidth,
279 | height: 60.0,
280 | margin: EdgeInsets.all(7.0),
281 | child: FlatButton(
282 | color: Colors.green,
283 | padding: EdgeInsets.all(0.0),
284 | onPressed: () {
285 | print("---------- Get Items Button Pressed");
286 | this._getProduct();
287 | },
288 | child: Container(
289 | padding: EdgeInsets.symmetric(horizontal: 20.0),
290 | alignment: Alignment(0.0, 0.0),
291 | child: Text(
292 | 'Get Items',
293 | style: TextStyle(
294 | fontSize: 16.0,
295 | ),
296 | ),
297 | ),
298 | )),
299 | Container(
300 | width: buttonWidth,
301 | height: 60.0,
302 | margin: EdgeInsets.all(7.0),
303 | child: FlatButton(
304 | color: Colors.green,
305 | padding: EdgeInsets.all(0.0),
306 | onPressed: () {
307 | print(
308 | "---------- Get Purchases Button Pressed");
309 | this._getPurchases();
310 | },
311 | child: Container(
312 | padding: EdgeInsets.symmetric(horizontal: 20.0),
313 | alignment: Alignment(0.0, 0.0),
314 | child: Text(
315 | 'Get Purchases',
316 | style: TextStyle(
317 | fontSize: 16.0,
318 | ),
319 | ),
320 | ),
321 | )),
322 | Container(
323 | width: buttonWidth,
324 | height: 60.0,
325 | margin: EdgeInsets.all(7.0),
326 | child: FlatButton(
327 | color: Colors.green,
328 | padding: EdgeInsets.all(0.0),
329 | onPressed: () {
330 | print(
331 | "---------- Get Purchase History Button Pressed");
332 | this._getPurchaseHistory();
333 | },
334 | child: Container(
335 | padding: EdgeInsets.symmetric(horizontal: 20.0),
336 | alignment: Alignment(0.0, 0.0),
337 | child: Text(
338 | 'Get Purchase History',
339 | style: TextStyle(
340 | fontSize: 16.0,
341 | ),
342 | ),
343 | ),
344 | )),
345 | ]),
346 | ],
347 | ),
348 | Column(
349 | children: this._renderInApps(),
350 | ),
351 | Column(
352 | children: this._renderPurchases(),
353 | ),
354 | ],
355 | ),
356 | ],
357 | ),
358 | );
359 | }
360 | }
361 | ```
362 |
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.class
3 | .gradle
4 | /local.properties
5 | /.idea/workspace.xml
6 | /.idea/libraries
7 | .DS_Store
8 | /build
9 | /captures
10 | GeneratedPluginRegistrant.java
11 |
--------------------------------------------------------------------------------
/example/android/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | android
4 | Project android created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.buildship.core.gradleprojectbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.buildship.core.gradleprojectnature
16 |
17 |
18 |
19 | 1636786187287
20 |
21 | 30
22 |
23 | org.eclipse.core.resources.regexFilterMatcher
24 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/example/android/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | arguments=
2 | auto.sync=false
3 | build.scans.enabled=false
4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
5 | connection.project.dir=
6 | eclipse.preferences.version=1
7 | gradle.user.home=
8 | java.home=/Library/Java/JavaVirtualMachines/zulu-11.jdk/Contents/Home
9 | jvm.arguments=
10 | offline.mode=false
11 | override.workspace.settings=true
12 | show.console.view=true
13 | show.executions.view=true
14 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | throw new GradleException("versionCode not found. Define flutter.versionCode in the local.properties file.")
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | throw new GradleException("versionName not found. Define flutter.versionName in the local.properties file.")
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
26 |
27 | android {
28 | compileSdkVersion 33
29 |
30 | lintOptions {
31 | disable 'InvalidPackage'
32 | }
33 |
34 | defaultConfig {
35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
36 | applicationId "com.dooboolab.flutterinapppurchaseexample"
37 | minSdkVersion 21
38 | targetSdkVersion 33
39 | versionCode flutterVersionCode.toInteger()
40 | versionName flutterVersionName
41 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
42 | }
43 |
44 | buildTypes {
45 | release {
46 | // TODO: Add your own signing config for the release build.
47 | // Signing with the debug keys for now, so `flutter run --release` works.
48 | signingConfig signingConfigs.debug
49 | }
50 | }
51 | }
52 |
53 | flutter {
54 | source '../..'
55 | }
56 |
57 | dependencies {
58 | testImplementation 'junit:junit:4.13.2'
59 | androidTestImplementation 'androidx.test:runner:1.5.2'
60 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
61 | }
62 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
15 |
18 |
26 |
27 |
28 |
32 |
35 |
38 |
39 |
40 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
54 |
55 |
57 |
58 |
59 |
60 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/dooboolab/iapexample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.dooboolab.iapexample;
2 |
3 | import android.os.Bundle;
4 | import io.flutter.embedding.android.FlutterActivity;
5 |
6 | public class MainActivity extends FlutterActivity {
7 | @Override
8 | protected void onCreate(Bundle savedInstanceState) {
9 | super.onCreate(savedInstanceState);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/dooboolab/test/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.dooboolab.test;
2 |
3 | import android.os.Bundle;
4 | import io.flutter.embedding.android.FlutterActivity;
5 |
6 | public class MainActivity extends FlutterActivity {
7 | @Override
8 | protected void onCreate(Bundle savedInstanceState) {
9 | super.onCreate(savedInstanceState);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:7.4.2'
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | google()
15 | jcenter()
16 | }
17 | }
18 |
19 | rootProject.buildDir = '../build'
20 | subprojects {
21 | project.buildDir = "${rootProject.buildDir}/${project.name}"
22 | }
23 | subprojects {
24 | project.evaluationDependsOn(':app')
25 | }
26 |
27 | tasks.register("clean", Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | android.enableJetifier=true
2 | android.useAndroidX=true
3 | org.gradle.jvmargs=-Xmx1536M
4 | android.enableR8=true
5 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip
7 |
--------------------------------------------------------------------------------
/example/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/example/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
--------------------------------------------------------------------------------
/example/flutter_inapp_purchase_example.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/example/flutter_inapp_purchase_example_android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vagrant/
3 | .sconsign.dblite
4 | .svn/
5 |
6 | .DS_Store
7 | *.swp
8 | profile
9 |
10 | DerivedData/
11 | build/
12 | GeneratedPluginRegistrant.h
13 | GeneratedPluginRegistrant.m
14 |
15 | .generated/
16 |
17 | *.pbxuser
18 | *.mode1v3
19 | *.mode2v3
20 | *.perspectivev3
21 |
22 | !default.pbxuser
23 | !default.mode1v3
24 | !default.mode2v3
25 | !default.perspectivev3
26 |
27 | xcuserdata
28 |
29 | *.moved-aside
30 |
31 | *.pyc
32 | *sync/
33 | Icon?
34 | .tags*
35 |
36 | /Flutter/app.flx
37 | /Flutter/app.zip
38 | /Flutter/flutter_assets/
39 | /Flutter/App.framework
40 | /Flutter/Flutter.framework
41 | /Flutter/Generated.xcconfig
42 | /ServiceDefinitions.json
43 |
44 | Pods/
45 | .symlinks/
46 |
--------------------------------------------------------------------------------
/example/ios/Flutter/.last_build_id:
--------------------------------------------------------------------------------
1 | 64b7d48f089c80732795ca9f2fc67c24
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Flutter.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # This podspec is NOT to be published. It is only used as a local source!
3 | # This is a generated file; do not edit or check into version control.
4 | #
5 |
6 | Pod::Spec.new do |s|
7 | s.name = 'Flutter'
8 | s.version = '1.0.0'
9 | s.summary = 'A UI toolkit for beautiful and fast apps.'
10 | s.homepage = 'https://flutter.dev'
11 | s.license = { :type => 'BSD' }
12 | s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
13 | s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }
14 | s.ios.deployment_target = '11.0'
15 | # Framework linking is handled by Flutter tooling, not CocoaPods.
16 | # Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs.
17 | s.vendored_frameworks = 'path/to/nothing'
18 | end
19 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '11.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
32 | end
33 |
34 | post_install do |installer|
35 | installer.pods_project.targets.each do |target|
36 | flutter_additional_ios_build_settings(target)
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/example/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - flutter_inapp_purchase (0.0.1):
4 | - Flutter
5 |
6 | DEPENDENCIES:
7 | - Flutter (from `Flutter`)
8 | - flutter_inapp_purchase (from `.symlinks/plugins/flutter_inapp_purchase/ios`)
9 |
10 | EXTERNAL SOURCES:
11 | Flutter:
12 | :path: Flutter
13 | flutter_inapp_purchase:
14 | :path: ".symlinks/plugins/flutter_inapp_purchase/ios"
15 |
16 | SPEC CHECKSUMS:
17 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
18 | flutter_inapp_purchase: 5c6a1ac3f11b11d0c8c0321c0c41c1f05805e4c8
19 |
20 | PODFILE CHECKSUM: 663715e941f9adb426e33bf9376914006f9ea95b
21 |
22 | COCOAPODS: 1.13.0
23 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 54;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 27672194C9D5FA8DDDF731B8 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A72A37C715E27BACEF745D36 /* libPods-Runner.a */; };
12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
13 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
14 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; };
15 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
16 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
17 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
18 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
19 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
20 | /* End PBXBuildFile section */
21 |
22 | /* Begin PBXCopyFilesBuildPhase section */
23 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
24 | isa = PBXCopyFilesBuildPhase;
25 | buildActionMask = 2147483647;
26 | dstPath = "";
27 | dstSubfolderSpec = 10;
28 | files = (
29 | );
30 | name = "Embed Frameworks";
31 | runOnlyForDeploymentPostprocessing = 0;
32 | };
33 | /* End PBXCopyFilesBuildPhase section */
34 |
35 | /* Begin PBXFileReference section */
36 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
37 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
38 | 1D70DAC946071716E420D20E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
39 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
40 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
41 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
42 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
43 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
44 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
45 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
46 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
47 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
48 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
49 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
50 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
51 | A72A37C715E27BACEF745D36 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
52 | FF0FE51B5618DEE6A1B14EAD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
53 | /* End PBXFileReference section */
54 |
55 | /* Begin PBXFrameworksBuildPhase section */
56 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
57 | isa = PBXFrameworksBuildPhase;
58 | buildActionMask = 2147483647;
59 | files = (
60 | 27672194C9D5FA8DDDF731B8 /* libPods-Runner.a in Frameworks */,
61 | );
62 | runOnlyForDeploymentPostprocessing = 0;
63 | };
64 | /* End PBXFrameworksBuildPhase section */
65 |
66 | /* Begin PBXGroup section */
67 | 296F2EDE9AD48161597CA429 /* Frameworks */ = {
68 | isa = PBXGroup;
69 | children = (
70 | A72A37C715E27BACEF745D36 /* libPods-Runner.a */,
71 | );
72 | name = Frameworks;
73 | sourceTree = "";
74 | };
75 | 9740EEB11CF90186004384FC /* Flutter */ = {
76 | isa = PBXGroup;
77 | children = (
78 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
79 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
80 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
81 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
82 | );
83 | name = Flutter;
84 | sourceTree = "";
85 | };
86 | 97C146E51CF9000F007C117D = {
87 | isa = PBXGroup;
88 | children = (
89 | 9740EEB11CF90186004384FC /* Flutter */,
90 | 97C146F01CF9000F007C117D /* Runner */,
91 | 97C146EF1CF9000F007C117D /* Products */,
92 | C7B32ECD7F7FE068F924AA78 /* Pods */,
93 | 296F2EDE9AD48161597CA429 /* Frameworks */,
94 | );
95 | sourceTree = "";
96 | };
97 | 97C146EF1CF9000F007C117D /* Products */ = {
98 | isa = PBXGroup;
99 | children = (
100 | 97C146EE1CF9000F007C117D /* Runner.app */,
101 | );
102 | name = Products;
103 | sourceTree = "";
104 | };
105 | 97C146F01CF9000F007C117D /* Runner */ = {
106 | isa = PBXGroup;
107 | children = (
108 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
109 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
110 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
111 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
112 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
113 | 97C147021CF9000F007C117D /* Info.plist */,
114 | 97C146F11CF9000F007C117D /* Supporting Files */,
115 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
116 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
117 | );
118 | path = Runner;
119 | sourceTree = "";
120 | };
121 | 97C146F11CF9000F007C117D /* Supporting Files */ = {
122 | isa = PBXGroup;
123 | children = (
124 | 97C146F21CF9000F007C117D /* main.m */,
125 | );
126 | name = "Supporting Files";
127 | sourceTree = "";
128 | };
129 | C7B32ECD7F7FE068F924AA78 /* Pods */ = {
130 | isa = PBXGroup;
131 | children = (
132 | 1D70DAC946071716E420D20E /* Pods-Runner.debug.xcconfig */,
133 | FF0FE51B5618DEE6A1B14EAD /* Pods-Runner.release.xcconfig */,
134 | );
135 | name = Pods;
136 | sourceTree = "";
137 | };
138 | /* End PBXGroup section */
139 |
140 | /* Begin PBXNativeTarget section */
141 | 97C146ED1CF9000F007C117D /* Runner */ = {
142 | isa = PBXNativeTarget;
143 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
144 | buildPhases = (
145 | 6D038B862F639D466FC324F4 /* [CP] Check Pods Manifest.lock */,
146 | 9740EEB61CF901F6004384FC /* Run Script */,
147 | 97C146EA1CF9000F007C117D /* Sources */,
148 | 97C146EB1CF9000F007C117D /* Frameworks */,
149 | 97C146EC1CF9000F007C117D /* Resources */,
150 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
151 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
152 | );
153 | buildRules = (
154 | );
155 | dependencies = (
156 | );
157 | name = Runner;
158 | productName = Runner;
159 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
160 | productType = "com.apple.product-type.application";
161 | };
162 | /* End PBXNativeTarget section */
163 |
164 | /* Begin PBXProject section */
165 | 97C146E61CF9000F007C117D /* Project object */ = {
166 | isa = PBXProject;
167 | attributes = {
168 | LastUpgradeCheck = 1430;
169 | ORGANIZATIONNAME = "The Chromium Authors";
170 | TargetAttributes = {
171 | 97C146ED1CF9000F007C117D = {
172 | CreatedOnToolsVersion = 7.3.1;
173 | DevelopmentTeam = LCXV255WTL;
174 | };
175 | };
176 | };
177 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
178 | compatibilityVersion = "Xcode 3.2";
179 | developmentRegion = English;
180 | hasScannedForEncodings = 0;
181 | knownRegions = (
182 | en,
183 | Base,
184 | );
185 | mainGroup = 97C146E51CF9000F007C117D;
186 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
187 | projectDirPath = "";
188 | projectRoot = "";
189 | targets = (
190 | 97C146ED1CF9000F007C117D /* Runner */,
191 | );
192 | };
193 | /* End PBXProject section */
194 |
195 | /* Begin PBXResourcesBuildPhase section */
196 | 97C146EC1CF9000F007C117D /* Resources */ = {
197 | isa = PBXResourcesBuildPhase;
198 | buildActionMask = 2147483647;
199 | files = (
200 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
201 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */,
202 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
203 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
204 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
205 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
206 | );
207 | runOnlyForDeploymentPostprocessing = 0;
208 | };
209 | /* End PBXResourcesBuildPhase section */
210 |
211 | /* Begin PBXShellScriptBuildPhase section */
212 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
213 | isa = PBXShellScriptBuildPhase;
214 | alwaysOutOfDate = 1;
215 | buildActionMask = 2147483647;
216 | files = (
217 | );
218 | inputPaths = (
219 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
220 | );
221 | name = "Thin Binary";
222 | outputPaths = (
223 | );
224 | runOnlyForDeploymentPostprocessing = 0;
225 | shellPath = /bin/sh;
226 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
227 | };
228 | 6D038B862F639D466FC324F4 /* [CP] Check Pods Manifest.lock */ = {
229 | isa = PBXShellScriptBuildPhase;
230 | buildActionMask = 2147483647;
231 | files = (
232 | );
233 | inputPaths = (
234 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
235 | "${PODS_ROOT}/Manifest.lock",
236 | );
237 | name = "[CP] Check Pods Manifest.lock";
238 | outputPaths = (
239 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
240 | );
241 | runOnlyForDeploymentPostprocessing = 0;
242 | shellPath = /bin/sh;
243 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
244 | showEnvVarsInLog = 0;
245 | };
246 | 9740EEB61CF901F6004384FC /* Run Script */ = {
247 | isa = PBXShellScriptBuildPhase;
248 | alwaysOutOfDate = 1;
249 | buildActionMask = 2147483647;
250 | files = (
251 | );
252 | inputPaths = (
253 | );
254 | name = "Run Script";
255 | outputPaths = (
256 | );
257 | runOnlyForDeploymentPostprocessing = 0;
258 | shellPath = /bin/sh;
259 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
260 | };
261 | /* End PBXShellScriptBuildPhase section */
262 |
263 | /* Begin PBXSourcesBuildPhase section */
264 | 97C146EA1CF9000F007C117D /* Sources */ = {
265 | isa = PBXSourcesBuildPhase;
266 | buildActionMask = 2147483647;
267 | files = (
268 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
269 | 97C146F31CF9000F007C117D /* main.m in Sources */,
270 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
271 | );
272 | runOnlyForDeploymentPostprocessing = 0;
273 | };
274 | /* End PBXSourcesBuildPhase section */
275 |
276 | /* Begin PBXVariantGroup section */
277 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
278 | isa = PBXVariantGroup;
279 | children = (
280 | 97C146FB1CF9000F007C117D /* Base */,
281 | );
282 | name = Main.storyboard;
283 | sourceTree = "";
284 | };
285 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
286 | isa = PBXVariantGroup;
287 | children = (
288 | 97C147001CF9000F007C117D /* Base */,
289 | );
290 | name = LaunchScreen.storyboard;
291 | sourceTree = "";
292 | };
293 | /* End PBXVariantGroup section */
294 |
295 | /* Begin XCBuildConfiguration section */
296 | 97C147031CF9000F007C117D /* Debug */ = {
297 | isa = XCBuildConfiguration;
298 | buildSettings = {
299 | ALWAYS_SEARCH_USER_PATHS = NO;
300 | CLANG_ANALYZER_NONNULL = YES;
301 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
302 | CLANG_CXX_LIBRARY = "libc++";
303 | CLANG_ENABLE_MODULES = YES;
304 | CLANG_ENABLE_OBJC_ARC = YES;
305 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
306 | CLANG_WARN_BOOL_CONVERSION = YES;
307 | CLANG_WARN_COMMA = YES;
308 | CLANG_WARN_CONSTANT_CONVERSION = YES;
309 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
310 | CLANG_WARN_EMPTY_BODY = YES;
311 | CLANG_WARN_ENUM_CONVERSION = YES;
312 | CLANG_WARN_INFINITE_RECURSION = YES;
313 | CLANG_WARN_INT_CONVERSION = YES;
314 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
315 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
316 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
317 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
318 | CLANG_WARN_STRICT_PROTOTYPES = YES;
319 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
320 | CLANG_WARN_UNREACHABLE_CODE = YES;
321 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
322 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
323 | COPY_PHASE_STRIP = NO;
324 | DEBUG_INFORMATION_FORMAT = dwarf;
325 | ENABLE_STRICT_OBJC_MSGSEND = YES;
326 | ENABLE_TESTABILITY = YES;
327 | GCC_C_LANGUAGE_STANDARD = gnu99;
328 | GCC_DYNAMIC_NO_PIC = NO;
329 | GCC_NO_COMMON_BLOCKS = YES;
330 | GCC_OPTIMIZATION_LEVEL = 0;
331 | GCC_PREPROCESSOR_DEFINITIONS = (
332 | "DEBUG=1",
333 | "$(inherited)",
334 | );
335 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
336 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
337 | GCC_WARN_UNDECLARED_SELECTOR = YES;
338 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
339 | GCC_WARN_UNUSED_FUNCTION = YES;
340 | GCC_WARN_UNUSED_VARIABLE = YES;
341 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
342 | MTL_ENABLE_DEBUG_INFO = YES;
343 | ONLY_ACTIVE_ARCH = YES;
344 | SDKROOT = iphoneos;
345 | TARGETED_DEVICE_FAMILY = "1,2";
346 | };
347 | name = Debug;
348 | };
349 | 97C147041CF9000F007C117D /* Release */ = {
350 | isa = XCBuildConfiguration;
351 | buildSettings = {
352 | ALWAYS_SEARCH_USER_PATHS = NO;
353 | CLANG_ANALYZER_NONNULL = YES;
354 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
355 | CLANG_CXX_LIBRARY = "libc++";
356 | CLANG_ENABLE_MODULES = YES;
357 | CLANG_ENABLE_OBJC_ARC = YES;
358 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
359 | CLANG_WARN_BOOL_CONVERSION = YES;
360 | CLANG_WARN_COMMA = YES;
361 | CLANG_WARN_CONSTANT_CONVERSION = YES;
362 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
363 | CLANG_WARN_EMPTY_BODY = YES;
364 | CLANG_WARN_ENUM_CONVERSION = YES;
365 | CLANG_WARN_INFINITE_RECURSION = YES;
366 | CLANG_WARN_INT_CONVERSION = YES;
367 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
368 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
369 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
370 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
371 | CLANG_WARN_STRICT_PROTOTYPES = YES;
372 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
373 | CLANG_WARN_UNREACHABLE_CODE = YES;
374 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
375 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
376 | COPY_PHASE_STRIP = NO;
377 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
378 | ENABLE_NS_ASSERTIONS = NO;
379 | ENABLE_STRICT_OBJC_MSGSEND = YES;
380 | GCC_C_LANGUAGE_STANDARD = gnu99;
381 | GCC_NO_COMMON_BLOCKS = YES;
382 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
383 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
384 | GCC_WARN_UNDECLARED_SELECTOR = YES;
385 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
386 | GCC_WARN_UNUSED_FUNCTION = YES;
387 | GCC_WARN_UNUSED_VARIABLE = YES;
388 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
389 | MTL_ENABLE_DEBUG_INFO = NO;
390 | SDKROOT = iphoneos;
391 | TARGETED_DEVICE_FAMILY = "1,2";
392 | VALIDATE_PRODUCT = YES;
393 | };
394 | name = Release;
395 | };
396 | 97C147061CF9000F007C117D /* Debug */ = {
397 | isa = XCBuildConfiguration;
398 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
399 | buildSettings = {
400 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
401 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
402 | DEVELOPMENT_TEAM = LCXV255WTL;
403 | ENABLE_BITCODE = NO;
404 | FRAMEWORK_SEARCH_PATHS = (
405 | "$(inherited)",
406 | "$(PROJECT_DIR)/Flutter",
407 | );
408 | INFOPLIST_FILE = Runner/Info.plist;
409 | LD_RUNPATH_SEARCH_PATHS = (
410 | "$(inherited)",
411 | "@executable_path/Frameworks",
412 | );
413 | LIBRARY_SEARCH_PATHS = (
414 | "$(inherited)",
415 | "$(PROJECT_DIR)/Flutter",
416 | );
417 | PRODUCT_BUNDLE_IDENTIFIER = com.dooboolab.test;
418 | PRODUCT_NAME = "$(TARGET_NAME)";
419 | VERSIONING_SYSTEM = "apple-generic";
420 | };
421 | name = Debug;
422 | };
423 | 97C147071CF9000F007C117D /* Release */ = {
424 | isa = XCBuildConfiguration;
425 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
426 | buildSettings = {
427 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
428 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
429 | DEVELOPMENT_TEAM = LCXV255WTL;
430 | ENABLE_BITCODE = NO;
431 | FRAMEWORK_SEARCH_PATHS = (
432 | "$(inherited)",
433 | "$(PROJECT_DIR)/Flutter",
434 | );
435 | INFOPLIST_FILE = Runner/Info.plist;
436 | LD_RUNPATH_SEARCH_PATHS = (
437 | "$(inherited)",
438 | "@executable_path/Frameworks",
439 | );
440 | LIBRARY_SEARCH_PATHS = (
441 | "$(inherited)",
442 | "$(PROJECT_DIR)/Flutter",
443 | );
444 | PRODUCT_BUNDLE_IDENTIFIER = com.dooboolab.test;
445 | PRODUCT_NAME = "$(TARGET_NAME)";
446 | VERSIONING_SYSTEM = "apple-generic";
447 | };
448 | name = Release;
449 | };
450 | /* End XCBuildConfiguration section */
451 |
452 | /* Begin XCConfigurationList section */
453 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
454 | isa = XCConfigurationList;
455 | buildConfigurations = (
456 | 97C147031CF9000F007C117D /* Debug */,
457 | 97C147041CF9000F007C117D /* Release */,
458 | );
459 | defaultConfigurationIsVisible = 0;
460 | defaultConfigurationName = Release;
461 | };
462 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
463 | isa = XCConfigurationList;
464 | buildConfigurations = (
465 | 97C147061CF9000F007C117D /* Debug */,
466 | 97C147071CF9000F007C117D /* Release */,
467 | );
468 | defaultConfigurationIsVisible = 0;
469 | defaultConfigurationName = Release;
470 | };
471 | /* End XCConfigurationList section */
472 | };
473 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
474 | }
475 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
68 |
69 |
70 |
71 |
72 |
73 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : FlutterAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #include "AppDelegate.h"
2 | #include "GeneratedPluginRegistrant.h"
3 |
4 | @implementation AppDelegate
5 |
6 | - (BOOL)application:(UIApplication *)application
7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
8 | [GeneratedPluginRegistrant registerWithRegistry:self];
9 | // Override point for customization after application launch.
10 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
11 | }
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | flutter_inapp_purchase_example
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 | CADisableMinimumFrameDurationOnPhone
45 |
46 | UIApplicationSupportsIndirectInputEvents
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/example/ios/Runner/main.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char* argv[]) {
6 | @autoreleasepool {
7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'dart:async';
3 | import 'dart:io';
4 |
5 | import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
6 |
7 | void main() => runApp(new MyApp());
8 |
9 | class MyApp extends StatelessWidget {
10 | @override
11 | Widget build(BuildContext context) {
12 | return MaterialApp(
13 | title: 'Flutter Demo',
14 | home: Scaffold(
15 | appBar: AppBar(
16 | title: Text('Flutter Inapp Plugin by dooboolab'),
17 | ),
18 | body: InApp()),
19 | );
20 | }
21 | }
22 |
23 | class InApp extends StatefulWidget {
24 | @override
25 | _InAppState createState() => new _InAppState();
26 | }
27 |
28 | class _InAppState extends State {
29 | late dynamic _purchaseUpdatedSubscription;
30 | late dynamic _purchaseErrorSubscription;
31 | late dynamic _connectionSubscription;
32 | final List _productLists = Platform.isAndroid
33 | ? [
34 | 'android.test.purchased',
35 | 'point_1000',
36 | '5000_point',
37 | 'android.test.canceled',
38 | ]
39 | : ['com.cooni.point1000', 'com.cooni.point5000'];
40 |
41 | List _items = [];
42 | List _purchases = [];
43 |
44 | @override
45 | void initState() {
46 | super.initState();
47 | initPlatformState();
48 | }
49 |
50 | @override
51 | void dispose() {
52 | if (_connectionSubscription != null) {
53 | _connectionSubscription.cancel();
54 | _connectionSubscription = null;
55 | }
56 | super.dispose();
57 | }
58 |
59 | // Platform messages are asynchronous, so we initialize in an async method.
60 | Future initPlatformState() async {
61 | // prepare
62 | var result = await FlutterInappPurchase.instance.initialize();
63 | print('result: $result');
64 |
65 | // If the widget was removed from the tree while the asynchronous platform
66 | // message was in flight, we want to discard the reply rather than calling
67 | // setState to update our non-existent appearance.
68 | if (!mounted) return;
69 |
70 | // refresh items for android
71 | try {
72 | String msg = await FlutterInappPurchase.instance.consumeAll();
73 | print('consumeAllItems: $msg');
74 | } catch (err) {
75 | print('consumeAllItems error: $err');
76 | }
77 |
78 | _connectionSubscription =
79 | FlutterInappPurchase.connectionUpdated.listen((connected) {
80 | print('connected: $connected');
81 | });
82 |
83 | _purchaseUpdatedSubscription =
84 | FlutterInappPurchase.purchaseUpdated.listen((productItem) {
85 | print('purchase-updated: $productItem');
86 | });
87 |
88 | _purchaseErrorSubscription =
89 | FlutterInappPurchase.purchaseError.listen((purchaseError) {
90 | print('purchase-error: $purchaseError');
91 | });
92 | }
93 |
94 | void _requestPurchase(IAPItem item) {
95 | FlutterInappPurchase.instance.requestPurchase(item.productId!);
96 | }
97 |
98 | Future _getProduct() async {
99 | List items =
100 | await FlutterInappPurchase.instance.getProducts(_productLists);
101 | for (var item in items) {
102 | print('${item.toString()}');
103 | this._items.add(item);
104 | }
105 |
106 | setState(() {
107 | this._items = items;
108 | this._purchases = [];
109 | });
110 | }
111 |
112 | Future _getPurchases() async {
113 | List? items =
114 | await FlutterInappPurchase.instance.getAvailablePurchases();
115 | for (var item in items!) {
116 | print('${item.toString()}');
117 | this._purchases.add(item);
118 | }
119 |
120 | setState(() {
121 | this._items = [];
122 | this._purchases = items;
123 | });
124 | }
125 |
126 | Future _getPurchaseHistory() async {
127 | List? items =
128 | await FlutterInappPurchase.instance.getPurchaseHistory();
129 | for (var item in items!) {
130 | print('${item.toString()}');
131 | this._purchases.add(item);
132 | }
133 |
134 | setState(() {
135 | this._items = [];
136 | this._purchases = items;
137 | });
138 | }
139 |
140 | List _renderInApps() {
141 | List widgets = this
142 | ._items
143 | .map((item) => Container(
144 | margin: EdgeInsets.symmetric(vertical: 10.0),
145 | child: Container(
146 | child: Column(
147 | children: [
148 | Container(
149 | margin: EdgeInsets.only(bottom: 5.0),
150 | child: Text(
151 | item.toString(),
152 | style: TextStyle(
153 | fontSize: 18.0,
154 | color: Colors.black,
155 | ),
156 | ),
157 | ),
158 | MaterialButton(
159 | color: Colors.orange,
160 | onPressed: () {
161 | print("---------- Buy Item Button Pressed");
162 | this._requestPurchase(item);
163 | },
164 | child: Row(
165 | children: [
166 | Expanded(
167 | child: Container(
168 | height: 48.0,
169 | alignment: Alignment(-1.0, 0.0),
170 | child: Text('Buy Item'),
171 | ),
172 | ),
173 | ],
174 | ),
175 | ),
176 | ],
177 | ),
178 | ),
179 | ))
180 | .toList();
181 | return widgets;
182 | }
183 |
184 | List _renderPurchases() {
185 | List widgets = this
186 | ._purchases
187 | .map((item) => Container(
188 | margin: EdgeInsets.symmetric(vertical: 10.0),
189 | child: Container(
190 | child: Column(
191 | children: [
192 | Container(
193 | margin: EdgeInsets.only(bottom: 5.0),
194 | child: Text(
195 | item.toString(),
196 | style: TextStyle(
197 | fontSize: 18.0,
198 | color: Colors.black,
199 | ),
200 | ),
201 | )
202 | ],
203 | ),
204 | ),
205 | ))
206 | .toList();
207 | return widgets;
208 | }
209 |
210 | @override
211 | Widget build(BuildContext context) {
212 | double screenWidth = MediaQuery.of(context).size.width - 20;
213 | double buttonWidth = (screenWidth / 3) - 20;
214 |
215 | return Container(
216 | padding: EdgeInsets.all(10.0),
217 | child: ListView(
218 | children: [
219 | Column(
220 | crossAxisAlignment: CrossAxisAlignment.start,
221 | mainAxisAlignment: MainAxisAlignment.start,
222 | children: [
223 | Container(
224 | child: Text(
225 | 'Running on: ${Platform.operatingSystem} - ${Platform.operatingSystemVersion}\n',
226 | style: TextStyle(fontSize: 18.0),
227 | ),
228 | ),
229 | Column(
230 | children: [
231 | Row(
232 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
233 | children: [
234 | Container(
235 | width: buttonWidth,
236 | height: 60.0,
237 | margin: EdgeInsets.all(7.0),
238 | child: MaterialButton(
239 | color: Colors.amber,
240 | padding: EdgeInsets.all(0.0),
241 | onPressed: () async {
242 | print("---------- Connect Billing Button Pressed");
243 | await FlutterInappPurchase.instance.initialize();
244 | },
245 | child: Container(
246 | padding: EdgeInsets.symmetric(horizontal: 20.0),
247 | alignment: Alignment(0.0, 0.0),
248 | child: Text(
249 | 'Connect Billing',
250 | style: TextStyle(
251 | fontSize: 16.0,
252 | ),
253 | ),
254 | ),
255 | ),
256 | ),
257 | Container(
258 | width: buttonWidth,
259 | height: 60.0,
260 | margin: EdgeInsets.all(7.0),
261 | child: MaterialButton(
262 | color: Colors.amber,
263 | padding: EdgeInsets.all(0.0),
264 | onPressed: () async {
265 | print("---------- End Connection Button Pressed");
266 | await FlutterInappPurchase.instance.finalize();
267 | if (_purchaseUpdatedSubscription != null) {
268 | _purchaseUpdatedSubscription.cancel();
269 | _purchaseUpdatedSubscription = null;
270 | }
271 | if (_purchaseErrorSubscription != null) {
272 | _purchaseErrorSubscription.cancel();
273 | _purchaseErrorSubscription = null;
274 | }
275 | setState(() {
276 | this._items = [];
277 | this._purchases = [];
278 | });
279 | },
280 | child: Container(
281 | padding: EdgeInsets.symmetric(horizontal: 20.0),
282 | alignment: Alignment(0.0, 0.0),
283 | child: Text(
284 | 'End Connection',
285 | style: TextStyle(
286 | fontSize: 16.0,
287 | ),
288 | ),
289 | ),
290 | ),
291 | ),
292 | ],
293 | ),
294 | Row(
295 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
296 | children: [
297 | Container(
298 | width: buttonWidth,
299 | height: 60.0,
300 | margin: EdgeInsets.all(7.0),
301 | child: MaterialButton(
302 | color: Colors.green,
303 | padding: EdgeInsets.all(0.0),
304 | onPressed: () {
305 | print("---------- Get Items Button Pressed");
306 | this._getProduct();
307 | },
308 | child: Container(
309 | padding: EdgeInsets.symmetric(horizontal: 20.0),
310 | alignment: Alignment(0.0, 0.0),
311 | child: Text(
312 | 'Get Items',
313 | style: TextStyle(
314 | fontSize: 16.0,
315 | ),
316 | ),
317 | ),
318 | )),
319 | Container(
320 | width: buttonWidth,
321 | height: 60.0,
322 | margin: EdgeInsets.all(7.0),
323 | child: MaterialButton(
324 | color: Colors.green,
325 | padding: EdgeInsets.all(0.0),
326 | onPressed: () {
327 | print(
328 | "---------- Get Purchases Button Pressed");
329 | this._getPurchases();
330 | },
331 | child: Container(
332 | padding: EdgeInsets.symmetric(horizontal: 20.0),
333 | alignment: Alignment(0.0, 0.0),
334 | child: Text(
335 | 'Get Purchases',
336 | style: TextStyle(
337 | fontSize: 16.0,
338 | ),
339 | ),
340 | ),
341 | )),
342 | Container(
343 | width: buttonWidth,
344 | height: 60.0,
345 | margin: EdgeInsets.all(7.0),
346 | child: MaterialButton(
347 | color: Colors.green,
348 | padding: EdgeInsets.all(0.0),
349 | onPressed: () {
350 | print(
351 | "---------- Get Purchase History Button Pressed");
352 | this._getPurchaseHistory();
353 | },
354 | child: Container(
355 | padding: EdgeInsets.symmetric(horizontal: 20.0),
356 | alignment: Alignment(0.0, 0.0),
357 | child: Text(
358 | 'Get Purchase History',
359 | style: TextStyle(
360 | fontSize: 16.0,
361 | ),
362 | ),
363 | ),
364 | )),
365 | ]),
366 | ],
367 | ),
368 | Column(
369 | children: this._renderInApps(),
370 | ),
371 | Column(
372 | children: this._renderPurchases(),
373 | ),
374 | ],
375 | ),
376 | ],
377 | ),
378 | );
379 | }
380 | }
381 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_inapp_purchase_example
2 | description: Demonstrates how to use the flutter_inapp_purchase plugin.
3 |
4 | # The following defines the version and build number for your application.
5 | # A version number is three numbers separated by dots, like 1.2.43
6 | # followed by an optional build number separated by a +.
7 | # Both the version and the builder number may be overridden in flutter
8 | # build by specifying --build-name and --build-number, respectively.
9 | # Read more about versioning at semver.org.
10 | version: 1.0.0+1
11 |
12 | environment:
13 | sdk: ">=2.12.0 <3.0.0"
14 | flutter: ">=2.0.0"
15 |
16 | dependencies:
17 | flutter:
18 | sdk: flutter
19 |
20 | # The following adds the Cupertino Icons font to your application.
21 | # Use with the CupertinoIcons class for iOS style icons.
22 | cupertino_icons: ^1.0.0
23 |
24 | dev_dependencies:
25 | flutter_test:
26 | sdk: flutter
27 |
28 | flutter_inapp_purchase:
29 | path: ../
30 |
31 | # For information on the generic Dart part of this file, see the
32 | # following page: https://www.dartlang.org/tools/pub/pubspec
33 |
34 | # The following section is specific to Flutter.
35 | flutter:
36 |
37 | # The following line ensures that the Material Icons font is
38 | # included with your application, so that you can use the icons in
39 | # the material Icons class.
40 | uses-material-design: true
41 |
42 | # To add assets to your application, add an assets section, like this:
43 | # assets:
44 | # - images/a_dot_burr.jpeg
45 | # - images/a_dot_ham.jpeg
46 |
47 | # An image asset can refer to one or more resolution-specific "variants", see
48 | # https://flutter.io/assets-and-images/#resolution-aware.
49 |
50 | # For details regarding adding assets from package dependencies, see
51 | # https://flutter.io/assets-and-images/#from-packages
52 |
53 | # To add custom fonts to your application, add a fonts section here,
54 | # in this "flutter" section. Each entry in this list should have a
55 | # "family" key with the font family name, and a "fonts" key with a
56 | # list giving the asset and other descriptors for the font. For
57 | # example:
58 | # fonts:
59 | # - family: Schyler
60 | # fonts:
61 | # - asset: fonts/Schyler-Regular.ttf
62 | # - asset: fonts/Schyler-Italic.ttf
63 | # style: italic
64 | # - family: Trajan Pro
65 | # fonts:
66 | # - asset: fonts/TrajanPro.ttf
67 | # - asset: fonts/TrajanPro_Bold.ttf
68 | # weight: 700
69 | #
70 | # For details regarding fonts from package dependencies,
71 | # see https://flutter.io/custom-fonts/#from-packages
72 |
--------------------------------------------------------------------------------
/example/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter
3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to
4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties
5 | // are correct.
6 |
7 | // import 'package:flutter/material.dart';
8 | // import 'package:flutter_test/flutter_test.dart';
9 |
10 | // import 'package:flutter_inapp_purchase_example/main.dart';
11 |
12 | void main() {
13 | // testWidgets('Verify Platform version', (WidgetTester tester) async {
14 | // // Build our app and trigger a frame.
15 | // await tester.pumpWidget(new MyApp());
16 |
17 | // // Verify that platform version is retrieved.
18 | // expect(
19 | // find.byWidgetPredicate(
20 | // (Widget widget) =>
21 | // widget is Text && widget.data.startsWith('Running on:'),
22 | // ),
23 | // findsOneWidget);
24 | // });
25 | }
26 |
--------------------------------------------------------------------------------
/flutter_inapp_purchase_android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vagrant/
3 | .sconsign.dblite
4 | .svn/
5 |
6 | .DS_Store
7 | *.swp
8 | profile
9 |
10 | DerivedData/
11 | build/
12 | GeneratedPluginRegistrant.h
13 | GeneratedPluginRegistrant.m
14 |
15 | .generated/
16 |
17 | *.pbxuser
18 | *.mode1v3
19 | *.mode2v3
20 | *.perspectivev3
21 |
22 | !default.pbxuser
23 | !default.mode1v3
24 | !default.mode2v3
25 | !default.perspectivev3
26 |
27 | xcuserdata
28 |
29 | *.moved-aside
30 |
31 | *.pyc
32 | *sync/
33 | Icon?
34 | .tags*
35 |
36 | /Flutter/Generated.xcconfig
37 |
--------------------------------------------------------------------------------
/ios/Assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyochan/flutter_inapp_purchase/e93bc132ae5a28a2f20f98786384a04f88bb588e/ios/Assets/.gitkeep
--------------------------------------------------------------------------------
/ios/Classes/FlutterInappPurchasePlugin.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface FlutterInappPurchasePlugin : NSObject{
5 | SKProductsRequest *productsRequest;
6 | NSMutableArray *validProducts;
7 | }
8 | @end
9 |
--------------------------------------------------------------------------------
/ios/Classes/IAPPromotionObserver.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @protocol IAPPromotionObserverDelegate;
4 |
5 | @interface IAPPromotionObserver: NSObject
6 |
7 | @property (strong, nonatomic, readonly) SKPayment *payment;
8 | @property (strong, nonatomic, readonly) SKProduct *product;
9 | @property (weak, nonatomic) id delegate;
10 |
11 | + (instancetype)sharedObserver;
12 | + (void)startObserving;
13 |
14 | @end
15 |
16 | @protocol IAPPromotionObserverDelegate
17 |
18 | @required
19 | - (BOOL)shouldAddStorePayment:(SKPayment *)payment forProduct:(SKProduct *)product;
20 |
21 | @end
22 |
--------------------------------------------------------------------------------
/ios/Classes/IAPPromotionObserver.m:
--------------------------------------------------------------------------------
1 | #import "IAPPromotionObserver.h"
2 |
3 | @interface IAPPromotionObserver () {
4 | SKPayment *_promotedPayment;
5 | SKProduct *_promotedProduct;
6 | }
7 |
8 | @end
9 |
10 | @implementation IAPPromotionObserver
11 |
12 | + (instancetype)sharedObserver {
13 | static IAPPromotionObserver *sharedInstance = nil;
14 | static dispatch_once_t onceToken;
15 |
16 | dispatch_once(&onceToken, ^{
17 | sharedInstance = [[IAPPromotionObserver alloc] init];
18 | });
19 | return sharedInstance;
20 | }
21 |
22 | + (void)startObserving {
23 | [IAPPromotionObserver sharedObserver];
24 | }
25 |
26 | - (SKPayment *)payment {
27 | return _promotedPayment;
28 | }
29 |
30 | - (SKProduct *)product {
31 | return _promotedProduct;
32 | }
33 |
34 | - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
35 | }
36 |
37 | - (BOOL)paymentQueue:(SKPaymentQueue *)queue shouldAddStorePayment:(SKPayment *)payment forProduct:(SKProduct *)product {
38 | _promotedProduct = product;
39 | _promotedPayment = payment;
40 |
41 | if (self.delegate != nil && [self.delegate respondsToSelector:@selector(shouldAddStorePayment:forProduct:)]) {
42 | return [self.delegate shouldAddStorePayment:payment forProduct:product];
43 | }
44 | return NO;
45 | }
46 |
47 | @end
48 |
--------------------------------------------------------------------------------
/ios/flutter_inapp_purchase.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
3 | #
4 | Pod::Spec.new do |s|
5 | s.name = 'flutter_inapp_purchase'
6 | s.version = '0.0.1'
7 | s.summary = 'In App Purchase plugin for flutter. This project has been forked by react-native-iap and we are willing to share same experience with that on react-native.'
8 | s.description = <<-DESC
9 | In App Purchase plugin for flutter. This project has been forked by react-native-iap and we are willing to share same experience with that on react-native.
10 | DESC
11 | s.homepage = 'http://example.com'
12 | s.license = { :file => '../LICENSE' }
13 | s.author = { 'Your Company' => 'email@example.com' }
14 | s.source = { :path => '.' }
15 | s.source_files = 'Classes/**/*'
16 | s.public_header_files = 'Classes/**/*.h'
17 | s.dependency 'Flutter'
18 |
19 | s.ios.deployment_target = '8.0'
20 | end
21 |
22 |
--------------------------------------------------------------------------------
/issue_template.md:
--------------------------------------------------------------------------------
1 | ### Version of flutter_inapp_purchase
2 |
3 | ### Platforms you faced the error (IOS or Android or both?)
4 |
5 | ### Expected behavior
6 |
7 | ### Actual behavior
8 |
9 | ### Tested environment (Emulator? Real Device?)
10 |
11 | ### Steps to reproduce the behavior
12 |
--------------------------------------------------------------------------------
/lib/Store.dart:
--------------------------------------------------------------------------------
1 | enum Store { none, playStore, amazon, appStore }
2 |
--------------------------------------------------------------------------------
/lib/modules.dart:
--------------------------------------------------------------------------------
1 | enum ResponseCodeAndroid {
2 | BILLING_RESPONSE_RESULT_OK,
3 | BILLING_RESPONSE_RESULT_USER_CANCELED,
4 | BILLING_RESPONSE_RESULT_SERVICE_UNAVAILABLE,
5 | BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
6 | BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE,
7 | BILLING_RESPONSE_RESULT_DEVELOPER_ERROR,
8 | BILLING_RESPONSE_RESULT_ERROR,
9 | BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED,
10 | BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED,
11 | UNKNOWN,
12 | }
13 |
14 | /// An item available for purchase from either the `Google Play Store` or `iOS AppStore`
15 | class IAPItem {
16 | final String? productId;
17 | final String? price;
18 | final String? currency;
19 | final String? localizedPrice;
20 | final String? title;
21 | final String? description;
22 | final String? introductoryPrice;
23 |
24 | /// ios only
25 | final String? subscriptionPeriodNumberIOS;
26 | final String? subscriptionPeriodUnitIOS;
27 | final String? introductoryPriceNumberIOS;
28 | final String? introductoryPricePaymentModeIOS;
29 | final String? introductoryPriceNumberOfPeriodsIOS;
30 | final String? introductoryPriceSubscriptionPeriodIOS;
31 | final List? discountsIOS;
32 |
33 | /// android only
34 | final String? signatureAndroid;
35 | final List? subscriptionOffersAndroid;
36 | final String? subscriptionPeriodAndroid;
37 |
38 | final String? iconUrl;
39 | final String? originalJson;
40 | final String originalPrice;
41 |
42 | /// Create [IAPItem] from a Map that was previously JSON formatted
43 | IAPItem.fromJSON(Map json)
44 | : productId = json['productId'] as String?,
45 | price = json['price'] as String?,
46 | currency = json['currency'] as String?,
47 | localizedPrice = json['localizedPrice'] as String?,
48 | title = json['title'] as String?,
49 | description = json['description'] as String?,
50 | introductoryPrice = json['introductoryPrice'] as String?,
51 | introductoryPricePaymentModeIOS =
52 | json['introductoryPricePaymentModeIOS'] as String?,
53 | introductoryPriceNumberOfPeriodsIOS =
54 | json['introductoryPriceNumberOfPeriodsIOS'] as String?,
55 | introductoryPriceSubscriptionPeriodIOS =
56 | json['introductoryPriceSubscriptionPeriodIOS'] as String?,
57 | introductoryPriceNumberIOS =
58 | json['introductoryPriceNumberIOS'] as String?,
59 | subscriptionPeriodNumberIOS =
60 | json['subscriptionPeriodNumberIOS'] as String?,
61 | subscriptionPeriodUnitIOS =
62 | json['subscriptionPeriodUnitIOS'] as String?,
63 | subscriptionPeriodAndroid =
64 | json['subscriptionPeriodAndroid'] as String?,
65 | signatureAndroid = json['signatureAndroid'] as String?,
66 | iconUrl = json['iconUrl'] as String?,
67 | originalJson = json['originalJson'] as String?,
68 | originalPrice = json['originalPrice'].toString(),
69 | discountsIOS = _extractDiscountIOS(json['discounts']),
70 | subscriptionOffersAndroid =
71 | _extractSubscriptionOffersAndroid(json['subscriptionOffers']);
72 |
73 | /// wow, i find if i want to save a IAPItem, there is not "toJson" to cast it into String...
74 | /// i'm sorry to see that... so,
75 | ///
76 | /// you can cast a IAPItem to json(Map) via invoke this method.
77 | /// for example:
78 | /// String str = convert.jsonEncode(item)
79 | ///
80 | /// and then get IAPItem from "str" above
81 | /// IAPItem item = IAPItem.fromJSON(convert.jsonDecode(str));
82 | Map toJson() {
83 | final Map data = Map();
84 | data['productId'] = this.productId;
85 | data['price'] = this.price;
86 | data['currency'] = this.currency;
87 | data['localizedPrice'] = this.localizedPrice;
88 | data['title'] = this.title;
89 | data['description'] = this.description;
90 | data['introductoryPrice'] = this.introductoryPrice;
91 |
92 | data['subscriptionPeriodNumberIOS'] = this.subscriptionPeriodNumberIOS;
93 | data['subscriptionPeriodUnitIOS'] = this.subscriptionPeriodUnitIOS;
94 | data['introductoryPricePaymentModeIOS'] =
95 | this.introductoryPricePaymentModeIOS;
96 | data['introductoryPriceNumberOfPeriodsIOS'] =
97 | this.introductoryPriceNumberOfPeriodsIOS;
98 | data['introductoryPriceSubscriptionPeriodIOS'] =
99 | this.introductoryPriceSubscriptionPeriodIOS;
100 | data['subscriptionPeriodAndroid'] = this.subscriptionPeriodAndroid;
101 | data['signatureAndroid'] = this.signatureAndroid;
102 |
103 | data['iconUrl'] = this.iconUrl;
104 | data['originalJson'] = this.originalJson;
105 | data['originalPrice'] = this.originalPrice;
106 | data['discounts'] = this.discountsIOS;
107 | return data;
108 | }
109 |
110 | /// Return the contents of this class as a string
111 | @override
112 | String toString() {
113 | return 'productId: $productId, '
114 | 'price: $price, '
115 | 'currency: $currency, '
116 | 'localizedPrice: $localizedPrice, '
117 | 'title: $title, '
118 | 'description: $description, '
119 | 'introductoryPrice: $introductoryPrice, '
120 | 'subscriptionPeriodNumberIOS: $subscriptionPeriodNumberIOS, '
121 | 'subscriptionPeriodUnitIOS: $subscriptionPeriodUnitIOS, '
122 | 'introductoryPricePaymentModeIOS: $introductoryPricePaymentModeIOS, '
123 | 'introductoryPriceNumberOfPeriodsIOS: $introductoryPriceNumberOfPeriodsIOS, '
124 | 'introductoryPriceSubscriptionPeriodIOS: $introductoryPriceSubscriptionPeriodIOS, '
125 | 'subscriptionPeriodAndroid: $subscriptionPeriodAndroid, '
126 | 'iconUrl: $iconUrl, '
127 | 'originalJson: $originalJson, '
128 | 'originalPrice: $originalPrice, '
129 | 'discounts: $discountsIOS, ';
130 | }
131 |
132 | static List? _extractDiscountIOS(dynamic json) {
133 | List? list = json as List?;
134 | List? discounts;
135 |
136 | if (list != null) {
137 | discounts = list
138 | .map(
139 | (dynamic discount) =>
140 | DiscountIOS.fromJSON(discount as Map),
141 | )
142 | .toList();
143 | }
144 |
145 | return discounts;
146 | }
147 |
148 | static List? _extractSubscriptionOffersAndroid(
149 | dynamic json) {
150 | List? list = json as List?;
151 | List? offers;
152 |
153 | if (list != null) {
154 | offers = list
155 | .map(
156 | (dynamic offer) => SubscriptionOfferAndroid.fromJSON(
157 | offer as Map),
158 | )
159 | .toList();
160 | }
161 |
162 | return offers;
163 | }
164 | }
165 |
166 | class SubscriptionOfferAndroid {
167 | String? offerId;
168 | String? basePlanId;
169 | String? offerToken;
170 | List? pricingPhases;
171 |
172 | SubscriptionOfferAndroid.fromJSON(Map json)
173 | : offerId = json["offerId"] as String?,
174 | basePlanId = json["basePlanId"] as String?,
175 | offerToken = json["offerToken"] as String?,
176 | pricingPhases = _extractAndroidPricingPhase(json["pricingPhases"]);
177 |
178 | static List? _extractAndroidPricingPhase(dynamic json) {
179 | List? list = json as List?;
180 | List? phases;
181 |
182 | if (list != null) {
183 | phases = list
184 | .map(
185 | (dynamic phase) =>
186 | PricingPhaseAndroid.fromJSON(phase as Map),
187 | )
188 | .toList();
189 | }
190 |
191 | return phases;
192 | }
193 | }
194 |
195 | class PricingPhaseAndroid {
196 | String? price;
197 | String? formattedPrice;
198 | String? billingPeriod;
199 | String? currencyCode;
200 | int? recurrenceMode;
201 | int? billingCycleCount;
202 |
203 | PricingPhaseAndroid.fromJSON(Map json)
204 | : price = json["price"] as String?,
205 | formattedPrice = json["formattedPrice"] as String?,
206 | billingPeriod = json["billingPeriod"] as String?,
207 | currencyCode = json["currencyCode"] as String?,
208 | recurrenceMode = json["recurrenceMode"] as int?,
209 | billingCycleCount = json["billingCycleCount"] as int?;
210 | }
211 |
212 | class DiscountIOS {
213 | String? identifier;
214 | String? type;
215 | String? numberOfPeriods;
216 | double? price;
217 | String? localizedPrice;
218 | String? paymentMode;
219 | String? subscriptionPeriod;
220 |
221 | /// Create [DiscountIOS] from a Map that was previously JSON formatted
222 | DiscountIOS.fromJSON(Map json)
223 | : identifier = json['identifier'] as String?,
224 | type = json['type'] as String?,
225 | numberOfPeriods = json['numberOfPeriods'] as String?,
226 | price = json['price'] as double?,
227 | localizedPrice = json['localizedPrice'] as String?,
228 | paymentMode = json['paymentMode'] as String?,
229 | subscriptionPeriod = json['subscriptionPeriod'] as String?;
230 |
231 | Map toJson() {
232 | final Map data = Map();
233 | data['identifier'] = this.identifier;
234 | data['type'] = this.type;
235 | data['numberOfPeriods'] = this.numberOfPeriods;
236 | data['price'] = this.price;
237 | data['localizedPrice'] = this.localizedPrice;
238 | data['paymentMode'] = this.paymentMode;
239 | data['subscriptionPeriod'] = this.subscriptionPeriod;
240 | return data;
241 | }
242 |
243 | /// Return the contents of this class as a string
244 | @override
245 | String toString() {
246 | return 'identifier: $identifier, '
247 | 'type: $type, '
248 | 'numberOfPeriods: $numberOfPeriods, '
249 | 'price: $price, '
250 | 'localizedPrice: $localizedPrice, '
251 | 'paymentMode: $paymentMode, '
252 | 'subscriptionPeriod: $subscriptionPeriod, ';
253 | }
254 | }
255 |
256 | /// An item which was purchased from either the `Google Play Store` or `iOS AppStore`
257 | class PurchasedItem {
258 | final String? productId;
259 | final String? transactionId;
260 | final DateTime? transactionDate;
261 | final String? transactionReceipt;
262 | final String? purchaseToken;
263 |
264 | // Android only
265 | final String? dataAndroid;
266 | final String? signatureAndroid;
267 | final bool? autoRenewingAndroid;
268 | final bool? isAcknowledgedAndroid;
269 | final PurchaseState? purchaseStateAndroid;
270 |
271 | // iOS only
272 | final DateTime? originalTransactionDateIOS;
273 | final String? originalTransactionIdentifierIOS;
274 | final TransactionState? transactionStateIOS;
275 |
276 | /// Create [PurchasedItem] from a Map that was previously JSON formatted
277 | PurchasedItem.fromJSON(Map json)
278 | : productId = json['productId'] as String?,
279 | transactionId = json['transactionId'] as String?,
280 | transactionDate = _extractDate(json['transactionDate']),
281 | transactionReceipt = json['transactionReceipt'] as String?,
282 | purchaseToken = json['purchaseToken'] as String?,
283 | dataAndroid = json['dataAndroid'] as String?,
284 | signatureAndroid = json['signatureAndroid'] as String?,
285 | isAcknowledgedAndroid = json['isAcknowledgedAndroid'] as bool?,
286 | autoRenewingAndroid = json['autoRenewingAndroid'] as bool?,
287 | purchaseStateAndroid =
288 | _decodePurchaseStateAndroid(json['purchaseStateAndroid'] as int?),
289 | originalTransactionDateIOS =
290 | _extractDate(json['originalTransactionDateIOS']),
291 | originalTransactionIdentifierIOS =
292 | json['originalTransactionIdentifierIOS'] as String?,
293 | transactionStateIOS =
294 | _decodeTransactionStateIOS(json['transactionStateIOS'] as int?);
295 |
296 | /// This returns transaction dates in ISO 8601 format.
297 | @override
298 | String toString() {
299 | return 'productId: $productId, '
300 | 'transactionId: $transactionId, '
301 | 'transactionDate: ${transactionDate?.toIso8601String()}, '
302 | 'transactionReceipt: $transactionReceipt, '
303 | 'purchaseToken: $purchaseToken, '
304 |
305 | /// android specific
306 | 'dataAndroid: $dataAndroid, '
307 | 'signatureAndroid: $signatureAndroid, '
308 | 'isAcknowledgedAndroid: $isAcknowledgedAndroid, '
309 | 'autoRenewingAndroid: $autoRenewingAndroid, '
310 | 'purchaseStateAndroid: $purchaseStateAndroid, '
311 |
312 | /// ios specific
313 | 'originalTransactionDateIOS: ${originalTransactionDateIOS?.toIso8601String()}, '
314 | 'originalTransactionIdentifierIOS: $originalTransactionIdentifierIOS, '
315 | 'transactionStateIOS: $transactionStateIOS';
316 | }
317 |
318 | /// Coerce miliseconds since epoch in double, int, or String into DateTime format
319 | static DateTime? _extractDate(dynamic timestamp) {
320 | if (timestamp == null) return null;
321 |
322 | int _toInt() => double.parse(timestamp.toString()).toInt();
323 | return DateTime.fromMillisecondsSinceEpoch(_toInt());
324 | }
325 | }
326 |
327 | class PurchaseResult {
328 | final int? responseCode;
329 | final String? debugMessage;
330 | final String? code;
331 | final String? message;
332 |
333 | PurchaseResult({
334 | this.responseCode,
335 | this.debugMessage,
336 | this.code,
337 | this.message,
338 | });
339 |
340 | PurchaseResult.fromJSON(Map json)
341 | : responseCode = json['responseCode'] as int?,
342 | debugMessage = json['debugMessage'] as String?,
343 | code = json['code'] as String?,
344 | message = json['message'] as String?;
345 |
346 | Map toJson() => {
347 | "responseCode": responseCode ?? 0,
348 | "debugMessage": debugMessage ?? '',
349 | "code": code ?? '',
350 | "message": message ?? '',
351 | };
352 |
353 | @override
354 | String toString() {
355 | return 'responseCode: $responseCode, '
356 | 'debugMessage: $debugMessage, '
357 | 'code: $code, '
358 | 'message: $message';
359 | }
360 | }
361 |
362 | class ConnectionResult {
363 | final bool? connected;
364 |
365 | ConnectionResult({
366 | this.connected,
367 | });
368 |
369 | ConnectionResult.fromJSON(Map json)
370 | : connected = json['connected'] as bool?;
371 |
372 | Map toJson() => {
373 | "connected": connected ?? false,
374 | };
375 |
376 | @override
377 | String toString() {
378 | return 'connected: $connected';
379 | }
380 | }
381 |
382 | /// See also https://developer.apple.com/documentation/storekit/skpaymenttransactionstate
383 | enum TransactionState {
384 | /// A transaction that is being processed by the App Store.
385 | purchasing,
386 |
387 | /// A successfully processed transaction.
388 | purchased,
389 |
390 | /// A failed transaction.
391 | failed,
392 |
393 | /// A transaction that restores content previously purchased by the user.
394 | restored,
395 |
396 | /// A transaction that is in the queue, but its final status is pending external action such as Ask to Buy.
397 | deferred,
398 | }
399 |
400 | TransactionState? _decodeTransactionStateIOS(int? rawValue) {
401 | switch (rawValue) {
402 | case 0:
403 | return TransactionState.purchasing;
404 | case 1:
405 | return TransactionState.purchased;
406 | case 2:
407 | return TransactionState.failed;
408 | case 3:
409 | return TransactionState.restored;
410 | case 4:
411 | return TransactionState.deferred;
412 | default:
413 | return null;
414 | }
415 | }
416 |
417 | /// See also https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState
418 | enum PurchaseState {
419 | pending,
420 |
421 | purchased,
422 |
423 | unspecified,
424 | }
425 |
426 | PurchaseState? _decodePurchaseStateAndroid(int? rawValue) {
427 | switch (rawValue) {
428 | case 0:
429 | return PurchaseState.unspecified;
430 | case 1:
431 | return PurchaseState.purchased;
432 | case 2:
433 | return PurchaseState.pending;
434 | default:
435 | return null;
436 | }
437 | }
438 |
--------------------------------------------------------------------------------
/lib/utils.dart:
--------------------------------------------------------------------------------
1 | import 'modules.dart';
2 | import 'dart:convert';
3 |
4 | List extractItems(dynamic result) {
5 | List list = json.decode(result.toString());
6 | List products = list
7 | .map(
8 | (dynamic product) => IAPItem.fromJSON(product as Map),
9 | )
10 | .toList();
11 |
12 | return products;
13 | }
14 |
15 | List? extractPurchased(dynamic result) {
16 | List? decoded = json
17 | .decode(result.toString())
18 | .map(
19 | (dynamic product) =>
20 | PurchasedItem.fromJSON(product as Map),
21 | )
22 | .toList();
23 |
24 | return decoded;
25 | }
26 |
27 | List? extractResult(dynamic result) {
28 | List? decoded = json
29 | .decode(result.toString())
30 | .map(
31 | (dynamic product) =>
32 | PurchaseResult.fromJSON(product as Map),
33 | )
34 | .toList();
35 |
36 | return decoded;
37 | }
38 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_inapp_purchase
2 | description: In App Purchase plugin for flutter. This project has been forked by
3 | react-native-iap and we are willing to share same experience with that on
4 | react-native.
5 | version: 5.6.2
6 | homepage: https://github.com/dooboolab/flutter_inapp_purchase/blob/main/pubspec.yaml
7 | environment:
8 | sdk: ">=2.15.0 <4.0.0"
9 | flutter: ">=2.0.0"
10 |
11 | dependencies:
12 | flutter:
13 | sdk: flutter
14 | http: ^1.1.0
15 | meta: ^1.9.1
16 | platform: ^3.1.3
17 |
18 | dev_dependencies:
19 | flutter_test:
20 | sdk: flutter
21 | mockito: ^5.4.2
22 | test: ^1.24.3
23 |
24 | # For information on the generic Dart part of this file, see the
25 | # following page: https://www.dartlang.org/tools/pub/pubspec
26 | # The following section is specific to Flutter.
27 | flutter:
28 | plugin:
29 | platforms:
30 | android:
31 | package: com.dooboolab.flutterinapppurchase
32 | pluginClass: FlutterInappPurchasePlugin
33 | mainClass: FlutterInappPurchasePlugin
34 | ios:
35 | pluginClass: FlutterInappPurchasePlugin
36 |
--------------------------------------------------------------------------------
/test/utils_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 |
3 | enum _TestEnum { Hoge }
4 |
5 | void main() {
6 | group('utils', () {
7 | test('EnumUtil.getValueString', () async {
8 | String value = _TestEnum.Hoge.name;
9 | expect(value, "Hoge");
10 | });
11 | });
12 | }
13 |
--------------------------------------------------------------------------------