├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── ir │ └── cafebazaar │ └── flutter_poolakey │ ├── FlutterPoolakeyPlugin.kt │ ├── Mappers.kt │ └── PaymentActivity.kt ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── sample │ │ │ │ │ └── android │ │ │ │ │ └── trivialdrivesample │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── 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-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── assets │ ├── buy_gas.png │ ├── free.png │ ├── gas0.png │ ├── gas1.png │ ├── gas2.png │ ├── gas3.png │ ├── gas4.png │ ├── gas_inf.png │ ├── get_infinite_gas.png │ ├── get_infinite_gas_trial.png │ ├── premium.png │ ├── title.png │ └── upgrade_app.png ├── lib │ ├── data.dart │ └── main.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── lib ├── flutter_poolakey.dart ├── purchase_info.dart └── sku_details.dart ├── pubspec.yaml ├── repo_files └── flutter_poolakey.jpg └── test └── foolakey_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .classpath 21 | .project 22 | .settings/ 23 | .vscode/ 24 | 25 | # Flutter repo-specific 26 | /bin/cache/ 27 | /bin/internal/bootstrap.bat 28 | /bin/internal/bootstrap.sh 29 | /bin/mingit/ 30 | /dev/benchmarks/mega_gallery/ 31 | /dev/bots/.recipe_deps 32 | /dev/bots/android_tools/ 33 | /dev/devicelab/ABresults*.json 34 | /dev/docs/doc/ 35 | /dev/docs/flutter.docs.zip 36 | /dev/docs/lib/ 37 | /dev/docs/pubspec.yaml 38 | /dev/integration_tests/**/xcuserdata 39 | /dev/integration_tests/**/Pods 40 | /packages/flutter/coverage/ 41 | version 42 | analysis_benchmark.json 43 | 44 | # packages file containing multi-root paths 45 | .packages.generated 46 | 47 | # Flutter/Dart/Pub related 48 | **/doc/api/ 49 | .dart_tool/ 50 | .flutter-plugins 51 | .flutter-plugins-dependencies 52 | **/generated_plugin_registrant.dart 53 | .packages 54 | .pub-cache/ 55 | .pub/ 56 | build/ 57 | flutter_*.png 58 | linked_*.ds 59 | unlinked.ds 60 | unlinked_spec.ds 61 | 62 | # Android related 63 | **/android/**/gradle-wrapper.jar 64 | **/android/.gradle 65 | **/android/captures/ 66 | **/android/gradlew 67 | **/android/gradlew.bat 68 | **/android/local.properties 69 | **/android/**/GeneratedPluginRegistrant.java 70 | **/android/key.properties 71 | *.jks 72 | 73 | # iOS/XCode related 74 | **/ios/**/*.mode1v3 75 | **/ios/**/*.mode2v3 76 | **/ios/**/*.moved-aside 77 | **/ios/**/*.pbxuser 78 | **/ios/**/*.perspectivev3 79 | **/ios/**/*sync/ 80 | **/ios/**/.sconsign.dblite 81 | **/ios/**/.tags* 82 | **/ios/**/.vagrant/ 83 | **/ios/**/DerivedData/ 84 | **/ios/**/Icon? 85 | **/ios/**/Pods/ 86 | **/ios/**/.symlinks/ 87 | **/ios/**/profile 88 | **/ios/**/xcuserdata 89 | **/ios/.generated/ 90 | **/ios/Flutter/.last_build_id 91 | **/ios/Flutter/App.framework 92 | **/ios/Flutter/Flutter.framework 93 | **/ios/Flutter/Flutter.podspec 94 | **/ios/Flutter/Generated.xcconfig 95 | **/ios/Flutter/ephemeral 96 | **/ios/Flutter/app.flx 97 | **/ios/Flutter/app.zip 98 | **/ios/Flutter/flutter_assets/ 99 | **/ios/Flutter/flutter_export_environment.sh 100 | **/ios/ServiceDefinitions.json 101 | **/ios/Runner/GeneratedPluginRegistrant.* 102 | 103 | # macOS 104 | **/macos/Flutter/GeneratedPluginRegistrant.swift 105 | 106 | # Coverage 107 | coverage/ 108 | 109 | # Symbols 110 | app.*.symbols 111 | 112 | # Exceptions to above rules. 113 | !**/ios/**/default.mode1v3 114 | !**/ios/**/default.mode2v3 115 | !**/ios/**/default.pbxuser 116 | !**/ios/**/default.perspectivev3 117 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 118 | !/dev/ci/**/Gemfile.lock -------------------------------------------------------------------------------- /.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: adc687823a831bbebe28bdccfac1a628ca621513 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.2.0-1.0.0-alpha01 2 | * Update gradle version to 7.5 #29 3 | * Fix illegal state exception on connect #23 4 | * Apply new versioning schema 5 | 6 | ## 2.2.0 7 | * Upgrade Poolakey dependency to 2.2.0 8 | * Update README.md 9 | 10 | ## 1.3.0 11 | * Add (trivial) example project. 12 | * Upgrade Poolakey dependency to 1.2.3 13 | * Fix exception on destroy flutter activity 14 | * Support android 12 15 | 16 | ## 1.2.3 17 | * Upgrade Poolakey dependency to 1.2.3 18 | * Fix exception on destroy main-activity 19 | 20 | ## 1.0.0-rc1 21 | * Write documentations in github WIKI and linked in README.md 22 | * reformat code 23 | * ready to go 24 | 25 | ## 0.1.0 26 | * Upgrade poolakey dependency to 1.1.0-beta02 27 | * Add `getInAppSkuDetails` function to retrieve sku details of your purchase products. 28 | * Add `getSubscriptionSkuDetails` function to retrieve sku details of your subscription products. 29 | 30 | ## 0.0.2 31 | * Add query tag flag in AndroidManifest 32 | 33 | ## 0.0.1 34 | Initial Version 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Mohammad Mahdi Nouri 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![pub package](https://img.shields.io/pub/v/flutter_poolakey.svg)](https://pub.dartlang.org/packages/flutter_poolakey) 4 | 5 | ### Android In-App Billing *Flutter* SDK for [Cafe Bazaar](https://cafebazaar.ir/?l=en) App Store. 6 | 7 | ## Getting Started 8 | 9 | To start working with Flutter Poolakey, you need to add its dependency in your 10 | project's `pubspec.yaml` file: 11 | 12 | ### Dependency 13 | 14 | ```yaml 15 | dependencies: 16 | flutter_poolakey: ^2.2.0-1.0.0-alpha01 17 | ``` 18 | 19 | Then run the below command to retrieve it: 20 | 21 | ```shell 22 | flutter packages get 23 | ``` 24 | 25 | And then Go to the allprojects section of your project gradle file and add the JitPack repository to the repositories block: 26 | ```groovy 27 | allprojects { 28 | repositories { 29 | // add JitPack 30 | maven { url 'https://jitpack.io' } 31 | } 32 | } 33 | ``` 34 | 35 | ### Import it 36 | 37 | Now in your Dart code, you can use: 38 | 39 | ```dart 40 | import 'package:flutter_poolakey/flutter_poolakey.dart'; 41 | ``` 42 | 43 | ### How to use 44 | 45 | For more information regarding the usage of flutter Poolakey, please check out 46 | the [wiki](https://github.com/cafebazaar/flutter_poolakey/wiki) page. 47 | 48 | ### Sample 49 | 50 | There is a fully functional sample application that demonstrates the usage of flutter Poolakey, all you have 51 | to do is cloning the project and running 52 | the [app](https://github.com/cafebazaar/flutter_poolakey/tree/master/example) module. 53 | 54 | 55 | #### flutter_poolakey is a wrapper around [Poolakey](https://github.com/cafebazaar/Poolakey) to use in Flutter. 56 | 57 | > [Poolakey](https://github.com/cafebazaar/Poolakey) is an Android In-App Billing SDK 58 | > for [Cafe Bazaar](https://cafebazaar.ir/?l=en) App Store. 59 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/tasks.xml 43 | .idea/gradle.xml 44 | .idea/assetWizardSettings.xml 45 | .idea/dictionaries 46 | .idea/libraries 47 | # Android Studio 3 in .gitignore file. 48 | .idea/caches 49 | .idea/modules.xml 50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 51 | .idea/navEditor.xml 52 | 53 | # Keystore files 54 | # Uncomment the following lines if you do not want to check your keystore files in. 55 | #*.jks 56 | #*.keystore 57 | 58 | # External native build folder generated in Android Studio 2.2 and later 59 | .externalNativeBuild 60 | .cxx/ 61 | 62 | # Google Services (e.g. APIs or Firebase) 63 | # google-services.json 64 | 65 | # Freeline 66 | freeline.py 67 | freeline/ 68 | freeline_project_description.json 69 | 70 | # fastlane 71 | fastlane/report.xml 72 | fastlane/Preview.html 73 | fastlane/screenshots 74 | fastlane/test_output 75 | fastlane/readme.md 76 | 77 | # Version control 78 | vcs.xml 79 | 80 | # lint 81 | lint/intermediates/ 82 | lint/generated/ 83 | lint/outputs/ 84 | lint/tmp/ 85 | # lint/reports/ 86 | 87 | # Android Profiling 88 | *.hprof -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = "1.7.10" 3 | ext.poolakey_version = "2.2.0" 4 | repositories { 5 | jcenter() 6 | maven { url "https://jitpack.io" } 7 | google() 8 | maven { url "https://plugins.gradle.org/m2/" } 9 | maven { 10 | url "https://maven.google.com/" 11 | name "Google" 12 | } 13 | } 14 | 15 | dependencies { 16 | classpath 'com.android.tools.build:gradle:7.4.2' 17 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 18 | } 19 | } 20 | 21 | plugins { 22 | id "com.android.library" 23 | id "kotlin-android" 24 | } 25 | 26 | group 'ir.cafebazaar.flutter_poolakey' 27 | version '2.2.0-1.0.0-alpha01' 28 | 29 | allprojects { 30 | repositories { 31 | jcenter() 32 | maven { url "https://jitpack.io" } 33 | google() 34 | maven { url "https://plugins.gradle.org/m2/" } 35 | maven { 36 | url "https://maven.google.com/" 37 | name "Google" 38 | } 39 | } 40 | } 41 | 42 | android { 43 | namespace 'ir.cafebazaar.flutter_poolakey' 44 | compileSdkVersion 34 45 | 46 | compileOptions { 47 | sourceCompatibility JavaVersion.VERSION_1_8 48 | targetCompatibility JavaVersion.VERSION_1_8 49 | } 50 | 51 | buildFeatures { 52 | buildConfig true 53 | } 54 | 55 | kotlinOptions { 56 | jvmTarget = '1.8' 57 | } 58 | 59 | sourceSets { 60 | main.java.srcDirs += 'src/main/kotlin' 61 | } 62 | 63 | defaultConfig { 64 | minSdkVersion 16 65 | versionCode 2002001001 66 | versionName "2.2.0-1.0.0-alpha01" 67 | } 68 | 69 | buildTypes { 70 | debug { 71 | buildConfigField "String", "POOLAKEY_VERSION", '"' + poolakey_version + '"' 72 | } 73 | release { 74 | buildConfigField "String", "POOLAKEY_VERSION", '"' + poolakey_version + '"' 75 | } 76 | } 77 | 78 | dependencies { 79 | implementation "com.github.cafebazaar.Poolakey:poolakey:$poolakey_version" 80 | } 81 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'flutter_poolakey' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /android/src/main/kotlin/ir/cafebazaar/flutter_poolakey/FlutterPoolakeyPlugin.kt: -------------------------------------------------------------------------------- 1 | package ir.cafebazaar.flutter_poolakey 2 | 3 | import android.app.Activity 4 | import androidx.annotation.NonNull 5 | 6 | import io.flutter.embedding.engine.plugins.FlutterPlugin 7 | import io.flutter.embedding.engine.plugins.activity.ActivityAware 8 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding 9 | import io.flutter.plugin.common.MethodCall 10 | import io.flutter.plugin.common.MethodChannel 11 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 12 | import io.flutter.plugin.common.MethodChannel.Result 13 | import ir.cafebazaar.poolakey.Connection 14 | import ir.cafebazaar.poolakey.ConnectionState 15 | import ir.cafebazaar.poolakey.Payment 16 | import ir.cafebazaar.poolakey.callback.PurchaseCallback 17 | import ir.cafebazaar.poolakey.config.PaymentConfiguration 18 | import ir.cafebazaar.poolakey.config.SecurityCheck 19 | 20 | class FlutterPoolakeyPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { 21 | private lateinit var channel: MethodChannel 22 | private var activityBinding: ActivityPluginBinding? = null 23 | private lateinit var flutterPluginBinding: FlutterPlugin.FlutterPluginBinding 24 | 25 | private val requireActivity: Activity 26 | get() = activityBinding!!.activity 27 | 28 | private var purchaseCallback: (PurchaseCallback.() -> Unit)? = null 29 | 30 | private lateinit var paymentConnection: Connection 31 | private lateinit var payment: Payment 32 | 33 | override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 34 | this.flutterPluginBinding = flutterPluginBinding 35 | } 36 | 37 | override fun onAttachedToActivity(binding: ActivityPluginBinding) { 38 | activityBinding = binding 39 | channel = 40 | MethodChannel(flutterPluginBinding.binaryMessenger, "ir.cafebazaar.flutter_poolakey") 41 | channel.setMethodCallHandler(this) 42 | } 43 | 44 | override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { 45 | when (call.method) { 46 | "version" -> { 47 | getVersion(result); 48 | } 49 | 50 | "connect" -> { 51 | val inAppBillingKey = call.argument("in_app_billing_key") 52 | connect(inAppBillingKey, result) 53 | } 54 | 55 | "disconnect" -> { 56 | disconnect(result) 57 | } 58 | 59 | "purchase" -> { 60 | startActivity( 61 | activity = requireActivity, 62 | command = PaymentActivity.Command.Purchase, 63 | productId = call.argument("product_id")!!, 64 | result = result, 65 | payload = call.argument("payload"), 66 | dynamicPriceToken = call.argument("dynamicPriceToken"), 67 | ) 68 | } 69 | 70 | "subscribe" -> { 71 | startActivity( 72 | activity = requireActivity, 73 | command = PaymentActivity.Command.Subscribe, 74 | productId = call.argument("product_id")!!, 75 | result = result, 76 | payload = call.argument("payload"), 77 | dynamicPriceToken = call.argument("dynamicPriceToken"), 78 | ) 79 | } 80 | 81 | "consume" -> { 82 | val purchaseToken = call.argument("purchase_token")!! 83 | consume(purchaseToken, result) 84 | } 85 | 86 | "get_all_purchased_products" -> { 87 | getAllPurchasedProducts(result) 88 | } 89 | 90 | "get_all_subscribed_products" -> { 91 | getAllSubscribedProducts(result) 92 | } 93 | 94 | "get_in_app_sku_details" -> { 95 | val skuIds = call.argument>("sku_ids")!! 96 | getInAppSkuDetails(skuIds, result) 97 | } 98 | 99 | "get_subscription_sku_details" -> { 100 | val skuIds = call.argument>("sku_ids")!! 101 | getSubscriptionSkuDetails(skuIds, result) 102 | } 103 | 104 | "checkTrialSubscription" -> { 105 | checkTrialSubscription(result) 106 | } 107 | 108 | else -> result.notImplemented() 109 | } 110 | } 111 | 112 | private fun getVersion(result: Result) { 113 | result.success(BuildConfig.POOLAKEY_VERSION) 114 | } 115 | 116 | private fun connect(inAppBillingKey: String?, result: Result) { 117 | val securityCheck = if (inAppBillingKey != null) { 118 | SecurityCheck.Enable(rsaPublicKey = inAppBillingKey) 119 | } else { 120 | SecurityCheck.Disable 121 | } 122 | val paymentConfiguration = PaymentConfiguration(localSecurityCheck = securityCheck) 123 | 124 | payment = Payment(context = requireActivity, config = paymentConfiguration) 125 | 126 | paymentConnection = payment.connect { 127 | connectionSucceed { 128 | channel.invokeMethod("connectionSucceed", null) 129 | } 130 | connectionFailed { 131 | channel.invokeMethod("connectionFailed", it.toString(), null) 132 | } 133 | disconnected { 134 | channel.invokeMethod("disconnected", null) 135 | } 136 | } 137 | } 138 | 139 | private fun disconnect(result: Result) { 140 | paymentConnection.disconnect() 141 | result.success(null) 142 | } 143 | 144 | fun startActivity( 145 | activity: Activity, 146 | command: PaymentActivity.Command, 147 | productId: String, 148 | result: Result, 149 | payload: String 150 | ?, 151 | dynamicPriceToken: String 152 | ? 153 | ) { 154 | if (paymentConnection.getState() != ConnectionState.Connected) { 155 | result.error("PURCHASE_FAILED", "In order to purchasing, connect to Poolakey!", null) 156 | return 157 | } 158 | 159 | PaymentActivity.start( 160 | activity, 161 | command, 162 | productId, 163 | payment, 164 | result, 165 | payload, 166 | dynamicPriceToken 167 | ) 168 | } 169 | 170 | private fun consume(purchaseToken: String, result: Result) { 171 | if (paymentConnection.getState() != ConnectionState.Connected) { 172 | result.error( 173 | "PAYMENT_CONNECTION_IS_NOT_CONNECTED", 174 | "PaymentConnection is not connected (state: ${paymentConnection.getState()})", 175 | null 176 | ) 177 | return 178 | } 179 | payment.consumeProduct(purchaseToken) { 180 | consumeSucceed { 181 | result.success(true) 182 | } 183 | consumeFailed { 184 | result.error("CONSUME_FAILED", it.toString(), null) 185 | } 186 | } 187 | } 188 | 189 | private fun checkTrialSubscription(result: Result) { 190 | if (paymentConnection.getState() != ConnectionState.Connected) { 191 | result.error( 192 | "PAYMENT_CONNECTION_IS_NOT_CONNECTED", 193 | "PaymentConnection is not connected (state: ${paymentConnection.getState()})", 194 | null 195 | ) 196 | return 197 | } 198 | payment.checkTrialSubscription { 199 | checkTrialSubscriptionSucceed { trialSubscriptionInfo -> 200 | result.success( 201 | hashMapOf( 202 | "isAvailable" to trialSubscriptionInfo.isAvailable, 203 | "trialPeriodDays" to trialSubscriptionInfo.trialPeriodDays 204 | ) 205 | ) 206 | } 207 | 208 | checkTrialSubscriptionFailed { 209 | result.error("CHECK_TRIAL_FAILED", it.toString(), null) 210 | 211 | } 212 | } 213 | } 214 | 215 | private fun getAllPurchasedProducts(result: Result) { 216 | if (paymentConnection.getState() != ConnectionState.Connected) { 217 | result.error( 218 | "PAYMENT_CONNECTION_IS_NOT_CONNECTED", 219 | "PaymentConnection is not connected (state: ${paymentConnection.getState()})", 220 | null 221 | ) 222 | return 223 | } 224 | payment.getPurchasedProducts { 225 | querySucceed { purchasedItems -> 226 | result.success(purchasedItems.map { it.toMap() }) 227 | } 228 | queryFailed { 229 | result.error("QUERY_PURCHASED_PRODUCT_FAILED", it.toString(), null) 230 | } 231 | } 232 | } 233 | 234 | private fun getAllSubscribedProducts(result: Result) { 235 | if (paymentConnection.getState() != ConnectionState.Connected) { 236 | result.error( 237 | "PAYMENT_CONNECTION_IS_NOT_CONNECTED", 238 | "PaymentConnection is not connected (state: ${paymentConnection.getState()})", 239 | null 240 | ) 241 | return 242 | } 243 | payment.getSubscribedProducts { 244 | querySucceed { purchasedItems -> 245 | result.success(purchasedItems.map { it.toMap() }) 246 | } 247 | queryFailed { 248 | result.error("QUERY_SUBSCRIBED_PRODUCT_FAILED", it.toString(), null) 249 | } 250 | } 251 | } 252 | 253 | private fun getInAppSkuDetails(skuIds: List, result: Result) { 254 | if (paymentConnection.getState() != ConnectionState.Connected) { 255 | result.error( 256 | "PAYMENT_CONNECTION_IS_NOT_CONNECTED", 257 | "PaymentConnection is not connected (state: ${paymentConnection.getState()})", 258 | null 259 | ) 260 | return 261 | } 262 | 263 | payment.getInAppSkuDetails(skuIds = skuIds) { 264 | getSkuDetailsSucceed { 265 | result.success(it.map { skuDetails -> skuDetails.toMap() }) 266 | } 267 | getSkuDetailsFailed { 268 | result.error("QUERY_GET_IN_APP_SKU_DETAILS_FAILED", it.toString(), null) 269 | } 270 | } 271 | } 272 | 273 | private fun getSubscriptionSkuDetails(skuIds: List, result: Result) { 274 | if (paymentConnection.getState() != ConnectionState.Connected) { 275 | result.error( 276 | "PAYMENT_CONNECTION_IS_NOT_CONNECTED", 277 | "PaymentConnection is not connected (state: ${paymentConnection.getState()})", 278 | null 279 | ) 280 | return 281 | } 282 | 283 | payment.getSubscriptionSkuDetails(skuIds = skuIds) { 284 | getSkuDetailsSucceed { 285 | result.success(it.map { skuDetails -> skuDetails.toMap() }) 286 | } 287 | getSkuDetailsFailed { 288 | result.error("QUERY_GET_SUBSCRIPTION_SKU_DETAILS_FAILED", it.toString(), null) 289 | } 290 | } 291 | } 292 | 293 | override fun onDetachedFromActivityForConfigChanges() { 294 | // No op 295 | } 296 | 297 | override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { 298 | // No op 299 | } 300 | 301 | override fun onDetachedFromActivity() { 302 | activityBinding = null 303 | purchaseCallback = null 304 | channel.setMethodCallHandler(null) 305 | if (::paymentConnection.isInitialized) { 306 | paymentConnection.disconnect() 307 | } 308 | } 309 | 310 | override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /android/src/main/kotlin/ir/cafebazaar/flutter_poolakey/Mappers.kt: -------------------------------------------------------------------------------- 1 | package ir.cafebazaar.flutter_poolakey 2 | 3 | import ir.cafebazaar.poolakey.entity.PurchaseInfo 4 | import ir.cafebazaar.poolakey.entity.SkuDetails 5 | 6 | internal fun PurchaseInfo.toMap() = hashMapOf( 7 | "orderId" to orderId, 8 | "purchaseToken" to purchaseToken, 9 | "payload" to payload, 10 | "packageName" to packageName, 11 | "purchaseState" to purchaseState.toString(), 12 | "purchaseTime" to purchaseTime, 13 | "productId" to productId, 14 | "originalJson" to originalJson, 15 | "dataSignature" to dataSignature 16 | ) 17 | 18 | internal fun SkuDetails.toMap() = hashMapOf( 19 | "sku" to sku, 20 | "type" to type, 21 | "price" to price, 22 | "title" to title, 23 | "description" to description 24 | ) 25 | -------------------------------------------------------------------------------- /android/src/main/kotlin/ir/cafebazaar/flutter_poolakey/PaymentActivity.kt: -------------------------------------------------------------------------------- 1 | package ir.cafebazaar.flutter_poolakey 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import androidx.activity.ComponentActivity 7 | import io.flutter.plugin.common.MethodChannel 8 | import ir.cafebazaar.poolakey.Payment 9 | import ir.cafebazaar.poolakey.callback.PurchaseCallback 10 | import ir.cafebazaar.poolakey.request.PurchaseRequest 11 | import java.security.InvalidParameterException 12 | 13 | class PaymentActivity : ComponentActivity() { 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | 17 | val callback: PurchaseCallback.() -> Unit = { 18 | purchaseSucceed { 19 | result.success(it.toMap()) 20 | finish() 21 | } 22 | purchaseCanceled { 23 | result.error("PURCHASE_CANCELLED", "Purchase flow has been canceled", null) 24 | finish() 25 | } 26 | purchaseFailed { 27 | result.error("PURCHASE_FAILED", "Purchase flow has been failed", null) 28 | finish() 29 | } 30 | purchaseFlowBegan { 31 | // Nothing 32 | } 33 | failedToBeginFlow { 34 | result.error("FAILED_TO_BEGIN_FLOW", it.toString(), null) 35 | finish() 36 | } 37 | } 38 | when (command) { 39 | Command.Purchase -> purchaseProduct(callback) 40 | Command.Subscribe -> subscribeProduct(callback) 41 | else -> throw InvalidParameterException("Undefined command: $command") 42 | } 43 | } 44 | 45 | 46 | private fun purchaseProduct(callback: PurchaseCallback.() -> Unit) { 47 | payment.purchaseProduct( 48 | activityResultRegistry, 49 | PurchaseRequest(productId, payload, dynamicPriceToken), 50 | callback 51 | ) 52 | } 53 | 54 | private fun subscribeProduct(callback: PurchaseCallback.() -> Unit) { 55 | payment.subscribeProduct( 56 | activityResultRegistry, 57 | PurchaseRequest(productId, payload, dynamicPriceToken), 58 | callback 59 | ) 60 | } 61 | 62 | companion object { 63 | 64 | private lateinit var command: PaymentActivity.Command 65 | private lateinit var productId: String 66 | private lateinit var payment: Payment 67 | private lateinit var result: MethodChannel.Result 68 | private var payload: String? = null 69 | private var dynamicPriceToken: String? = null 70 | 71 | @JvmStatic 72 | fun start( 73 | activity: Activity, 74 | command: Command, 75 | productId: String, 76 | payment: Payment, 77 | result: MethodChannel.Result, 78 | payload: String?, 79 | dynamicPriceToken: String? 80 | ) { 81 | val intent = Intent(activity, PaymentActivity::class.java) 82 | PaymentActivity.command = command 83 | PaymentActivity.productId = productId 84 | PaymentActivity.payment = payment 85 | PaymentActivity.result = result 86 | PaymentActivity.payload = payload 87 | PaymentActivity.dynamicPriceToken = dynamicPriceToken 88 | activity.startActivity(intent) 89 | } 90 | } 91 | 92 | enum class Command { 93 | Purchase, 94 | Subscribe 95 | } 96 | } -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /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: adc687823a831bbebe28bdccfac1a628ca621513 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # flutter_poolakey_example 2 | 3 | Demonstrates how to use the flutter_poolakey plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | android { 8 | namespace = "com.sample.android.trivialdrivesample" 9 | compileSdkVersion = 34 10 | sourceSets { 11 | main.java.srcDirs += 'src/main/kotlin' 12 | } 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_1_8 15 | targetCompatibility = JavaVersion.VERSION_1_8 16 | } 17 | 18 | kotlinOptions { 19 | jvmTarget = JavaVersion.VERSION_1_8 20 | } 21 | 22 | defaultConfig { 23 | applicationId "ir.cafebazaar.poolakeysample" 24 | // You can update the following values to match your application needs. 25 | // For more information, see: https://flutter.dev/to/review-gradle-config. 26 | minSdkVersion = flutter.minSdkVersion 27 | targetSdkVersion = flutter.targetSdkVersion 28 | versionCode = 1 29 | versionName = "1.0" 30 | } 31 | 32 | buildTypes { 33 | release { 34 | // TODO: Add your own signing config for the release build. 35 | // Signing with the debug keys for now, so `flutter run --release` works. 36 | signingConfig = signingConfigs.debug 37 | } 38 | } 39 | } 40 | 41 | flutter { 42 | source = "../.." 43 | } -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 14 | 18 | 21 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/sample/android/trivialdrivesample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.sample.android.trivialdrivesample 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /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/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/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/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/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/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/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/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/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/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | jcenter() 4 | maven { url "https://jitpack.io" } 5 | google() 6 | mavenCentral() 7 | } 8 | } 9 | 10 | rootProject.buildDir = "../build" 11 | subprojects { 12 | project.buildDir = "${rootProject.buildDir}/${project.name}" 13 | } 14 | subprojects { 15 | project.evaluationDependsOn(":app") 16 | } 17 | 18 | tasks.register("clean", Delete) { 19 | delete rootProject.buildDir 20 | } 21 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 6 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | maven { url "https://jitpack.io" } 17 | } 18 | } 19 | 20 | plugins { 21 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 22 | id "com.android.application" version "7.4.0" apply false 23 | id "org.jetbrains.kotlin.android" version "1.7.10" apply false 24 | } 25 | 26 | rootProject.name = 'flutter_poolakey' 27 | include ":app" -------------------------------------------------------------------------------- /example/assets/buy_gas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/example/assets/buy_gas.png -------------------------------------------------------------------------------- /example/assets/free.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/example/assets/free.png -------------------------------------------------------------------------------- /example/assets/gas0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/example/assets/gas0.png -------------------------------------------------------------------------------- /example/assets/gas1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/example/assets/gas1.png -------------------------------------------------------------------------------- /example/assets/gas2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/example/assets/gas2.png -------------------------------------------------------------------------------- /example/assets/gas3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/example/assets/gas3.png -------------------------------------------------------------------------------- /example/assets/gas4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/example/assets/gas4.png -------------------------------------------------------------------------------- /example/assets/gas_inf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/example/assets/gas_inf.png -------------------------------------------------------------------------------- /example/assets/get_infinite_gas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/example/assets/get_infinite_gas.png -------------------------------------------------------------------------------- /example/assets/get_infinite_gas_trial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/example/assets/get_infinite_gas_trial.png -------------------------------------------------------------------------------- /example/assets/premium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/example/assets/premium.png -------------------------------------------------------------------------------- /example/assets/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/example/assets/title.png -------------------------------------------------------------------------------- /example/assets/upgrade_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/example/assets/upgrade_app.png -------------------------------------------------------------------------------- /example/lib/data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_poolakey/flutter_poolakey.dart'; 2 | 3 | class ProductItem { 4 | final String id; 5 | final String icon; 6 | final bool consumable; 7 | SkuDetails? skuDetails; 8 | PurchaseInfo? purchaseInfo; 9 | 10 | ProductItem(this.id, this.icon, this.consumable); 11 | } 12 | 13 | class TrialSubscription extends SkuDetails { 14 | bool isAvailable = false; 15 | int trialPeriodDays = 0; 16 | TrialSubscription( 17 | String sku, String type, String price, String title, String description) 18 | : super(sku, type, price, title, description); 19 | 20 | static TrialSubscription fromSkuDetails(SkuDetails skuDetails) { 21 | return TrialSubscription(skuDetails.sku, skuDetails.type, skuDetails.price, 22 | skuDetails.title, skuDetails.description); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_poolakey/flutter_poolakey.dart'; 3 | 4 | void main() { 5 | runApp(const MyApp()); 6 | } 7 | 8 | class MyApp extends StatelessWidget { 9 | const MyApp({Key? key}) : super(key: key); 10 | 11 | // This widget is the root of your application. 12 | @override 13 | Widget build(BuildContext context) { 14 | return MaterialApp( 15 | title: 'Trivial Example for Flutter-Poolakey', 16 | theme: ThemeData( 17 | // This is the theme of your application. 18 | // 19 | // Try running your application with "flutter run". You'll see the 20 | // application has a blue toolbar. Then, without quitting the app, try 21 | // changing the primarySwatch below to Colors.green and then invoke 22 | // "hot reload" (press "r" in the console where you ran "flutter run", 23 | // or simply save your changes to "hot reload" in a Flutter IDE). 24 | // Notice that the counter didn't reset back to zero; the application 25 | // is not restarted. 26 | primarySwatch: Colors.green, 27 | brightness: Brightness.dark, 28 | ), 29 | home: const MyHomePage(title: 'Trivial Example for Flutter-Poolakey'), 30 | ); 31 | } 32 | } 33 | 34 | class MyHomePage extends StatefulWidget { 35 | const MyHomePage({Key? key, required this.title}) : super(key: key); 36 | 37 | // This widget is the home page of your application. It is stateful, meaning 38 | // that it has a State object (defined below) that contains fields that affect 39 | // how it looks. 40 | 41 | // This class is the configuration for the state. It holds the values (in this 42 | // case the title) provided by the parent (in this case the App widget) and 43 | // used by the build method of the State. Fields in a Widget subclass are 44 | // always marked "final". 45 | 46 | final String title; 47 | 48 | @override 49 | State createState() => _MyHomePageState(); 50 | } 51 | 52 | class _MyHomePageState extends State { 53 | final dynamicPriceTokenController = TextEditingController(); 54 | final productIdController = TextEditingController(); 55 | bool connected = false; 56 | String status = ""; 57 | bool consume = true; 58 | 59 | @override 60 | void initState() { 61 | _initShop(); 62 | super.initState(); 63 | } 64 | 65 | Future _initShop() async { 66 | var rsaKey = 67 | "MIHNMA0GCSqGSIb3DQEBAQUAA4G7ADCBtwKBrwDbkRScfggn+JSs+DzcZK20ZbxKPKv060aekC4dxqapamlgf9PncC5/4sqhUU4SdeKE770H1s7dJhmV5QEnzLawJTgiTzD3RFcadl2H4dduro/KxVyAe5nNKE/Xg+uRalLU/Hw9Or44m2xDyWESWj8sqweaGDUnsoHWJFsyVwwIj15fx3cDX6kjObC0gYns1o79x+COWCqyIlDwE2Pf7Xum55FASKFH8lqlYpEzR38CAwEAAQ== "; 68 | try { 69 | await FlutterPoolakey.connect( 70 | rsaKey, 71 | onSucceed: () { 72 | connected = true; 73 | _updateState("Service: Connected"); 74 | }, 75 | onFailed: () { 76 | connected = false; 77 | _updateState("Service: Not Connected"); 78 | }, 79 | onDisconnected: () { 80 | connected = false; 81 | _updateState("Service: Not Connected"); 82 | }, 83 | ); 84 | } on Exception catch (e) { 85 | showSnackBar(e.toString()); 86 | _updateState("Service: Failed to Connect"); 87 | } 88 | } 89 | 90 | void _updateState(String message) { 91 | setState(() { 92 | status = message; 93 | }); 94 | showSnackBar(message); 95 | } 96 | 97 | @override 98 | Widget build(BuildContext context) { 99 | return Scaffold( 100 | body: Center( 101 | child: Padding( 102 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 103 | child: Column( 104 | mainAxisSize: MainAxisSize.min, 105 | children: [ 106 | Text(status), 107 | const SizedBox(height: 8), 108 | TextField( 109 | controller: productIdController, 110 | decoration: InputDecoration( 111 | border: OutlineInputBorder(), 112 | hintText: 'Product id', 113 | ), 114 | ), 115 | const SizedBox(height: 8), 116 | TextField( 117 | controller: dynamicPriceTokenController, 118 | decoration: InputDecoration( 119 | border: OutlineInputBorder(), 120 | hintText: 'Dynamic price token', 121 | ), 122 | ), 123 | Row( 124 | children: [ 125 | Text('Consume Purchase'), 126 | Spacer(), 127 | Switch( 128 | value: consume, 129 | onChanged: (checked) { 130 | setState(() { 131 | consume = checked; 132 | }); 133 | }), 134 | ], 135 | ), 136 | FilledButton( 137 | onPressed: () { 138 | purchaseProduct( 139 | productIdController.text, 140 | "purchasePayload", 141 | dynamicPriceTokenController.text, 142 | ); 143 | }, 144 | child: Text('Purchase')), 145 | FilledButton( 146 | onPressed: () { 147 | subscribeProduct( 148 | productIdController.text, 149 | "subscribePayload", 150 | dynamicPriceTokenController.text, 151 | ); 152 | }, 153 | child: Text('Subscribe')), 154 | FilledButton( 155 | onPressed: checkUserPurchasedItem, 156 | child: Text('Check if user purchased this item')), 157 | FilledButton( 158 | onPressed: checkUserSubscribedItem, 159 | child: Text('Check if user subscribed this item')), 160 | FilledButton( 161 | onPressed: () { 162 | getSkuDetailOfInAppItem(productIdController.text); 163 | }, 164 | child: Text('Get Sku detail of in-app item')), 165 | FilledButton( 166 | onPressed: () { 167 | getSkuDetailOfSubscriptionItem(productIdController.text); 168 | }, 169 | child: Text('Get Sku detail of subscription item')), 170 | FilledButton( 171 | onPressed: checkTrialSubscription, 172 | child: Text('Check Trial subscription')) 173 | ], 174 | ), 175 | ), 176 | ), 177 | ); 178 | } 179 | 180 | Future subscribeProduct( 181 | String productId, 182 | String payload, 183 | String? dynamicPriceToken, 184 | ) async { 185 | if (!connected) { 186 | showSnackBar('Service: Not Connected'); 187 | return; 188 | } 189 | 190 | try { 191 | PurchaseInfo? response = await FlutterPoolakey.subscribe(productId, 192 | payload: payload, dynamicPriceToken: dynamicPriceToken ?? ""); 193 | } catch (e) { 194 | showSnackBar("subscribeProduct ${e.toString()}"); 195 | return; 196 | } 197 | } 198 | 199 | Future purchaseProduct( 200 | String productId, 201 | String payload, 202 | String? dynamicPriceToken, 203 | ) async { 204 | if (!connected) { 205 | showSnackBar('Service: Not Connected'); 206 | return; 207 | } 208 | try { 209 | PurchaseInfo? response = await FlutterPoolakey.purchase(productId, 210 | payload: payload, dynamicPriceToken: dynamicPriceToken ?? ""); 211 | if (consume) { 212 | consumePurchasedItem(response.purchaseToken); 213 | } 214 | } catch (e) { 215 | showSnackBar("purchaseProduct ${e.toString()}"); 216 | return; 217 | } 218 | } 219 | 220 | Future checkUserSubscribedItem() async { 221 | if (!connected) { 222 | showSnackBar('Service: Not Connected'); 223 | return; 224 | } 225 | 226 | try { 227 | List? response = 228 | await FlutterPoolakey.getAllSubscribedProducts(); 229 | bool result = response 230 | .any((element) => element.productId == productIdController.text); 231 | if (result) { 232 | showSnackBar("User has bought this item"); 233 | } else { 234 | showSnackBar("User has not bought this item"); 235 | } 236 | } catch (e) { 237 | showSnackBar("checkUserSubscribedItem ${e.toString()}"); 238 | return; 239 | } 240 | } 241 | 242 | Future checkUserPurchasedItem() async { 243 | if (!connected) { 244 | showSnackBar('Service: Not Connected'); 245 | return; 246 | } 247 | 248 | try { 249 | List? response = 250 | await FlutterPoolakey.getAllPurchasedProducts(); 251 | bool result = response 252 | .any((element) => element.productId == productIdController.text); 253 | if (result) { 254 | showSnackBar("User has bought this item"); 255 | } else { 256 | showSnackBar("User has not bought this item"); 257 | } 258 | } catch (e) { 259 | showSnackBar("checkUserPurchasedItem ${e.toString()}"); 260 | return; 261 | } 262 | } 263 | 264 | Future getSkuDetailOfSubscriptionItem(String skuValueInput) async { 265 | if (!connected) { 266 | showSnackBar('Service: Not Connected'); 267 | return; 268 | } 269 | 270 | try { 271 | List? response = 272 | await FlutterPoolakey.getSubscriptionSkuDetails([skuValueInput]); 273 | showSnackBar("Detail Of Subscription Item ${response.toString()}"); 274 | } catch (e) { 275 | showSnackBar("getSkuDetailOfSubscriptionItem ${e.toString()}"); 276 | return; 277 | } 278 | } 279 | 280 | Future getSkuDetailOfInAppItem(String skuValueInput) async { 281 | if (!connected) { 282 | showSnackBar('Service: Not Connected'); 283 | return; 284 | } 285 | 286 | try { 287 | List? response = 288 | await FlutterPoolakey.getInAppSkuDetails([skuValueInput]); 289 | showSnackBar("Detail Of InApp Item ${response.toString()}"); 290 | } catch (e) { 291 | showSnackBar("getSkuDetailOfInAppItem ${e.toString()}"); 292 | return; 293 | } 294 | } 295 | 296 | Future checkTrialSubscription() async { 297 | if (!connected) { 298 | showSnackBar('Service: Not Connected'); 299 | return; 300 | } 301 | 302 | try { 303 | Map response = await FlutterPoolakey.checkTrialSubscription(); 304 | showSnackBar("isAvailable ${response["isAvailable"].toString()}"); 305 | } catch (e) { 306 | showSnackBar("checkTrialSubscription ${e.toString()}"); 307 | return; 308 | } 309 | } 310 | 311 | Future consumePurchasedItem(String purchaseToken) async { 312 | if (!connected) { 313 | showSnackBar('Service: Not Connected'); 314 | return; 315 | } 316 | 317 | try { 318 | bool? response = await FlutterPoolakey.consume(purchaseToken); 319 | showSnackBar("consumePurchasedItem success $response"); 320 | } catch (e) { 321 | showSnackBar(e.toString()); 322 | return; 323 | } 324 | } 325 | 326 | void showSnackBar(String message) { 327 | ScaffoldMessenger.of(context) 328 | .showSnackBar(SnackBar(content: Text(message))); 329 | } 330 | 331 | @override 332 | void dispose() { 333 | dynamicPriceTokenController.dispose(); 334 | productIdController.dispose(); 335 | super.dispose(); 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_poolakey_example 2 | description: Demonstrates how to use the flutter_poolakey plugin. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | # publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | environment: 9 | sdk: ">=2.12.0 <3.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | flutter_poolakey: 16 | # When depending on this package from a real application you should use: 17 | # flutter_poolakey: ^x.y.z 18 | # See https://dart.dev/tools/pub/dependencies#version-constraints 19 | # The example app is bundled with the plugin so we use a path dependency on 20 | # the parent directory to use the current plugin's version. 21 | path: ../ 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^1.0.2 26 | 27 | dev_dependencies: 28 | flutter_test: 29 | sdk: flutter 30 | 31 | # For information on the generic Dart part of this file, see the 32 | # following page: https://dart.dev/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.dev/assets-and-images/#resolution-aware. 49 | 50 | # For details regarding adding assets from package dependencies, see 51 | # https://flutter.dev/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 | assets: 71 | - assets/ 72 | # For details regarding fonts from package dependencies, 73 | # see https://flutter.dev/custom-fonts/#from-packages 74 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | void main() { 11 | testWidgets('Verify Platform version', (WidgetTester tester) async {}); 12 | } 13 | -------------------------------------------------------------------------------- /lib/flutter_poolakey.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/services.dart'; 4 | 5 | import 'purchase_info.dart'; 6 | import 'sku_details.dart'; 7 | 8 | export 'purchase_info.dart'; 9 | export 'sku_details.dart'; 10 | 11 | /// [FlutterPoolakey] is a Flutter In-App Billing SDK for Cafe Bazaar App Store. 12 | /// 13 | /// It only works in the Android platform (Because [Cafebazaar](https://cafebazaar.ir/?l=en) only supports Android) 14 | /// It uses [Poolakey](https://github.com/cafebazaar/Poolakey) SDK under the hood. 15 | class FlutterPoolakey { 16 | static const MethodChannel _channel = 17 | const MethodChannel('ir.cafebazaar.flutter_poolakey'); 18 | 19 | static Future getVersion() async { 20 | return await _channel.invokeMethod('version', {}); 21 | } 22 | 23 | /// Initializes the connection between your app and the bazaar app 24 | /// 25 | /// You must call this method before any other methods of this library. 26 | /// [inAppBillingKey] is the RSA key which you can find it in your (pishkhan panel)[https://pishkhan.cafebazaar.ir]. 27 | /// You can also disable the local security check (only if you are using Bazaar's REST API) 28 | /// by passing null as [inAppBillingKey]. 29 | /// 30 | /// You should listen to [onSucceed] callback to make sure FlutterPoolakey is connected successfully. 31 | /// 32 | /// You should listen to [onFailed] callback to handle connect error. 33 | /// 34 | /// You should listen to [onDisconnected] callback and call [FlutterPoolakey.connect] to reconnect again. 35 | /// 36 | /// This function may return an error, you should handle the error and check the stacktrace to resolve it. 37 | static Future connect(String? inAppBillingKey, 38 | {VoidCallback? onSucceed, 39 | VoidCallback? onFailed, 40 | VoidCallback? onDisconnected}) async { 41 | _registerConnectCallBack(onSucceed, onFailed, onDisconnected); 42 | await _channel 43 | .invokeMethod('connect', {'in_app_billing_key': inAppBillingKey}); 44 | } 45 | 46 | static void _registerConnectCallBack(VoidCallback? onConnectionSucceed, 47 | VoidCallback? onConnectionFailed, VoidCallback? onDisconnected) { 48 | if (onConnectionSucceed == null && 49 | onConnectionFailed == null && 50 | onDisconnected == null) { 51 | return; 52 | } 53 | _channel.setMethodCallHandler((call) { 54 | if (call.method == 'disconnected') { 55 | onDisconnected?.call(); 56 | return Future.value(true); 57 | } else if (call.method == 'connectionSucceed') { 58 | onConnectionSucceed?.call(); 59 | return Future.value(true); 60 | } else if (call.method == 'connectionFailed') { 61 | onConnectionFailed?.call(); 62 | return Future.value(true); 63 | } 64 | throw StateError('method ${call.method} is not supported'); 65 | }); 66 | } 67 | 68 | ///To avoid problems such as Memory Leak, 69 | ///you must disconnect from the market in the dispose method of your page widget 70 | /// or when you no longer have anything to do with it: 71 | static Future disconnect() async { 72 | return _channel.invokeMethod('disconnect'); 73 | } 74 | 75 | /// Initiates the purchase flow 76 | /// 77 | /// [productId] is your product's identifier (also known as SKU). 78 | /// [payload] is an optional parameter that you can send, bazaar saves it in the [PurchaseInfo]. 79 | /// You can access it through your back-end API. 80 | /// [dynamicPriceToken] This is a token that the developer can apply to any user to change the price of the product. 81 | /// for more info about this please read the site documentation. 82 | /// 83 | /// If any error happened (like when user cancels the flow) it throws a [PlatformException] with a stacktrace. 84 | /// You must handle the error with your logic. 85 | /// 86 | /// If it succeeded, it returns the [PurchaseInfo] of the purchased product. 87 | static Future purchase( 88 | String productId, { 89 | String payload = "", 90 | String dynamicPriceToken = "", 91 | }) async { 92 | final map = await _channel.invokeMethod('purchase', { 93 | 'product_id': productId, 94 | 'payload': payload, 95 | 'dynamicPriceToken': dynamicPriceToken 96 | }); 97 | return PurchaseInfo.fromMap(map); 98 | } 99 | 100 | /// Initializes the subscription flow 101 | /// 102 | /// [productId] is your product's identifier (also known as SKU). 103 | /// [payload] is an optional parameter that you can send, bazaar saves it in the [PurchaseInfo]. 104 | /// You can access it through your back-end API. 105 | /// [dynamicPriceToken] This is a token that the developer can apply to any user to change the price of the product. 106 | /// for more info about this please read the site documentation. 107 | /// 108 | /// If any error happened (like when user cancels the flow) it throws a [PlatformException] with a stacktrace. 109 | /// You must handle the error with your logic. 110 | /// 111 | /// If it succeeded, it returns the [PurchaseInfo] of the purchased product. 112 | static Future subscribe( 113 | String productId, { 114 | String payload = "", 115 | String dynamicPriceToken = "", 116 | }) async { 117 | final map = await _channel.invokeMethod('subscribe', { 118 | 'product_id': productId, 119 | 'payload': payload, 120 | 'dynamicPriceToken': dynamicPriceToken 121 | }); 122 | return PurchaseInfo.fromMap(map); 123 | } 124 | 125 | /// Consumes a consumable product 126 | /// 127 | /// It consumes a product which you defined consumable in your business logic. 128 | /// If you consume a product, user can buy that product again. 129 | /// Otherwise user can only buy a purchasable product once. 130 | /// 131 | /// If any error happened it throws a [PlatformException] with a stacktrace. 132 | /// You must handle the error with your logic. 133 | /// 134 | /// [purchaseToken] is the purchase identifier, you can find it in [PurchaseInfo.purchaseToken] 135 | /// 136 | /// It returns true if the process is successful 137 | static Future consume(String purchaseToken) async { 138 | return await _channel 139 | .invokeMethod('consume', {'purchase_token': purchaseToken}); 140 | } 141 | 142 | /// Returns all purchases list 143 | /// 144 | /// Retrieves list of [PurchaseInfo] which contains all purchased products in user's inventory. 145 | static Future> getAllPurchasedProducts() async { 146 | final List list = await _channel.invokeMethod("get_all_purchased_products"); 147 | return list.map((map) => PurchaseInfo.fromMap(map)).toList(); 148 | } 149 | 150 | /// Check Trial-Subscription existanse 151 | /// 152 | /// It availbles if not subscribe any item. 153 | static Future checkTrialSubscription() async { 154 | return await _channel.invokeMethod("checkTrialSubscription"); 155 | } 156 | 157 | /// Queries a purchased product in the user's inventory 158 | /// 159 | /// It queries and finds a [PurchaseInfo] (or null if it doesn't find) using provided [productId] 160 | /// in the user's purchased products. 161 | /// 162 | /// If any error happened during the query flow, it throws a [PlatformException] with a stack trace. 163 | /// You must handle the error with your logic. 164 | /// 165 | /// It returns a [PurchaseInfo] if this product exists in the user's inventory. 166 | /// If it doesn't find any, it returns null (That's why [PurchaseInfo] is nullable). 167 | static Future queryPurchasedProduct(String productId) async { 168 | try { 169 | return (await getAllPurchasedProducts()).find(productId); 170 | } catch (e) { 171 | return Future.error(e); 172 | } 173 | } 174 | 175 | /// Returns all subscribed products 176 | /// 177 | /// Retrieves list of [PurchaseInfo] which contains all subscribed products in user's inventory. 178 | static Future> getAllSubscribedProducts() async { 179 | final List list = 180 | await _channel.invokeMethod("get_all_subscribed_products"); 181 | return list.map((map) => PurchaseInfo.fromMap(map)).toList(); 182 | } 183 | 184 | /// Queries a subscribed product in the user's inventory 185 | /// 186 | /// It queries and finds a [PurchaseInfo] (or null if it doesn't find) using provided [productId] 187 | /// in the user's subscribed products. 188 | /// 189 | /// If any error happened during the query flow, it throws a [PlatformException] with a stack trace. 190 | /// You must handle the error with your logic. 191 | /// 192 | /// It returns a [PurchaseInfo] if this product exists in the user's inventory. 193 | /// If it doesn't find any, it returns null (That's why [PurchaseInfo] is nullable). 194 | static Future querySubscribedProduct(String productId) async { 195 | try { 196 | return (await getAllSubscribedProducts()).find(productId); 197 | } catch (e) { 198 | return Future.error(e); 199 | } 200 | } 201 | 202 | /// Returns sku details of your purchasable products 203 | /// 204 | /// You can use this function to get detail of inApp product sku's, 205 | /// [skuIds] Contain all sku id's that you want to get info about it. 206 | static Future> getInAppSkuDetails( 207 | List skuIds) async { 208 | final List list = await _channel 209 | .invokeMethod("get_in_app_sku_details", {'sku_ids': skuIds}); 210 | return list.map((map) => SkuDetails.fromMap(map)).toList(); 211 | } 212 | 213 | /// Returns sku details of your subscribable products 214 | /// 215 | /// You can use this function to get detail of subscription product sku's, 216 | /// [skuIds] Contain all sku id's that you want to get info about it. 217 | static Future> getSubscriptionSkuDetails( 218 | List skuIds) async { 219 | final List list = await _channel 220 | .invokeMethod("get_subscription_sku_details", {'sku_ids': skuIds}); 221 | return list.map((map) => SkuDetails.fromMap(map)).toList(); 222 | } 223 | } 224 | 225 | extension _purchaseInfoExtension on List { 226 | PurchaseInfo? find(String productId) { 227 | for (final info in this) { 228 | if (info.productId == productId) { 229 | return info; 230 | } 231 | } 232 | return null; 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /lib/purchase_info.dart: -------------------------------------------------------------------------------- 1 | class PurchaseInfo { 2 | final String orderId; 3 | final String purchaseToken; 4 | final String payload; 5 | final String packageName; 6 | final PurchaseState purchaseState; 7 | final int purchaseTime; 8 | final String productId; 9 | final String originalJson; 10 | final String dataSignature; 11 | 12 | PurchaseInfo( 13 | this.orderId, 14 | this.purchaseToken, 15 | this.payload, 16 | this.packageName, 17 | this.purchaseState, 18 | this.purchaseTime, 19 | this.productId, 20 | this.originalJson, 21 | this.dataSignature); 22 | 23 | factory PurchaseInfo.fromMap(Map typeName) { 24 | return PurchaseInfo( 25 | typeName['orderId'] as String, 26 | typeName['purchaseToken'] as String, 27 | typeName['payload'] as String, 28 | typeName['packageName'] as String, 29 | parsePurchaseState(typeName['purchaseState']), 30 | typeName['purchaseTime'] as int, 31 | typeName['productId'] as String, 32 | typeName['originalJson'] as String, 33 | typeName['dataSignature'] as String, 34 | ); 35 | } 36 | 37 | @override 38 | String toString() => 'orderId: $orderId,' 39 | '\npurchaseToken: $purchaseToken,' 40 | '\npayload: $payload,' 41 | '\npackageName: $packageName,' 42 | '\npurchaseState: $purchaseState,' 43 | '\npurchaseTime: $purchaseTime,' 44 | '\nproductId: $productId,' 45 | '\noriginalJson: $originalJson,' 46 | '\ndataSignature: $dataSignature'; 47 | } 48 | 49 | enum PurchaseState { PURCHASED, REFUNDED } 50 | 51 | PurchaseState parsePurchaseState(String state) { 52 | switch (state) { 53 | case 'PURCHASED': 54 | return PurchaseState.PURCHASED; 55 | case 'REFUNDED': 56 | return PurchaseState.REFUNDED; 57 | } 58 | throw StateError('state is not defined'); 59 | } 60 | -------------------------------------------------------------------------------- /lib/sku_details.dart: -------------------------------------------------------------------------------- 1 | class SkuDetails { 2 | final String sku; 3 | final String type; 4 | final String price; 5 | final String title; 6 | final String description; 7 | 8 | SkuDetails(this.sku, this.type, this.price, this.title, this.description); 9 | 10 | factory SkuDetails.fromMap(Map skuDetailsMap) { 11 | return SkuDetails( 12 | skuDetailsMap['sku'] as String, 13 | skuDetailsMap['type'] as String, 14 | skuDetailsMap['price'] as String, 15 | skuDetailsMap['title'] as String, 16 | skuDetailsMap['description'] as String, 17 | ); 18 | } 19 | 20 | @override 21 | String toString() => 'sku: $sku,' 22 | '\ntype: $type,' 23 | '\nprice: $price,' 24 | '\ntitle: $title,' 25 | '\ndescription: $description'; 26 | } 27 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_poolakey 2 | description: flutter_poolakey is a wrapper around the `Poolakey` to use in Flutter. Poolakey is an Android In-App Billing SDK for `Cafe Bazaar` App Store. All specific features in payment of Cafe Bazaar, will be added on `Poolakey`. 3 | version: 2.2.0-1.0.0-alpha01 4 | homepage: https://github.com/cafebazaar/flutter_poolakey 5 | 6 | environment: 7 | sdk: '>=3.0.6 <4.0.0' 8 | flutter: ">=3.3.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | plugin_platform_interface: ^2.0.2 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | 19 | flutter: 20 | plugin: 21 | platforms: 22 | android: 23 | package: ir.cafebazaar.flutter_poolakey 24 | pluginClass: FlutterPoolakeyPlugin -------------------------------------------------------------------------------- /repo_files/flutter_poolakey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafebazaar/flutter_poolakey/8f462b8a965fbcce0a9f5acfc0bd16ea52f7db9c/repo_files/flutter_poolakey.jpg -------------------------------------------------------------------------------- /test/foolakey_test.dart: -------------------------------------------------------------------------------- 1 | void main() {} 2 | --------------------------------------------------------------------------------