From 8637c24020983fa34e112abc3931644d41a7fd6c Mon Sep 17 00:00:00 2001 From: lijuan Date: Wed, 6 Nov 2024 15:56:30 +0800 Subject: [PATCH] =?UTF-8?q?'in=5Fapp=5Fpurchase-v3.1.13=E5=8D=87=E7=BA=A7F?= =?UTF-8?q?lutter3.22'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: lijuan --- .../in_app_purchase/in_app_purchase/README.md | 2 +- .../example/android/app/build.gradle | 4 +- .../in_app_purchase/example/ios/Podfile | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 37 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- .../in_app_purchase/example/ohos/.gitignore | 19 + .../example/ohos/AppScope/app.json5 | 10 + .../resources/base/element/string.json | 8 + .../resources/base/media/app_icon.png | Bin 0 -> 6790 bytes .../example/ohos/build-profile.json5 | 27 + .../example/ohos/entry/.gitignore | 7 + .../example/ohos/entry/build-profile.json5 | 29 + .../example/ohos/entry/hvigorfile.ts | 17 + .../example/ohos/entry/oh-package.json5 | 12 + .../main/ets/entryability/EntryAbility.ets | 24 + .../ohos/entry/src/main/ets/pages/Index.ets | 38 + .../example/ohos/entry/src/main/module.json5 | 53 + .../main/resources/base/element/color.json | 8 + .../main/resources/base/element/string.json | 16 + .../src/main/resources/base/media/icon.png | Bin 0 -> 6790 bytes .../resources/base/profile/main_pages.json | 5 + .../main/resources/en_US/element/string.json | 16 + .../main/resources/zh_CN/element/string.json | 16 + .../src/ohosTest/ets/test/Ability.test.ets | 50 + .../entry/src/ohosTest/ets/test/List.test.ets | 20 + .../ohosTest/ets/testability/TestAbility.ets | 63 + .../ohosTest/ets/testability/pages/Index.ets | 49 + .../ets/testrunner/OpenHarmonyTestRunner.ts | 64 + .../ohos/entry/src/ohosTest/module.json5 | 51 + .../resources/base/element/color.json | 8 + .../resources/base/element/string.json | 16 + .../ohosTest/resources/base/media/icon.png | Bin 0 -> 6790 bytes .../resources/base/profile/test_pages.json | 5 + .../example/ohos/hvigor/hvigor-config.json5 | 20 + .../example/ohos/hvigorfile.ts | 21 + .../example/ohos/oh-package.json5 | 21 + .../in_app_purchase/example/pubspec.yaml | 13 +- .../in_app_purchase/lib/in_app_purchase.dart | 22 +- .../in_app_purchase/pubspec.yaml | 16 +- .../test/in_app_purchase_test.dart | 14 - .../in_app_purchase_android/CHANGELOG.md | 36 - .../android/build.gradle | 15 +- .../inapppurchase/BillingClientFactory.java | 11 +- .../BillingClientFactoryImpl.java | 47 +- .../inapppurchase/InAppPurchasePlugin.java | 15 +- .../plugins/inapppurchase/Messages.java | 3149 ----------------- .../inapppurchase/MethodCallHandlerImpl.java | 524 ++- .../inapppurchase/PluginPurchaseListener.java | 35 +- .../plugins/inapppurchase/Translator.java | 334 +- .../BillingClientFactoryImplTest.java | 105 - .../inapppurchase/MethodCallHandlerTest.java | 1122 +++--- .../plugins/inapppurchase/TranslatorTest.java | 207 +- .../in_app_purchase_android/example/README.md | 55 - .../example/android/.gitignore | 2 - .../example/android/app/build.gradle | 8 +- .../android/app/src/main/AndroidManifest.xml | 1 - .../android/keystore.example.properties | 3 +- .../in_app_purchase_test.dart | 3 +- .../example/lib/main.dart | 212 +- .../example/pubspec.yaml | 4 +- .../lib/billing_client_wrappers.dart | 2 - ...illing_only_reporting_details_wrapper.dart | 81 - ...ling_only_reporting_details_wrapper.g.dart | 17 - .../billing_client_manager.dart | 65 +- .../billing_client_wrapper.dart | 312 +- .../billing_client_wrapper.g.dart | 6 - .../billing_config_wrapper.dart | 76 - .../billing_config_wrapper.g.dart | 15 - .../billing_response_wrapper.dart | 4 - ...e_time_purchase_offer_details_wrapper.dart | 3 +- .../product_details_wrapper.dart | 7 +- .../purchase_wrapper.dart | 12 +- .../subscription_offer_details_wrapper.dart | 6 +- .../user_choice_details_wrapper.dart | 133 - .../user_choice_details_wrapper.g.dart | 45 - .../lib/src/channel.dart | 9 + .../src/in_app_purchase_android_platform.dart | 23 +- ...pp_purchase_android_platform_addition.dart | 76 +- .../lib/src/messages.g.dart | 1407 -------- .../lib/src/pigeon_converters.dart | 259 -- .../google_play_user_choice_details.dart | 101 - .../lib/src/types/translator.dart | 47 - .../lib/src/types/types.dart | 1 - .../pigeons/copyright.txt | 3 - .../pigeons/messages.dart | 416 --- .../in_app_purchase_android/pubspec.yaml | 10 +- .../billing_client_manager_test.dart | 120 +- .../billing_client_wrapper_test.dart | 622 ++-- .../billing_client_wrapper_test.mocks.dart | 428 --- ...rchase_android_platform_addition_test.dart | 252 +- ...in_app_purchase_android_platform_test.dart | 662 ++-- .../test/stub_in_app_purchase_platform.dart | 48 + .../test/test_conversion_utils.dart | 113 - .../test/types/translator_test.dart | 71 - .../in_app_purchase_ohos/AUTHORS | 67 + .../in_app_purchase_ohos/CHANGELOG.md | 3 + .../in_app_purchase_ohos/LICENSE | 25 + .../in_app_purchase_ohos/README.md | 24 + .../in_app_purchase_ohos/build.yaml | 8 + .../in_app_purchase_ohos/example/README.md | 9 + .../in_app_purchase_test.dart | 20 + .../example/lib/consumable_store.dart | 52 + .../example/lib/main.dart | 414 +++ .../example/ohos/.gitignore | 19 + .../example/ohos/AppScope/app.json5 | 10 + .../resources/base/element/string.json | 8 + .../resources/base/media/app_icon.png | Bin 0 -> 6790 bytes .../example/ohos/build-profile.json5 | 27 + .../example/ohos/entry/.gitignore | 7 + .../example/ohos/entry/build-profile.json5 | 29 + .../example/ohos/entry/hvigorfile.ts | 17 + .../example/ohos/entry/oh-package.json5 | 13 + .../main/ets/entryability/EntryAbility.ets | 24 + .../ohos/entry/src/main/ets/pages/Index.ets | 38 + .../example/ohos/entry/src/main/module.json5 | 53 + .../main/resources/base/element/color.json | 8 + .../main/resources/base/element/string.json | 16 + .../src/main/resources/base/media/icon.png | Bin 0 -> 6790 bytes .../resources/base/profile/main_pages.json | 5 + .../main/resources/en_US/element/string.json | 16 + .../main/resources/zh_CN/element/string.json | 16 + .../src/ohosTest/ets/test/Ability.test.ets | 50 + .../entry/src/ohosTest/ets/test/List.test.ets | 20 + .../ohosTest/ets/testability/TestAbility.ets | 63 + .../ohosTest/ets/testability/pages/Index.ets | 49 + .../ets/testrunner/OpenHarmonyTestRunner.ts | 64 + .../ohos/entry/src/ohosTest/module.json5 | 51 + .../resources/base/element/color.json | 8 + .../resources/base/element/string.json | 16 + .../ohosTest/resources/base/media/icon.png | Bin 0 -> 6790 bytes .../resources/base/profile/test_pages.json | 5 + .../example/ohos/hvigor/hvigor-config.json5 | 20 + .../example/ohos/hvigorfile.ts | 21 + .../example/ohos/oh-package.json5 | 22 + .../in_app_purchase_ohos/example/pubspec.yaml | 33 + .../lib/iap_kit_wrappers.dart | 9 + .../lib/in_app_purchase_ohos.dart | 7 + .../in_app_purchase_ohos/lib/src/channel.dart | 9 + .../lib/src/iap_kit_wrappers/README.md | 5 + .../src/iap_kit_wrappers/enum_converters.dart | 143 + .../iap_kit_wrappers/enum_converters.g.dart | 42 + .../ik_payment_queue_wrapper.dart | 264 ++ .../ik_payment_queue_wrapper.g.dart | 50 + .../ik_payment_transaction_wrappers.dart | 172 + .../ik_payment_transaction_wrappers.g.dart | 36 + .../iap_kit_wrappers/ik_product_wrapper.dart | 279 ++ .../ik_product_wrapper.g.dart | 69 + .../iap_kit_wrappers/ik_receipt_manager.dart | 18 + .../iap_kit_wrappers/ik_request_maker.dart | 46 + .../src/in_app_purchase_ohos_platform.dart | 219 ++ ...n_app_purchase_ohos_platform_addition.dart | 33 + .../types/app_gallery_product_details.dart | 43 + .../types/app_gallery_purchase_details.dart | 79 + .../src/types/app_gallery_purchase_param.dart | 18 + .../lib/src/types/types.dart | 7 + .../in_app_purchase_ohos/ohos/.gitignore | 6 + .../ohos/BuildProfile.ets | 32 + .../in_app_purchase_ohos/ohos/Index.ets | 17 + .../ohos/build-profile.json5 | 31 + .../ohos/consumer-rules.txt | 0 .../in_app_purchase_ohos/ohos/hvigorfile.ts | 21 + .../ohos/obfuscation-rules.txt | 23 + .../ohos/oh-package.json5 | 11 + .../ohos/src/main/ets/common/JWTUtil.ts | 60 + .../ohos/src/main/ets/common/TsUtil.ts | 24 + .../ets/components/InAppPurchasePlugin.ets | 69 + .../ets/components/MethodCallHandlerImpl.ets | 288 ++ .../src/main/ets/components/MethodNames.ets | 26 + .../src/main/ets/components/ProductType.ets | 21 + .../src/main/ets/components/PurchaseData.ets | 20 + .../ets/components/PurchaseOrderPayload.ets | 50 + .../main/ets/components/TransactionState.ts | 23 + .../ohos/src/main/module.json5 | 11 + .../main/resources/base/element/string.json | 8 + .../main/resources/en_US/element/string.json | 8 + .../main/resources/zh_CN/element/string.json | 8 + .../src/ohosTest/ets/test/Ability.test.ets | 50 + .../ohos/src/ohosTest/ets/test/List.test.ets | 20 + .../ohos/src/ohosTest/module.json5 | 13 + .../ohos/src/test/List.test.ets | 20 + .../ohos/src/test/LocalUnit.test.ets | 48 + .../in_app_purchase_ohos/pubspec.yaml | 35 + .../CHANGELOG.md | 7 +- .../lib/src/in_app_purchase_platform.dart | 17 - .../pubspec.yaml | 6 +- .../test/in_app_purchase_platform_test.dart | 12 - .../in_app_purchase_storekit/CHANGELOG.md | 38 - .../darwin/Classes/FIAObjectTranslator.h | 30 +- .../darwin/Classes/FIAObjectTranslator.m | 263 +- .../darwin/Classes/FIAPReceiptManager.m | 7 +- .../Classes/InAppPurchasePlugin+TestOnly.h | 32 - .../darwin/Classes/InAppPurchasePlugin.h | 4 +- .../darwin/Classes/InAppPurchasePlugin.m | 279 +- .../darwin/Classes/messages.g.h | 286 -- .../darwin/Classes/messages.g.m | 897 ----- .../darwin/Resources/PrivacyInfo.xcprivacy | 14 - .../darwin/in_app_purchase_storekit.podspec | 3 +- .../ios/Flutter/AppFrameworkInfo.plist | 2 +- .../example/ios/Podfile | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 47 +- .../xcshareddata/xcschemes/Runner.xcscheme | 5 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- .../example/pubspec.yaml | 4 +- .../RunnerTests/InAppPurchasePluginTests.m | 878 ++--- .../example/shared/RunnerTests/Stubs.h | 4 - .../example/shared/RunnerTests/Stubs.m | 31 +- .../shared/RunnerTests/TranslatorTests.m | 146 +- .../in_app_purchase_storekit_platform.dart | 8 - .../lib/src/messages.g.dart | 963 ----- .../sk_payment_queue_wrapper.dart | 101 +- .../sk_payment_transaction_wrappers.dart | 38 +- .../sk_product_wrapper.dart | 231 +- .../sk_receipt_manager.dart | 8 +- .../store_kit_wrappers/sk_request_maker.dart | 23 +- .../sk_storefront_wrapper.dart | 8 - .../pigeons/copyright.txt | 3 - .../pigeons/messages.dart | 261 -- .../in_app_purchase_storekit/pubspec.yaml | 9 +- .../test/fakes/fake_storekit_platform.dart | 282 +- ...rchase_storekit_platform_addtion_test.dart | 13 +- ...n_app_purchase_storekit_platform_test.dart | 50 +- .../pigeon_converter_test.dart | 107 - .../sk_methodchannel_apis_test.dart | 198 +- .../sk_payment_queue_delegate_api_test.dart | 38 +- .../sk_test_stub_objects.dart | 69 - .../test/test_api.g.dart | 532 --- 226 files changed, 7457 insertions(+), 14487 deletions(-) create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/.gitignore create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/AppScope/app.json5 create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/AppScope/resources/base/element/string.json create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/AppScope/resources/base/media/app_icon.png create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/build-profile.json5 create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/.gitignore create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/build-profile.json5 create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/hvigorfile.ts create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/oh-package.json5 create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/main/ets/pages/Index.ets create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/main/module.json5 create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/main/resources/base/element/color.json create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/main/resources/base/element/string.json create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/main/resources/base/media/icon.png create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/main/resources/base/profile/main_pages.json create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/main/resources/en_US/element/string.json create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/main/resources/zh_CN/element/string.json create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/ets/test/List.test.ets create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/module.json5 create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/resources/base/element/color.json create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/resources/base/element/string.json create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/resources/base/media/icon.png create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/hvigor/hvigor-config.json5 create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/hvigorfile.ts create mode 100644 packages/in_app_purchase/in_app_purchase/example/ohos/oh-package.json5 delete mode 100644 packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Messages.java delete mode 100644 packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImplTest.java delete mode 100644 packages/in_app_purchase/in_app_purchase_android/example/android/.gitignore delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.g.dart create mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/channel.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/messages.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/pigeon_converters.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_user_choice_details.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/types/translator.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/pigeons/copyright.txt delete mode 100644 packages/in_app_purchase/in_app_purchase_android/pigeons/messages.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.mocks.dart create mode 100644 packages/in_app_purchase/in_app_purchase_android/test/stub_in_app_purchase_platform.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/test/test_conversion_utils.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/test/types/translator_test.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/AUTHORS create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/CHANGELOG.md create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/LICENSE create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/README.md create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/build.yaml create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/README.md create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/integration_test/in_app_purchase_test.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/lib/consumable_store.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/lib/main.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/.gitignore create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/AppScope/app.json5 create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/AppScope/resources/base/element/string.json create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/AppScope/resources/base/media/app_icon.png create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/build-profile.json5 create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/.gitignore create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/build-profile.json5 create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/hvigorfile.ts create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/oh-package.json5 create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/main/ets/pages/Index.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/main/module.json5 create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/main/resources/base/element/color.json create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/main/resources/base/element/string.json create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/main/resources/base/media/icon.png create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/main/resources/base/profile/main_pages.json create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/main/resources/en_US/element/string.json create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/main/resources/zh_CN/element/string.json create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/ets/test/List.test.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/module.json5 create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/resources/base/element/color.json create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/resources/base/element/string.json create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/resources/base/media/icon.png create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/hvigor/hvigor-config.json5 create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/hvigorfile.ts create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/ohos/oh-package.json5 create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/example/pubspec.yaml create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/iap_kit_wrappers.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/in_app_purchase_ohos.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/channel.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/README.md create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/enum_converters.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/enum_converters.g.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_payment_queue_wrapper.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_payment_queue_wrapper.g.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_payment_transaction_wrappers.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_payment_transaction_wrappers.g.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_product_wrapper.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_product_wrapper.g.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_receipt_manager.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_request_maker.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/in_app_purchase_ohos_platform.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/in_app_purchase_ohos_platform_addition.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/types/app_gallery_product_details.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/types/app_gallery_purchase_details.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/types/app_gallery_purchase_param.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/lib/src/types/types.dart create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/.gitignore create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/BuildProfile.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/Index.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/build-profile.json5 create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/consumer-rules.txt create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/hvigorfile.ts create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/obfuscation-rules.txt create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/oh-package.json5 create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/common/JWTUtil.ts create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/common/TsUtil.ts create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/InAppPurchasePlugin.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/MethodCallHandlerImpl.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/MethodNames.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/ProductType.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/PurchaseData.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/PurchaseOrderPayload.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/TransactionState.ts create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/module.json5 create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/resources/base/element/string.json create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/resources/en_US/element/string.json create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/resources/zh_CN/element/string.json create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/ohosTest/ets/test/Ability.test.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/ohosTest/ets/test/List.test.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/ohosTest/module.json5 create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/test/List.test.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/ohos/src/test/LocalUnit.test.ets create mode 100644 packages/in_app_purchase/in_app_purchase_ohos/pubspec.yaml delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/darwin/Resources/PrivacyInfo.xcprivacy delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/pigeons/copyright.txt delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/pigeon_converter_test.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart diff --git a/packages/in_app_purchase/in_app_purchase/README.md b/packages/in_app_purchase/in_app_purchase/README.md index 9242ecee4..59cb2c2dd 100644 --- a/packages/in_app_purchase/in_app_purchase/README.md +++ b/packages/in_app_purchase/in_app_purchase/README.md @@ -7,7 +7,7 @@ which can be the App Store (on iOS and macOS) or Google Play (on Android). | | Android | iOS | macOS | |-------------|---------|-------|--------| -| **Support** | SDK 16+ | 12.0+ | 10.15+ | +| **Support** | SDK 16+ | 11.0+ | 10.15+ |

R?p|tHQo6fg38|$UVM!6BLrPFWk?s;$LOP{GmJpBl$qoSA!PUg~PA65-S00{{S`XKG6NkG0RgjEntPrmV+?0|00mu7;+5 zrdpa{2QLqPJ4Y{j7=Mrl{BaxrkdY69+c~(w{Fv-v&aR%aEI&JYSeRTLWm!zbv;?)_ ziZB;fwGbbeL5Q}YLx`J$lp~A09KK8t_z}PZ=4ZzgdeKtgoc+o5EvN9A1K1_<>M?MBqb#!ASf&# zEX?<)!RH(7>1P+j=jqG(58}TVN-$psA6K}atCuI!KTJD&FMmH-78ZejBm)0qc{ESp z|LuG1{QnBUJRg_E=h1#XMWt2%fcoN@l7eAS!Es?Q+;XsRNPhiiE=@AqlLkJzF`O18 zbsbSmKN=aaq8k3NFYZfDWpKmM!coBU0(XnL8R{4=i|wi{!uWYM2je{U{B*K2PVdu&=E zTq*-XsEsJ$u5H4g6DIm2Y!DN`>^v|AqlwuCD;w45K0@eqauiqWf7l&o)+YLHm~|L~ z7$0v5mkobriU!H<@mVJHLlmQqzQ3d6Rh_-|%Yy2li*tHO>_vcnuZ7OR_xkAIuIU&x z-|8Y0wj|6|a6_I(v91y%k_kNw6pnkNdxjqG8!%Vz_d%c_!X+6-;1`GC9_FpjoHev5fEV7RhJ>r=mh-jp$fqbqRJ=obwdgLDVP5+s zy1=_DWG0Y-Jb3t^WXmkr(d9~08k-|#Ly zaNOmT(^9tIb&eb4%CzIT zAm3CUtWSr1t4?h1kk#NBi{U|pJslvME{q|_eS^3En>SOqSxyuN1x;Is@8~m?*>}** znrRFArP!K_52RpX*&JHMR<^lVdm8ypJ}0R(SD(51j;6@ni$6bQ+2XL+R^|NnSp5}(kzvMZ^(@4fD_{QVu$(&K6H|C37TG1Am9Re{<<3gd zh@`>;BqkXMW&p0T6rt|iB$)~CvFe(XC)F9WgAZn*0@t$oZo;!*}r@_`h?KKH&6A@3= zISXoQB+~`op>NP-buiA*^0n{@i{_?MRG)&k)c)k_F+-2Lud!S9pc+i`s74NpBCaGF zXN+pHkubw*msGBTY27BKHv)RRh3;nMg4&$fD_6X9Vt~;_4D+5XPH~#Kn-yjcy!$}1 zigv#FNY>TqMhtIBb@UoF!cE~Q8~;!Pek>SQQwHnHuWKoVBosAiOr}q>!>aE*Krc)V zBUMEcJ5NU0g8}-h6i1zpMY9>m4ne?=U2~`w7K7Q0gB_=p@$5K7p6}thw z-~3dMj?YNX2X$lZ+7ngQ$=s}3mizNN@kE%OtB)?c&i~2L55z8^=yz;xMHLmlY>&Q# zJj?!)M#q_SyfkQh)k?j8IfLtB)ZCp|*vf4_B zos?73yd^h-Ac+;?E4*bpf=o*^3x3-`TVjbY4n6!EN10K6o@fxdyps05Vo3PU)otB} z`3kR+2w7_C#8Z!q`J)p{Vh!+m9-UP!$STp+Hb}}#@#_u^SsUQg<}59< zTvH3%XS4G+6FF^(m6bVF&nSUIXcl;nw{=H$%fgeJ>CgDYiLdpDXr{;-AnG z8dvcrHYVMI&`R6;GWekI@Ir3!uo)oz4^{6q0m^}@f2tM9&=YHNi6-?rh0-{+k@cQm zdp`g#YdQn%MDVg2GR>wZ`n2<0l4)9nx1Wfr&!Dvz=bPwU!h2S?ez6MVc5APE4-xLB zi&W9Q8k2@0w!C53g?iAIQ}~p*3O(@zja6KQ=M3zfW*_6o5SwR-)6VBh~m7{^-=MC-owYH5-u40a}a0liho3QZZ5L{bS_xM1)4}19)zTU$$MY zq3eZML1WC{K%YFd`Be0M-rkO^l?h{kM{$2oK1*A@HVJ57*yhDkUF!2WZ&oA4Y-sK( zCY69%#`mBCi6>6uw(x4gbFaP0+FD*JKJ-q!F1E?vLJ+d35!I5d7@^eU?(CS|C^tmI5?lv@s{{*|1F zFg|OzNpZ0hxljdjaW%45O0MOttRrd(Z?h{HYbB-KFUx&9GfFL3b8NwZ$zNu)WbBD` zYkj$^UB5%3Pj1MDr>S2Ejr9pUcgA!;ZG!@{uAy12)vG=*^9-|dNQBc8&`oxBlU~#y zs!anJX&T?57Jdr^sb>e+V`MVfY>Y0ESg7MG<7W0g&bR-ZYzzZ%2H&Etcp zcd6QeXO1D!5A#zM0lx*GH}`M)2~ZFLE;sP^RSB5wVMNfiZXPd(cmO>j=OSA3`o5r& zna(|^jGXbdN7PK)U8b7^zYtYkkeb%<%F~=OqB~kXMQkq}ii|skh@WSRt>5za;cjP0 zZ~nD%6)wzedqE}BMLt~qKwlvTr33))#uP~xyw#*Eaa|DbMQ_%mG0U8numf8)0DX`r zRoG2bM;#g|p-8gWnwRV5SCW0tLjLO&9Z?K>FImeIxlGUgo0Zk`9Qzhj1eco~7XZy+hXc@YF&ZQ=? zn*^1O56yK^x{y}q`j7}blGCx%dydV!c7)g~tJzmHhV=W~jbWRRR{1<^oDK+1clprm zz$eCy7y9+?{E|YgkW~}}iB#I4XoJ*xr8R?i_Hv$=Cof5bo-Nj~f`-DLebH}&0% zfQj9@WGd4;N~Y?mzQsHJTJq6!Qzl^-vwol(+fMt#Pl=Wh#lI5Vmu@QM0=_r+1wHt` z+8WZ~c2}KQQ+q)~2Ki77QvV&`xb|xVcTms99&cD$Zz4+-^R4kvUBxG8gDk7Y`K*)JZ^2rL(+ZWV~%W(@6 z)0bPArG#BROa_PHs~&WplQ_UIrpd)1N1QGPfv!J(Z9jNT#i%H?CE6|pPZb9hJ1JW4 z^q;ft#!HRNV0YgPojzIYT`8LuET2rUe-J|c!9l4`^*;4WtY@Ew@pL>wkjmMgGfN7 ze}}GtmU0@<_#08~I-Suk=^*9GLW=H4xhsml;vAV{%hy5Eegl@!6qKqbG024%n2HHw zCc@ivW_$@5ZoHP70(7D+(`PvgjW1Pd`wsiuv-aCukMrafwDm)B!xXVy*j2opohhoU zcJz%ADmj>i3`-3-$7nQKBQQuGY;2Qt&+(L~C>vSGFj5{Mlv?T_^dql;{zkpe4R1}R z%XfZyQ}wr*sr>jrKgm*PWLjuVc%6&&`Kbf1SuFpHPN&>W)$GmqC;pIoBC`=4-hPY8 zT*>%I2fP}vGW;R=^!1be?ta2UQd2>alOFFbVl;(SQJ4Jk#)4Z0^wpWEVvY4=vyDk@ zqlModi@iVPMC+{?rm=4(n+<;|lmUO@UKYA>EPTS~AndtK^Wy^%#3<;(dQdk3WaUkRtzSMC9}7x2||CNpF#(3T4C)@ z$~RWs`BNABKX|{cmBt>Q=&gkXl&x!!NK_%5hW0LS)Z4PB>%sV?F-{Wyj#s7W%$F{D zXdK^Fp3wvy+48+GP6F_|^PCRx=ddcTO3sG;B23A49~Qaw31SZ0Rc~`r4qqt%#OGW{ zCA_(LG5^N>yzUn&kAgVmxb=EA8s&tBXC}S1CZ(KoW)(%^JjLTPo^fs`Va;`=YlVPgmB$!yB}<(4ym6OeZ3xAJJ#;)2+B%p3P1Wt+d$eo`vz`T zXfUP2))kBDPoscH;Jc7I3NU<({|@wM$&GaDt`n7WLgIY3IA7A6-_R?z8N3mz|}*i z(zl5ot--Oq@f2-nv{X(ujT2T(k1vY_qh93pK@>H-qc%2Xta)IP0Q%zt%bqYgI`o!wv!0QerB`nCN^1n|@$sVOQ!V0teVG!I z_fD%JvfDeT1cK#-{o6Gv7}& zY0#NWin~kVaf$aufV&;63Hbs|`QVZWpDX6IMk1Hj2G}fiH9e-^6u2zf^FIr^BwD<6zjw63+{yUe8PUFvk8v{sJ=R{d#`O!sz`Q13~< zPT$JS(w=yQfU2`zPCNfSw=&zup@DXc(98afjhv@1w_f!m2Z>rMJ19AB&dB%P#Ls3b z=lK7OILM+SQ&VEd=1GN6o&>YVVtIzoZ%=Z_SdqJN2}E43{bE`>w+A;=y->@^k{oCC z$F*WTY&?34;kfyFV?b*Xb1Pq`Z=%OgwEg)Rz)tx=`f%5#w_INP=x&z5!jI;#;N$ma zhO)+MDm;SxOEVL15; zGq(v2pL3&P1Sl)8P*;G-fd{l1QJsv@e@d8)1PK4w2m*M%V3j-V~L^$i|&C@b?D?9tfwE{B^}Z$k8e5FmQ>v7Xz)sG32g9t}YBt zyR$+*_00RmPx+0mW+vVG4mxd(n$(eQf3-w>JPl2UJpafrPaL5@2j}%{VE-) zBI%6Qpj*dsdH<;g!S!avA~bv^0E+ zfyJbSjPb+j;J52U)<|cIcntQBI2T#>2;tOxu{%D?kML476AErF(qN9hPva5Nkc@BF zC-tLF@3ZFb%Kpj)M<{)x*l|*Ia@ECeXo2E4h2f!aV=cHAhi_E_mfUth(sM4^hJq7B zQsGWqdZUm9S%F`$nQ*_#NcuD`&)Ek%_s{&^78{9Hm ztri&rYLOxgFdG>O@+XHy z9#;|&vBCPXH5Mon^I`jSuR$&~ZWtyB67ujzFSj!51>#C}C17~TffQ{c-!QFQkTQ%! zIR^b1`zHx|*1GU?tbBx23weFLz5H?y_Q%N&t$}k?w+``2A=aotj0;2v$~AL z{scF-cL{wsdrmPvf#a9OHyYLcwQD4Kcm)`LLwMh4WT~p29f7M!iafJSU`IV}QY5Wa z(n44-9oA}?J{a+ah*@31WTs#&J#o1`H98#6IQf;Wv0N_!);f&9g7o-k(lW5rWnDUR zQBFIRG+X=6NnsI@mxnwm;tf5;_Uxg?jZ8m-m0}&6+DA!qam(p$mN5R})yA_7m$q@| zFEd|dpS595rxQr-n#GjI5i-AhnUE>Cr;jpCqSrD~EwK_DqI^7%3#p5)%T_od!t3SOmH9MyXeeGO2(UQL;ax|x?Ncixmeo1=$ z{-);Au{*tfzOG?KQ~K|ak8-HQ?`Pekhe2WM(8s{xv-p>Zmu_6{G!-oE$7$mY`MOJorI=+mMx?H;`pr!;fVYz?5~yXBACruWB`Ph zZM}90_<^OBxIhyZ9BW$`>6JvO;%VFpqVr8|7t3~AmxYak6?`Pp#c;**_SYmi`&z23 z`p6_~ePvH)C6x-G9$hgL=eVALq`-AiamN>!3~Lxw&{H(b{B(7xSRm6<3<{%{yXiH# zos5Rv1L+8fUKJLo%P>4I&$}yR?p|tHQo6fg38|$UVM!6BLrPFWk?s;$LOP{GmJpBl$qoSA!PUg~PA65-S00{{S`XKG6NkG0RgjEntPrmV+?0|00mu7;+5 zrdpa{2QLqPJ4Y{j7=Mrl{BaxrkdY69+c~(w{Fv-v&aR%aEI&JYSeRTLWm!zbv;?)_ ziZB;fwGbbeL5Q}YLx`J$lp~A09KK8t_z}PZ=4ZzgdeKtgoc+o5EvN9A1K1_<>M?MBqb#!ASf&# zEX?<)!RH(7>1P+j=jqG(58}TVN-$psA6K}atCuI!KTJD&FMmH-78ZejBm)0qc{ESp z|LuG1{QnBUJRg_E=h1#XMWt2%fcoN@l7eAS!Es?Q+;XsRNPhiiE=@AqlLkJzF`O18 zbsbSmKN=aaq8k3NFYZfDWpKmM!coBU0(XnL8R{4=i|wi{!uWYM2je{U{B*K2PVdu&=E zTq*-XsEsJ$u5H4g6DIm2Y!DN`>^v|AqlwuCD;w45K0@eqauiqWf7l&o)+YLHm~|L~ z7$0v5mkobriU!H<@mVJHLlmQqzQ3d6Rh_-|%Yy2li*tHO>_vcnuZ7OR_xkAIuIU&x z-|8Y0wj|6|a6_I(v91y%k_kNw6pnkNdxjqG8!%Vz_d%c_!X+6-;1`GC9_FpjoHev5fEV7RhJ>r=mh-jp$fqbqRJ=obwdgLDVP5+s zy1=_DWG0Y-Jb3t^WXmkr(d9~08k-|#Ly zaNOmT(^9tIb&eb4%CzIT zAm3CUtWSr1t4?h1kk#NBi{U|pJslvME{q|_eS^3En>SOqSxyuN1x;Is@8~m?*>}** znrRFArP!K_52RpX*&JHMR<^lVdm8ypJ}0R(SD(51j;6@ni$6bQ+2XL+R^|NnSp5}(kzvMZ^(@4fD_{QVu$(&K6H|C37TG1Am9Re{<<3gd zh@`>;BqkXMW&p0T6rt|iB$)~CvFe(XC)F9WgAZn*0@t$oZo;!*}r@_`h?KKH&6A@3= zISXoQB+~`op>NP-buiA*^0n{@i{_?MRG)&k)c)k_F+-2Lud!S9pc+i`s74NpBCaGF zXN+pHkubw*msGBTY27BKHv)RRh3;nMg4&$fD_6X9Vt~;_4D+5XPH~#Kn-yjcy!$}1 zigv#FNY>TqMhtIBb@UoF!cE~Q8~;!Pek>SQQwHnHuWKoVBosAiOr}q>!>aE*Krc)V zBUMEcJ5NU0g8}-h6i1zpMY9>m4ne?=U2~`w7K7Q0gB_=p@$5K7p6}thw z-~3dMj?YNX2X$lZ+7ngQ$=s}3mizNN@kE%OtB)?c&i~2L55z8^=yz;xMHLmlY>&Q# zJj?!)M#q_SyfkQh)k?j8IfLtB)ZCp|*vf4_B zos?73yd^h-Ac+;?E4*bpf=o*^3x3-`TVjbY4n6!EN10K6o@fxdyps05Vo3PU)otB} z`3kR+2w7_C#8Z!q`J)p{Vh!+m9-UP!$STp+Hb}}#@#_u^SsUQg<}59< zTvH3%XS4G+6FF^(m6bVF&nSUIXcl;nw{=H$%fgeJ>CgDYiLdpDXr{;-AnG z8dvcrHYVMI&`R6;GWekI@Ir3!uo)oz4^{6q0m^}@f2tM9&=YHNi6-?rh0-{+k@cQm zdp`g#YdQn%MDVg2GR>wZ`n2<0l4)9nx1Wfr&!Dvz=bPwU!h2S?ez6MVc5APE4-xLB zi&W9Q8k2@0w!C53g?iAIQ}~p*3O(@zja6KQ=M3zfW*_6o5SwR-)6VBh~m7{^-=MC-owYH5-u40a}a0liho3QZZ5L{bS_xM1)4}19)zTU$$MY zq3eZML1WC{K%YFd`Be0M-rkO^l?h{kM{$2oK1*A@HVJ57*yhDkUF!2WZ&oA4Y-sK( zCY69%#`mBCi6>6uw(x4gbFaP0+FD*JKJ-q!F1E?vLJ+d35!I5d7@^eU?(CS|C^tmI5?lv@s{{*|1F zFg|OzNpZ0hxljdjaW%45O0MOttRrd(Z?h{HYbB-KFUx&9GfFL3b8NwZ$zNu)WbBD` zYkj$^UB5%3Pj1MDr>S2Ejr9pUcgA!;ZG!@{uAy12)vG=*^9-|dNQBc8&`oxBlU~#y zs!anJX&T?57Jdr^sb>e+V`MVfY>Y0ESg7MG<7W0g&bR-ZYzzZ%2H&Etcp zcd6QeXO1D!5A#zM0lx*GH}`M)2~ZFLE;sP^RSB5wVMNfiZXPd(cmO>j=OSA3`o5r& zna(|^jGXbdN7PK)U8b7^zYtYkkeb%<%F~=OqB~kXMQkq}ii|skh@WSRt>5za;cjP0 zZ~nD%6)wzedqE}BMLt~qKwlvTr33))#uP~xyw#*Eaa|DbMQ_%mG0U8numf8)0DX`r zRoG2bM;#g|p-8gWnwRV5SCW0tLjLO&9Z?K>FImeIxlGUgo0Zk`9Qzhj1eco~7XZy+hXc@YF&ZQ=? zn*^1O56yK^x{y}q`j7}blGCx%dydV!c7)g~tJzmHhV=W~jbWRRR{1<^oDK+1clprm zz$eCy7y9+?{E|YgkW~}}iB#I4XoJ*xr8R?i_Hv$=Cof5bo-Nj~f`-DLebH}&0% zfQj9@WGd4;N~Y?mzQsHJTJq6!Qzl^-vwol(+fMt#Pl=Wh#lI5Vmu@QM0=_r+1wHt` z+8WZ~c2}KQQ+q)~2Ki77QvV&`xb|xVcTms99&cD$Zz4+-^R4kvUBxG8gDk7Y`K*)JZ^2rL(+ZWV~%W(@6 z)0bPArG#BROa_PHs~&WplQ_UIrpd)1N1QGPfv!J(Z9jNT#i%H?CE6|pPZb9hJ1JW4 z^q;ft#!HRNV0YgPojzIYT`8LuET2rUe-J|c!9l4`^*;4WtY@Ew@pL>wkjmMgGfN7 ze}}GtmU0@<_#08~I-Suk=^*9GLW=H4xhsml;vAV{%hy5Eegl@!6qKqbG024%n2HHw zCc@ivW_$@5ZoHP70(7D+(`PvgjW1Pd`wsiuv-aCukMrafwDm)B!xXVy*j2opohhoU zcJz%ADmj>i3`-3-$7nQKBQQuGY;2Qt&+(L~C>vSGFj5{Mlv?T_^dql;{zkpe4R1}R z%XfZyQ}wr*sr>jrKgm*PWLjuVc%6&&`Kbf1SuFpHPN&>W)$GmqC;pIoBC`=4-hPY8 zT*>%I2fP}vGW;R=^!1be?ta2UQd2>alOFFbVl;(SQJ4Jk#)4Z0^wpWEVvY4=vyDk@ zqlModi@iVPMC+{?rm=4(n+<;|lmUO@UKYA>EPTS~AndtK^Wy^%#3<;(dQdk3WaUkRtzSMC9}7x2||CNpF#(3T4C)@ z$~RWs`BNABKX|{cmBt>Q=&gkXl&x!!NK_%5hW0LS)Z4PB>%sV?F-{Wyj#s7W%$F{D zXdK^Fp3wvy+48+GP6F_|^PCRx=ddcTO3sG;B23A49~Qaw31SZ0Rc~`r4qqt%#OGW{ zCA_(LG5^N>yzUn&kAgVmxb=EA8s&tBXC}S1CZ(KoW)(%^JjLTPo^fs`Va;`=YlVPgmB$!yB}<(4ym6OeZ3xAJJ#;)2+B%p3P1Wt+d$eo`vz`T zXfUP2))kBDPoscH;Jc7I3NU<({|@wM$&GaDt`n7WLgIY3IA7A6-_R?z8N3mz|}*i z(zl5ot--Oq@f2-nv{X(ujT2T(k1vY_qh93pK@>H-qc%2Xta)IP0Q%zt%bqYgI`o!wv!0QerB`nCN^1n|@$sVOQ!V0teVG!I z_fD%JvfDeT1cK#-{o6Gv7}& zY0#NWin~kVaf$aufV&;63Hbs|`QVZWpDX6IMk1Hj2G}fiH9e-^6u2zf^FIr^BwD<6zjw63+{yUe8PUFvk8v{sJ=R{d#`O!sz`Q13~< zPT$JS(w=yQfU2`zPCNfSw=&zup@DXc(98afjhv@1w_f!m2Z>rMJ19AB&dB%P#Ls3b z=lK7OILM+SQ&VEd=1GN6o&>YVVtIzoZ%=Z_SdqJN2}E43{bE`>w+A;=y->@^k{oCC z$F*WTY&?34;kfyFV?b*Xb1Pq`Z=%OgwEg)Rz)tx=`f%5#w_INP=x&z5!jI;#;N$ma zhO)+MDm;SxOEVL15; zGq(v2pL3&P1Sl)8P*;G-fd{l1QJsv@e@d8)1PK4w2m*M%V3j-V~L^$i|&C@b?D?9tfwE{B^}Z$k8e5FmQ>v7Xz)sG32g9t}YBt zyR$+*_00RmPx+0mW+vVG4mxd(n$(eQf3-w>JPl2UJpafrPaL5@2j}%{VE-) zBI%6Qpj*dsdH<;g!S!avA~bv^0E+ zfyJbSjPb+j;J52U)<|cIcntQBI2T#>2;tOxu{%D?kML476AErF(qN9hPva5Nkc@BF zC-tLF@3ZFb%Kpj)M<{)x*l|*Ia@ECeXo2E4h2f!aV=cHAhi_E_mfUth(sM4^hJq7B zQsGWqdZUm9S%F`$nQ*_#NcuD`&)Ek%_s{&^78{9Hm ztri&rYLOxgFdG>O@+XHy z9#;|&vBCPXH5Mon^I`jSuR$&~ZWtyB67ujzFSj!51>#C}C17~TffQ{c-!QFQkTQ%! zIR^b1`zHx|*1GU?tbBx23weFLz5H?y_Q%N&t$}k?w+``2A=aotj0;2v$~AL z{scF-cL{wsdrmPvf#a9OHyYLcwQD4Kcm)`LLwMh4WT~p29f7M!iafJSU`IV}QY5Wa z(n44-9oA}?J{a+ah*@31WTs#&J#o1`H98#6IQf;Wv0N_!);f&9g7o-k(lW5rWnDUR zQBFIRG+X=6NnsI@mxnwm;tf5;_Uxg?jZ8m-m0}&6+DA!qam(p$mN5R})yA_7m$q@| zFEd|dpS595rxQr-n#GjI5i-AhnUE>Cr;jpCqSrD~EwK_DqI^7%3#p5)%T_od!t3SOmH9MyXeeGO2(UQL;ax|x?Ncixmeo1=$ z{-);Au{*tfzOG?KQ~K|ak8-HQ?`Pekhe2WM(8s{xv-p>Zmu_6{G!-oE$7$mY`MOJorI=+mMx?H;`pr!;fVYz?5~yXBACruWB`Ph zZM}90_<^OBxIhyZ9BW$`>6JvO;%VFpqVr8|7t3~AmxYak6?`Pp#c;**_SYmi`&z23 z`p6_~ePvH)C6x-G9$hgL=eVALq`-AiamN>!3~Lxw&{H(b{B(7xSRm6<3<{%{yXiH# zos5Rv1L+8fUKJLo%P>4I&$}y { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', + JSON.stringify(data) ?? ''); + }); + } + + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } + + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } + + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets b/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets new file mode 100644 index 000000000..cef0447cd --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import hilog from '@ohos.hilog'; + +@Entry +@Component +struct Index { + aboutToAppear() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility index aboutToAppear'); + } + @State message: string = 'Hello World' + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + Button() { + Text('next page') + .fontSize(20) + .fontWeight(FontWeight.Bold) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .width('35%') + .height('5%') + .onClick(()=>{ + }) + } + .width('100%') + } + .height('100%') + } + } \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts b/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts new file mode 100644 index 000000000..1def08f2e --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts @@ -0,0 +1,64 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import hilog from '@ohos.hilog'; +import TestRunner from '@ohos.application.testRunner'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +var abilityDelegator = undefined +var abilityDelegatorArguments = undefined + +async function onAbilityCreateCallback() { + hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback'); +} + +async function addAbilityMonitorCallback(err: any) { + hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); +} + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare '); + } + + async onRun() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility' + let lMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + }; + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName + var debug = abilityDelegatorArguments.parameters['-D'] + if (debug == 'true') + { + cmd += ' -D' + } + hilog.info(0x0000, 'testTag', 'cmd : %{public}s', cmd); + abilityDelegator.executeShellCommand(cmd, + (err: any, d: any) => { + hilog.info(0x0000, 'testTag', 'executeShellCommand : err : %{public}s', JSON.stringify(err) ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.stdResult ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.exitCode ?? ''); + }) + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/module.json5 b/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/module.json5 new file mode 100644 index 000000000..fab77ce2e --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/module.json5 @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:test_pages", + "abilities": [ + { + "name": "TestAbility", + "srcEntry": "./ets/testability/TestAbility.ets", + "description": "$string:TestAbility_desc", + "icon": "$media:icon", + "label": "$string:TestAbility_label", + "exported": true, + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "skills": [ + { + "actions": [ + "action.system.home" + ], + "entities": [ + "entity.system.home" + ] + } + ] + } + ] + } +} diff --git a/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/resources/base/element/color.json b/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/resources/base/element/color.json new file mode 100644 index 000000000..3c712962d --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/resources/base/element/string.json b/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/resources/base/element/string.json new file mode 100644 index 000000000..65d8fa5a7 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + } + ] +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/resources/base/media/icon.png b/packages/in_app_purchase/in_app_purchase/example/ohos/entry/src/ohosTest/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 zcmX|G1ymHk)?T_}Vd;>R?p|tHQo6fg38|$UVM!6BLrPFWk?s;$LOP{GmJpBl$qoSA!PUg~PA65-S00{{S`XKG6NkG0RgjEntPrmV+?0|00mu7;+5 zrdpa{2QLqPJ4Y{j7=Mrl{BaxrkdY69+c~(w{Fv-v&aR%aEI&JYSeRTLWm!zbv;?)_ ziZB;fwGbbeL5Q}YLx`J$lp~A09KK8t_z}PZ=4ZzgdeKtgoc+o5EvN9A1K1_<>M?MBqb#!ASf&# zEX?<)!RH(7>1P+j=jqG(58}TVN-$psA6K}atCuI!KTJD&FMmH-78ZejBm)0qc{ESp z|LuG1{QnBUJRg_E=h1#XMWt2%fcoN@l7eAS!Es?Q+;XsRNPhiiE=@AqlLkJzF`O18 zbsbSmKN=aaq8k3NFYZfDWpKmM!coBU0(XnL8R{4=i|wi{!uWYM2je{U{B*K2PVdu&=E zTq*-XsEsJ$u5H4g6DIm2Y!DN`>^v|AqlwuCD;w45K0@eqauiqWf7l&o)+YLHm~|L~ z7$0v5mkobriU!H<@mVJHLlmQqzQ3d6Rh_-|%Yy2li*tHO>_vcnuZ7OR_xkAIuIU&x z-|8Y0wj|6|a6_I(v91y%k_kNw6pnkNdxjqG8!%Vz_d%c_!X+6-;1`GC9_FpjoHev5fEV7RhJ>r=mh-jp$fqbqRJ=obwdgLDVP5+s zy1=_DWG0Y-Jb3t^WXmkr(d9~08k-|#Ly zaNOmT(^9tIb&eb4%CzIT zAm3CUtWSr1t4?h1kk#NBi{U|pJslvME{q|_eS^3En>SOqSxyuN1x;Is@8~m?*>}** znrRFArP!K_52RpX*&JHMR<^lVdm8ypJ}0R(SD(51j;6@ni$6bQ+2XL+R^|NnSp5}(kzvMZ^(@4fD_{QVu$(&K6H|C37TG1Am9Re{<<3gd zh@`>;BqkXMW&p0T6rt|iB$)~CvFe(XC)F9WgAZn*0@t$oZo;!*}r@_`h?KKH&6A@3= zISXoQB+~`op>NP-buiA*^0n{@i{_?MRG)&k)c)k_F+-2Lud!S9pc+i`s74NpBCaGF zXN+pHkubw*msGBTY27BKHv)RRh3;nMg4&$fD_6X9Vt~;_4D+5XPH~#Kn-yjcy!$}1 zigv#FNY>TqMhtIBb@UoF!cE~Q8~;!Pek>SQQwHnHuWKoVBosAiOr}q>!>aE*Krc)V zBUMEcJ5NU0g8}-h6i1zpMY9>m4ne?=U2~`w7K7Q0gB_=p@$5K7p6}thw z-~3dMj?YNX2X$lZ+7ngQ$=s}3mizNN@kE%OtB)?c&i~2L55z8^=yz;xMHLmlY>&Q# zJj?!)M#q_SyfkQh)k?j8IfLtB)ZCp|*vf4_B zos?73yd^h-Ac+;?E4*bpf=o*^3x3-`TVjbY4n6!EN10K6o@fxdyps05Vo3PU)otB} z`3kR+2w7_C#8Z!q`J)p{Vh!+m9-UP!$STp+Hb}}#@#_u^SsUQg<}59< zTvH3%XS4G+6FF^(m6bVF&nSUIXcl;nw{=H$%fgeJ>CgDYiLdpDXr{;-AnG z8dvcrHYVMI&`R6;GWekI@Ir3!uo)oz4^{6q0m^}@f2tM9&=YHNi6-?rh0-{+k@cQm zdp`g#YdQn%MDVg2GR>wZ`n2<0l4)9nx1Wfr&!Dvz=bPwU!h2S?ez6MVc5APE4-xLB zi&W9Q8k2@0w!C53g?iAIQ}~p*3O(@zja6KQ=M3zfW*_6o5SwR-)6VBh~m7{^-=MC-owYH5-u40a}a0liho3QZZ5L{bS_xM1)4}19)zTU$$MY zq3eZML1WC{K%YFd`Be0M-rkO^l?h{kM{$2oK1*A@HVJ57*yhDkUF!2WZ&oA4Y-sK( zCY69%#`mBCi6>6uw(x4gbFaP0+FD*JKJ-q!F1E?vLJ+d35!I5d7@^eU?(CS|C^tmI5?lv@s{{*|1F zFg|OzNpZ0hxljdjaW%45O0MOttRrd(Z?h{HYbB-KFUx&9GfFL3b8NwZ$zNu)WbBD` zYkj$^UB5%3Pj1MDr>S2Ejr9pUcgA!;ZG!@{uAy12)vG=*^9-|dNQBc8&`oxBlU~#y zs!anJX&T?57Jdr^sb>e+V`MVfY>Y0ESg7MG<7W0g&bR-ZYzzZ%2H&Etcp zcd6QeXO1D!5A#zM0lx*GH}`M)2~ZFLE;sP^RSB5wVMNfiZXPd(cmO>j=OSA3`o5r& zna(|^jGXbdN7PK)U8b7^zYtYkkeb%<%F~=OqB~kXMQkq}ii|skh@WSRt>5za;cjP0 zZ~nD%6)wzedqE}BMLt~qKwlvTr33))#uP~xyw#*Eaa|DbMQ_%mG0U8numf8)0DX`r zRoG2bM;#g|p-8gWnwRV5SCW0tLjLO&9Z?K>FImeIxlGUgo0Zk`9Qzhj1eco~7XZy+hXc@YF&ZQ=? zn*^1O56yK^x{y}q`j7}blGCx%dydV!c7)g~tJzmHhV=W~jbWRRR{1<^oDK+1clprm zz$eCy7y9+?{E|YgkW~}}iB#I4XoJ*xr8R?i_Hv$=Cof5bo-Nj~f`-DLebH}&0% zfQj9@WGd4;N~Y?mzQsHJTJq6!Qzl^-vwol(+fMt#Pl=Wh#lI5Vmu@QM0=_r+1wHt` z+8WZ~c2}KQQ+q)~2Ki77QvV&`xb|xVcTms99&cD$Zz4+-^R4kvUBxG8gDk7Y`K*)JZ^2rL(+ZWV~%W(@6 z)0bPArG#BROa_PHs~&WplQ_UIrpd)1N1QGPfv!J(Z9jNT#i%H?CE6|pPZb9hJ1JW4 z^q;ft#!HRNV0YgPojzIYT`8LuET2rUe-J|c!9l4`^*;4WtY@Ew@pL>wkjmMgGfN7 ze}}GtmU0@<_#08~I-Suk=^*9GLW=H4xhsml;vAV{%hy5Eegl@!6qKqbG024%n2HHw zCc@ivW_$@5ZoHP70(7D+(`PvgjW1Pd`wsiuv-aCukMrafwDm)B!xXVy*j2opohhoU zcJz%ADmj>i3`-3-$7nQKBQQuGY;2Qt&+(L~C>vSGFj5{Mlv?T_^dql;{zkpe4R1}R z%XfZyQ}wr*sr>jrKgm*PWLjuVc%6&&`Kbf1SuFpHPN&>W)$GmqC;pIoBC`=4-hPY8 zT*>%I2fP}vGW;R=^!1be?ta2UQd2>alOFFbVl;(SQJ4Jk#)4Z0^wpWEVvY4=vyDk@ zqlModi@iVPMC+{?rm=4(n+<;|lmUO@UKYA>EPTS~AndtK^Wy^%#3<;(dQdk3WaUkRtzSMC9}7x2||CNpF#(3T4C)@ z$~RWs`BNABKX|{cmBt>Q=&gkXl&x!!NK_%5hW0LS)Z4PB>%sV?F-{Wyj#s7W%$F{D zXdK^Fp3wvy+48+GP6F_|^PCRx=ddcTO3sG;B23A49~Qaw31SZ0Rc~`r4qqt%#OGW{ zCA_(LG5^N>yzUn&kAgVmxb=EA8s&tBXC}S1CZ(KoW)(%^JjLTPo^fs`Va;`=YlVPgmB$!yB}<(4ym6OeZ3xAJJ#;)2+B%p3P1Wt+d$eo`vz`T zXfUP2))kBDPoscH;Jc7I3NU<({|@wM$&GaDt`n7WLgIY3IA7A6-_R?z8N3mz|}*i z(zl5ot--Oq@f2-nv{X(ujT2T(k1vY_qh93pK@>H-qc%2Xta)IP0Q%zt%bqYgI`o!wv!0QerB`nCN^1n|@$sVOQ!V0teVG!I z_fD%JvfDeT1cK#-{o6Gv7}& zY0#NWin~kVaf$aufV&;63Hbs|`QVZWpDX6IMk1Hj2G}fiH9e-^6u2zf^FIr^BwD<6zjw63+{yUe8PUFvk8v{sJ=R{d#`O!sz`Q13~< zPT$JS(w=yQfU2`zPCNfSw=&zup@DXc(98afjhv@1w_f!m2Z>rMJ19AB&dB%P#Ls3b z=lK7OILM+SQ&VEd=1GN6o&>YVVtIzoZ%=Z_SdqJN2}E43{bE`>w+A;=y->@^k{oCC z$F*WTY&?34;kfyFV?b*Xb1Pq`Z=%OgwEg)Rz)tx=`f%5#w_INP=x&z5!jI;#;N$ma zhO)+MDm;SxOEVL15; zGq(v2pL3&P1Sl)8P*;G-fd{l1QJsv@e@d8)1PK4w2m*M%V3j-V~L^$i|&C@b?D?9tfwE{B^}Z$k8e5FmQ>v7Xz)sG32g9t}YBt zyR$+*_00RmPx+0mW+vVG4mxd(n$(eQf3-w>JPl2UJpafrPaL5@2j}%{VE-) zBI%6Qpj*dsdH<;g!S!avA~bv^0E+ zfyJbSjPb+j;J52U)<|cIcntQBI2T#>2;tOxu{%D?kML476AErF(qN9hPva5Nkc@BF zC-tLF@3ZFb%Kpj)M<{)x*l|*Ia@ECeXo2E4h2f!aV=cHAhi_E_mfUth(sM4^hJq7B zQsGWqdZUm9S%F`$nQ*_#NcuD`&)Ek%_s{&^78{9Hm ztri&rYLOxgFdG>O@+XHy z9#;|&vBCPXH5Mon^I`jSuR$&~ZWtyB67ujzFSj!51>#C}C17~TffQ{c-!QFQkTQ%! zIR^b1`zHx|*1GU?tbBx23weFLz5H?y_Q%N&t$}k?w+``2A=aotj0;2v$~AL z{scF-cL{wsdrmPvf#a9OHyYLcwQD4Kcm)`LLwMh4WT~p29f7M!iafJSU`IV}QY5Wa z(n44-9oA}?J{a+ah*@31WTs#&J#o1`H98#6IQf;Wv0N_!);f&9g7o-k(lW5rWnDUR zQBFIRG+X=6NnsI@mxnwm;tf5;_Uxg?jZ8m-m0}&6+DA!qam(p$mN5R})yA_7m$q@| zFEd|dpS595rxQr-n#GjI5i-AhnUE>Cr;jpCqSrD~EwK_DqI^7%3#p5)%T_od!t3SOmH9MyXeeGO2(UQL;ax|x?Ncixmeo1=$ z{-);Au{*tfzOG?KQ~K|ak8-HQ?`Pekhe2WM(8s{xv-p>Zmu_6{G!-oE$7$mY`MOJorI=+mMx?H;`pr!;fVYz?5~yXBACruWB`Ph zZM}90_<^OBxIhyZ9BW$`>6JvO;%VFpqVr8|7t3~AmxYak6?`Pp#c;**_SYmi`&z23 z`p6_~ePvH)C6x-G9$hgL=eVALq`-AiamN>!3~Lxw&{H(b{B(7xSRm6<3<{%{yXiH# zos5Rv1L+8fUKJLo%P>4I&$}y=3.13.0" + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" dependencies: flutter: @@ -18,7 +18,14 @@ dependencies: path: ../ in_app_purchase_android: ^0.3.0 in_app_purchase_storekit: ^0.3.4 - shared_preferences: ^2.0.0 + shared_preferences: + git: + url: https://gitee.com/openharmony-sig/flutter_packages.git + path: packages/shared_preferences/shared_preferences + +dependency_overrides: + in_app_purchase_ohos: + path: ../../in_app_purchase_ohos dev_dependencies: flutter_test: diff --git a/packages/in_app_purchase/in_app_purchase/lib/in_app_purchase.dart b/packages/in_app_purchase/in_app_purchase/lib/in_app_purchase.dart index 1cb3beda4..877f91b39 100644 --- a/packages/in_app_purchase/in_app_purchase/lib/in_app_purchase.dart +++ b/packages/in_app_purchase/in_app_purchase/lib/in_app_purchase.dart @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; import 'package:in_app_purchase_android/in_app_purchase_android.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; +import 'package:in_app_purchase_ohos/in_app_purchase_ohos.dart'; export 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart' show @@ -16,7 +17,8 @@ export 'package:in_app_purchase_platform_interface/in_app_purchase_platform_inte PurchaseDetails, PurchaseParam, PurchaseStatus, - PurchaseVerificationData; + PurchaseVerificationData, + PurchaseStatus; /// Basic API for making in app purchases across multiple platforms. class InAppPurchase implements InAppPurchasePlatformAdditionProvider { @@ -37,6 +39,8 @@ class InAppPurchase implements InAppPurchasePlatformAdditionProvider { } else if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) { InAppPurchaseStoreKitPlatform.registerPlatform(); + } else if (defaultTargetPlatform == TargetPlatform.ohos) { + InAppPurchaseOhosPlatform.registerPlatform(); } _instance = InAppPurchase._(); @@ -209,20 +213,4 @@ class InAppPurchase implements InAppPurchasePlatformAdditionProvider { InAppPurchasePlatform.instance.restorePurchases( applicationUserName: applicationUserName, ); - - /// Returns the user's country. - /// - /// Android: - /// Returns Play billing country code based on ISO-3166-1 alpha2 format. - /// - /// See: https://developer.android.com/reference/com/android/billingclient/api/BillingConfig - /// See: https://unicode.org/cldr/charts/latest/supplemental/territory_containment_un_m_49.html - /// - /// iOS: - /// Returns the country code from SKStoreFrontWrapper. - /// - /// See: https://developer.apple.com/documentation/storekit/skstorefront?language=objc - /// - /// - Future countryCode() => InAppPurchasePlatform.instance.countryCode(); } diff --git a/packages/in_app_purchase/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/in_app_purchase/pubspec.yaml index 3f31fcd2e..1361a239e 100644 --- a/packages/in_app_purchase/in_app_purchase/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase/pubspec.yaml @@ -2,17 +2,19 @@ name: in_app_purchase description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play. repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 3.2.0 +version: 3.1.13 environment: - sdk: ^3.2.3 - flutter: ">=3.16.6" + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" flutter: plugin: platforms: android: default_package: in_app_purchase_android + ohos: + default_package: in_app_purchase_ohos ios: default_package: in_app_purchase_storekit macos: @@ -21,9 +23,11 @@ flutter: dependencies: flutter: sdk: flutter - in_app_purchase_android: ^0.3.4 - in_app_purchase_platform_interface: ^1.4.0 - in_app_purchase_storekit: ^0.3.14 + in_app_purchase_android: ^0.3.0 + in_app_purchase_ohos: + path: ../in_app_purchase_ohos/ + in_app_purchase_platform_interface: ^1.0.0 + in_app_purchase_storekit: ^0.3.4 dev_dependencies: flutter_test: diff --git a/packages/in_app_purchase/in_app_purchase/test/in_app_purchase_test.dart b/packages/in_app_purchase/in_app_purchase/test/in_app_purchase_test.dart index c4ed81988..58f7398ad 100644 --- a/packages/in_app_purchase/in_app_purchase/test/in_app_purchase_test.dart +++ b/packages/in_app_purchase/in_app_purchase/test/in_app_purchase_test.dart @@ -55,14 +55,6 @@ void main() { ]); }); - test('countryCode', () async { - final String country = await inAppPurchase.countryCode(); - expect(country, 'USA'); - expect(fakePlatform.log, [ - isMethodCall('countryCode', arguments: null), - ]); - }); - test('purchaseStream', () async { final bool isEmptyStream = await inAppPurchase.purchaseStream.isEmpty; expect(isEmptyStream, true); @@ -200,10 +192,4 @@ class MockInAppPurchasePlatform extends Fake log.add(const MethodCall('restorePurchases')); return Future.value(); } - - @override - Future countryCode() { - log.add(const MethodCall('countryCode')); - return Future.value('USA'); - } } diff --git a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md index 20d700ac4..fb472b9d1 100644 --- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md @@ -1,39 +1,3 @@ -## 0.3.4 - -* Adds `countryCode` API. - -## 0.3.3+1 - -* Moves alternative billing listener creation to BillingClientFactoryImpl. - -## 0.3.3 - -* Converts data objects in internal platform communication to Pigeon. -* Deprecates JSON serialization and deserialization for Billing Client wrapper - objects. - -## 0.3.2+1 - -* Converts internal platform communication to Pigeon. - -## 0.3.2 - -* Adds UserChoiceBilling APIs to platform addition. -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. - -## 0.3.1 - -* Adds alternative-billing-only APIs to InAppPurchaseAndroidPlatformAddition. - -## 0.3.0+18 - -* Adds new getCountryCode() method to InAppPurchaseAndroidPlatformAddition to get a customer's country code. -* Updates compileSdk version to 34. - -## 0.3.0+17 - -* Bumps androidx.annotation:annotation from 1.7.0 to 1.7.1. - ## 0.3.0+16 * Updates minimum supported SDK version to Flutter 3.10/Dart 3.0. diff --git a/packages/in_app_purchase/in_app_purchase_android/android/build.gradle b/packages/in_app_purchase/in_app_purchase_android/android/build.gradle index a55e39fc8..8482d3f3e 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/build.gradle +++ b/packages/in_app_purchase/in_app_purchase_android/android/build.gradle @@ -26,11 +26,10 @@ android { if (project.android.hasProperty("namespace")) { namespace 'io.flutter.plugins.inapppurchase' } - - compileSdk 34 + compileSdkVersion 33 defaultConfig { - minSdk 19 + minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { @@ -58,16 +57,14 @@ android { } dependencies { - implementation 'androidx.annotation:annotation:1.7.1' + implementation 'androidx.annotation:annotation:1.7.0' // org.jetbrains.kotlin:kotlin-bom artifact purpose is to align kotlin stdlib and related code versions. // See: https://youtrack.jetbrains.com/issue/KT-55297/kotlin-stdlib-should-declare-constraints-on-kotlin-stdlib-jdk8-and-kotlin-stdlib-jdk7 implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.22")) - implementation 'com.android.billingclient:billing:6.1.0' + implementation 'com.android.billingclient:billing:6.0.1' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.json:json:20240303' + testImplementation 'org.json:json:20231013' testImplementation 'org.mockito:mockito-core:5.4.0' - testImplementation 'androidx.test:core:1.5.0' - testImplementation 'org.robolectric:robolectric:4.10.3' - androidTestImplementation 'androidx.test:runner:1.5.2' + androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java index 979e54a37..81fdf27be 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java @@ -7,7 +7,7 @@ package io.flutter.plugins.inapppurchase; import android.content.Context; import androidx.annotation.NonNull; import com.android.billingclient.api.BillingClient; -import io.flutter.plugins.inapppurchase.Messages.PlatformBillingChoiceMode; +import io.flutter.plugin.common.MethodChannel; /** Responsible for creating a {@link BillingClient} object. */ interface BillingClientFactory { @@ -16,13 +16,8 @@ interface BillingClientFactory { * Creates and returns a {@link BillingClient}. * * @param context The context used to create the {@link BillingClient}. - * @param callbackApi The callback API to be used by the {@link BillingClient}. - * @param billingChoiceMode Enables the ability to offer alternative billing or Google Play - * billing. + * @param channel The method channel used to create the {@link BillingClient}. * @return The {@link BillingClient} object that is created. */ - BillingClient createBillingClient( - @NonNull Context context, - @NonNull Messages.InAppPurchaseCallbackApi callbackApi, - PlatformBillingChoiceMode billingChoiceMode); + BillingClient createBillingClient(@NonNull Context context, @NonNull MethodChannel channel); } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java index 460414b2e..201d57c7f 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java @@ -4,60 +4,19 @@ package io.flutter.plugins.inapppurchase; -import static io.flutter.plugins.inapppurchase.Translator.fromUserChoiceDetails; - import android.content.Context; import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; import com.android.billingclient.api.BillingClient; -import com.android.billingclient.api.UserChoiceBillingListener; -import io.flutter.Log; -import io.flutter.plugins.inapppurchase.Messages.PlatformBillingChoiceMode; +import io.flutter.plugin.common.MethodChannel; /** The implementation for {@link BillingClientFactory} for the plugin. */ final class BillingClientFactoryImpl implements BillingClientFactory { @Override public BillingClient createBillingClient( - @NonNull Context context, - @NonNull Messages.InAppPurchaseCallbackApi callbackApi, - PlatformBillingChoiceMode billingChoiceMode) { + @NonNull Context context, @NonNull MethodChannel channel) { BillingClient.Builder builder = BillingClient.newBuilder(context).enablePendingPurchases(); - switch (billingChoiceMode) { - case ALTERNATIVE_BILLING_ONLY: - // https://developer.android.com/google/play/billing/alternative/alternative-billing-without-user-choice-in-app - builder.enableAlternativeBillingOnly(); - break; - case USER_CHOICE_BILLING: - builder.enableUserChoiceBilling(createUserChoiceBillingListener(callbackApi)); - break; - case PLAY_BILLING_ONLY: - // Do nothing. - break; - default: - Log.e( - "BillingClientFactoryImpl", - "Unknown BillingChoiceMode " + billingChoiceMode + ", Defaulting to PLAY_BILLING_ONLY"); - break; - } - return builder.setListener(new PluginPurchaseListener(callbackApi)).build(); - } - - @VisibleForTesting - /* package */ UserChoiceBillingListener createUserChoiceBillingListener( - @NonNull Messages.InAppPurchaseCallbackApi callbackApi) { - return userChoiceDetails -> - callbackApi.userSelectedalternativeBilling( - fromUserChoiceDetails(userChoiceDetails), - new Messages.VoidResult() { - @Override - public void success() {} - @Override - public void error(@NonNull Throwable error) { - io.flutter.Log.e( - "IN_APP_PURCHASE", "userSelectedalternativeBilling handler error: " + error); - } - }); + return builder.setListener(new PluginPurchaseListener(channel)).build(); } } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java index cc153dc43..4db2ca5d7 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java @@ -13,6 +13,7 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodChannel; /** Wraps a {@link BillingClient} instance and responds to Dart calls for it. */ public class InAppPurchasePlugin implements FlutterPlugin, ActivityAware { @@ -24,6 +25,7 @@ public class InAppPurchasePlugin implements FlutterPlugin, ActivityAware { // code owner of this package. static final String PROXY_VALUE = "io.flutter.plugins.inapppurchase"; + private MethodChannel methodChannel; private MethodCallHandlerImpl methodCallHandler; /** Plugin registration. */ @@ -43,7 +45,7 @@ public class InAppPurchasePlugin implements FlutterPlugin, ActivityAware { @Override public void onDetachedFromEngine(@NonNull FlutterPlugin.FlutterPluginBinding binding) { - teardownMethodChannel(binding.getBinaryMessenger()); + teardownMethodChannel(); } @Override @@ -69,15 +71,16 @@ public class InAppPurchasePlugin implements FlutterPlugin, ActivityAware { } private void setUpMethodChannel(BinaryMessenger messenger, Context context) { - Messages.InAppPurchaseCallbackApi handler = new Messages.InAppPurchaseCallbackApi(messenger); + methodChannel = new MethodChannel(messenger, "plugins.flutter.io/in_app_purchase"); methodCallHandler = new MethodCallHandlerImpl( - /*activity=*/ null, context, handler, new BillingClientFactoryImpl()); - Messages.InAppPurchaseApi.setUp(messenger, methodCallHandler); + /*activity=*/ null, context, methodChannel, new BillingClientFactoryImpl()); + methodChannel.setMethodCallHandler(methodCallHandler); } - private void teardownMethodChannel(BinaryMessenger messenger) { - Messages.InAppPurchaseApi.setUp(messenger, null); + private void teardownMethodChannel() { + methodChannel.setMethodCallHandler(null); + methodChannel = null; methodCallHandler = null; } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Messages.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Messages.java deleted file mode 100644 index 6a465b4bb..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Messages.java +++ /dev/null @@ -1,3149 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// Autogenerated from Pigeon (v17.1.2), do not edit directly. -// See also: https://pub.dev/packages/pigeon - -package io.flutter.plugins.inapppurchase; - -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.CLASS; - -import android.util.Log; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import io.flutter.plugin.common.BasicMessageChannel; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MessageCodec; -import io.flutter.plugin.common.StandardMessageCodec; -import java.io.ByteArrayOutputStream; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** Generated class from Pigeon. */ -@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"}) -public class Messages { - - /** Error class for passing custom error details to Flutter via a thrown PlatformException. */ - public static class FlutterError extends RuntimeException { - - /** The error code. */ - public final String code; - - /** The error details. Must be a datatype supported by the api codec. */ - public final Object details; - - public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details) { - super(message); - this.code = code; - this.details = details; - } - } - - @NonNull - protected static ArrayList wrapError(@NonNull Throwable exception) { - ArrayList errorList = new ArrayList(3); - if (exception instanceof FlutterError) { - FlutterError error = (FlutterError) exception; - errorList.add(error.code); - errorList.add(error.getMessage()); - errorList.add(error.details); - } else { - errorList.add(exception.toString()); - errorList.add(exception.getClass().getSimpleName()); - errorList.add( - "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); - } - return errorList; - } - - @NonNull - protected static FlutterError createConnectionError(@NonNull String channelName) { - return new FlutterError( - "channel-error", "Unable to establish connection on channel: " + channelName + ".", ""); - } - - @Target(METHOD) - @Retention(CLASS) - @interface CanIgnoreReturnValue {} - - /** Pigeon version of Java BillingClient.ProductType. */ - public enum PlatformProductType { - INAPP(0), - SUBS(1); - - final int index; - - private PlatformProductType(final int index) { - this.index = index; - } - } - - /** Pigeon version of billing_client_wrapper.dart's BillingChoiceMode. */ - public enum PlatformBillingChoiceMode { - /** - * Billing through google play. - * - *

Default state. - */ - PLAY_BILLING_ONLY(0), - /** Billing through app provided flow. */ - ALTERNATIVE_BILLING_ONLY(1), - /** Users can choose Play billing or alternative billing. */ - USER_CHOICE_BILLING(2); - - final int index; - - private PlatformBillingChoiceMode(final int index) { - this.index = index; - } - } - - /** Pigeon version of Java Purchase.PurchaseState. */ - public enum PlatformPurchaseState { - UNSPECIFIED(0), - PURCHASED(1), - PENDING(2); - - final int index; - - private PlatformPurchaseState(final int index) { - this.index = index; - } - } - - /** Pigeon version of Java ProductDetails.RecurrenceMode. */ - public enum PlatformRecurrenceMode { - FINITE_RECURRING(0), - INFINITE_RECURRING(1), - NON_RECURRING(2); - - final int index; - - private PlatformRecurrenceMode(final int index) { - this.index = index; - } - } - - /** - * Pigeon version of Java QueryProductDetailsParams.Product. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformQueryProduct { - private @NonNull String productId; - - public @NonNull String getProductId() { - return productId; - } - - public void setProductId(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"productId\" is null."); - } - this.productId = setterArg; - } - - private @NonNull PlatformProductType productType; - - public @NonNull PlatformProductType getProductType() { - return productType; - } - - public void setProductType(@NonNull PlatformProductType setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"productType\" is null."); - } - this.productType = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - PlatformQueryProduct() {} - - public static final class Builder { - - private @Nullable String productId; - - @CanIgnoreReturnValue - public @NonNull Builder setProductId(@NonNull String setterArg) { - this.productId = setterArg; - return this; - } - - private @Nullable PlatformProductType productType; - - @CanIgnoreReturnValue - public @NonNull Builder setProductType(@NonNull PlatformProductType setterArg) { - this.productType = setterArg; - return this; - } - - public @NonNull PlatformQueryProduct build() { - PlatformQueryProduct pigeonReturn = new PlatformQueryProduct(); - pigeonReturn.setProductId(productId); - pigeonReturn.setProductType(productType); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(2); - toListResult.add(productId); - toListResult.add(productType == null ? null : productType.index); - return toListResult; - } - - static @NonNull PlatformQueryProduct fromList(@NonNull ArrayList list) { - PlatformQueryProduct pigeonResult = new PlatformQueryProduct(); - Object productId = list.get(0); - pigeonResult.setProductId((String) productId); - Object productType = list.get(1); - pigeonResult.setProductType(PlatformProductType.values()[(int) productType]); - return pigeonResult; - } - } - - /** - * Pigeon version of Java AccountIdentifiers. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformAccountIdentifiers { - private @Nullable String obfuscatedAccountId; - - public @Nullable String getObfuscatedAccountId() { - return obfuscatedAccountId; - } - - public void setObfuscatedAccountId(@Nullable String setterArg) { - this.obfuscatedAccountId = setterArg; - } - - private @Nullable String obfuscatedProfileId; - - public @Nullable String getObfuscatedProfileId() { - return obfuscatedProfileId; - } - - public void setObfuscatedProfileId(@Nullable String setterArg) { - this.obfuscatedProfileId = setterArg; - } - - public static final class Builder { - - private @Nullable String obfuscatedAccountId; - - @CanIgnoreReturnValue - public @NonNull Builder setObfuscatedAccountId(@Nullable String setterArg) { - this.obfuscatedAccountId = setterArg; - return this; - } - - private @Nullable String obfuscatedProfileId; - - @CanIgnoreReturnValue - public @NonNull Builder setObfuscatedProfileId(@Nullable String setterArg) { - this.obfuscatedProfileId = setterArg; - return this; - } - - public @NonNull PlatformAccountIdentifiers build() { - PlatformAccountIdentifiers pigeonReturn = new PlatformAccountIdentifiers(); - pigeonReturn.setObfuscatedAccountId(obfuscatedAccountId); - pigeonReturn.setObfuscatedProfileId(obfuscatedProfileId); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(2); - toListResult.add(obfuscatedAccountId); - toListResult.add(obfuscatedProfileId); - return toListResult; - } - - static @NonNull PlatformAccountIdentifiers fromList(@NonNull ArrayList list) { - PlatformAccountIdentifiers pigeonResult = new PlatformAccountIdentifiers(); - Object obfuscatedAccountId = list.get(0); - pigeonResult.setObfuscatedAccountId((String) obfuscatedAccountId); - Object obfuscatedProfileId = list.get(1); - pigeonResult.setObfuscatedProfileId((String) obfuscatedProfileId); - return pigeonResult; - } - } - - /** - * Pigeon version of Java BillingResult. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformBillingResult { - private @NonNull Long responseCode; - - public @NonNull Long getResponseCode() { - return responseCode; - } - - public void setResponseCode(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"responseCode\" is null."); - } - this.responseCode = setterArg; - } - - private @NonNull String debugMessage; - - public @NonNull String getDebugMessage() { - return debugMessage; - } - - public void setDebugMessage(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"debugMessage\" is null."); - } - this.debugMessage = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - PlatformBillingResult() {} - - public static final class Builder { - - private @Nullable Long responseCode; - - @CanIgnoreReturnValue - public @NonNull Builder setResponseCode(@NonNull Long setterArg) { - this.responseCode = setterArg; - return this; - } - - private @Nullable String debugMessage; - - @CanIgnoreReturnValue - public @NonNull Builder setDebugMessage(@NonNull String setterArg) { - this.debugMessage = setterArg; - return this; - } - - public @NonNull PlatformBillingResult build() { - PlatformBillingResult pigeonReturn = new PlatformBillingResult(); - pigeonReturn.setResponseCode(responseCode); - pigeonReturn.setDebugMessage(debugMessage); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(2); - toListResult.add(responseCode); - toListResult.add(debugMessage); - return toListResult; - } - - static @NonNull PlatformBillingResult fromList(@NonNull ArrayList list) { - PlatformBillingResult pigeonResult = new PlatformBillingResult(); - Object responseCode = list.get(0); - pigeonResult.setResponseCode( - (responseCode == null) - ? null - : ((responseCode instanceof Integer) ? (Integer) responseCode : (Long) responseCode)); - Object debugMessage = list.get(1); - pigeonResult.setDebugMessage((String) debugMessage); - return pigeonResult; - } - } - - /** - * Pigeon version of Java ProductDetails.OneTimePurchaseOfferDetails. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformOneTimePurchaseOfferDetails { - private @NonNull Long priceAmountMicros; - - public @NonNull Long getPriceAmountMicros() { - return priceAmountMicros; - } - - public void setPriceAmountMicros(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"priceAmountMicros\" is null."); - } - this.priceAmountMicros = setterArg; - } - - private @NonNull String formattedPrice; - - public @NonNull String getFormattedPrice() { - return formattedPrice; - } - - public void setFormattedPrice(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"formattedPrice\" is null."); - } - this.formattedPrice = setterArg; - } - - private @NonNull String priceCurrencyCode; - - public @NonNull String getPriceCurrencyCode() { - return priceCurrencyCode; - } - - public void setPriceCurrencyCode(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"priceCurrencyCode\" is null."); - } - this.priceCurrencyCode = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - PlatformOneTimePurchaseOfferDetails() {} - - public static final class Builder { - - private @Nullable Long priceAmountMicros; - - @CanIgnoreReturnValue - public @NonNull Builder setPriceAmountMicros(@NonNull Long setterArg) { - this.priceAmountMicros = setterArg; - return this; - } - - private @Nullable String formattedPrice; - - @CanIgnoreReturnValue - public @NonNull Builder setFormattedPrice(@NonNull String setterArg) { - this.formattedPrice = setterArg; - return this; - } - - private @Nullable String priceCurrencyCode; - - @CanIgnoreReturnValue - public @NonNull Builder setPriceCurrencyCode(@NonNull String setterArg) { - this.priceCurrencyCode = setterArg; - return this; - } - - public @NonNull PlatformOneTimePurchaseOfferDetails build() { - PlatformOneTimePurchaseOfferDetails pigeonReturn = - new PlatformOneTimePurchaseOfferDetails(); - pigeonReturn.setPriceAmountMicros(priceAmountMicros); - pigeonReturn.setFormattedPrice(formattedPrice); - pigeonReturn.setPriceCurrencyCode(priceCurrencyCode); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(3); - toListResult.add(priceAmountMicros); - toListResult.add(formattedPrice); - toListResult.add(priceCurrencyCode); - return toListResult; - } - - static @NonNull PlatformOneTimePurchaseOfferDetails fromList(@NonNull ArrayList list) { - PlatformOneTimePurchaseOfferDetails pigeonResult = new PlatformOneTimePurchaseOfferDetails(); - Object priceAmountMicros = list.get(0); - pigeonResult.setPriceAmountMicros( - (priceAmountMicros == null) - ? null - : ((priceAmountMicros instanceof Integer) - ? (Integer) priceAmountMicros - : (Long) priceAmountMicros)); - Object formattedPrice = list.get(1); - pigeonResult.setFormattedPrice((String) formattedPrice); - Object priceCurrencyCode = list.get(2); - pigeonResult.setPriceCurrencyCode((String) priceCurrencyCode); - return pigeonResult; - } - } - - /** - * Pigeon version of Java ProductDetails. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformProductDetails { - private @NonNull String description; - - public @NonNull String getDescription() { - return description; - } - - public void setDescription(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"description\" is null."); - } - this.description = setterArg; - } - - private @NonNull String name; - - public @NonNull String getName() { - return name; - } - - public void setName(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"name\" is null."); - } - this.name = setterArg; - } - - private @NonNull String productId; - - public @NonNull String getProductId() { - return productId; - } - - public void setProductId(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"productId\" is null."); - } - this.productId = setterArg; - } - - private @NonNull PlatformProductType productType; - - public @NonNull PlatformProductType getProductType() { - return productType; - } - - public void setProductType(@NonNull PlatformProductType setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"productType\" is null."); - } - this.productType = setterArg; - } - - private @NonNull String title; - - public @NonNull String getTitle() { - return title; - } - - public void setTitle(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"title\" is null."); - } - this.title = setterArg; - } - - private @Nullable PlatformOneTimePurchaseOfferDetails oneTimePurchaseOfferDetails; - - public @Nullable PlatformOneTimePurchaseOfferDetails getOneTimePurchaseOfferDetails() { - return oneTimePurchaseOfferDetails; - } - - public void setOneTimePurchaseOfferDetails( - @Nullable PlatformOneTimePurchaseOfferDetails setterArg) { - this.oneTimePurchaseOfferDetails = setterArg; - } - - private @Nullable List subscriptionOfferDetails; - - public @Nullable List getSubscriptionOfferDetails() { - return subscriptionOfferDetails; - } - - public void setSubscriptionOfferDetails( - @Nullable List setterArg) { - this.subscriptionOfferDetails = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - PlatformProductDetails() {} - - public static final class Builder { - - private @Nullable String description; - - @CanIgnoreReturnValue - public @NonNull Builder setDescription(@NonNull String setterArg) { - this.description = setterArg; - return this; - } - - private @Nullable String name; - - @CanIgnoreReturnValue - public @NonNull Builder setName(@NonNull String setterArg) { - this.name = setterArg; - return this; - } - - private @Nullable String productId; - - @CanIgnoreReturnValue - public @NonNull Builder setProductId(@NonNull String setterArg) { - this.productId = setterArg; - return this; - } - - private @Nullable PlatformProductType productType; - - @CanIgnoreReturnValue - public @NonNull Builder setProductType(@NonNull PlatformProductType setterArg) { - this.productType = setterArg; - return this; - } - - private @Nullable String title; - - @CanIgnoreReturnValue - public @NonNull Builder setTitle(@NonNull String setterArg) { - this.title = setterArg; - return this; - } - - private @Nullable PlatformOneTimePurchaseOfferDetails oneTimePurchaseOfferDetails; - - @CanIgnoreReturnValue - public @NonNull Builder setOneTimePurchaseOfferDetails( - @Nullable PlatformOneTimePurchaseOfferDetails setterArg) { - this.oneTimePurchaseOfferDetails = setterArg; - return this; - } - - private @Nullable List subscriptionOfferDetails; - - @CanIgnoreReturnValue - public @NonNull Builder setSubscriptionOfferDetails( - @Nullable List setterArg) { - this.subscriptionOfferDetails = setterArg; - return this; - } - - public @NonNull PlatformProductDetails build() { - PlatformProductDetails pigeonReturn = new PlatformProductDetails(); - pigeonReturn.setDescription(description); - pigeonReturn.setName(name); - pigeonReturn.setProductId(productId); - pigeonReturn.setProductType(productType); - pigeonReturn.setTitle(title); - pigeonReturn.setOneTimePurchaseOfferDetails(oneTimePurchaseOfferDetails); - pigeonReturn.setSubscriptionOfferDetails(subscriptionOfferDetails); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(7); - toListResult.add(description); - toListResult.add(name); - toListResult.add(productId); - toListResult.add(productType == null ? null : productType.index); - toListResult.add(title); - toListResult.add( - (oneTimePurchaseOfferDetails == null) ? null : oneTimePurchaseOfferDetails.toList()); - toListResult.add(subscriptionOfferDetails); - return toListResult; - } - - static @NonNull PlatformProductDetails fromList(@NonNull ArrayList list) { - PlatformProductDetails pigeonResult = new PlatformProductDetails(); - Object description = list.get(0); - pigeonResult.setDescription((String) description); - Object name = list.get(1); - pigeonResult.setName((String) name); - Object productId = list.get(2); - pigeonResult.setProductId((String) productId); - Object productType = list.get(3); - pigeonResult.setProductType(PlatformProductType.values()[(int) productType]); - Object title = list.get(4); - pigeonResult.setTitle((String) title); - Object oneTimePurchaseOfferDetails = list.get(5); - pigeonResult.setOneTimePurchaseOfferDetails( - (oneTimePurchaseOfferDetails == null) - ? null - : PlatformOneTimePurchaseOfferDetails.fromList( - (ArrayList) oneTimePurchaseOfferDetails)); - Object subscriptionOfferDetails = list.get(6); - pigeonResult.setSubscriptionOfferDetails( - (List) subscriptionOfferDetails); - return pigeonResult; - } - } - - /** - * Pigeon version of ProductDetailsResponseWrapper, which contains the components of the Java - * ProductDetailsResponseListener callback. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformProductDetailsResponse { - private @NonNull PlatformBillingResult billingResult; - - public @NonNull PlatformBillingResult getBillingResult() { - return billingResult; - } - - public void setBillingResult(@NonNull PlatformBillingResult setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"billingResult\" is null."); - } - this.billingResult = setterArg; - } - - private @NonNull List productDetails; - - public @NonNull List getProductDetails() { - return productDetails; - } - - public void setProductDetails(@NonNull List setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"productDetails\" is null."); - } - this.productDetails = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - PlatformProductDetailsResponse() {} - - public static final class Builder { - - private @Nullable PlatformBillingResult billingResult; - - @CanIgnoreReturnValue - public @NonNull Builder setBillingResult(@NonNull PlatformBillingResult setterArg) { - this.billingResult = setterArg; - return this; - } - - private @Nullable List productDetails; - - @CanIgnoreReturnValue - public @NonNull Builder setProductDetails(@NonNull List setterArg) { - this.productDetails = setterArg; - return this; - } - - public @NonNull PlatformProductDetailsResponse build() { - PlatformProductDetailsResponse pigeonReturn = new PlatformProductDetailsResponse(); - pigeonReturn.setBillingResult(billingResult); - pigeonReturn.setProductDetails(productDetails); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(2); - toListResult.add((billingResult == null) ? null : billingResult.toList()); - toListResult.add(productDetails); - return toListResult; - } - - static @NonNull PlatformProductDetailsResponse fromList(@NonNull ArrayList list) { - PlatformProductDetailsResponse pigeonResult = new PlatformProductDetailsResponse(); - Object billingResult = list.get(0); - pigeonResult.setBillingResult( - (billingResult == null) - ? null - : PlatformBillingResult.fromList((ArrayList) billingResult)); - Object productDetails = list.get(1); - pigeonResult.setProductDetails((List) productDetails); - return pigeonResult; - } - } - - /** - * Pigeon version of AlternativeBillingOnlyReportingDetailsWrapper, which contains the components - * of the Java AlternativeBillingOnlyReportingDetailsListener callback. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformAlternativeBillingOnlyReportingDetailsResponse { - private @NonNull PlatformBillingResult billingResult; - - public @NonNull PlatformBillingResult getBillingResult() { - return billingResult; - } - - public void setBillingResult(@NonNull PlatformBillingResult setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"billingResult\" is null."); - } - this.billingResult = setterArg; - } - - private @NonNull String externalTransactionToken; - - public @NonNull String getExternalTransactionToken() { - return externalTransactionToken; - } - - public void setExternalTransactionToken(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"externalTransactionToken\" is null."); - } - this.externalTransactionToken = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - PlatformAlternativeBillingOnlyReportingDetailsResponse() {} - - public static final class Builder { - - private @Nullable PlatformBillingResult billingResult; - - @CanIgnoreReturnValue - public @NonNull Builder setBillingResult(@NonNull PlatformBillingResult setterArg) { - this.billingResult = setterArg; - return this; - } - - private @Nullable String externalTransactionToken; - - @CanIgnoreReturnValue - public @NonNull Builder setExternalTransactionToken(@NonNull String setterArg) { - this.externalTransactionToken = setterArg; - return this; - } - - public @NonNull PlatformAlternativeBillingOnlyReportingDetailsResponse build() { - PlatformAlternativeBillingOnlyReportingDetailsResponse pigeonReturn = - new PlatformAlternativeBillingOnlyReportingDetailsResponse(); - pigeonReturn.setBillingResult(billingResult); - pigeonReturn.setExternalTransactionToken(externalTransactionToken); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(2); - toListResult.add((billingResult == null) ? null : billingResult.toList()); - toListResult.add(externalTransactionToken); - return toListResult; - } - - static @NonNull PlatformAlternativeBillingOnlyReportingDetailsResponse fromList( - @NonNull ArrayList list) { - PlatformAlternativeBillingOnlyReportingDetailsResponse pigeonResult = - new PlatformAlternativeBillingOnlyReportingDetailsResponse(); - Object billingResult = list.get(0); - pigeonResult.setBillingResult( - (billingResult == null) - ? null - : PlatformBillingResult.fromList((ArrayList) billingResult)); - Object externalTransactionToken = list.get(1); - pigeonResult.setExternalTransactionToken((String) externalTransactionToken); - return pigeonResult; - } - } - - /** - * Pigeon version of BillingConfigWrapper, which contains the components of the Java - * BillingConfigResponseListener callback. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformBillingConfigResponse { - private @NonNull PlatformBillingResult billingResult; - - public @NonNull PlatformBillingResult getBillingResult() { - return billingResult; - } - - public void setBillingResult(@NonNull PlatformBillingResult setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"billingResult\" is null."); - } - this.billingResult = setterArg; - } - - private @NonNull String countryCode; - - public @NonNull String getCountryCode() { - return countryCode; - } - - public void setCountryCode(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"countryCode\" is null."); - } - this.countryCode = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - PlatformBillingConfigResponse() {} - - public static final class Builder { - - private @Nullable PlatformBillingResult billingResult; - - @CanIgnoreReturnValue - public @NonNull Builder setBillingResult(@NonNull PlatformBillingResult setterArg) { - this.billingResult = setterArg; - return this; - } - - private @Nullable String countryCode; - - @CanIgnoreReturnValue - public @NonNull Builder setCountryCode(@NonNull String setterArg) { - this.countryCode = setterArg; - return this; - } - - public @NonNull PlatformBillingConfigResponse build() { - PlatformBillingConfigResponse pigeonReturn = new PlatformBillingConfigResponse(); - pigeonReturn.setBillingResult(billingResult); - pigeonReturn.setCountryCode(countryCode); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(2); - toListResult.add((billingResult == null) ? null : billingResult.toList()); - toListResult.add(countryCode); - return toListResult; - } - - static @NonNull PlatformBillingConfigResponse fromList(@NonNull ArrayList list) { - PlatformBillingConfigResponse pigeonResult = new PlatformBillingConfigResponse(); - Object billingResult = list.get(0); - pigeonResult.setBillingResult( - (billingResult == null) - ? null - : PlatformBillingResult.fromList((ArrayList) billingResult)); - Object countryCode = list.get(1); - pigeonResult.setCountryCode((String) countryCode); - return pigeonResult; - } - } - - /** - * Pigeon version of Java BillingFlowParams. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformBillingFlowParams { - private @NonNull String product; - - public @NonNull String getProduct() { - return product; - } - - public void setProduct(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"product\" is null."); - } - this.product = setterArg; - } - - private @NonNull Long prorationMode; - - public @NonNull Long getProrationMode() { - return prorationMode; - } - - public void setProrationMode(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"prorationMode\" is null."); - } - this.prorationMode = setterArg; - } - - private @Nullable String offerToken; - - public @Nullable String getOfferToken() { - return offerToken; - } - - public void setOfferToken(@Nullable String setterArg) { - this.offerToken = setterArg; - } - - private @Nullable String accountId; - - public @Nullable String getAccountId() { - return accountId; - } - - public void setAccountId(@Nullable String setterArg) { - this.accountId = setterArg; - } - - private @Nullable String obfuscatedProfileId; - - public @Nullable String getObfuscatedProfileId() { - return obfuscatedProfileId; - } - - public void setObfuscatedProfileId(@Nullable String setterArg) { - this.obfuscatedProfileId = setterArg; - } - - private @Nullable String oldProduct; - - public @Nullable String getOldProduct() { - return oldProduct; - } - - public void setOldProduct(@Nullable String setterArg) { - this.oldProduct = setterArg; - } - - private @Nullable String purchaseToken; - - public @Nullable String getPurchaseToken() { - return purchaseToken; - } - - public void setPurchaseToken(@Nullable String setterArg) { - this.purchaseToken = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - PlatformBillingFlowParams() {} - - public static final class Builder { - - private @Nullable String product; - - @CanIgnoreReturnValue - public @NonNull Builder setProduct(@NonNull String setterArg) { - this.product = setterArg; - return this; - } - - private @Nullable Long prorationMode; - - @CanIgnoreReturnValue - public @NonNull Builder setProrationMode(@NonNull Long setterArg) { - this.prorationMode = setterArg; - return this; - } - - private @Nullable String offerToken; - - @CanIgnoreReturnValue - public @NonNull Builder setOfferToken(@Nullable String setterArg) { - this.offerToken = setterArg; - return this; - } - - private @Nullable String accountId; - - @CanIgnoreReturnValue - public @NonNull Builder setAccountId(@Nullable String setterArg) { - this.accountId = setterArg; - return this; - } - - private @Nullable String obfuscatedProfileId; - - @CanIgnoreReturnValue - public @NonNull Builder setObfuscatedProfileId(@Nullable String setterArg) { - this.obfuscatedProfileId = setterArg; - return this; - } - - private @Nullable String oldProduct; - - @CanIgnoreReturnValue - public @NonNull Builder setOldProduct(@Nullable String setterArg) { - this.oldProduct = setterArg; - return this; - } - - private @Nullable String purchaseToken; - - @CanIgnoreReturnValue - public @NonNull Builder setPurchaseToken(@Nullable String setterArg) { - this.purchaseToken = setterArg; - return this; - } - - public @NonNull PlatformBillingFlowParams build() { - PlatformBillingFlowParams pigeonReturn = new PlatformBillingFlowParams(); - pigeonReturn.setProduct(product); - pigeonReturn.setProrationMode(prorationMode); - pigeonReturn.setOfferToken(offerToken); - pigeonReturn.setAccountId(accountId); - pigeonReturn.setObfuscatedProfileId(obfuscatedProfileId); - pigeonReturn.setOldProduct(oldProduct); - pigeonReturn.setPurchaseToken(purchaseToken); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(7); - toListResult.add(product); - toListResult.add(prorationMode); - toListResult.add(offerToken); - toListResult.add(accountId); - toListResult.add(obfuscatedProfileId); - toListResult.add(oldProduct); - toListResult.add(purchaseToken); - return toListResult; - } - - static @NonNull PlatformBillingFlowParams fromList(@NonNull ArrayList list) { - PlatformBillingFlowParams pigeonResult = new PlatformBillingFlowParams(); - Object product = list.get(0); - pigeonResult.setProduct((String) product); - Object prorationMode = list.get(1); - pigeonResult.setProrationMode( - (prorationMode == null) - ? null - : ((prorationMode instanceof Integer) - ? (Integer) prorationMode - : (Long) prorationMode)); - Object offerToken = list.get(2); - pigeonResult.setOfferToken((String) offerToken); - Object accountId = list.get(3); - pigeonResult.setAccountId((String) accountId); - Object obfuscatedProfileId = list.get(4); - pigeonResult.setObfuscatedProfileId((String) obfuscatedProfileId); - Object oldProduct = list.get(5); - pigeonResult.setOldProduct((String) oldProduct); - Object purchaseToken = list.get(6); - pigeonResult.setPurchaseToken((String) purchaseToken); - return pigeonResult; - } - } - - /** - * Pigeon version of Java ProductDetails.PricingPhase. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformPricingPhase { - private @NonNull Long billingCycleCount; - - public @NonNull Long getBillingCycleCount() { - return billingCycleCount; - } - - public void setBillingCycleCount(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"billingCycleCount\" is null."); - } - this.billingCycleCount = setterArg; - } - - private @NonNull PlatformRecurrenceMode recurrenceMode; - - public @NonNull PlatformRecurrenceMode getRecurrenceMode() { - return recurrenceMode; - } - - public void setRecurrenceMode(@NonNull PlatformRecurrenceMode setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"recurrenceMode\" is null."); - } - this.recurrenceMode = setterArg; - } - - private @NonNull Long priceAmountMicros; - - public @NonNull Long getPriceAmountMicros() { - return priceAmountMicros; - } - - public void setPriceAmountMicros(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"priceAmountMicros\" is null."); - } - this.priceAmountMicros = setterArg; - } - - private @NonNull String billingPeriod; - - public @NonNull String getBillingPeriod() { - return billingPeriod; - } - - public void setBillingPeriod(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"billingPeriod\" is null."); - } - this.billingPeriod = setterArg; - } - - private @NonNull String formattedPrice; - - public @NonNull String getFormattedPrice() { - return formattedPrice; - } - - public void setFormattedPrice(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"formattedPrice\" is null."); - } - this.formattedPrice = setterArg; - } - - private @NonNull String priceCurrencyCode; - - public @NonNull String getPriceCurrencyCode() { - return priceCurrencyCode; - } - - public void setPriceCurrencyCode(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"priceCurrencyCode\" is null."); - } - this.priceCurrencyCode = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - PlatformPricingPhase() {} - - public static final class Builder { - - private @Nullable Long billingCycleCount; - - @CanIgnoreReturnValue - public @NonNull Builder setBillingCycleCount(@NonNull Long setterArg) { - this.billingCycleCount = setterArg; - return this; - } - - private @Nullable PlatformRecurrenceMode recurrenceMode; - - @CanIgnoreReturnValue - public @NonNull Builder setRecurrenceMode(@NonNull PlatformRecurrenceMode setterArg) { - this.recurrenceMode = setterArg; - return this; - } - - private @Nullable Long priceAmountMicros; - - @CanIgnoreReturnValue - public @NonNull Builder setPriceAmountMicros(@NonNull Long setterArg) { - this.priceAmountMicros = setterArg; - return this; - } - - private @Nullable String billingPeriod; - - @CanIgnoreReturnValue - public @NonNull Builder setBillingPeriod(@NonNull String setterArg) { - this.billingPeriod = setterArg; - return this; - } - - private @Nullable String formattedPrice; - - @CanIgnoreReturnValue - public @NonNull Builder setFormattedPrice(@NonNull String setterArg) { - this.formattedPrice = setterArg; - return this; - } - - private @Nullable String priceCurrencyCode; - - @CanIgnoreReturnValue - public @NonNull Builder setPriceCurrencyCode(@NonNull String setterArg) { - this.priceCurrencyCode = setterArg; - return this; - } - - public @NonNull PlatformPricingPhase build() { - PlatformPricingPhase pigeonReturn = new PlatformPricingPhase(); - pigeonReturn.setBillingCycleCount(billingCycleCount); - pigeonReturn.setRecurrenceMode(recurrenceMode); - pigeonReturn.setPriceAmountMicros(priceAmountMicros); - pigeonReturn.setBillingPeriod(billingPeriod); - pigeonReturn.setFormattedPrice(formattedPrice); - pigeonReturn.setPriceCurrencyCode(priceCurrencyCode); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(6); - toListResult.add(billingCycleCount); - toListResult.add(recurrenceMode == null ? null : recurrenceMode.index); - toListResult.add(priceAmountMicros); - toListResult.add(billingPeriod); - toListResult.add(formattedPrice); - toListResult.add(priceCurrencyCode); - return toListResult; - } - - static @NonNull PlatformPricingPhase fromList(@NonNull ArrayList list) { - PlatformPricingPhase pigeonResult = new PlatformPricingPhase(); - Object billingCycleCount = list.get(0); - pigeonResult.setBillingCycleCount( - (billingCycleCount == null) - ? null - : ((billingCycleCount instanceof Integer) - ? (Integer) billingCycleCount - : (Long) billingCycleCount)); - Object recurrenceMode = list.get(1); - pigeonResult.setRecurrenceMode(PlatformRecurrenceMode.values()[(int) recurrenceMode]); - Object priceAmountMicros = list.get(2); - pigeonResult.setPriceAmountMicros( - (priceAmountMicros == null) - ? null - : ((priceAmountMicros instanceof Integer) - ? (Integer) priceAmountMicros - : (Long) priceAmountMicros)); - Object billingPeriod = list.get(3); - pigeonResult.setBillingPeriod((String) billingPeriod); - Object formattedPrice = list.get(4); - pigeonResult.setFormattedPrice((String) formattedPrice); - Object priceCurrencyCode = list.get(5); - pigeonResult.setPriceCurrencyCode((String) priceCurrencyCode); - return pigeonResult; - } - } - - /** - * Pigeon version of Java Purchase. - * - *

See also PurchaseWrapper on the Dart side. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformPurchase { - private @Nullable String orderId; - - public @Nullable String getOrderId() { - return orderId; - } - - public void setOrderId(@Nullable String setterArg) { - this.orderId = setterArg; - } - - private @NonNull String packageName; - - public @NonNull String getPackageName() { - return packageName; - } - - public void setPackageName(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"packageName\" is null."); - } - this.packageName = setterArg; - } - - private @NonNull Long purchaseTime; - - public @NonNull Long getPurchaseTime() { - return purchaseTime; - } - - public void setPurchaseTime(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"purchaseTime\" is null."); - } - this.purchaseTime = setterArg; - } - - private @NonNull String purchaseToken; - - public @NonNull String getPurchaseToken() { - return purchaseToken; - } - - public void setPurchaseToken(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"purchaseToken\" is null."); - } - this.purchaseToken = setterArg; - } - - private @NonNull String signature; - - public @NonNull String getSignature() { - return signature; - } - - public void setSignature(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"signature\" is null."); - } - this.signature = setterArg; - } - - private @NonNull List products; - - public @NonNull List getProducts() { - return products; - } - - public void setProducts(@NonNull List setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"products\" is null."); - } - this.products = setterArg; - } - - private @NonNull Boolean isAutoRenewing; - - public @NonNull Boolean getIsAutoRenewing() { - return isAutoRenewing; - } - - public void setIsAutoRenewing(@NonNull Boolean setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"isAutoRenewing\" is null."); - } - this.isAutoRenewing = setterArg; - } - - private @NonNull String originalJson; - - public @NonNull String getOriginalJson() { - return originalJson; - } - - public void setOriginalJson(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"originalJson\" is null."); - } - this.originalJson = setterArg; - } - - private @NonNull String developerPayload; - - public @NonNull String getDeveloperPayload() { - return developerPayload; - } - - public void setDeveloperPayload(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"developerPayload\" is null."); - } - this.developerPayload = setterArg; - } - - private @NonNull Boolean isAcknowledged; - - public @NonNull Boolean getIsAcknowledged() { - return isAcknowledged; - } - - public void setIsAcknowledged(@NonNull Boolean setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"isAcknowledged\" is null."); - } - this.isAcknowledged = setterArg; - } - - private @NonNull Long quantity; - - public @NonNull Long getQuantity() { - return quantity; - } - - public void setQuantity(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"quantity\" is null."); - } - this.quantity = setterArg; - } - - private @NonNull PlatformPurchaseState purchaseState; - - public @NonNull PlatformPurchaseState getPurchaseState() { - return purchaseState; - } - - public void setPurchaseState(@NonNull PlatformPurchaseState setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"purchaseState\" is null."); - } - this.purchaseState = setterArg; - } - - private @Nullable PlatformAccountIdentifiers accountIdentifiers; - - public @Nullable PlatformAccountIdentifiers getAccountIdentifiers() { - return accountIdentifiers; - } - - public void setAccountIdentifiers(@Nullable PlatformAccountIdentifiers setterArg) { - this.accountIdentifiers = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - PlatformPurchase() {} - - public static final class Builder { - - private @Nullable String orderId; - - @CanIgnoreReturnValue - public @NonNull Builder setOrderId(@Nullable String setterArg) { - this.orderId = setterArg; - return this; - } - - private @Nullable String packageName; - - @CanIgnoreReturnValue - public @NonNull Builder setPackageName(@NonNull String setterArg) { - this.packageName = setterArg; - return this; - } - - private @Nullable Long purchaseTime; - - @CanIgnoreReturnValue - public @NonNull Builder setPurchaseTime(@NonNull Long setterArg) { - this.purchaseTime = setterArg; - return this; - } - - private @Nullable String purchaseToken; - - @CanIgnoreReturnValue - public @NonNull Builder setPurchaseToken(@NonNull String setterArg) { - this.purchaseToken = setterArg; - return this; - } - - private @Nullable String signature; - - @CanIgnoreReturnValue - public @NonNull Builder setSignature(@NonNull String setterArg) { - this.signature = setterArg; - return this; - } - - private @Nullable List products; - - @CanIgnoreReturnValue - public @NonNull Builder setProducts(@NonNull List setterArg) { - this.products = setterArg; - return this; - } - - private @Nullable Boolean isAutoRenewing; - - @CanIgnoreReturnValue - public @NonNull Builder setIsAutoRenewing(@NonNull Boolean setterArg) { - this.isAutoRenewing = setterArg; - return this; - } - - private @Nullable String originalJson; - - @CanIgnoreReturnValue - public @NonNull Builder setOriginalJson(@NonNull String setterArg) { - this.originalJson = setterArg; - return this; - } - - private @Nullable String developerPayload; - - @CanIgnoreReturnValue - public @NonNull Builder setDeveloperPayload(@NonNull String setterArg) { - this.developerPayload = setterArg; - return this; - } - - private @Nullable Boolean isAcknowledged; - - @CanIgnoreReturnValue - public @NonNull Builder setIsAcknowledged(@NonNull Boolean setterArg) { - this.isAcknowledged = setterArg; - return this; - } - - private @Nullable Long quantity; - - @CanIgnoreReturnValue - public @NonNull Builder setQuantity(@NonNull Long setterArg) { - this.quantity = setterArg; - return this; - } - - private @Nullable PlatformPurchaseState purchaseState; - - @CanIgnoreReturnValue - public @NonNull Builder setPurchaseState(@NonNull PlatformPurchaseState setterArg) { - this.purchaseState = setterArg; - return this; - } - - private @Nullable PlatformAccountIdentifiers accountIdentifiers; - - @CanIgnoreReturnValue - public @NonNull Builder setAccountIdentifiers( - @Nullable PlatformAccountIdentifiers setterArg) { - this.accountIdentifiers = setterArg; - return this; - } - - public @NonNull PlatformPurchase build() { - PlatformPurchase pigeonReturn = new PlatformPurchase(); - pigeonReturn.setOrderId(orderId); - pigeonReturn.setPackageName(packageName); - pigeonReturn.setPurchaseTime(purchaseTime); - pigeonReturn.setPurchaseToken(purchaseToken); - pigeonReturn.setSignature(signature); - pigeonReturn.setProducts(products); - pigeonReturn.setIsAutoRenewing(isAutoRenewing); - pigeonReturn.setOriginalJson(originalJson); - pigeonReturn.setDeveloperPayload(developerPayload); - pigeonReturn.setIsAcknowledged(isAcknowledged); - pigeonReturn.setQuantity(quantity); - pigeonReturn.setPurchaseState(purchaseState); - pigeonReturn.setAccountIdentifiers(accountIdentifiers); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(13); - toListResult.add(orderId); - toListResult.add(packageName); - toListResult.add(purchaseTime); - toListResult.add(purchaseToken); - toListResult.add(signature); - toListResult.add(products); - toListResult.add(isAutoRenewing); - toListResult.add(originalJson); - toListResult.add(developerPayload); - toListResult.add(isAcknowledged); - toListResult.add(quantity); - toListResult.add(purchaseState == null ? null : purchaseState.index); - toListResult.add((accountIdentifiers == null) ? null : accountIdentifiers.toList()); - return toListResult; - } - - static @NonNull PlatformPurchase fromList(@NonNull ArrayList list) { - PlatformPurchase pigeonResult = new PlatformPurchase(); - Object orderId = list.get(0); - pigeonResult.setOrderId((String) orderId); - Object packageName = list.get(1); - pigeonResult.setPackageName((String) packageName); - Object purchaseTime = list.get(2); - pigeonResult.setPurchaseTime( - (purchaseTime == null) - ? null - : ((purchaseTime instanceof Integer) ? (Integer) purchaseTime : (Long) purchaseTime)); - Object purchaseToken = list.get(3); - pigeonResult.setPurchaseToken((String) purchaseToken); - Object signature = list.get(4); - pigeonResult.setSignature((String) signature); - Object products = list.get(5); - pigeonResult.setProducts((List) products); - Object isAutoRenewing = list.get(6); - pigeonResult.setIsAutoRenewing((Boolean) isAutoRenewing); - Object originalJson = list.get(7); - pigeonResult.setOriginalJson((String) originalJson); - Object developerPayload = list.get(8); - pigeonResult.setDeveloperPayload((String) developerPayload); - Object isAcknowledged = list.get(9); - pigeonResult.setIsAcknowledged((Boolean) isAcknowledged); - Object quantity = list.get(10); - pigeonResult.setQuantity( - (quantity == null) - ? null - : ((quantity instanceof Integer) ? (Integer) quantity : (Long) quantity)); - Object purchaseState = list.get(11); - pigeonResult.setPurchaseState(PlatformPurchaseState.values()[(int) purchaseState]); - Object accountIdentifiers = list.get(12); - pigeonResult.setAccountIdentifiers( - (accountIdentifiers == null) - ? null - : PlatformAccountIdentifiers.fromList((ArrayList) accountIdentifiers)); - return pigeonResult; - } - } - - /** - * Pigeon version of PurchaseHistoryRecord. - * - *

See also PurchaseHistoryRecordWrapper on the Dart side. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformPurchaseHistoryRecord { - private @NonNull Long quantity; - - public @NonNull Long getQuantity() { - return quantity; - } - - public void setQuantity(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"quantity\" is null."); - } - this.quantity = setterArg; - } - - private @NonNull Long purchaseTime; - - public @NonNull Long getPurchaseTime() { - return purchaseTime; - } - - public void setPurchaseTime(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"purchaseTime\" is null."); - } - this.purchaseTime = setterArg; - } - - private @Nullable String developerPayload; - - public @Nullable String getDeveloperPayload() { - return developerPayload; - } - - public void setDeveloperPayload(@Nullable String setterArg) { - this.developerPayload = setterArg; - } - - private @NonNull String originalJson; - - public @NonNull String getOriginalJson() { - return originalJson; - } - - public void setOriginalJson(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"originalJson\" is null."); - } - this.originalJson = setterArg; - } - - private @NonNull String purchaseToken; - - public @NonNull String getPurchaseToken() { - return purchaseToken; - } - - public void setPurchaseToken(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"purchaseToken\" is null."); - } - this.purchaseToken = setterArg; - } - - private @NonNull String signature; - - public @NonNull String getSignature() { - return signature; - } - - public void setSignature(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"signature\" is null."); - } - this.signature = setterArg; - } - - private @NonNull List products; - - public @NonNull List getProducts() { - return products; - } - - public void setProducts(@NonNull List setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"products\" is null."); - } - this.products = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - PlatformPurchaseHistoryRecord() {} - - public static final class Builder { - - private @Nullable Long quantity; - - @CanIgnoreReturnValue - public @NonNull Builder setQuantity(@NonNull Long setterArg) { - this.quantity = setterArg; - return this; - } - - private @Nullable Long purchaseTime; - - @CanIgnoreReturnValue - public @NonNull Builder setPurchaseTime(@NonNull Long setterArg) { - this.purchaseTime = setterArg; - return this; - } - - private @Nullable String developerPayload; - - @CanIgnoreReturnValue - public @NonNull Builder setDeveloperPayload(@Nullable String setterArg) { - this.developerPayload = setterArg; - return this; - } - - private @Nullable String originalJson; - - @CanIgnoreReturnValue - public @NonNull Builder setOriginalJson(@NonNull String setterArg) { - this.originalJson = setterArg; - return this; - } - - private @Nullable String purchaseToken; - - @CanIgnoreReturnValue - public @NonNull Builder setPurchaseToken(@NonNull String setterArg) { - this.purchaseToken = setterArg; - return this; - } - - private @Nullable String signature; - - @CanIgnoreReturnValue - public @NonNull Builder setSignature(@NonNull String setterArg) { - this.signature = setterArg; - return this; - } - - private @Nullable List products; - - @CanIgnoreReturnValue - public @NonNull Builder setProducts(@NonNull List setterArg) { - this.products = setterArg; - return this; - } - - public @NonNull PlatformPurchaseHistoryRecord build() { - PlatformPurchaseHistoryRecord pigeonReturn = new PlatformPurchaseHistoryRecord(); - pigeonReturn.setQuantity(quantity); - pigeonReturn.setPurchaseTime(purchaseTime); - pigeonReturn.setDeveloperPayload(developerPayload); - pigeonReturn.setOriginalJson(originalJson); - pigeonReturn.setPurchaseToken(purchaseToken); - pigeonReturn.setSignature(signature); - pigeonReturn.setProducts(products); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(7); - toListResult.add(quantity); - toListResult.add(purchaseTime); - toListResult.add(developerPayload); - toListResult.add(originalJson); - toListResult.add(purchaseToken); - toListResult.add(signature); - toListResult.add(products); - return toListResult; - } - - static @NonNull PlatformPurchaseHistoryRecord fromList(@NonNull ArrayList list) { - PlatformPurchaseHistoryRecord pigeonResult = new PlatformPurchaseHistoryRecord(); - Object quantity = list.get(0); - pigeonResult.setQuantity( - (quantity == null) - ? null - : ((quantity instanceof Integer) ? (Integer) quantity : (Long) quantity)); - Object purchaseTime = list.get(1); - pigeonResult.setPurchaseTime( - (purchaseTime == null) - ? null - : ((purchaseTime instanceof Integer) ? (Integer) purchaseTime : (Long) purchaseTime)); - Object developerPayload = list.get(2); - pigeonResult.setDeveloperPayload((String) developerPayload); - Object originalJson = list.get(3); - pigeonResult.setOriginalJson((String) originalJson); - Object purchaseToken = list.get(4); - pigeonResult.setPurchaseToken((String) purchaseToken); - Object signature = list.get(5); - pigeonResult.setSignature((String) signature); - Object products = list.get(6); - pigeonResult.setProducts((List) products); - return pigeonResult; - } - } - - /** - * Pigeon version of PurchasesHistoryResult, which contains the components of the Java - * PurchaseHistoryResponseListener callback. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformPurchaseHistoryResponse { - private @NonNull PlatformBillingResult billingResult; - - public @NonNull PlatformBillingResult getBillingResult() { - return billingResult; - } - - public void setBillingResult(@NonNull PlatformBillingResult setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"billingResult\" is null."); - } - this.billingResult = setterArg; - } - - private @NonNull List purchases; - - public @NonNull List getPurchases() { - return purchases; - } - - public void setPurchases(@NonNull List setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"purchases\" is null."); - } - this.purchases = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - PlatformPurchaseHistoryResponse() {} - - public static final class Builder { - - private @Nullable PlatformBillingResult billingResult; - - @CanIgnoreReturnValue - public @NonNull Builder setBillingResult(@NonNull PlatformBillingResult setterArg) { - this.billingResult = setterArg; - return this; - } - - private @Nullable List purchases; - - @CanIgnoreReturnValue - public @NonNull Builder setPurchases(@NonNull List setterArg) { - this.purchases = setterArg; - return this; - } - - public @NonNull PlatformPurchaseHistoryResponse build() { - PlatformPurchaseHistoryResponse pigeonReturn = new PlatformPurchaseHistoryResponse(); - pigeonReturn.setBillingResult(billingResult); - pigeonReturn.setPurchases(purchases); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(2); - toListResult.add((billingResult == null) ? null : billingResult.toList()); - toListResult.add(purchases); - return toListResult; - } - - static @NonNull PlatformPurchaseHistoryResponse fromList(@NonNull ArrayList list) { - PlatformPurchaseHistoryResponse pigeonResult = new PlatformPurchaseHistoryResponse(); - Object billingResult = list.get(0); - pigeonResult.setBillingResult( - (billingResult == null) - ? null - : PlatformBillingResult.fromList((ArrayList) billingResult)); - Object purchases = list.get(1); - pigeonResult.setPurchases((List) purchases); - return pigeonResult; - } - } - - /** - * Pigeon version of PurchasesResultWrapper, which contains the components of the Java - * PurchasesResponseListener callback. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformPurchasesResponse { - private @NonNull PlatformBillingResult billingResult; - - public @NonNull PlatformBillingResult getBillingResult() { - return billingResult; - } - - public void setBillingResult(@NonNull PlatformBillingResult setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"billingResult\" is null."); - } - this.billingResult = setterArg; - } - - private @NonNull List purchases; - - public @NonNull List getPurchases() { - return purchases; - } - - public void setPurchases(@NonNull List setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"purchases\" is null."); - } - this.purchases = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - PlatformPurchasesResponse() {} - - public static final class Builder { - - private @Nullable PlatformBillingResult billingResult; - - @CanIgnoreReturnValue - public @NonNull Builder setBillingResult(@NonNull PlatformBillingResult setterArg) { - this.billingResult = setterArg; - return this; - } - - private @Nullable List purchases; - - @CanIgnoreReturnValue - public @NonNull Builder setPurchases(@NonNull List setterArg) { - this.purchases = setterArg; - return this; - } - - public @NonNull PlatformPurchasesResponse build() { - PlatformPurchasesResponse pigeonReturn = new PlatformPurchasesResponse(); - pigeonReturn.setBillingResult(billingResult); - pigeonReturn.setPurchases(purchases); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(2); - toListResult.add((billingResult == null) ? null : billingResult.toList()); - toListResult.add(purchases); - return toListResult; - } - - static @NonNull PlatformPurchasesResponse fromList(@NonNull ArrayList list) { - PlatformPurchasesResponse pigeonResult = new PlatformPurchasesResponse(); - Object billingResult = list.get(0); - pigeonResult.setBillingResult( - (billingResult == null) - ? null - : PlatformBillingResult.fromList((ArrayList) billingResult)); - Object purchases = list.get(1); - pigeonResult.setPurchases((List) purchases); - return pigeonResult; - } - } - - /** - * Pigeon version of Java ProductDetails.SubscriptionOfferDetails. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformSubscriptionOfferDetails { - private @NonNull String basePlanId; - - public @NonNull String getBasePlanId() { - return basePlanId; - } - - public void setBasePlanId(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"basePlanId\" is null."); - } - this.basePlanId = setterArg; - } - - private @Nullable String offerId; - - public @Nullable String getOfferId() { - return offerId; - } - - public void setOfferId(@Nullable String setterArg) { - this.offerId = setterArg; - } - - private @NonNull String offerToken; - - public @NonNull String getOfferToken() { - return offerToken; - } - - public void setOfferToken(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"offerToken\" is null."); - } - this.offerToken = setterArg; - } - - private @NonNull List offerTags; - - public @NonNull List getOfferTags() { - return offerTags; - } - - public void setOfferTags(@NonNull List setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"offerTags\" is null."); - } - this.offerTags = setterArg; - } - - private @NonNull List pricingPhases; - - public @NonNull List getPricingPhases() { - return pricingPhases; - } - - public void setPricingPhases(@NonNull List setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"pricingPhases\" is null."); - } - this.pricingPhases = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - PlatformSubscriptionOfferDetails() {} - - public static final class Builder { - - private @Nullable String basePlanId; - - @CanIgnoreReturnValue - public @NonNull Builder setBasePlanId(@NonNull String setterArg) { - this.basePlanId = setterArg; - return this; - } - - private @Nullable String offerId; - - @CanIgnoreReturnValue - public @NonNull Builder setOfferId(@Nullable String setterArg) { - this.offerId = setterArg; - return this; - } - - private @Nullable String offerToken; - - @CanIgnoreReturnValue - public @NonNull Builder setOfferToken(@NonNull String setterArg) { - this.offerToken = setterArg; - return this; - } - - private @Nullable List offerTags; - - @CanIgnoreReturnValue - public @NonNull Builder setOfferTags(@NonNull List setterArg) { - this.offerTags = setterArg; - return this; - } - - private @Nullable List pricingPhases; - - @CanIgnoreReturnValue - public @NonNull Builder setPricingPhases(@NonNull List setterArg) { - this.pricingPhases = setterArg; - return this; - } - - public @NonNull PlatformSubscriptionOfferDetails build() { - PlatformSubscriptionOfferDetails pigeonReturn = new PlatformSubscriptionOfferDetails(); - pigeonReturn.setBasePlanId(basePlanId); - pigeonReturn.setOfferId(offerId); - pigeonReturn.setOfferToken(offerToken); - pigeonReturn.setOfferTags(offerTags); - pigeonReturn.setPricingPhases(pricingPhases); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(5); - toListResult.add(basePlanId); - toListResult.add(offerId); - toListResult.add(offerToken); - toListResult.add(offerTags); - toListResult.add(pricingPhases); - return toListResult; - } - - static @NonNull PlatformSubscriptionOfferDetails fromList(@NonNull ArrayList list) { - PlatformSubscriptionOfferDetails pigeonResult = new PlatformSubscriptionOfferDetails(); - Object basePlanId = list.get(0); - pigeonResult.setBasePlanId((String) basePlanId); - Object offerId = list.get(1); - pigeonResult.setOfferId((String) offerId); - Object offerToken = list.get(2); - pigeonResult.setOfferToken((String) offerToken); - Object offerTags = list.get(3); - pigeonResult.setOfferTags((List) offerTags); - Object pricingPhases = list.get(4); - pigeonResult.setPricingPhases((List) pricingPhases); - return pigeonResult; - } - } - - /** - * Pigeon version of UserChoiceDetailsWrapper and Java UserChoiceDetails. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformUserChoiceDetails { - private @Nullable String originalExternalTransactionId; - - public @Nullable String getOriginalExternalTransactionId() { - return originalExternalTransactionId; - } - - public void setOriginalExternalTransactionId(@Nullable String setterArg) { - this.originalExternalTransactionId = setterArg; - } - - private @NonNull String externalTransactionToken; - - public @NonNull String getExternalTransactionToken() { - return externalTransactionToken; - } - - public void setExternalTransactionToken(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"externalTransactionToken\" is null."); - } - this.externalTransactionToken = setterArg; - } - - private @NonNull List products; - - public @NonNull List getProducts() { - return products; - } - - public void setProducts(@NonNull List setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"products\" is null."); - } - this.products = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - PlatformUserChoiceDetails() {} - - public static final class Builder { - - private @Nullable String originalExternalTransactionId; - - @CanIgnoreReturnValue - public @NonNull Builder setOriginalExternalTransactionId(@Nullable String setterArg) { - this.originalExternalTransactionId = setterArg; - return this; - } - - private @Nullable String externalTransactionToken; - - @CanIgnoreReturnValue - public @NonNull Builder setExternalTransactionToken(@NonNull String setterArg) { - this.externalTransactionToken = setterArg; - return this; - } - - private @Nullable List products; - - @CanIgnoreReturnValue - public @NonNull Builder setProducts(@NonNull List setterArg) { - this.products = setterArg; - return this; - } - - public @NonNull PlatformUserChoiceDetails build() { - PlatformUserChoiceDetails pigeonReturn = new PlatformUserChoiceDetails(); - pigeonReturn.setOriginalExternalTransactionId(originalExternalTransactionId); - pigeonReturn.setExternalTransactionToken(externalTransactionToken); - pigeonReturn.setProducts(products); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(3); - toListResult.add(originalExternalTransactionId); - toListResult.add(externalTransactionToken); - toListResult.add(products); - return toListResult; - } - - static @NonNull PlatformUserChoiceDetails fromList(@NonNull ArrayList list) { - PlatformUserChoiceDetails pigeonResult = new PlatformUserChoiceDetails(); - Object originalExternalTransactionId = list.get(0); - pigeonResult.setOriginalExternalTransactionId((String) originalExternalTransactionId); - Object externalTransactionToken = list.get(1); - pigeonResult.setExternalTransactionToken((String) externalTransactionToken); - Object products = list.get(2); - pigeonResult.setProducts((List) products); - return pigeonResult; - } - } - - /** - * Pigeon version of UserChoiseDetails.Product. - * - *

Generated class from Pigeon that represents data sent in messages. - */ - public static final class PlatformUserChoiceProduct { - private @NonNull String id; - - public @NonNull String getId() { - return id; - } - - public void setId(@NonNull String setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"id\" is null."); - } - this.id = setterArg; - } - - private @Nullable String offerToken; - - public @Nullable String getOfferToken() { - return offerToken; - } - - public void setOfferToken(@Nullable String setterArg) { - this.offerToken = setterArg; - } - - private @NonNull PlatformProductType type; - - public @NonNull PlatformProductType getType() { - return type; - } - - public void setType(@NonNull PlatformProductType setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"type\" is null."); - } - this.type = setterArg; - } - - /** Constructor is non-public to enforce null safety; use Builder. */ - PlatformUserChoiceProduct() {} - - public static final class Builder { - - private @Nullable String id; - - @CanIgnoreReturnValue - public @NonNull Builder setId(@NonNull String setterArg) { - this.id = setterArg; - return this; - } - - private @Nullable String offerToken; - - @CanIgnoreReturnValue - public @NonNull Builder setOfferToken(@Nullable String setterArg) { - this.offerToken = setterArg; - return this; - } - - private @Nullable PlatformProductType type; - - @CanIgnoreReturnValue - public @NonNull Builder setType(@NonNull PlatformProductType setterArg) { - this.type = setterArg; - return this; - } - - public @NonNull PlatformUserChoiceProduct build() { - PlatformUserChoiceProduct pigeonReturn = new PlatformUserChoiceProduct(); - pigeonReturn.setId(id); - pigeonReturn.setOfferToken(offerToken); - pigeonReturn.setType(type); - return pigeonReturn; - } - } - - @NonNull - ArrayList toList() { - ArrayList toListResult = new ArrayList(3); - toListResult.add(id); - toListResult.add(offerToken); - toListResult.add(type == null ? null : type.index); - return toListResult; - } - - static @NonNull PlatformUserChoiceProduct fromList(@NonNull ArrayList list) { - PlatformUserChoiceProduct pigeonResult = new PlatformUserChoiceProduct(); - Object id = list.get(0); - pigeonResult.setId((String) id); - Object offerToken = list.get(1); - pigeonResult.setOfferToken((String) offerToken); - Object type = list.get(2); - pigeonResult.setType(PlatformProductType.values()[(int) type]); - return pigeonResult; - } - } - - /** Asynchronous error handling return type for non-nullable API method returns. */ - public interface Result { - /** Success case callback method for handling returns. */ - void success(@NonNull T result); - - /** Failure case callback method for handling errors. */ - void error(@NonNull Throwable error); - } - /** Asynchronous error handling return type for nullable API method returns. */ - public interface NullableResult { - /** Success case callback method for handling returns. */ - void success(@Nullable T result); - - /** Failure case callback method for handling errors. */ - void error(@NonNull Throwable error); - } - /** Asynchronous error handling return type for void API method returns. */ - public interface VoidResult { - /** Success case callback method for handling returns. */ - void success(); - - /** Failure case callback method for handling errors. */ - void error(@NonNull Throwable error); - } - - private static class InAppPurchaseApiCodec extends StandardMessageCodec { - public static final InAppPurchaseApiCodec INSTANCE = new InAppPurchaseApiCodec(); - - private InAppPurchaseApiCodec() {} - - @Override - protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { - switch (type) { - case (byte) 128: - return PlatformAccountIdentifiers.fromList((ArrayList) readValue(buffer)); - case (byte) 129: - return PlatformAlternativeBillingOnlyReportingDetailsResponse.fromList( - (ArrayList) readValue(buffer)); - case (byte) 130: - return PlatformBillingConfigResponse.fromList((ArrayList) readValue(buffer)); - case (byte) 131: - return PlatformBillingFlowParams.fromList((ArrayList) readValue(buffer)); - case (byte) 132: - return PlatformBillingResult.fromList((ArrayList) readValue(buffer)); - case (byte) 133: - return PlatformOneTimePurchaseOfferDetails.fromList( - (ArrayList) readValue(buffer)); - case (byte) 134: - return PlatformPricingPhase.fromList((ArrayList) readValue(buffer)); - case (byte) 135: - return PlatformProductDetails.fromList((ArrayList) readValue(buffer)); - case (byte) 136: - return PlatformProductDetailsResponse.fromList((ArrayList) readValue(buffer)); - case (byte) 137: - return PlatformPurchase.fromList((ArrayList) readValue(buffer)); - case (byte) 138: - return PlatformPurchaseHistoryRecord.fromList((ArrayList) readValue(buffer)); - case (byte) 139: - return PlatformPurchaseHistoryResponse.fromList((ArrayList) readValue(buffer)); - case (byte) 140: - return PlatformPurchasesResponse.fromList((ArrayList) readValue(buffer)); - case (byte) 141: - return PlatformQueryProduct.fromList((ArrayList) readValue(buffer)); - case (byte) 142: - return PlatformSubscriptionOfferDetails.fromList((ArrayList) readValue(buffer)); - default: - return super.readValueOfType(type, buffer); - } - } - - @Override - protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { - if (value instanceof PlatformAccountIdentifiers) { - stream.write(128); - writeValue(stream, ((PlatformAccountIdentifiers) value).toList()); - } else if (value instanceof PlatformAlternativeBillingOnlyReportingDetailsResponse) { - stream.write(129); - writeValue( - stream, ((PlatformAlternativeBillingOnlyReportingDetailsResponse) value).toList()); - } else if (value instanceof PlatformBillingConfigResponse) { - stream.write(130); - writeValue(stream, ((PlatformBillingConfigResponse) value).toList()); - } else if (value instanceof PlatformBillingFlowParams) { - stream.write(131); - writeValue(stream, ((PlatformBillingFlowParams) value).toList()); - } else if (value instanceof PlatformBillingResult) { - stream.write(132); - writeValue(stream, ((PlatformBillingResult) value).toList()); - } else if (value instanceof PlatformOneTimePurchaseOfferDetails) { - stream.write(133); - writeValue(stream, ((PlatformOneTimePurchaseOfferDetails) value).toList()); - } else if (value instanceof PlatformPricingPhase) { - stream.write(134); - writeValue(stream, ((PlatformPricingPhase) value).toList()); - } else if (value instanceof PlatformProductDetails) { - stream.write(135); - writeValue(stream, ((PlatformProductDetails) value).toList()); - } else if (value instanceof PlatformProductDetailsResponse) { - stream.write(136); - writeValue(stream, ((PlatformProductDetailsResponse) value).toList()); - } else if (value instanceof PlatformPurchase) { - stream.write(137); - writeValue(stream, ((PlatformPurchase) value).toList()); - } else if (value instanceof PlatformPurchaseHistoryRecord) { - stream.write(138); - writeValue(stream, ((PlatformPurchaseHistoryRecord) value).toList()); - } else if (value instanceof PlatformPurchaseHistoryResponse) { - stream.write(139); - writeValue(stream, ((PlatformPurchaseHistoryResponse) value).toList()); - } else if (value instanceof PlatformPurchasesResponse) { - stream.write(140); - writeValue(stream, ((PlatformPurchasesResponse) value).toList()); - } else if (value instanceof PlatformQueryProduct) { - stream.write(141); - writeValue(stream, ((PlatformQueryProduct) value).toList()); - } else if (value instanceof PlatformSubscriptionOfferDetails) { - stream.write(142); - writeValue(stream, ((PlatformSubscriptionOfferDetails) value).toList()); - } else { - super.writeValue(stream, value); - } - } - } - - /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ - public interface InAppPurchaseApi { - /** Wraps BillingClient#isReady. */ - @NonNull - Boolean isReady(); - /** Wraps BillingClient#startConnection(BillingClientStateListener). */ - void startConnection( - @NonNull Long callbackHandle, - @NonNull PlatformBillingChoiceMode billingMode, - @NonNull Result result); - /** Wraps BillingClient#endConnection(BillingClientStateListener). */ - void endConnection(); - /** - * Wraps BillingClient#getBillingConfigAsync(GetBillingConfigParams, - * BillingConfigResponseListener). - */ - void getBillingConfigAsync(@NonNull Result result); - /** Wraps BillingClient#launchBillingFlow(Activity, BillingFlowParams). */ - @NonNull - PlatformBillingResult launchBillingFlow(@NonNull PlatformBillingFlowParams params); - /** - * Wraps BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, - * AcknowledgePurchaseResponseListener). - */ - void acknowledgePurchase( - @NonNull String purchaseToken, @NonNull Result result); - /** Wraps BillingClient#consumeAsync(ConsumeParams, ConsumeResponseListener). */ - void consumeAsync(@NonNull String purchaseToken, @NonNull Result result); - /** Wraps BillingClient#queryPurchasesAsync(QueryPurchaseParams, PurchaseResponseListener). */ - void queryPurchasesAsync( - @NonNull PlatformProductType productType, - @NonNull Result result); - /** - * Wraps BillingClient#queryPurchaseHistoryAsync(QueryPurchaseHistoryParams, - * PurchaseHistoryResponseListener). - */ - void queryPurchaseHistoryAsync( - @NonNull PlatformProductType productType, - @NonNull Result result); - /** - * Wraps BillingClient#queryProductDetailsAsync(QueryProductDetailsParams, - * ProductDetailsResponseListener). - */ - void queryProductDetailsAsync( - @NonNull List products, - @NonNull Result result); - /** Wraps BillingClient#isFeatureSupported(String). */ - @NonNull - Boolean isFeatureSupported(@NonNull String feature); - /** Wraps BillingClient#isAlternativeBillingOnlyAvailableAsync(). */ - void isAlternativeBillingOnlyAvailableAsync(@NonNull Result result); - /** Wraps BillingClient#showAlternativeBillingOnlyInformationDialog(). */ - void showAlternativeBillingOnlyInformationDialog(@NonNull Result result); - /** - * Wraps - * BillingClient#createAlternativeBillingOnlyReportingDetailsAsync(AlternativeBillingOnlyReportingDetailsListener). - */ - void createAlternativeBillingOnlyReportingDetailsAsync( - @NonNull Result result); - - /** The codec used by InAppPurchaseApi. */ - static @NonNull MessageCodec getCodec() { - return InAppPurchaseApiCodec.INSTANCE; - } - /** - * Sets up an instance of `InAppPurchaseApi` to handle messages through the `binaryMessenger`. - */ - static void setUp(@NonNull BinaryMessenger binaryMessenger, @Nullable InAppPurchaseApi api) { - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.isReady", - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList(); - try { - Boolean output = api.isReady(); - wrapped.add(0, output); - } catch (Throwable exception) { - ArrayList wrappedError = wrapError(exception); - wrapped = wrappedError; - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.startConnection", - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList(); - ArrayList args = (ArrayList) message; - Number callbackHandleArg = (Number) args.get(0); - PlatformBillingChoiceMode billingModeArg = - PlatformBillingChoiceMode.values()[(int) args.get(1)]; - Result resultCallback = - new Result() { - public void success(PlatformBillingResult result) { - wrapped.add(0, result); - reply.reply(wrapped); - } - - public void error(Throwable error) { - ArrayList wrappedError = wrapError(error); - reply.reply(wrappedError); - } - }; - - api.startConnection( - (callbackHandleArg == null) ? null : callbackHandleArg.longValue(), - billingModeArg, - resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.endConnection", - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList(); - try { - api.endConnection(); - wrapped.add(0, null); - } catch (Throwable exception) { - ArrayList wrappedError = wrapError(exception); - wrapped = wrappedError; - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.getBillingConfigAsync", - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList(); - Result resultCallback = - new Result() { - public void success(PlatformBillingConfigResponse result) { - wrapped.add(0, result); - reply.reply(wrapped); - } - - public void error(Throwable error) { - ArrayList wrappedError = wrapError(error); - reply.reply(wrappedError); - } - }; - - api.getBillingConfigAsync(resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.launchBillingFlow", - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList(); - ArrayList args = (ArrayList) message; - PlatformBillingFlowParams paramsArg = (PlatformBillingFlowParams) args.get(0); - try { - PlatformBillingResult output = api.launchBillingFlow(paramsArg); - wrapped.add(0, output); - } catch (Throwable exception) { - ArrayList wrappedError = wrapError(exception); - wrapped = wrappedError; - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.acknowledgePurchase", - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList(); - ArrayList args = (ArrayList) message; - String purchaseTokenArg = (String) args.get(0); - Result resultCallback = - new Result() { - public void success(PlatformBillingResult result) { - wrapped.add(0, result); - reply.reply(wrapped); - } - - public void error(Throwable error) { - ArrayList wrappedError = wrapError(error); - reply.reply(wrappedError); - } - }; - - api.acknowledgePurchase(purchaseTokenArg, resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.consumeAsync", - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList(); - ArrayList args = (ArrayList) message; - String purchaseTokenArg = (String) args.get(0); - Result resultCallback = - new Result() { - public void success(PlatformBillingResult result) { - wrapped.add(0, result); - reply.reply(wrapped); - } - - public void error(Throwable error) { - ArrayList wrappedError = wrapError(error); - reply.reply(wrappedError); - } - }; - - api.consumeAsync(purchaseTokenArg, resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.queryPurchasesAsync", - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList(); - ArrayList args = (ArrayList) message; - PlatformProductType productTypeArg = - PlatformProductType.values()[(int) args.get(0)]; - Result resultCallback = - new Result() { - public void success(PlatformPurchasesResponse result) { - wrapped.add(0, result); - reply.reply(wrapped); - } - - public void error(Throwable error) { - ArrayList wrappedError = wrapError(error); - reply.reply(wrappedError); - } - }; - - api.queryPurchasesAsync(productTypeArg, resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.queryPurchaseHistoryAsync", - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList(); - ArrayList args = (ArrayList) message; - PlatformProductType productTypeArg = - PlatformProductType.values()[(int) args.get(0)]; - Result resultCallback = - new Result() { - public void success(PlatformPurchaseHistoryResponse result) { - wrapped.add(0, result); - reply.reply(wrapped); - } - - public void error(Throwable error) { - ArrayList wrappedError = wrapError(error); - reply.reply(wrappedError); - } - }; - - api.queryPurchaseHistoryAsync(productTypeArg, resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.queryProductDetailsAsync", - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList(); - ArrayList args = (ArrayList) message; - List productsArg = (List) args.get(0); - Result resultCallback = - new Result() { - public void success(PlatformProductDetailsResponse result) { - wrapped.add(0, result); - reply.reply(wrapped); - } - - public void error(Throwable error) { - ArrayList wrappedError = wrapError(error); - reply.reply(wrappedError); - } - }; - - api.queryProductDetailsAsync(productsArg, resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.isFeatureSupported", - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList(); - ArrayList args = (ArrayList) message; - String featureArg = (String) args.get(0); - try { - Boolean output = api.isFeatureSupported(featureArg); - wrapped.add(0, output); - } catch (Throwable exception) { - ArrayList wrappedError = wrapError(exception); - wrapped = wrappedError; - } - reply.reply(wrapped); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.isAlternativeBillingOnlyAvailableAsync", - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList(); - Result resultCallback = - new Result() { - public void success(PlatformBillingResult result) { - wrapped.add(0, result); - reply.reply(wrapped); - } - - public void error(Throwable error) { - ArrayList wrappedError = wrapError(error); - reply.reply(wrappedError); - } - }; - - api.isAlternativeBillingOnlyAvailableAsync(resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.showAlternativeBillingOnlyInformationDialog", - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList(); - Result resultCallback = - new Result() { - public void success(PlatformBillingResult result) { - wrapped.add(0, result); - reply.reply(wrapped); - } - - public void error(Throwable error) { - ArrayList wrappedError = wrapError(error); - reply.reply(wrappedError); - } - }; - - api.showAlternativeBillingOnlyInformationDialog(resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - { - BasicMessageChannel channel = - new BasicMessageChannel<>( - binaryMessenger, - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.createAlternativeBillingOnlyReportingDetailsAsync", - getCodec()); - if (api != null) { - channel.setMessageHandler( - (message, reply) -> { - ArrayList wrapped = new ArrayList(); - Result resultCallback = - new Result() { - public void success( - PlatformAlternativeBillingOnlyReportingDetailsResponse result) { - wrapped.add(0, result); - reply.reply(wrapped); - } - - public void error(Throwable error) { - ArrayList wrappedError = wrapError(error); - reply.reply(wrappedError); - } - }; - - api.createAlternativeBillingOnlyReportingDetailsAsync(resultCallback); - }); - } else { - channel.setMessageHandler(null); - } - } - } - } - - private static class InAppPurchaseCallbackApiCodec extends StandardMessageCodec { - public static final InAppPurchaseCallbackApiCodec INSTANCE = - new InAppPurchaseCallbackApiCodec(); - - private InAppPurchaseCallbackApiCodec() {} - - @Override - protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { - switch (type) { - case (byte) 128: - return PlatformAccountIdentifiers.fromList((ArrayList) readValue(buffer)); - case (byte) 129: - return PlatformBillingResult.fromList((ArrayList) readValue(buffer)); - case (byte) 130: - return PlatformPurchase.fromList((ArrayList) readValue(buffer)); - case (byte) 131: - return PlatformPurchasesResponse.fromList((ArrayList) readValue(buffer)); - case (byte) 132: - return PlatformUserChoiceDetails.fromList((ArrayList) readValue(buffer)); - case (byte) 133: - return PlatformUserChoiceProduct.fromList((ArrayList) readValue(buffer)); - default: - return super.readValueOfType(type, buffer); - } - } - - @Override - protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { - if (value instanceof PlatformAccountIdentifiers) { - stream.write(128); - writeValue(stream, ((PlatformAccountIdentifiers) value).toList()); - } else if (value instanceof PlatformBillingResult) { - stream.write(129); - writeValue(stream, ((PlatformBillingResult) value).toList()); - } else if (value instanceof PlatformPurchase) { - stream.write(130); - writeValue(stream, ((PlatformPurchase) value).toList()); - } else if (value instanceof PlatformPurchasesResponse) { - stream.write(131); - writeValue(stream, ((PlatformPurchasesResponse) value).toList()); - } else if (value instanceof PlatformUserChoiceDetails) { - stream.write(132); - writeValue(stream, ((PlatformUserChoiceDetails) value).toList()); - } else if (value instanceof PlatformUserChoiceProduct) { - stream.write(133); - writeValue(stream, ((PlatformUserChoiceProduct) value).toList()); - } else { - super.writeValue(stream, value); - } - } - } - - /** Generated class from Pigeon that represents Flutter messages that can be called from Java. */ - public static class InAppPurchaseCallbackApi { - private final @NonNull BinaryMessenger binaryMessenger; - - public InAppPurchaseCallbackApi(@NonNull BinaryMessenger argBinaryMessenger) { - this.binaryMessenger = argBinaryMessenger; - } - - /** Public interface for sending reply. */ - /** The codec used by InAppPurchaseCallbackApi. */ - static @NonNull MessageCodec getCodec() { - return InAppPurchaseCallbackApiCodec.INSTANCE; - } - /** Called for BillingClientStateListener#onBillingServiceDisconnected(). */ - public void onBillingServiceDisconnected( - @NonNull Long callbackHandleArg, @NonNull VoidResult result) { - final String channelName = - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseCallbackApi.onBillingServiceDisconnected"; - BasicMessageChannel channel = - new BasicMessageChannel<>(binaryMessenger, channelName, getCodec()); - channel.send( - new ArrayList(Collections.singletonList(callbackHandleArg)), - channelReply -> { - if (channelReply instanceof List) { - List listReply = (List) channelReply; - if (listReply.size() > 1) { - result.error( - new FlutterError( - (String) listReply.get(0), - (String) listReply.get(1), - (String) listReply.get(2))); - } else { - result.success(); - } - } else { - result.error(createConnectionError(channelName)); - } - }); - } - /** Called for PurchasesUpdatedListener#onPurchasesUpdated(BillingResult, List). */ - public void onPurchasesUpdated( - @NonNull PlatformPurchasesResponse updateArg, @NonNull VoidResult result) { - final String channelName = - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseCallbackApi.onPurchasesUpdated"; - BasicMessageChannel channel = - new BasicMessageChannel<>(binaryMessenger, channelName, getCodec()); - channel.send( - new ArrayList(Collections.singletonList(updateArg)), - channelReply -> { - if (channelReply instanceof List) { - List listReply = (List) channelReply; - if (listReply.size() > 1) { - result.error( - new FlutterError( - (String) listReply.get(0), - (String) listReply.get(1), - (String) listReply.get(2))); - } else { - result.success(); - } - } else { - result.error(createConnectionError(channelName)); - } - }); - } - /** Called for UserChoiceBillingListener#userSelectedAlternativeBilling(UserChoiceDetails). */ - public void userSelectedalternativeBilling( - @NonNull PlatformUserChoiceDetails detailsArg, @NonNull VoidResult result) { - final String channelName = - "dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseCallbackApi.userSelectedalternativeBilling"; - BasicMessageChannel channel = - new BasicMessageChannel<>(binaryMessenger, channelName, getCodec()); - channel.send( - new ArrayList(Collections.singletonList(detailsArg)), - channelReply -> { - if (channelReply instanceof List) { - List listReply = (List) channelReply; - if (listReply.size() > 1) { - result.error( - new FlutterError( - (String) listReply.get(0), - (String) listReply.get(1), - (String) listReply.get(2))); - } else { - result.success(); - } - } else { - result.error(createConnectionError(channelName)); - } - }); - } - } -} diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java index 11ea6da21..514b2675a 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java @@ -4,14 +4,11 @@ package io.flutter.plugins.inapppurchase; -import static io.flutter.plugins.inapppurchase.Translator.fromAlternativeBillingOnlyReportingDetails; -import static io.flutter.plugins.inapppurchase.Translator.fromBillingConfig; import static io.flutter.plugins.inapppurchase.Translator.fromBillingResult; import static io.flutter.plugins.inapppurchase.Translator.fromProductDetailsList; import static io.flutter.plugins.inapppurchase.Translator.fromPurchaseHistoryRecordList; import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList; import static io.flutter.plugins.inapppurchase.Translator.toProductList; -import static io.flutter.plugins.inapppurchase.Translator.toProductTypeString; import android.app.Activity; import android.app.Application; @@ -28,49 +25,65 @@ import com.android.billingclient.api.BillingFlowParams; import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.ConsumeParams; import com.android.billingclient.api.ConsumeResponseListener; -import com.android.billingclient.api.GetBillingConfigParams; import com.android.billingclient.api.ProductDetails; import com.android.billingclient.api.QueryProductDetailsParams; +import com.android.billingclient.api.QueryProductDetailsParams.Product; import com.android.billingclient.api.QueryPurchaseHistoryParams; import com.android.billingclient.api.QueryPurchasesParams; -import io.flutter.plugins.inapppurchase.Messages.FlutterError; -import io.flutter.plugins.inapppurchase.Messages.InAppPurchaseApi; -import io.flutter.plugins.inapppurchase.Messages.InAppPurchaseCallbackApi; -import io.flutter.plugins.inapppurchase.Messages.PlatformBillingChoiceMode; -import io.flutter.plugins.inapppurchase.Messages.PlatformBillingFlowParams; -import io.flutter.plugins.inapppurchase.Messages.PlatformBillingResult; -import io.flutter.plugins.inapppurchase.Messages.PlatformProductDetailsResponse; -import io.flutter.plugins.inapppurchase.Messages.PlatformProductType; -import io.flutter.plugins.inapppurchase.Messages.PlatformPurchaseHistoryResponse; -import io.flutter.plugins.inapppurchase.Messages.PlatformPurchasesResponse; -import io.flutter.plugins.inapppurchase.Messages.PlatformQueryProduct; -import io.flutter.plugins.inapppurchase.Messages.Result; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; /** Handles method channel for the plugin. */ -class MethodCallHandlerImpl implements Application.ActivityLifecycleCallbacks, InAppPurchaseApi { +class MethodCallHandlerImpl + implements MethodChannel.MethodCallHandler, Application.ActivityLifecycleCallbacks { + + @VisibleForTesting + static final class MethodNames { + static final String IS_READY = "BillingClient#isReady()"; + static final String START_CONNECTION = + "BillingClient#startConnection(BillingClientStateListener)"; + static final String END_CONNECTION = "BillingClient#endConnection()"; + static final String ON_DISCONNECT = "BillingClientStateListener#onBillingServiceDisconnected()"; + static final String QUERY_PRODUCT_DETAILS = + "BillingClient#queryProductDetailsAsync(QueryProductDetailsParams, ProductDetailsResponseListener)"; + static final String LAUNCH_BILLING_FLOW = + "BillingClient#launchBillingFlow(Activity, BillingFlowParams)"; + static final String QUERY_PURCHASES_ASYNC = + "BillingClient#queryPurchasesAsync(QueryPurchaseParams, PurchaseResponseListener)"; + static final String QUERY_PURCHASE_HISTORY_ASYNC = + "BillingClient#queryPurchaseHistoryAsync(QueryPurchaseHistoryParams, PurchaseHistoryResponseListener)"; + static final String CONSUME_PURCHASE_ASYNC = + "BillingClient#consumeAsync(ConsumeParams, ConsumeResponseListener)"; + static final String ACKNOWLEDGE_PURCHASE = + "BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)"; + static final String IS_FEATURE_SUPPORTED = "BillingClient#isFeatureSupported(String)"; + static final String GET_CONNECTION_STATE = "BillingClient#getConnectionState()"; + + private MethodNames() {} + } + // TODO(gmackall): Replace uses of deprecated ProrationMode enum values with new // ReplacementMode enum values. // https://github.com/flutter/flutter/issues/128957. @SuppressWarnings(value = "deprecation") - @VisibleForTesting - static final int PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY = + private static final int PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY = com.android.billingclient.api.BillingFlowParams.ProrationMode .UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY; private static final String TAG = "InAppPurchasePlugin"; private static final String LOAD_PRODUCT_DOC_URL = "https://github.com/flutter/packages/blob/main/packages/in_app_purchase/in_app_purchase/README.md#loading-products-for-sale"; - @VisibleForTesting static final String ACTIVITY_UNAVAILABLE = "ACTIVITY_UNAVAILABLE"; @Nullable private BillingClient billingClient; private final BillingClientFactory billingClientFactory; @Nullable private Activity activity; private final Context applicationContext; - final InAppPurchaseCallbackApi callbackApi; + final MethodChannel methodChannel; private final HashMap cachedProducts = new HashMap<>(); @@ -78,12 +91,12 @@ class MethodCallHandlerImpl implements Application.ActivityLifecycleCallbacks, I MethodCallHandlerImpl( @Nullable Activity activity, @NonNull Context applicationContext, - @NonNull InAppPurchaseCallbackApi callbackApi, + @NonNull MethodChannel methodChannel, @NonNull BillingClientFactory billingClientFactory) { this.billingClientFactory = billingClientFactory; this.applicationContext = applicationContext; this.activity = activity; - this.callbackApi = callbackApi; + this.methodChannel = methodChannel; } /** @@ -95,22 +108,22 @@ class MethodCallHandlerImpl implements Application.ActivityLifecycleCallbacks, I } @Override - public void onActivityCreated(@NonNull Activity activity, Bundle savedInstanceState) {} + public void onActivityCreated(Activity activity, Bundle savedInstanceState) {} @Override - public void onActivityStarted(@NonNull Activity activity) {} + public void onActivityStarted(Activity activity) {} @Override - public void onActivityResumed(@NonNull Activity activity) {} + public void onActivityResumed(Activity activity) {} @Override - public void onActivityPaused(@NonNull Activity activity) {} + public void onActivityPaused(Activity activity) {} @Override - public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {} + public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} @Override - public void onActivityDestroyed(@NonNull Activity activity) { + public void onActivityDestroyed(Activity activity) { if (this.activity == activity && this.applicationContext != null) { ((Application) this.applicationContext).unregisterActivityLifecycleCallbacks(this); endBillingClientConnection(); @@ -118,84 +131,67 @@ class MethodCallHandlerImpl implements Application.ActivityLifecycleCallbacks, I } @Override - public void onActivityStopped(@NonNull Activity activity) {} + public void onActivityStopped(Activity activity) {} void onDetachedFromActivity() { endBillingClientConnection(); } @Override - public void showAlternativeBillingOnlyInformationDialog( - @NonNull Result result) { - if (billingClient == null) { - result.error(getNullBillingClientError()); - return; - } - if (activity == null) { - result.error(new FlutterError(ACTIVITY_UNAVAILABLE, "Not attempting to show dialog", null)); - return; - } - try { - billingClient.showAlternativeBillingOnlyInformationDialog( - activity, billingResult -> result.success(fromBillingResult(billingResult))); - } catch (RuntimeException e) { - result.error(new FlutterError("error", e.getMessage(), Log.getStackTraceString(e))); - } - } - - @Override - public void createAlternativeBillingOnlyReportingDetailsAsync( - @NonNull Result result) { - if (billingClient == null) { - result.error(getNullBillingClientError()); - return; - } - try { - billingClient.createAlternativeBillingOnlyReportingDetailsAsync( - ((billingResult, alternativeBillingOnlyReportingDetails) -> - result.success( - fromAlternativeBillingOnlyReportingDetails( - billingResult, alternativeBillingOnlyReportingDetails)))); - } catch (RuntimeException e) { - result.error(new FlutterError("error", e.getMessage(), Log.getStackTraceString(e))); - } - } - - @Override - public void isAlternativeBillingOnlyAvailableAsync( - @NonNull Result result) { - if (billingClient == null) { - result.error(getNullBillingClientError()); - return; - } - try { - billingClient.isAlternativeBillingOnlyAvailableAsync( - billingResult -> result.success(fromBillingResult(billingResult))); - } catch (RuntimeException e) { - result.error(new FlutterError("error", e.getMessage(), Log.getStackTraceString(e))); - } - } - - @Override - public void getBillingConfigAsync( - @NonNull Result result) { - if (billingClient == null) { - result.error(getNullBillingClientError()); - return; - } - try { - billingClient.getBillingConfigAsync( - GetBillingConfigParams.newBuilder().build(), - (billingResult, billingConfig) -> - result.success(fromBillingConfig(billingResult, billingConfig))); - } catch (RuntimeException e) { - result.error(new FlutterError("error", e.getMessage(), Log.getStackTraceString(e))); + public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + switch (call.method) { + case MethodNames.IS_READY: + isReady(result); + break; + case MethodNames.START_CONNECTION: + startConnection((int) call.argument("handle"), result); + break; + case MethodNames.END_CONNECTION: + endConnection(result); + break; + case MethodNames.QUERY_PRODUCT_DETAILS: + List productList = toProductList(call.argument("productList")); + queryProductDetailsAsync(productList, result); + break; + case MethodNames.LAUNCH_BILLING_FLOW: + launchBillingFlow( + (String) call.argument("product"), + (String) call.argument("offerToken"), + (String) call.argument("accountId"), + (String) call.argument("obfuscatedProfileId"), + (String) call.argument("oldProduct"), + (String) call.argument("purchaseToken"), + call.hasArgument("prorationMode") + ? (int) call.argument("prorationMode") + : PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY, + result); + break; + case MethodNames.QUERY_PURCHASES_ASYNC: + queryPurchasesAsync((String) call.argument("productType"), result); + break; + case MethodNames.QUERY_PURCHASE_HISTORY_ASYNC: + queryPurchaseHistoryAsync((String) call.argument("productType"), result); + break; + case MethodNames.CONSUME_PURCHASE_ASYNC: + consumeAsync((String) call.argument("purchaseToken"), result); + break; + case MethodNames.ACKNOWLEDGE_PURCHASE: + acknowledgePurchase((String) call.argument("purchaseToken"), result); + break; + case MethodNames.IS_FEATURE_SUPPORTED: + isFeatureSupported((String) call.argument("feature"), result); + break; + case MethodNames.GET_CONNECTION_STATE: + getConnectionState(result); + break; + default: + result.notImplemented(); } } - @Override - public void endConnection() { + private void endConnection(final MethodChannel.Result result) { endBillingClientConnection(); + result.success(null); } private void endBillingClientConnection() { @@ -205,59 +201,57 @@ class MethodCallHandlerImpl implements Application.ActivityLifecycleCallbacks, I } } - @Override - @NonNull - public Boolean isReady() { - if (billingClient == null) { - throw getNullBillingClientError(); + private void isReady(MethodChannel.Result result) { + if (billingClientError(result)) { + return; } - return billingClient.isReady(); + + result.success(billingClient.isReady()); } - @Override - public void queryProductDetailsAsync( - @NonNull List products, - @NonNull Result result) { - if (billingClient == null) { - result.error(getNullBillingClientError()); + private void queryProductDetailsAsync( + final List productList, final MethodChannel.Result result) { + if (billingClientError(result)) { return; } - try { - QueryProductDetailsParams params = - QueryProductDetailsParams.newBuilder().setProductList(toProductList(products)).build(); - billingClient.queryProductDetailsAsync( - params, - (billingResult, productDetailsList) -> { - updateCachedProducts(productDetailsList); - final PlatformProductDetailsResponse.Builder responseBuilder = - new PlatformProductDetailsResponse.Builder() - .setBillingResult(fromBillingResult(billingResult)) - .setProductDetails(fromProductDetailsList(productDetailsList)); - result.success(responseBuilder.build()); - }); - } catch (RuntimeException e) { - result.error(new FlutterError("error", e.getMessage(), Log.getStackTraceString(e))); - } + QueryProductDetailsParams params = + QueryProductDetailsParams.newBuilder().setProductList(productList).build(); + billingClient.queryProductDetailsAsync( + params, + (billingResult, productDetailsList) -> { + updateCachedProducts(productDetailsList); + final Map productDetailsResponse = new HashMap<>(); + productDetailsResponse.put("billingResult", fromBillingResult(billingResult)); + productDetailsResponse.put( + "productDetailsList", fromProductDetailsList(productDetailsList)); + result.success(productDetailsResponse); + }); } - @Override - public @NonNull PlatformBillingResult launchBillingFlow( - @NonNull PlatformBillingFlowParams params) { - if (billingClient == null) { - throw getNullBillingClientError(); + private void launchBillingFlow( + String product, + @Nullable String offerToken, + @Nullable String accountId, + @Nullable String obfuscatedProfileId, + @Nullable String oldProduct, + @Nullable String purchaseToken, + int prorationMode, + MethodChannel.Result result) { + if (billingClientError(result)) { + return; } - com.android.billingclient.api.ProductDetails productDetails = - cachedProducts.get(params.getProduct()); + com.android.billingclient.api.ProductDetails productDetails = cachedProducts.get(product); if (productDetails == null) { - throw new FlutterError( + result.error( "NOT_FOUND", "Details for product " - + params.getProduct() + + product + " are not available. It might because products were not fetched prior to the call. Please fetch the products first. An example of how to fetch the products could be found here: " + LOAD_PRODUCT_DOC_URL, null); + return; } @Nullable @@ -266,57 +260,58 @@ class MethodCallHandlerImpl implements Application.ActivityLifecycleCallbacks, I if (subscriptionOfferDetails != null) { boolean isValidOfferToken = false; for (ProductDetails.SubscriptionOfferDetails offerDetails : subscriptionOfferDetails) { - if (params.getOfferToken() != null - && params.getOfferToken().equals(offerDetails.getOfferToken())) { + if (offerToken != null && offerToken.equals(offerDetails.getOfferToken())) { isValidOfferToken = true; break; } } if (!isValidOfferToken) { - throw new FlutterError( + result.error( "INVALID_OFFER_TOKEN", "Offer token " - + params.getOfferToken() + + offerToken + " for product " - + params.getProduct() + + product + " is not valid. Make sure to only pass offer tokens that belong to the product. To obtain offer tokens for a product, fetch the products. An example of how to fetch the products could be found here: " + LOAD_PRODUCT_DOC_URL, null); + return; } } - if (params.getOldProduct() == null - && params.getProrationMode() - != PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY) { - throw new FlutterError( + if (oldProduct == null + && prorationMode != PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY) { + result.error( "IN_APP_PURCHASE_REQUIRE_OLD_PRODUCT", "launchBillingFlow failed because oldProduct is null. You must provide a valid oldProduct in order to use a proration mode.", null); - } else if (params.getOldProduct() != null - && !cachedProducts.containsKey(params.getOldProduct())) { - throw new FlutterError( + return; + } else if (oldProduct != null && !cachedProducts.containsKey(oldProduct)) { + result.error( "IN_APP_PURCHASE_INVALID_OLD_PRODUCT", "Details for product " - + params.getOldProduct() + + oldProduct + " are not available. It might because products were not fetched prior to the call. Please fetch the products first. An example of how to fetch the products could be found here: " + LOAD_PRODUCT_DOC_URL, null); + return; } if (activity == null) { - throw new FlutterError( - ACTIVITY_UNAVAILABLE, + result.error( + "ACTIVITY_UNAVAILABLE", "Details for product " - + params.getProduct() + + product + " are not available. This method must be run with the app in foreground.", null); + return; } BillingFlowParams.ProductDetailsParams.Builder productDetailsParamsBuilder = BillingFlowParams.ProductDetailsParams.newBuilder(); productDetailsParamsBuilder.setProductDetails(productDetails); - if (params.getOfferToken() != null) { - productDetailsParamsBuilder.setOfferToken(params.getOfferToken()); + if (offerToken != null) { + productDetailsParamsBuilder.setOfferToken(offerToken); } List productDetailsParamsList = new ArrayList<>(); @@ -324,24 +319,22 @@ class MethodCallHandlerImpl implements Application.ActivityLifecycleCallbacks, I BillingFlowParams.Builder paramsBuilder = BillingFlowParams.newBuilder().setProductDetailsParamsList(productDetailsParamsList); - if (params.getAccountId() != null && !params.getAccountId().isEmpty()) { - paramsBuilder.setObfuscatedAccountId(params.getAccountId()); + if (accountId != null && !accountId.isEmpty()) { + paramsBuilder.setObfuscatedAccountId(accountId); } - if (params.getObfuscatedProfileId() != null && !params.getObfuscatedProfileId().isEmpty()) { - paramsBuilder.setObfuscatedProfileId(params.getObfuscatedProfileId()); + if (obfuscatedProfileId != null && !obfuscatedProfileId.isEmpty()) { + paramsBuilder.setObfuscatedProfileId(obfuscatedProfileId); } BillingFlowParams.SubscriptionUpdateParams.Builder subscriptionUpdateParamsBuilder = BillingFlowParams.SubscriptionUpdateParams.newBuilder(); - if (params.getOldProduct() != null - && !params.getOldProduct().isEmpty() - && params.getPurchaseToken() != null) { - subscriptionUpdateParamsBuilder.setOldPurchaseToken(params.getPurchaseToken()); + if (oldProduct != null && !oldProduct.isEmpty() && purchaseToken != null) { + subscriptionUpdateParamsBuilder.setOldPurchaseToken(purchaseToken); // Set the prorationMode using a helper to minimize impact of deprecation warning suppression. - setReplaceProrationMode( - subscriptionUpdateParamsBuilder, params.getProrationMode().intValue()); + setReplaceProrationMode(subscriptionUpdateParamsBuilder, prorationMode); paramsBuilder.setSubscriptionUpdateParams(subscriptionUpdateParamsBuilder.build()); } - return fromBillingResult(billingClient.launchBillingFlow(activity, paramsBuilder.build())); + result.success( + fromBillingResult(billingClient.launchBillingFlow(activity, paramsBuilder.build()))); } // TODO(gmackall): Replace uses of deprecated setReplaceProrationMode. @@ -354,145 +347,105 @@ class MethodCallHandlerImpl implements Application.ActivityLifecycleCallbacks, I builder.setReplaceProrationMode(prorationMode); } - @Override - public void consumeAsync( - @NonNull String purchaseToken, @NonNull Result result) { - if (billingClient == null) { - result.error(getNullBillingClientError()); + private void consumeAsync(String purchaseToken, final MethodChannel.Result result) { + if (billingClientError(result)) { return; } - try { - ConsumeResponseListener listener = - (billingResult, outToken) -> result.success(fromBillingResult(billingResult)); - ConsumeParams.Builder paramsBuilder = - ConsumeParams.newBuilder().setPurchaseToken(purchaseToken); - ConsumeParams params = paramsBuilder.build(); + ConsumeResponseListener listener = + (billingResult, outToken) -> result.success(fromBillingResult(billingResult)); + ConsumeParams.Builder paramsBuilder = + ConsumeParams.newBuilder().setPurchaseToken(purchaseToken); - billingClient.consumeAsync(params, listener); - } catch (RuntimeException e) { - result.error(new FlutterError("error", e.getMessage(), Log.getStackTraceString(e))); - } + ConsumeParams params = paramsBuilder.build(); + + billingClient.consumeAsync(params, listener); } - @Override - public void queryPurchasesAsync( - @NonNull PlatformProductType productType, - @NonNull Result result) { - if (billingClient == null) { - result.error(getNullBillingClientError()); + private void queryPurchasesAsync(String productType, MethodChannel.Result result) { + if (billingClientError(result)) { return; } - try { - // Like in our connect call, consider the billing client responding a "success" here regardless - // of status code. - QueryPurchasesParams.Builder paramsBuilder = QueryPurchasesParams.newBuilder(); - paramsBuilder.setProductType(toProductTypeString(productType)); - billingClient.queryPurchasesAsync( - paramsBuilder.build(), - (billingResult, purchasesList) -> { - PlatformPurchasesResponse.Builder builder = - new PlatformPurchasesResponse.Builder() - .setBillingResult(fromBillingResult(billingResult)) - .setPurchases(fromPurchasesList(purchasesList)); - result.success(builder.build()); - }); - } catch (RuntimeException e) { - result.error(new FlutterError("error", e.getMessage(), Log.getStackTraceString(e))); - } + // Like in our connect call, consider the billing client responding a "success" here regardless + // of status code. + QueryPurchasesParams.Builder paramsBuilder = QueryPurchasesParams.newBuilder(); + paramsBuilder.setProductType(productType); + billingClient.queryPurchasesAsync( + paramsBuilder.build(), + (billingResult, purchasesList) -> { + final Map serialized = new HashMap<>(); + // The response code is no longer passed, as part of billing 4.0, so we pass OK here + // as success is implied by calling this callback. + serialized.put("responseCode", BillingClient.BillingResponseCode.OK); + serialized.put("billingResult", fromBillingResult(billingResult)); + serialized.put("purchasesList", fromPurchasesList(purchasesList)); + result.success(serialized); + }); } - @Override - public void queryPurchaseHistoryAsync( - @NonNull PlatformProductType productType, - @NonNull Result result) { - if (billingClient == null) { - result.error(getNullBillingClientError()); + private void queryPurchaseHistoryAsync(String productType, final MethodChannel.Result result) { + if (billingClientError(result)) { return; } - try { - billingClient.queryPurchaseHistoryAsync( - QueryPurchaseHistoryParams.newBuilder() - .setProductType(toProductTypeString(productType)) - .build(), - (billingResult, purchasesList) -> { - PlatformPurchaseHistoryResponse.Builder builder = - new PlatformPurchaseHistoryResponse.Builder() - .setBillingResult(fromBillingResult(billingResult)) - .setPurchases(fromPurchaseHistoryRecordList(purchasesList)); - result.success(builder.build()); - }); - } catch (RuntimeException e) { - result.error(new FlutterError("error", e.getMessage(), Log.getStackTraceString(e))); + billingClient.queryPurchaseHistoryAsync( + QueryPurchaseHistoryParams.newBuilder().setProductType(productType).build(), + (billingResult, purchasesList) -> { + final Map serialized = new HashMap<>(); + serialized.put("billingResult", fromBillingResult(billingResult)); + serialized.put("purchaseHistoryRecordList", fromPurchaseHistoryRecordList(purchasesList)); + result.success(serialized); + }); + } + + private void getConnectionState(final MethodChannel.Result result) { + if (billingClientError(result)) { + return; } + final Map serialized = new HashMap<>(); + serialized.put("connectionState", billingClient.getConnectionState()); + result.success(serialized); } - @Override - public void startConnection( - @NonNull Long handle, - @NonNull PlatformBillingChoiceMode billingMode, - @NonNull Result result) { + private void startConnection(final int handle, final MethodChannel.Result result) { if (billingClient == null) { - billingClient = - billingClientFactory.createBillingClient(applicationContext, callbackApi, billingMode); + billingClient = billingClientFactory.createBillingClient(applicationContext, methodChannel); } - try { - billingClient.startConnection( - new BillingClientStateListener() { - private boolean alreadyFinished = false; - - @Override - public void onBillingSetupFinished(@NonNull BillingResult billingResult) { - if (alreadyFinished) { - Log.d(TAG, "Tried to call onBillingSetupFinished multiple times."); - return; - } - alreadyFinished = true; - // Consider the fact that we've finished a success, leave it to the Dart side to - // validate the responseCode. - result.success(fromBillingResult(billingResult)); - } + billingClient.startConnection( + new BillingClientStateListener() { + private boolean alreadyFinished = false; - @Override - public void onBillingServiceDisconnected() { - callbackApi.onBillingServiceDisconnected( - handle, - new Messages.VoidResult() { - @Override - public void success() {} - - @Override - public void error(@NonNull Throwable error) { - io.flutter.Log.e( - "IN_APP_PURCHASE", - "onBillingServiceDisconnected handler error: " + error); - } - }); + @Override + public void onBillingSetupFinished(@NonNull BillingResult billingResult) { + if (alreadyFinished) { + Log.d(TAG, "Tried to call onBillingSetupFinished multiple times."); + return; } - }); - } catch (RuntimeException e) { - result.error(new FlutterError("error", e.getMessage(), Log.getStackTraceString(e))); - } + alreadyFinished = true; + // Consider the fact that we've finished a success, leave it to the Dart side to + // validate the responseCode. + result.success(fromBillingResult(billingResult)); + } + + @Override + public void onBillingServiceDisconnected() { + final Map arguments = new HashMap<>(); + arguments.put("handle", handle); + methodChannel.invokeMethod(MethodNames.ON_DISCONNECT, arguments); + } + }); } - @Override - public void acknowledgePurchase( - @NonNull String purchaseToken, @NonNull Result result) { - if (billingClient == null) { - result.error(getNullBillingClientError()); + private void acknowledgePurchase(String purchaseToken, final MethodChannel.Result result) { + if (billingClientError(result)) { return; } - try { - AcknowledgePurchaseParams params = - AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchaseToken).build(); - billingClient.acknowledgePurchase( - params, billingResult -> result.success(fromBillingResult(billingResult))); - } catch (RuntimeException e) { - result.error(new FlutterError("error", e.getMessage(), Log.getStackTraceString(e))); - } + AcknowledgePurchaseParams params = + AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchaseToken).build(); + billingClient.acknowledgePurchase( + params, billingResult -> result.success(fromBillingResult(billingResult))); } protected void updateCachedProducts(@Nullable List productDetailsList) { @@ -505,16 +458,21 @@ class MethodCallHandlerImpl implements Application.ActivityLifecycleCallbacks, I } } - private @NonNull FlutterError getNullBillingClientError() { - return new FlutterError("UNAVAILABLE", "BillingClient is unset. Try reconnecting.", null); + private boolean billingClientError(MethodChannel.Result result) { + if (billingClient != null) { + return false; + } + + result.error("UNAVAILABLE", "BillingClient is unset. Try reconnecting.", null); + return true; } - @Override - public @NonNull Boolean isFeatureSupported(@NonNull String feature) { - if (billingClient == null) { - throw getNullBillingClientError(); + private void isFeatureSupported(String feature, MethodChannel.Result result) { + if (billingClientError(result)) { + return; } + assert billingClient != null; BillingResult billingResult = billingClient.isFeatureSupported(feature); - return billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK; + result.success(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK); } } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java index 2dc99e73c..ce919f75d 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java @@ -9,36 +9,33 @@ import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.Purchase; import com.android.billingclient.api.PurchasesUpdatedListener; -import io.flutter.Log; +import io.flutter.plugin.common.MethodChannel; +import java.util.HashMap; import java.util.List; +import java.util.Map; class PluginPurchaseListener implements PurchasesUpdatedListener { - private final Messages.InAppPurchaseCallbackApi callbackApi; + private final MethodChannel channel; - PluginPurchaseListener(Messages.InAppPurchaseCallbackApi callbackApi) { - this.callbackApi = callbackApi; + @VisibleForTesting + static final String ON_PURCHASES_UPDATED = + "PurchasesUpdatedListener#onPurchasesUpdated(BillingResult, List)"; + + PluginPurchaseListener(MethodChannel channel) { + this.channel = channel; } @Override public void onPurchasesUpdated( @NonNull BillingResult billingResult, @Nullable List purchases) { - Messages.PlatformPurchasesResponse.Builder builder = - new Messages.PlatformPurchasesResponse.Builder() - .setBillingResult(fromBillingResult(billingResult)) - .setPurchases(fromPurchasesList(purchases)); - callbackApi.onPurchasesUpdated( - builder.build(), - new Messages.VoidResult() { - @Override - public void success() {} - - @Override - public void error(@NonNull Throwable error) { - Log.e("IN_APP_PURCHASE", "onPurchaseUpdated handler error: " + error); - } - }); + final Map callbackArgs = new HashMap<>(); + callbackArgs.put("billingResult", fromBillingResult(billingResult)); + callbackArgs.put("responseCode", billingResult.getResponseCode()); + callbackArgs.put("purchasesList", fromPurchasesList(purchases)); + channel.invokeMethod(ON_PURCHASES_UPDATED, callbackArgs); } } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java index c06b3acfb..9f397e4e9 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java @@ -7,129 +7,107 @@ package io.flutter.plugins.inapppurchase; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.billingclient.api.AccountIdentifiers; -import com.android.billingclient.api.AlternativeBillingOnlyReportingDetails; -import com.android.billingclient.api.BillingClient; -import com.android.billingclient.api.BillingConfig; import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.ProductDetails; import com.android.billingclient.api.Purchase; import com.android.billingclient.api.PurchaseHistoryRecord; import com.android.billingclient.api.QueryProductDetailsParams; -import com.android.billingclient.api.UserChoiceDetails; -import io.flutter.plugins.inapppurchase.Messages.FlutterError; -import io.flutter.plugins.inapppurchase.Messages.PlatformAccountIdentifiers; -import io.flutter.plugins.inapppurchase.Messages.PlatformAlternativeBillingOnlyReportingDetailsResponse; -import io.flutter.plugins.inapppurchase.Messages.PlatformBillingConfigResponse; -import io.flutter.plugins.inapppurchase.Messages.PlatformBillingResult; -import io.flutter.plugins.inapppurchase.Messages.PlatformOneTimePurchaseOfferDetails; -import io.flutter.plugins.inapppurchase.Messages.PlatformPricingPhase; -import io.flutter.plugins.inapppurchase.Messages.PlatformProductDetails; -import io.flutter.plugins.inapppurchase.Messages.PlatformProductType; -import io.flutter.plugins.inapppurchase.Messages.PlatformPurchase; -import io.flutter.plugins.inapppurchase.Messages.PlatformPurchaseHistoryRecord; -import io.flutter.plugins.inapppurchase.Messages.PlatformPurchaseState; -import io.flutter.plugins.inapppurchase.Messages.PlatformQueryProduct; -import io.flutter.plugins.inapppurchase.Messages.PlatformRecurrenceMode; -import io.flutter.plugins.inapppurchase.Messages.PlatformSubscriptionOfferDetails; -import io.flutter.plugins.inapppurchase.Messages.PlatformUserChoiceDetails; -import io.flutter.plugins.inapppurchase.Messages.PlatformUserChoiceProduct; import java.util.ArrayList; import java.util.Collections; import java.util.Currency; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; /** * Handles serialization and deserialization of {@link com.android.billingclient.api.BillingClient} * related objects. */ /*package*/ class Translator { - static @NonNull PlatformProductDetails fromProductDetail(@NonNull ProductDetails detail) { - return new PlatformProductDetails.Builder() - .setTitle(detail.getTitle()) - .setDescription(detail.getDescription()) - .setProductId(detail.getProductId()) - .setProductType(toPlatformProductType(detail.getProductType())) - .setName(detail.getName()) - .setOneTimePurchaseOfferDetails( - fromOneTimePurchaseOfferDetails(detail.getOneTimePurchaseOfferDetails())) - .setSubscriptionOfferDetails( - fromSubscriptionOfferDetailsList(detail.getSubscriptionOfferDetails())) - .build(); - } + static HashMap fromProductDetail(ProductDetails detail) { + HashMap info = new HashMap<>(); + info.put("title", detail.getTitle()); + info.put("description", detail.getDescription()); + info.put("productId", detail.getProductId()); + info.put("productType", detail.getProductType()); + info.put("name", detail.getName()); - static @NonNull List toProductList( - @NonNull List platformProducts) { - List products = new ArrayList<>(); - for (PlatformQueryProduct platformProduct : platformProducts) { - products.add(toProduct(platformProduct)); + @Nullable + ProductDetails.OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails = + detail.getOneTimePurchaseOfferDetails(); + if (oneTimePurchaseOfferDetails != null) { + info.put( + "oneTimePurchaseOfferDetails", + fromOneTimePurchaseOfferDetails(oneTimePurchaseOfferDetails)); } - return products; - } - static @NonNull QueryProductDetailsParams.Product toProduct( - @NonNull PlatformQueryProduct platformProduct) { + @Nullable + List subscriptionOfferDetailsList = + detail.getSubscriptionOfferDetails(); + if (subscriptionOfferDetailsList != null) { + info.put( + "subscriptionOfferDetails", + fromSubscriptionOfferDetailsList(subscriptionOfferDetailsList)); + } - return QueryProductDetailsParams.Product.newBuilder() - .setProductId(platformProduct.getProductId()) - .setProductType(toProductTypeString(platformProduct.getProductType())) - .build(); + return info; } - static @NonNull String toProductTypeString(PlatformProductType type) { - switch (type) { - case INAPP: - return BillingClient.ProductType.INAPP; - case SUBS: - return BillingClient.ProductType.SUBS; + static List toProductList(List serialized) { + List products = new ArrayList<>(); + for (Object productSerialized : serialized) { + @SuppressWarnings(value = "unchecked") + Map productMap = (Map) productSerialized; + products.add(toProduct(productMap)); } - throw new FlutterError("UNKNOWN_TYPE", "Unknown product type: " + type, null); + return products; } - static PlatformProductType toPlatformProductType(@NonNull String typeString) { - switch (typeString) { - case BillingClient.ProductType.INAPP: - // Fallback handling to avoid throwing an exception if a new type is added in the future. - default: - return PlatformProductType.INAPP; - case BillingClient.ProductType.SUBS: - return PlatformProductType.SUBS; - } + static QueryProductDetailsParams.Product toProduct(Map serialized) { + String productId = (String) serialized.get("productId"); + String productType = (String) serialized.get("productType"); + return QueryProductDetailsParams.Product.newBuilder() + .setProductId(productId) + .setProductType(productType) + .build(); } - static @NonNull List fromProductDetailsList( + static List> fromProductDetailsList( @Nullable List productDetailsList) { if (productDetailsList == null) { return Collections.emptyList(); } - ArrayList output = new ArrayList<>(); + ArrayList> output = new ArrayList<>(); for (ProductDetails detail : productDetailsList) { output.add(fromProductDetail(detail)); } return output; } - static @Nullable PlatformOneTimePurchaseOfferDetails fromOneTimePurchaseOfferDetails( + static HashMap fromOneTimePurchaseOfferDetails( @Nullable ProductDetails.OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails) { + HashMap serialized = new HashMap<>(); if (oneTimePurchaseOfferDetails == null) { - return null; + return serialized; } - return new PlatformOneTimePurchaseOfferDetails.Builder() - .setPriceAmountMicros(oneTimePurchaseOfferDetails.getPriceAmountMicros()) - .setPriceCurrencyCode(oneTimePurchaseOfferDetails.getPriceCurrencyCode()) - .setFormattedPrice(oneTimePurchaseOfferDetails.getFormattedPrice()) - .build(); + serialized.put("priceAmountMicros", oneTimePurchaseOfferDetails.getPriceAmountMicros()); + serialized.put("priceCurrencyCode", oneTimePurchaseOfferDetails.getPriceCurrencyCode()); + serialized.put("formattedPrice", oneTimePurchaseOfferDetails.getFormattedPrice()); + + return serialized; } - static @Nullable List fromSubscriptionOfferDetailsList( + static List> fromSubscriptionOfferDetailsList( @Nullable List subscriptionOfferDetailsList) { if (subscriptionOfferDetailsList == null) { - return null; + return Collections.emptyList(); } - ArrayList serialized = new ArrayList<>(); + ArrayList> serialized = new ArrayList<>(); + for (ProductDetails.SubscriptionOfferDetails subscriptionOfferDetails : subscriptionOfferDetailsList) { serialized.add(fromSubscriptionOfferDetails(subscriptionOfferDetails)); @@ -138,183 +116,119 @@ import java.util.Locale; return serialized; } - static @NonNull PlatformSubscriptionOfferDetails fromSubscriptionOfferDetails( - @NonNull ProductDetails.SubscriptionOfferDetails subscriptionOfferDetails) { - return new PlatformSubscriptionOfferDetails.Builder() - .setOfferId(subscriptionOfferDetails.getOfferId()) - .setBasePlanId(subscriptionOfferDetails.getBasePlanId()) - .setOfferTags(subscriptionOfferDetails.getOfferTags()) - .setOfferToken(subscriptionOfferDetails.getOfferToken()) - .setPricingPhases(fromPricingPhases(subscriptionOfferDetails.getPricingPhases())) - .build(); + static HashMap fromSubscriptionOfferDetails( + @Nullable ProductDetails.SubscriptionOfferDetails subscriptionOfferDetails) { + HashMap serialized = new HashMap<>(); + if (subscriptionOfferDetails == null) { + return serialized; + } + + serialized.put("offerId", subscriptionOfferDetails.getOfferId()); + serialized.put("basePlanId", subscriptionOfferDetails.getBasePlanId()); + serialized.put("offerTags", subscriptionOfferDetails.getOfferTags()); + serialized.put("offerIdToken", subscriptionOfferDetails.getOfferToken()); + + ProductDetails.PricingPhases pricingPhases = subscriptionOfferDetails.getPricingPhases(); + serialized.put("pricingPhases", fromPricingPhases(pricingPhases)); + + return serialized; } - static @NonNull List fromPricingPhases( + static List> fromPricingPhases( @NonNull ProductDetails.PricingPhases pricingPhases) { - ArrayList serialized = new ArrayList<>(); + ArrayList> serialized = new ArrayList<>(); + for (ProductDetails.PricingPhase pricingPhase : pricingPhases.getPricingPhaseList()) { serialized.add(fromPricingPhase(pricingPhase)); } return serialized; } - static @NonNull PlatformPricingPhase fromPricingPhase( - @NonNull ProductDetails.PricingPhase pricingPhase) { - return new PlatformPricingPhase.Builder() - .setFormattedPrice(pricingPhase.getFormattedPrice()) - .setPriceCurrencyCode(pricingPhase.getPriceCurrencyCode()) - .setPriceAmountMicros(pricingPhase.getPriceAmountMicros()) - .setBillingCycleCount((long) pricingPhase.getBillingCycleCount()) - .setBillingPeriod(pricingPhase.getBillingPeriod()) - .setRecurrenceMode(toPlatformRecurrenceMode(pricingPhase.getRecurrenceMode())) - .build(); - } + static HashMap fromPricingPhase( + @Nullable ProductDetails.PricingPhase pricingPhase) { + HashMap serialized = new HashMap<>(); - static PlatformRecurrenceMode toPlatformRecurrenceMode(int mode) { - switch (mode) { - case ProductDetails.RecurrenceMode.FINITE_RECURRING: - return PlatformRecurrenceMode.FINITE_RECURRING; - case ProductDetails.RecurrenceMode.INFINITE_RECURRING: - return PlatformRecurrenceMode.INFINITE_RECURRING; - case ProductDetails.RecurrenceMode.NON_RECURRING: - return PlatformRecurrenceMode.NON_RECURRING; + if (pricingPhase == null) { + return serialized; } - return PlatformRecurrenceMode.NON_RECURRING; - } - static PlatformPurchaseState toPlatformPurchaseState(int state) { - switch (state) { - case Purchase.PurchaseState.PURCHASED: - return PlatformPurchaseState.PURCHASED; - case Purchase.PurchaseState.PENDING: - return PlatformPurchaseState.PENDING; - case Purchase.PurchaseState.UNSPECIFIED_STATE: - return PlatformPurchaseState.UNSPECIFIED; - } - return PlatformPurchaseState.UNSPECIFIED; + serialized.put("formattedPrice", pricingPhase.getFormattedPrice()); + serialized.put("priceCurrencyCode", pricingPhase.getPriceCurrencyCode()); + serialized.put("priceAmountMicros", pricingPhase.getPriceAmountMicros()); + serialized.put("billingCycleCount", pricingPhase.getBillingCycleCount()); + serialized.put("billingPeriod", pricingPhase.getBillingPeriod()); + serialized.put("recurrenceMode", pricingPhase.getRecurrenceMode()); + + return serialized; } - static @NonNull PlatformPurchase fromPurchase(@NonNull Purchase purchase) { - PlatformPurchase.Builder builder = - new PlatformPurchase.Builder() - .setOrderId(purchase.getOrderId()) - .setPackageName(purchase.getPackageName()) - .setPurchaseTime(purchase.getPurchaseTime()) - .setPurchaseToken(purchase.getPurchaseToken()) - .setSignature(purchase.getSignature()) - .setProducts(purchase.getProducts()) - .setIsAutoRenewing(purchase.isAutoRenewing()) - .setOriginalJson(purchase.getOriginalJson()) - .setDeveloperPayload(purchase.getDeveloperPayload()) - .setIsAcknowledged(purchase.isAcknowledged()) - .setPurchaseState(toPlatformPurchaseState(purchase.getPurchaseState())) - .setQuantity((long) purchase.getQuantity()); + static HashMap fromPurchase(Purchase purchase) { + HashMap info = new HashMap<>(); + List products = purchase.getProducts(); + info.put("orderId", purchase.getOrderId()); + info.put("packageName", purchase.getPackageName()); + info.put("purchaseTime", purchase.getPurchaseTime()); + info.put("purchaseToken", purchase.getPurchaseToken()); + info.put("signature", purchase.getSignature()); + info.put("products", products); + info.put("isAutoRenewing", purchase.isAutoRenewing()); + info.put("originalJson", purchase.getOriginalJson()); + info.put("developerPayload", purchase.getDeveloperPayload()); + info.put("isAcknowledged", purchase.isAcknowledged()); + info.put("purchaseState", purchase.getPurchaseState()); + info.put("quantity", purchase.getQuantity()); AccountIdentifiers accountIdentifiers = purchase.getAccountIdentifiers(); if (accountIdentifiers != null) { - builder.setAccountIdentifiers( - new PlatformAccountIdentifiers.Builder() - .setObfuscatedAccountId(accountIdentifiers.getObfuscatedAccountId()) - .setObfuscatedProfileId(accountIdentifiers.getObfuscatedProfileId()) - .build()); + info.put("obfuscatedAccountId", accountIdentifiers.getObfuscatedAccountId()); + info.put("obfuscatedProfileId", accountIdentifiers.getObfuscatedProfileId()); } - return builder.build(); + return info; } - static @NonNull PlatformPurchaseHistoryRecord fromPurchaseHistoryRecord( - @NonNull PurchaseHistoryRecord purchaseHistoryRecord) { - return new PlatformPurchaseHistoryRecord.Builder() - .setPurchaseTime(purchaseHistoryRecord.getPurchaseTime()) - .setPurchaseToken(purchaseHistoryRecord.getPurchaseToken()) - .setSignature(purchaseHistoryRecord.getSignature()) - .setProducts(purchaseHistoryRecord.getProducts()) - .setDeveloperPayload(purchaseHistoryRecord.getDeveloperPayload()) - .setOriginalJson(purchaseHistoryRecord.getOriginalJson()) - .setQuantity((long) purchaseHistoryRecord.getQuantity()) - .build(); + static HashMap fromPurchaseHistoryRecord( + PurchaseHistoryRecord purchaseHistoryRecord) { + HashMap info = new HashMap<>(); + List products = purchaseHistoryRecord.getProducts(); + info.put("purchaseTime", purchaseHistoryRecord.getPurchaseTime()); + info.put("purchaseToken", purchaseHistoryRecord.getPurchaseToken()); + info.put("signature", purchaseHistoryRecord.getSignature()); + info.put("products", products); + info.put("developerPayload", purchaseHistoryRecord.getDeveloperPayload()); + info.put("originalJson", purchaseHistoryRecord.getOriginalJson()); + info.put("quantity", purchaseHistoryRecord.getQuantity()); + return info; } - static @NonNull List fromPurchasesList(@Nullable List purchases) { + static List> fromPurchasesList(@Nullable List purchases) { if (purchases == null) { return Collections.emptyList(); } - List serialized = new ArrayList<>(); + List> serialized = new ArrayList<>(); for (Purchase purchase : purchases) { serialized.add(fromPurchase(purchase)); } return serialized; } - static @NonNull List fromPurchaseHistoryRecordList( + static List> fromPurchaseHistoryRecordList( @Nullable List purchaseHistoryRecords) { if (purchaseHistoryRecords == null) { return Collections.emptyList(); } - List serialized = new ArrayList<>(); + List> serialized = new ArrayList<>(); for (PurchaseHistoryRecord purchaseHistoryRecord : purchaseHistoryRecords) { serialized.add(fromPurchaseHistoryRecord(purchaseHistoryRecord)); } return serialized; } - static @NonNull PlatformBillingResult fromBillingResult(@NonNull BillingResult billingResult) { - return new PlatformBillingResult.Builder() - .setResponseCode((long) billingResult.getResponseCode()) - .setDebugMessage(billingResult.getDebugMessage()) - .build(); - } - - static @NonNull PlatformUserChoiceDetails fromUserChoiceDetails( - @NonNull UserChoiceDetails userChoiceDetails) { - return new PlatformUserChoiceDetails.Builder() - .setExternalTransactionToken(userChoiceDetails.getExternalTransactionToken()) - .setOriginalExternalTransactionId(userChoiceDetails.getOriginalExternalTransactionId()) - .setProducts(fromUserChoiceProductsList(userChoiceDetails.getProducts())) - .build(); - } - - static @NonNull List fromUserChoiceProductsList( - @NonNull List productsList) { - if (productsList.isEmpty()) { - return Collections.emptyList(); - } - - ArrayList output = new ArrayList<>(); - for (UserChoiceDetails.Product product : productsList) { - output.add(fromUserChoiceProduct(product)); - } - return output; - } - - static @NonNull PlatformUserChoiceProduct fromUserChoiceProduct( - @NonNull UserChoiceDetails.Product product) { - return new PlatformUserChoiceProduct.Builder() - .setId(product.getId()) - .setOfferToken(product.getOfferToken()) - .setType(toPlatformProductType(product.getType())) - .build(); - } - - /** Converter from {@link BillingResult} and {@link BillingConfig} to map. */ - static @NonNull PlatformBillingConfigResponse fromBillingConfig( - @NonNull BillingResult result, @Nullable BillingConfig billingConfig) { - return new PlatformBillingConfigResponse.Builder() - .setBillingResult(fromBillingResult(result)) - .setCountryCode(billingConfig == null ? "" : billingConfig.getCountryCode()) - .build(); - } - - /** - * Converter from {@link BillingResult} and {@link AlternativeBillingOnlyReportingDetails} to map. - */ - static @NonNull PlatformAlternativeBillingOnlyReportingDetailsResponse - fromAlternativeBillingOnlyReportingDetails( - @NonNull BillingResult result, @Nullable AlternativeBillingOnlyReportingDetails details) { - return new PlatformAlternativeBillingOnlyReportingDetailsResponse.Builder() - .setBillingResult(fromBillingResult(result)) - .setExternalTransactionToken(details == null ? "" : details.getExternalTransactionToken()) - .build(); + static HashMap fromBillingResult(BillingResult billingResult) { + HashMap info = new HashMap<>(); + info.put("responseCode", billingResult.getResponseCode()); + info.put("debugMessage", billingResult.getDebugMessage()); + return info; } /** diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImplTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImplTest.java deleted file mode 100644 index d1c91fb82..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImplTest.java +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.inapppurchase; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; -import androidx.test.core.app.ApplicationProvider; -import com.android.billingclient.api.BillingClient; -import com.android.billingclient.api.UserChoiceBillingListener; -import com.android.billingclient.api.UserChoiceDetails; -import io.flutter.plugins.inapppurchase.Messages.InAppPurchaseCallbackApi; -import io.flutter.plugins.inapppurchase.Messages.PlatformBillingChoiceMode; -import io.flutter.plugins.inapppurchase.Messages.PlatformUserChoiceDetails; -import java.util.Collections; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; - -@RunWith(RobolectricTestRunner.class) -public class BillingClientFactoryImplTest { - - private AutoCloseable openMocks; - BillingClientFactoryImpl factory; - @Mock InAppPurchaseCallbackApi mockCallbackApi; - Context context; - - @Before - public void setUp() { - openMocks = MockitoAnnotations.openMocks(this); - // Context must be a "real/robolectric" context since the implementation of billing client - // calls methods on context. - context = ApplicationProvider.getApplicationContext(); - factory = spy(new BillingClientFactoryImpl()); - } - - @Test - public void playBillingOnly() { - // No logic to verify just ensure creation works. - BillingClient client = - factory.createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.PLAY_BILLING_ONLY); - assertNotNull(client); - } - - @Test - public void alternativeBillingOnly() { - // No logic to verify just ensure creation works. - BillingClient client = - factory.createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY); - assertNotNull(client); - } - - @Test - public void userChoiceBilling() { - final UserChoiceBillingListener listener = - factory.createUserChoiceBillingListener(mockCallbackApi); - doReturn(listener) - .when(factory) - .createUserChoiceBillingListener(any(InAppPurchaseCallbackApi.class)); - - final BillingClient billingClient = - factory.createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.USER_CHOICE_BILLING); - - UserChoiceDetails details = mock(UserChoiceDetails.class); - final String externalTransactionToken = "someLongTokenId1234"; - final String originalTransactionId = "originalTransactionId123456"; - when(details.getExternalTransactionToken()).thenReturn(externalTransactionToken); - when(details.getOriginalExternalTransactionId()).thenReturn(originalTransactionId); - when(details.getProducts()).thenReturn(Collections.emptyList()); - listener.userSelectedAlternativeBilling(details); - - ArgumentCaptor callbackCaptor = - ArgumentCaptor.forClass(PlatformUserChoiceDetails.class); - verify(mockCallbackApi, times(1)) - .userSelectedalternativeBilling(callbackCaptor.capture(), any()); - assertEquals(callbackCaptor.getValue().getExternalTransactionToken(), externalTransactionToken); - assertEquals( - callbackCaptor.getValue().getOriginalExternalTransactionId(), originalTransactionId); - assertTrue(callbackCaptor.getValue().getProducts().isEmpty()); - } - - @After - public void tearDown() throws Exception { - openMocks.close(); - } -} diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java index ca4289108..40aeb7ad6 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java @@ -4,18 +4,30 @@ package io.flutter.plugins.inapppurchase; -import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.ACTIVITY_UNAVAILABLE; -import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY; +import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.ACKNOWLEDGE_PURCHASE; +import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.CONSUME_PURCHASE_ASYNC; +import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.END_CONNECTION; +import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.IS_FEATURE_SUPPORTED; +import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.IS_READY; +import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.LAUNCH_BILLING_FLOW; +import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.ON_DISCONNECT; +import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.QUERY_PRODUCT_DETAILS; +import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.QUERY_PURCHASES_ASYNC; +import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.QUERY_PURCHASE_HISTORY_ASYNC; +import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodNames.START_CONNECTION; +import static io.flutter.plugins.inapppurchase.PluginPurchaseListener.ON_PURCHASES_UPDATED; +import static io.flutter.plugins.inapppurchase.Translator.fromBillingResult; +import static io.flutter.plugins.inapppurchase.Translator.fromProductDetailsList; +import static io.flutter.plugins.inapppurchase.Translator.fromPurchaseHistoryRecordList; +import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.Collections.unmodifiableList; import static java.util.stream.Collectors.toList; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.refEq; import static org.mockito.Mockito.doAnswer; @@ -28,22 +40,16 @@ import static org.mockito.Mockito.when; import android.app.Activity; import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.billingclient.api.AcknowledgePurchaseParams; import com.android.billingclient.api.AcknowledgePurchaseResponseListener; -import com.android.billingclient.api.AlternativeBillingOnlyAvailabilityListener; -import com.android.billingclient.api.AlternativeBillingOnlyInformationDialogListener; -import com.android.billingclient.api.AlternativeBillingOnlyReportingDetails; -import com.android.billingclient.api.AlternativeBillingOnlyReportingDetailsListener; import com.android.billingclient.api.BillingClient; -import com.android.billingclient.api.BillingClient.BillingResponseCode; import com.android.billingclient.api.BillingClientStateListener; -import com.android.billingclient.api.BillingConfig; -import com.android.billingclient.api.BillingConfigResponseListener; import com.android.billingclient.api.BillingFlowParams; import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.ConsumeParams; import com.android.billingclient.api.ConsumeResponseListener; -import com.android.billingclient.api.GetBillingConfigParams; import com.android.billingclient.api.ProductDetails; import com.android.billingclient.api.ProductDetailsResponseListener; import com.android.billingclient.api.Purchase; @@ -54,156 +60,86 @@ import com.android.billingclient.api.QueryProductDetailsParams; import com.android.billingclient.api.QueryPurchaseHistoryParams; import com.android.billingclient.api.QueryPurchasesParams; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.plugins.inapppurchase.Messages.FlutterError; -import io.flutter.plugins.inapppurchase.Messages.InAppPurchaseCallbackApi; -import io.flutter.plugins.inapppurchase.Messages.PlatformAlternativeBillingOnlyReportingDetailsResponse; -import io.flutter.plugins.inapppurchase.Messages.PlatformBillingChoiceMode; -import io.flutter.plugins.inapppurchase.Messages.PlatformBillingConfigResponse; -import io.flutter.plugins.inapppurchase.Messages.PlatformBillingFlowParams; -import io.flutter.plugins.inapppurchase.Messages.PlatformBillingResult; -import io.flutter.plugins.inapppurchase.Messages.PlatformProductDetailsResponse; -import io.flutter.plugins.inapppurchase.Messages.PlatformProductType; -import io.flutter.plugins.inapppurchase.Messages.PlatformPurchaseHistoryResponse; -import io.flutter.plugins.inapppurchase.Messages.PlatformPurchasesResponse; -import io.flutter.plugins.inapppurchase.Messages.PlatformQueryProduct; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.Result; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; -import java.util.Collections; +import java.util.HashMap; import java.util.List; -import java.util.Objects; -import org.junit.After; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import org.mockito.stubbing.Answer; public class MethodCallHandlerTest { - private AutoCloseable openMocks; private MethodCallHandlerImpl methodChannelHandler; - @Mock BillingClientFactory factory; + private BillingClientFactory factory; @Mock BillingClient mockBillingClient; - @Mock InAppPurchaseCallbackApi mockCallbackApi; - - @Spy - Messages.Result - platformAlternativeBillingOnlyReportingDetailsResult; - - @Spy Messages.Result platformBillingConfigResult; - @Spy Messages.Result platformBillingResult; - @Spy Messages.Result platformProductDetailsResult; - @Spy Messages.Result platformPurchaseHistoryResult; - @Spy Messages.Result platformPurchasesResult; - + @Mock MethodChannel mockMethodChannel; + @Spy Result result; @Mock Activity activity; @Mock Context context; @Mock ActivityPluginBinding mockActivityPluginBinding; - - private final Long DEFAULT_HANDLE = 1L; + @Captor ArgumentCaptor> resultCaptor; @Before public void setUp() { - openMocks = MockitoAnnotations.openMocks(this); - // Use the same client no matter if alternative billing is enabled or not. - when(factory.createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.PLAY_BILLING_ONLY)) - .thenReturn(mockBillingClient); - when(factory.createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY)) - .thenReturn(mockBillingClient); - when(factory.createBillingClient( - any(Context.class), - any(InAppPurchaseCallbackApi.class), - eq(PlatformBillingChoiceMode.USER_CHOICE_BILLING))) - .thenReturn(mockBillingClient); - methodChannelHandler = new MethodCallHandlerImpl(activity, context, mockCallbackApi, factory); + MockitoAnnotations.openMocks(this); + factory = (@NonNull Context context, @NonNull MethodChannel channel) -> mockBillingClient; + methodChannelHandler = new MethodCallHandlerImpl(activity, context, mockMethodChannel, factory); when(mockActivityPluginBinding.getActivity()).thenReturn(activity); } - @After - public void tearDown() throws Exception { - openMocks.close(); + @Test + public void invalidMethod() { + MethodCall call = new MethodCall("invalid", null); + methodChannelHandler.onMethodCall(call, result); + verify(result, times(1)).notImplemented(); } @Test public void isReady_true() { mockStartConnection(); + MethodCall call = new MethodCall(IS_READY, null); when(mockBillingClient.isReady()).thenReturn(true); - boolean result = methodChannelHandler.isReady(); - assertTrue(result); + methodChannelHandler.onMethodCall(call, result); + verify(result).success(true); } @Test public void isReady_false() { mockStartConnection(); + MethodCall call = new MethodCall(IS_READY, null); when(mockBillingClient.isReady()).thenReturn(false); - boolean result = methodChannelHandler.isReady(); - assertFalse(result); + methodChannelHandler.onMethodCall(call, result); + verify(result).success(false); } @Test public void isReady_clientDisconnected() { - methodChannelHandler.endConnection(); + MethodCall disconnectCall = new MethodCall(END_CONNECTION, null); + methodChannelHandler.onMethodCall(disconnectCall, mock(Result.class)); + MethodCall isReadyCall = new MethodCall(IS_READY, null); - // Assert that the synchronous call throws an exception. - FlutterError exception = assertThrows(FlutterError.class, () -> methodChannelHandler.isReady()); - assertEquals("UNAVAILABLE", exception.code); - assertTrue(Objects.requireNonNull(exception.getMessage()).contains("BillingClient")); - } + methodChannelHandler.onMethodCall(isReadyCall, result); - @Test - public void startConnection() { - ArgumentCaptor captor = - mockStartConnection(PlatformBillingChoiceMode.PLAY_BILLING_ONLY); - verify(platformBillingResult, never()).success(any()); - verify(factory, times(1)) - .createBillingClient(context, mockCallbackApi, PlatformBillingChoiceMode.PLAY_BILLING_ONLY); - - BillingResult billingResult = buildBillingResult(); - captor.getValue().onBillingSetupFinished(billingResult); - - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(PlatformBillingResult.class); - verify(platformBillingResult, times(1)).success(resultCaptor.capture()); - assertResultsMatch(resultCaptor.getValue(), billingResult); - verify(platformBillingResult, never()).error(any()); + verify(result).error(contains("UNAVAILABLE"), contains("BillingClient"), any()); + verify(result, never()).success(any()); } @Test - public void startConnectionAlternativeBillingOnly() { - ArgumentCaptor captor = - mockStartConnection(PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY); - verify(platformBillingResult, never()).success(any()); - verify(factory, times(1)) - .createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY); - - BillingResult billingResult = buildBillingResult(); - captor.getValue().onBillingSetupFinished(billingResult); - - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(PlatformBillingResult.class); - verify(platformBillingResult, times(1)).success(resultCaptor.capture()); - assertResultsMatch(resultCaptor.getValue(), billingResult); - verify(platformBillingResult, never()).error(any()); - } - - @Test - public void startConnectionUserChoiceBilling() { - ArgumentCaptor captor = - mockStartConnection(PlatformBillingChoiceMode.USER_CHOICE_BILLING); - verify(platformBillingResult, never()).success(any()); - - verify(factory, times(1)) - .createBillingClient( - any(Context.class), - any(InAppPurchaseCallbackApi.class), - eq(PlatformBillingChoiceMode.USER_CHOICE_BILLING)); - + public void startConnection() { + ArgumentCaptor captor = mockStartConnection(); + verify(result, never()).success(any()); BillingResult billingResult = BillingResult.newBuilder() .setResponseCode(100) @@ -211,70 +147,25 @@ public class MethodCallHandlerTest { .build(); captor.getValue().onBillingSetupFinished(billingResult); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(PlatformBillingResult.class); - verify(platformBillingResult, times(1)).success(resultCaptor.capture()); + verify(result, times(1)).success(fromBillingResult(billingResult)); } @Test - public void userChoiceBillingOnSecondConnection() { - // First connection. - ArgumentCaptor captor1 = - mockStartConnection(PlatformBillingChoiceMode.PLAY_BILLING_ONLY); - verify(platformBillingResult, never()).success(any()); - verify(factory, times(1)) - .createBillingClient(context, mockCallbackApi, PlatformBillingChoiceMode.PLAY_BILLING_ONLY); + public void startConnection_multipleCalls() { + Map arguments = new HashMap<>(); + arguments.put("handle", 1); + MethodCall call = new MethodCall(START_CONNECTION, arguments); + ArgumentCaptor captor = + ArgumentCaptor.forClass(BillingClientStateListener.class); + doNothing().when(mockBillingClient).startConnection(captor.capture()); + methodChannelHandler.onMethodCall(call, result); + verify(result, never()).success(any()); BillingResult billingResult1 = BillingResult.newBuilder() .setResponseCode(100) .setDebugMessage("dummy debug message") .build(); - final BillingClientStateListener stateListener = captor1.getValue(); - stateListener.onBillingSetupFinished(billingResult1); - verify(platformBillingResult, times(1)).success(any()); - Mockito.reset(platformBillingResult, mockCallbackApi, mockBillingClient); - - // Disconnect - methodChannelHandler.endConnection(); - - // Verify that the client is disconnected and that the OnDisconnect callback has - // been triggered - verify(mockBillingClient, times(1)).endConnection(); - stateListener.onBillingServiceDisconnected(); - verify(mockCallbackApi, times(1)).onBillingServiceDisconnected(eq(DEFAULT_HANDLE), any()); - Mockito.reset(platformBillingResult, mockCallbackApi, mockBillingClient); - - // Second connection. - ArgumentCaptor captor2 = - mockStartConnection(PlatformBillingChoiceMode.USER_CHOICE_BILLING); - verify(platformBillingResult, never()).success(any()); - verify(factory, times(1)) - .createBillingClient( - any(Context.class), - any(InAppPurchaseCallbackApi.class), - eq(PlatformBillingChoiceMode.USER_CHOICE_BILLING)); - - BillingResult billingResult2 = - BillingResult.newBuilder() - .setResponseCode(100) - .setDebugMessage("dummy debug message") - .build(); - captor2.getValue().onBillingSetupFinished(billingResult2); - - verify(platformBillingResult, times(1)).success(any()); - } - - @Test - public void startConnection_multipleCalls() { - ArgumentCaptor captor = - ArgumentCaptor.forClass(BillingClientStateListener.class); - doNothing().when(mockBillingClient).startConnection(captor.capture()); - - methodChannelHandler.startConnection( - DEFAULT_HANDLE, PlatformBillingChoiceMode.PLAY_BILLING_ONLY, platformBillingResult); - verify(platformBillingResult, never()).success(any()); - BillingResult billingResult1 = buildBillingResult(); BillingResult billingResult2 = BillingResult.newBuilder() .setResponseCode(200) @@ -290,220 +181,49 @@ public class MethodCallHandlerTest { captor.getValue().onBillingSetupFinished(billingResult2); captor.getValue().onBillingSetupFinished(billingResult3); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(PlatformBillingResult.class); - verify(platformBillingResult, times(1)).success(resultCaptor.capture()); - assertEquals( - resultCaptor.getValue().getResponseCode().longValue(), billingResult1.getResponseCode()); - assertEquals(resultCaptor.getValue().getDebugMessage(), billingResult1.getDebugMessage()); - verify(platformBillingResult, never()).error(any()); - } - - @Test - public void getBillingConfigSuccess() { - mockStartConnection(); - ArgumentCaptor paramsCaptor = - ArgumentCaptor.forClass(GetBillingConfigParams.class); - ArgumentCaptor listenerCaptor = - ArgumentCaptor.forClass(BillingConfigResponseListener.class); - BillingResult billingResult = buildBillingResult(); - final String expectedCountryCode = "US"; - final BillingConfig expectedConfig = mock(BillingConfig.class); - when(expectedConfig.getCountryCode()).thenReturn(expectedCountryCode); - - doNothing() - .when(mockBillingClient) - .getBillingConfigAsync(paramsCaptor.capture(), listenerCaptor.capture()); - - methodChannelHandler.getBillingConfigAsync(platformBillingConfigResult); - listenerCaptor.getValue().onBillingConfigResponse(billingResult, expectedConfig); - - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(PlatformBillingConfigResponse.class); - verify(platformBillingConfigResult, times(1)).success(resultCaptor.capture()); - assertResultsMatch(resultCaptor.getValue().getBillingResult(), billingResult); - assertEquals(resultCaptor.getValue().getCountryCode(), expectedCountryCode); - verify(platformBillingConfigResult, never()).error(any()); - } - - @Test - public void getBillingConfig_serviceDisconnected() { - methodChannelHandler.getBillingConfigAsync(platformBillingConfigResult); - - // Assert that the async call returns an error result. - verify(platformBillingConfigResult, never()).success(any()); - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(platformBillingConfigResult, times(1)).error(errorCaptor.capture()); - assertEquals("UNAVAILABLE", errorCaptor.getValue().code); - assertTrue( - Objects.requireNonNull(errorCaptor.getValue().getMessage()).contains("BillingClient")); - } - - @Test - public void createAlternativeBillingOnlyReportingDetailsSuccess() { - mockStartConnection(); - ArgumentCaptor listenerCaptor = - ArgumentCaptor.forClass(AlternativeBillingOnlyReportingDetailsListener.class); - BillingResult billingResult = buildBillingResult(BillingResponseCode.OK); - final AlternativeBillingOnlyReportingDetails expectedDetails = - mock(AlternativeBillingOnlyReportingDetails.class); - final String expectedExternalTransactionToken = "abc123youandme"; - - when(expectedDetails.getExternalTransactionToken()) - .thenReturn(expectedExternalTransactionToken); - doNothing() - .when(mockBillingClient) - .createAlternativeBillingOnlyReportingDetailsAsync(listenerCaptor.capture()); - - methodChannelHandler.createAlternativeBillingOnlyReportingDetailsAsync( - platformAlternativeBillingOnlyReportingDetailsResult); - listenerCaptor.getValue().onAlternativeBillingOnlyTokenResponse(billingResult, expectedDetails); - - verify(platformAlternativeBillingOnlyReportingDetailsResult, never()).error(any()); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(PlatformAlternativeBillingOnlyReportingDetailsResponse.class); - verify(platformAlternativeBillingOnlyReportingDetailsResult, times(1)) - .success(resultCaptor.capture()); - assertResultsMatch(resultCaptor.getValue().getBillingResult(), billingResult); - assertEquals( - resultCaptor.getValue().getExternalTransactionToken(), expectedExternalTransactionToken); - } - - @Test - public void createAlternativeBillingOnlyReportingDetails_serviceDisconnected() { - methodChannelHandler.createAlternativeBillingOnlyReportingDetailsAsync( - platformAlternativeBillingOnlyReportingDetailsResult); - - // Assert that the async call returns an error result. - verify(platformAlternativeBillingOnlyReportingDetailsResult, never()).success(any()); - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(platformAlternativeBillingOnlyReportingDetailsResult, times(1)) - .error(errorCaptor.capture()); - assertEquals("UNAVAILABLE", errorCaptor.getValue().code); - assertTrue( - Objects.requireNonNull(errorCaptor.getValue().getMessage()).contains("BillingClient")); - } - - @Test - public void isAlternativeBillingOnlyAvailableSuccess() { - mockStartConnection(); - ArgumentCaptor listenerCaptor = - ArgumentCaptor.forClass(AlternativeBillingOnlyAvailabilityListener.class); - BillingResult billingResult = buildBillingResult(BillingClient.BillingResponseCode.OK); - - doNothing() - .when(mockBillingClient) - .isAlternativeBillingOnlyAvailableAsync(listenerCaptor.capture()); - - methodChannelHandler.isAlternativeBillingOnlyAvailableAsync(platformBillingResult); - listenerCaptor.getValue().onAlternativeBillingOnlyAvailabilityResponse(billingResult); - - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(PlatformBillingResult.class); - verify(platformBillingResult, times(1)).success(resultCaptor.capture()); - assertResultsMatch(resultCaptor.getValue(), billingResult); - verify(platformBillingResult, never()).error(any()); - } - - @Test - public void isAlternativeBillingOnlyAvailable_serviceDisconnected() { - methodChannelHandler.isAlternativeBillingOnlyAvailableAsync(platformBillingResult); - - // Assert that the async call returns an error result. - verify(platformBillingResult, never()).success(any()); - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(platformBillingResult, times(1)).error(errorCaptor.capture()); - assertEquals("UNAVAILABLE", errorCaptor.getValue().code); - assertTrue( - Objects.requireNonNull(errorCaptor.getValue().getMessage()).contains("BillingClient")); - } - - @Test - public void showAlternativeBillingOnlyInformationDialogSuccess() { - mockStartConnection(); - ArgumentCaptor listenerCaptor = - ArgumentCaptor.forClass(AlternativeBillingOnlyInformationDialogListener.class); - BillingResult billingResult = buildBillingResult(BillingClient.BillingResponseCode.OK); - - when(mockBillingClient.showAlternativeBillingOnlyInformationDialog( - eq(activity), listenerCaptor.capture())) - .thenReturn(billingResult); - - methodChannelHandler.showAlternativeBillingOnlyInformationDialog(platformBillingResult); - listenerCaptor.getValue().onAlternativeBillingOnlyInformationDialogResponse(billingResult); - - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(PlatformBillingResult.class); - verify(platformBillingResult, times(1)).success(resultCaptor.capture()); - assertResultsMatch(resultCaptor.getValue(), billingResult); - verify(platformBillingResult, never()).error(any()); - } - - @Test - public void showAlternativeBillingOnlyInformationDialog_serviceDisconnected() { - methodChannelHandler.showAlternativeBillingOnlyInformationDialog(platformBillingResult); - - // Assert that the async call returns an error result. - verify(platformBillingResult, never()).success(any()); - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(platformBillingResult, times(1)).error(errorCaptor.capture()); - assertEquals("UNAVAILABLE", errorCaptor.getValue().code); - assertTrue( - Objects.requireNonNull(errorCaptor.getValue().getMessage()).contains("BillingClient")); - } - - @Test - public void showAlternativeBillingOnlyInformationDialog_NullActivity() { - mockStartConnection(); - methodChannelHandler.setActivity(null); - - methodChannelHandler.showAlternativeBillingOnlyInformationDialog(platformBillingResult); - - // Assert that the async call returns an error result. - verify(platformBillingResult, never()).success(any()); - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(platformBillingResult, times(1)).error(errorCaptor.capture()); - assertEquals(ACTIVITY_UNAVAILABLE, errorCaptor.getValue().code); - assertTrue( - Objects.requireNonNull(errorCaptor.getValue().getMessage()) - .contains("Not attempting to show dialog")); + verify(result, times(1)).success(fromBillingResult(billingResult1)); + verify(result, times(1)).success(any()); } @Test public void endConnection() { // Set up a connected BillingClient instance - final long disconnectCallbackHandle = 22; + final int disconnectCallbackHandle = 22; + Map arguments = new HashMap<>(); + arguments.put("handle", disconnectCallbackHandle); + MethodCall connectCall = new MethodCall(START_CONNECTION, arguments); ArgumentCaptor captor = ArgumentCaptor.forClass(BillingClientStateListener.class); doNothing().when(mockBillingClient).startConnection(captor.capture()); - @SuppressWarnings("unchecked") - final Messages.Result mockResult = mock(Messages.Result.class); - methodChannelHandler.startConnection( - disconnectCallbackHandle, PlatformBillingChoiceMode.PLAY_BILLING_ONLY, mockResult); + methodChannelHandler.onMethodCall(connectCall, mock(Result.class)); final BillingClientStateListener stateListener = captor.getValue(); // Disconnect the connected client - methodChannelHandler.endConnection(); + MethodCall disconnectCall = new MethodCall(END_CONNECTION, null); + methodChannelHandler.onMethodCall(disconnectCall, result); // Verify that the client is disconnected and that the OnDisconnect callback has // been triggered + verify(result, times(1)).success(any()); verify(mockBillingClient, times(1)).endConnection(); stateListener.onBillingServiceDisconnected(); - ArgumentCaptor handleCaptor = ArgumentCaptor.forClass(Long.class); - verify(mockCallbackApi, times(1)).onBillingServiceDisconnected(handleCaptor.capture(), any()); - assertEquals(handleCaptor.getValue().longValue(), disconnectCallbackHandle); + Map expectedInvocation = new HashMap<>(); + expectedInvocation.put("handle", disconnectCallbackHandle); + verify(mockMethodChannel, times(1)).invokeMethod(ON_DISCONNECT, expectedInvocation); } @Test public void queryProductDetailsAsync() { // Connect a billing client and set up the product query listeners - establishConnectedBillingClient(); - List productsIds = asList("id1", "id2"); - final List productList = - buildProductList(productsIds, PlatformProductType.INAPP); + establishConnectedBillingClient(/* arguments= */ null, /* result= */ null); + String productType = BillingClient.ProductType.INAPP; + List productsList = asList("id1", "id2"); + HashMap arguments = new HashMap<>(); + arguments.put("productList", buildProductMap(productsList, productType)); + MethodCall queryCall = new MethodCall(QUERY_PRODUCT_DETAILS, arguments); // Query for product details - methodChannelHandler.queryProductDetailsAsync(productList, platformProductDetailsResult); + methodChannelHandler.onMethodCall(queryCall, result); // Assert the arguments were forwarded correctly to BillingClient ArgumentCaptor paramCaptor = @@ -514,34 +234,38 @@ public class MethodCallHandlerTest { .queryProductDetailsAsync(paramCaptor.capture(), listenerCaptor.capture()); // Assert that we handed result BillingClient's response - List productDetailsResponse = singletonList(buildProductDetails("foo")); - BillingResult billingResult = buildBillingResult(); + int responseCode = 200; + List productDetailsResponse = asList(buildProductDetails("foo")); + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); listenerCaptor.getValue().onProductDetailsResponse(billingResult, productDetailsResponse); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(PlatformProductDetailsResponse.class); - verify(platformProductDetailsResult).success(resultCaptor.capture()); - PlatformProductDetailsResponse resultData = resultCaptor.getValue(); - assertResultsMatch(resultData.getBillingResult(), billingResult); - assertDetailListsMatch(productDetailsResponse, resultData.getProductDetails()); + verify(result).success(resultCaptor.capture()); + HashMap resultData = resultCaptor.getValue(); + assertEquals(resultData.get("billingResult"), fromBillingResult(billingResult)); + assertEquals( + resultData.get("productDetailsList"), fromProductDetailsList(productDetailsResponse)); } @Test public void queryProductDetailsAsync_clientDisconnected() { // Disconnect the Billing client and prepare a queryProductDetails call - methodChannelHandler.endConnection(); - List productsIds = asList("id1", "id2"); - final List productList = - buildProductList(productsIds, PlatformProductType.INAPP); - - methodChannelHandler.queryProductDetailsAsync(productList, platformProductDetailsResult); - - // Assert that the async call returns an error result. - verify(platformProductDetailsResult, never()).success(any()); - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(platformProductDetailsResult, times(1)).error(errorCaptor.capture()); - assertEquals("UNAVAILABLE", errorCaptor.getValue().code); - assertTrue( - Objects.requireNonNull(errorCaptor.getValue().getMessage()).contains("BillingClient")); + MethodCall disconnectCall = new MethodCall(END_CONNECTION, null); + methodChannelHandler.onMethodCall(disconnectCall, mock(Result.class)); + String productType = BillingClient.ProductType.INAPP; + List productsList = asList("id1", "id2"); + HashMap arguments = new HashMap<>(); + arguments.put("productList", buildProductMap(productsList, productType)); + MethodCall queryCall = new MethodCall(QUERY_PRODUCT_DETAILS, arguments); + + // Query for product details + methodChannelHandler.onMethodCall(queryCall, result); + + // Assert that we sent an error back. + verify(result).error(contains("UNAVAILABLE"), contains("BillingClient"), any()); + verify(result, never()).success(any()); } // Test launchBillingFlow not crash if `accountId` is `null` @@ -552,22 +276,30 @@ public class MethodCallHandlerTest { // Fetch the product details first and then prepare the launch billing flow call String productId = "foo"; queryForProducts(singletonList(productId)); - PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); - paramsBuilder.setProduct(productId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + HashMap arguments = new HashMap<>(); + arguments.put("product", productId); + arguments.put("accountId", null); + arguments.put("obfuscatedProfileId", null); + MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); // Launch the billing flow - BillingResult billingResult = buildBillingResult(); + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); - PlatformBillingResult platformResult = - methodChannelHandler.launchBillingFlow(paramsBuilder.build()); + methodChannelHandler.onMethodCall(launchCall, result); // Verify we pass the arguments to the billing flow ArgumentCaptor billingFlowParamsCaptor = ArgumentCaptor.forClass(BillingFlowParams.class); verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture()); - assertResultsMatch(platformResult, billingResult); + BillingFlowParams params = billingFlowParamsCaptor.getValue(); + + // Verify we pass the response code to result + verify(result, never()).error(any(), any(), any()); + verify(result, times(1)).success(fromBillingResult(billingResult)); } @Test @@ -576,25 +308,29 @@ public class MethodCallHandlerTest { String productId = "foo"; String accountId = "account"; queryForProducts(singletonList(productId)); - PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); - paramsBuilder.setProduct(productId); - paramsBuilder.setAccountId(accountId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + HashMap arguments = new HashMap<>(); + arguments.put("product", productId); + arguments.put("accountId", accountId); + arguments.put("oldProduct", null); + MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); // Launch the billing flow - BillingResult billingResult = buildBillingResult(); + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); - PlatformBillingResult platformResult = - methodChannelHandler.launchBillingFlow(paramsBuilder.build()); + methodChannelHandler.onMethodCall(launchCall, result); // Verify we pass the arguments to the billing flow ArgumentCaptor billingFlowParamsCaptor = ArgumentCaptor.forClass(BillingFlowParams.class); verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture()); - - // Verify the response. - assertResultsMatch(platformResult, billingResult); + BillingFlowParams params = billingFlowParamsCaptor.getValue(); + // Verify we pass the response code to result + verify(result, never()).error(any(), any(), any()); + verify(result, times(1)).success(fromBillingResult(billingResult)); } @Test @@ -605,19 +341,15 @@ public class MethodCallHandlerTest { String productId = "foo"; String accountId = "account"; queryForProducts(singletonList(productId)); - PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); - paramsBuilder.setProduct(productId); - paramsBuilder.setAccountId(accountId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); - - // Assert that the synchronous call throws an exception. - FlutterError exception = - assertThrows( - FlutterError.class, - () -> methodChannelHandler.launchBillingFlow(paramsBuilder.build())); - assertEquals("ACTIVITY_UNAVAILABLE", exception.code); - assertTrue(Objects.requireNonNull(exception.getMessage()).contains("foreground")); + HashMap arguments = new HashMap<>(); + arguments.put("product", productId); + arguments.put("accountId", accountId); + MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); + methodChannelHandler.onMethodCall(launchCall, result); + + // Verify we pass the response code to result + verify(result).error(contains("ACTIVITY_UNAVAILABLE"), contains("foreground"), any()); + verify(result, never()).success(any()); } @Test @@ -627,26 +359,30 @@ public class MethodCallHandlerTest { String accountId = "account"; String oldProductId = "oldFoo"; queryForProducts(unmodifiableList(asList(productId, oldProductId))); - PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); - paramsBuilder.setProduct(productId); - paramsBuilder.setAccountId(accountId); - paramsBuilder.setOldProduct(oldProductId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + HashMap arguments = new HashMap<>(); + arguments.put("product", productId); + arguments.put("accountId", accountId); + arguments.put("oldProduct", oldProductId); + MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); // Launch the billing flow - BillingResult billingResult = buildBillingResult(); + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); - PlatformBillingResult platformResult = - methodChannelHandler.launchBillingFlow(paramsBuilder.build()); + methodChannelHandler.onMethodCall(launchCall, result); // Verify we pass the arguments to the billing flow ArgumentCaptor billingFlowParamsCaptor = ArgumentCaptor.forClass(BillingFlowParams.class); verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture()); + BillingFlowParams params = billingFlowParamsCaptor.getValue(); - // Verify the response. - assertResultsMatch(platformResult, billingResult); + // Verify we pass the response code to result + verify(result, never()).error(any(), any(), any()); + verify(result, times(1)).success(fromBillingResult(billingResult)); } @Test @@ -655,25 +391,29 @@ public class MethodCallHandlerTest { String productId = "foo"; String accountId = "account"; queryForProducts(singletonList(productId)); - PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); - paramsBuilder.setProduct(productId); - paramsBuilder.setAccountId(accountId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + HashMap arguments = new HashMap<>(); + arguments.put("product", productId); + arguments.put("accountId", accountId); + MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); // Launch the billing flow - BillingResult billingResult = buildBillingResult(); + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); - PlatformBillingResult platformResult = - methodChannelHandler.launchBillingFlow(paramsBuilder.build()); + methodChannelHandler.onMethodCall(launchCall, result); // Verify we pass the arguments to the billing flow ArgumentCaptor billingFlowParamsCaptor = ArgumentCaptor.forClass(BillingFlowParams.class); verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture()); + BillingFlowParams params = billingFlowParamsCaptor.getValue(); - // Verify the response. - assertResultsMatch(platformResult, billingResult); + // Verify we pass the response code to result + verify(result, never()).error(any(), any(), any()); + verify(result, times(1)).success(fromBillingResult(billingResult)); } // TODO(gmackall): Replace uses of deprecated ProrationMode enum values with new @@ -689,26 +429,32 @@ public class MethodCallHandlerTest { String accountId = "account"; int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE; queryForProducts(unmodifiableList(asList(productId, oldProductId))); - PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); - paramsBuilder.setProduct(productId); - paramsBuilder.setAccountId(accountId); - paramsBuilder.setOldProduct(oldProductId); - paramsBuilder.setPurchaseToken(purchaseToken); - paramsBuilder.setProrationMode((long) prorationMode); + HashMap arguments = new HashMap<>(); + arguments.put("product", productId); + arguments.put("accountId", accountId); + arguments.put("oldProduct", oldProductId); + arguments.put("purchaseToken", purchaseToken); + arguments.put("prorationMode", prorationMode); + MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); // Launch the billing flow - BillingResult billingResult = buildBillingResult(); + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); - PlatformBillingResult platformResult = - methodChannelHandler.launchBillingFlow(paramsBuilder.build()); + methodChannelHandler.onMethodCall(launchCall, result); // Verify we pass the arguments to the billing flow ArgumentCaptor billingFlowParamsCaptor = ArgumentCaptor.forClass(BillingFlowParams.class); verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture()); + BillingFlowParams params = billingFlowParamsCaptor.getValue(); - // Verify the response. - assertResultsMatch(platformResult, billingResult); + // Verify we pass the response code to result + verify(result, never()).error(any(), any(), any()); + verify(result, times(1)).success(fromBillingResult(billingResult)); } // TODO(gmackall): Replace uses of deprecated ProrationMode enum values with new @@ -721,27 +467,32 @@ public class MethodCallHandlerTest { String productId = "foo"; String accountId = "account"; String queryOldProductId = "oldFoo"; + String oldProductId = null; int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE; queryForProducts(unmodifiableList(asList(productId, queryOldProductId))); - PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); - paramsBuilder.setProduct(productId); - paramsBuilder.setAccountId(accountId); - paramsBuilder.setOldProduct(null); - paramsBuilder.setProrationMode((long) prorationMode); + HashMap arguments = new HashMap<>(); + arguments.put("product", productId); + arguments.put("accountId", accountId); + arguments.put("oldProduct", oldProductId); + arguments.put("prorationMode", prorationMode); + MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); // Launch the billing flow - BillingResult billingResult = buildBillingResult(); + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); + methodChannelHandler.onMethodCall(launchCall, result); - // Assert that the synchronous call throws an exception. - FlutterError exception = - assertThrows( - FlutterError.class, - () -> methodChannelHandler.launchBillingFlow(paramsBuilder.build())); - assertEquals("IN_APP_PURCHASE_REQUIRE_OLD_PRODUCT", exception.code); - assertTrue( - Objects.requireNonNull(exception.getMessage()) - .contains("launchBillingFlow failed because oldProduct is null")); + // Assert that we sent an error back. + verify(result) + .error( + contains("IN_APP_PURCHASE_REQUIRE_OLD_PRODUCT"), + contains("launchBillingFlow failed because oldProduct is null"), + any()); + verify(result, never()).success(any()); } // TODO(gmackall): Replace uses of deprecated ProrationMode enum values with new @@ -757,112 +508,120 @@ public class MethodCallHandlerTest { String accountId = "account"; int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE; queryForProducts(unmodifiableList(asList(productId, oldProductId))); - PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); - paramsBuilder.setProduct(productId); - paramsBuilder.setAccountId(accountId); - paramsBuilder.setOldProduct(oldProductId); - paramsBuilder.setPurchaseToken(purchaseToken); - paramsBuilder.setProrationMode((long) prorationMode); + HashMap arguments = new HashMap<>(); + arguments.put("product", productId); + arguments.put("accountId", accountId); + arguments.put("oldProduct", oldProductId); + arguments.put("purchaseToken", purchaseToken); + arguments.put("prorationMode", prorationMode); + MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); // Launch the billing flow - BillingResult billingResult = buildBillingResult(); + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); - PlatformBillingResult platformResult = - methodChannelHandler.launchBillingFlow(paramsBuilder.build()); + methodChannelHandler.onMethodCall(launchCall, result); // Verify we pass the arguments to the billing flow ArgumentCaptor billingFlowParamsCaptor = ArgumentCaptor.forClass(BillingFlowParams.class); verify(mockBillingClient).launchBillingFlow(any(), billingFlowParamsCaptor.capture()); + BillingFlowParams params = billingFlowParamsCaptor.getValue(); - // Verify the response. - assertResultsMatch(platformResult, billingResult); + // Verify we pass the response code to result + verify(result, never()).error(any(), any(), any()); + verify(result, times(1)).success(fromBillingResult(billingResult)); } @Test public void launchBillingFlow_clientDisconnected() { // Prepare the launch call after disconnecting the client - methodChannelHandler.endConnection(); + MethodCall disconnectCall = new MethodCall(END_CONNECTION, null); + methodChannelHandler.onMethodCall(disconnectCall, mock(Result.class)); String productId = "foo"; String accountId = "account"; - PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); - paramsBuilder.setProduct(productId); - paramsBuilder.setAccountId(accountId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); - - // Assert that the synchronous call throws an exception. - FlutterError exception = - assertThrows( - FlutterError.class, - () -> methodChannelHandler.launchBillingFlow(paramsBuilder.build())); - assertEquals("UNAVAILABLE", exception.code); - assertTrue(Objects.requireNonNull(exception.getMessage()).contains("BillingClient")); + HashMap arguments = new HashMap<>(); + arguments.put("product", productId); + arguments.put("accountId", accountId); + MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); + + methodChannelHandler.onMethodCall(launchCall, result); + + // Assert that we sent an error back. + verify(result).error(contains("UNAVAILABLE"), contains("BillingClient"), any()); + verify(result, never()).success(any()); } @Test public void launchBillingFlow_productNotFound() { // Try to launch the billing flow for a random product ID - establishConnectedBillingClient(); + establishConnectedBillingClient(null, null); String productId = "foo"; String accountId = "account"; - PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); - paramsBuilder.setProduct(productId); - paramsBuilder.setAccountId(accountId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); - - // Assert that the synchronous call throws an exception. - FlutterError exception = - assertThrows( - FlutterError.class, - () -> methodChannelHandler.launchBillingFlow(paramsBuilder.build())); - assertEquals("NOT_FOUND", exception.code); - assertTrue(Objects.requireNonNull(exception.getMessage()).contains(productId)); + HashMap arguments = new HashMap<>(); + arguments.put("product", productId); + arguments.put("accountId", accountId); + MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); + + methodChannelHandler.onMethodCall(launchCall, result); + + // Assert that we sent an error back. + verify(result).error(contains("NOT_FOUND"), contains(productId), any()); + verify(result, never()).success(any()); } @Test public void launchBillingFlow_oldProductNotFound() { // Try to launch the billing flow for a random product ID - establishConnectedBillingClient(); + establishConnectedBillingClient(null, null); String productId = "foo"; String accountId = "account"; String oldProductId = "oldProduct"; queryForProducts(singletonList(productId)); - PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); - paramsBuilder.setProduct(productId); - paramsBuilder.setAccountId(accountId); - paramsBuilder.setOldProduct(oldProductId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); - - // Assert that the synchronous call throws an exception. - FlutterError exception = - assertThrows( - FlutterError.class, - () -> methodChannelHandler.launchBillingFlow(paramsBuilder.build())); - assertEquals("IN_APP_PURCHASE_INVALID_OLD_PRODUCT", exception.code); - assertTrue(Objects.requireNonNull(exception.getMessage()).contains(oldProductId)); + HashMap arguments = new HashMap<>(); + arguments.put("product", productId); + arguments.put("accountId", accountId); + arguments.put("oldProduct", oldProductId); + MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); + + methodChannelHandler.onMethodCall(launchCall, result); + + // Assert that we sent an error back. + verify(result) + .error(contains("IN_APP_PURCHASE_INVALID_OLD_PRODUCT"), contains(oldProductId), any()); + verify(result, never()).success(any()); } @Test public void queryPurchases_clientDisconnected() { - methodChannelHandler.endConnection(); + // Prepare the launch call after disconnecting the client + methodChannelHandler.onMethodCall(new MethodCall(END_CONNECTION, null), mock(Result.class)); - methodChannelHandler.queryPurchasesAsync(PlatformProductType.INAPP, platformPurchasesResult); + HashMap arguments = new HashMap<>(); + arguments.put("type", BillingClient.ProductType.INAPP); + methodChannelHandler.onMethodCall(new MethodCall(QUERY_PURCHASES_ASYNC, arguments), result); - // Assert that the async call returns an error result. - verify(platformPurchasesResult, never()).success(any()); - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(platformPurchasesResult, times(1)).error(errorCaptor.capture()); - assertEquals("UNAVAILABLE", errorCaptor.getValue().code); - assertTrue( - Objects.requireNonNull(errorCaptor.getValue().getMessage()).contains("BillingClient")); + // Assert that we sent an error back. + verify(result).error(contains("UNAVAILABLE"), contains("BillingClient"), any()); + verify(result, never()).success(any()); } @Test - public void queryPurchases_returns_success() { - establishConnectedBillingClient(); + public void queryPurchases_returns_success() throws Exception { + establishConnectedBillingClient(null, null); + + CountDownLatch lock = new CountDownLatch(1); + doAnswer( + (Answer) + invocation -> { + lock.countDown(); + return null; + }) + .when(result) + .success(any(HashMap.class)); ArgumentCaptor purchasesResponseListenerArgumentCaptor = ArgumentCaptor.forClass(PurchasesResponseListener.class); @@ -875,138 +634,153 @@ public class MethodCallHandlerTest { .setDebugMessage("hello message"); purchasesResponseListenerArgumentCaptor .getValue() - .onQueryPurchasesResponse(resultBuilder.build(), new ArrayList<>()); + .onQueryPurchasesResponse(resultBuilder.build(), new ArrayList()); return null; }) .when(mockBillingClient) .queryPurchasesAsync( any(QueryPurchasesParams.class), purchasesResponseListenerArgumentCaptor.capture()); - methodChannelHandler.queryPurchasesAsync(PlatformProductType.INAPP, platformPurchasesResult); + HashMap arguments = new HashMap<>(); + arguments.put("productType", BillingClient.ProductType.INAPP); + methodChannelHandler.onMethodCall(new MethodCall(QUERY_PURCHASES_ASYNC, arguments), result); - verify(platformPurchasesResult, never()).error(any()); + lock.await(5000, TimeUnit.MILLISECONDS); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(PlatformPurchasesResponse.class); - verify(platformPurchasesResult, times(1)).success(resultCaptor.capture()); + verify(result, never()).error(any(), any(), any()); - PlatformPurchasesResponse purchasesResponse = resultCaptor.getValue(); - assertEquals( - purchasesResponse.getBillingResult().getResponseCode().longValue(), - BillingClient.BillingResponseCode.OK); - assertTrue(purchasesResponse.getPurchases().isEmpty()); + @SuppressWarnings("unchecked") + ArgumentCaptor> hashMapCaptor = ArgumentCaptor.forClass(HashMap.class); + verify(result, times(1)).success(hashMapCaptor.capture()); + + HashMap map = hashMapCaptor.getValue(); + assert (map.containsKey("responseCode")); + assert (map.containsKey("billingResult")); + assert (map.containsKey("purchasesList")); + assert ((int) map.get("responseCode") == 0); } @Test public void queryPurchaseHistoryAsync() { // Set up an established billing client and all our mocked responses - establishConnectedBillingClient(); - BillingResult billingResult = buildBillingResult(); - final String purchaseToken = "foo"; - List purchasesList = - singletonList(buildPurchaseHistoryRecord(purchaseToken)); + establishConnectedBillingClient(null, null); + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); + List purchasesList = asList(buildPurchaseHistoryRecord("foo")); + HashMap arguments = new HashMap<>(); + arguments.put("productType", BillingClient.ProductType.INAPP); ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(PurchaseHistoryResponseListener.class); - methodChannelHandler.queryPurchaseHistoryAsync( - PlatformProductType.INAPP, platformPurchaseHistoryResult); + methodChannelHandler.onMethodCall( + new MethodCall(QUERY_PURCHASE_HISTORY_ASYNC, arguments), result); // Verify we pass the data to result verify(mockBillingClient) .queryPurchaseHistoryAsync(any(QueryPurchaseHistoryParams.class), listenerCaptor.capture()); listenerCaptor.getValue().onPurchaseHistoryResponse(billingResult, purchasesList); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(PlatformPurchaseHistoryResponse.class); - verify(platformPurchaseHistoryResult).success(resultCaptor.capture()); - PlatformPurchaseHistoryResponse result = resultCaptor.getValue(); - assertResultsMatch(result.getBillingResult(), billingResult); - assertEquals(1, result.getPurchases().size()); - assertEquals(purchaseToken, result.getPurchases().get(0).getPurchaseToken()); + verify(result).success(resultCaptor.capture()); + HashMap resultData = resultCaptor.getValue(); + assertEquals(fromBillingResult(billingResult), resultData.get("billingResult")); + assertEquals( + fromPurchaseHistoryRecordList(purchasesList), resultData.get("purchaseHistoryRecordList")); } @Test public void queryPurchaseHistoryAsync_clientDisconnected() { - methodChannelHandler.endConnection(); - - methodChannelHandler.queryPurchaseHistoryAsync( - PlatformProductType.INAPP, platformPurchaseHistoryResult); - - // Assert that the async call returns an error result. - verify(platformPurchaseHistoryResult, never()).success(any()); - ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(FlutterError.class); - verify(platformPurchaseHistoryResult, times(1)).error(errorCaptor.capture()); - assertEquals("UNAVAILABLE", errorCaptor.getValue().code); - assertTrue( - Objects.requireNonNull(errorCaptor.getValue().getMessage()).contains("BillingClient")); + // Prepare the launch call after disconnecting the client + methodChannelHandler.onMethodCall(new MethodCall(END_CONNECTION, null), mock(Result.class)); + + HashMap arguments = new HashMap<>(); + arguments.put("type", BillingClient.ProductType.INAPP); + methodChannelHandler.onMethodCall( + new MethodCall(QUERY_PURCHASE_HISTORY_ASYNC, arguments), result); + + // Assert that we sent an error back. + verify(result).error(contains("UNAVAILABLE"), contains("BillingClient"), any()); + verify(result, never()).success(any()); } @Test public void onPurchasesUpdatedListener() { - PluginPurchaseListener listener = new PluginPurchaseListener(mockCallbackApi); - - BillingResult billingResult = buildBillingResult(); - final String orderId = "foo"; - List purchasesList = singletonList(buildPurchase(orderId)); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(PlatformPurchasesResponse.class); - doNothing().when(mockCallbackApi).onPurchasesUpdated(resultCaptor.capture(), any()); + PluginPurchaseListener listener = new PluginPurchaseListener(mockMethodChannel); + + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); + List purchasesList = asList(buildPurchase("foo")); + doNothing() + .when(mockMethodChannel) + .invokeMethod(eq(ON_PURCHASES_UPDATED), resultCaptor.capture()); listener.onPurchasesUpdated(billingResult, purchasesList); - PlatformPurchasesResponse response = resultCaptor.getValue(); - assertResultsMatch(response.getBillingResult(), billingResult); - assertEquals(1, response.getPurchases().size()); - assertEquals(orderId, response.getPurchases().get(0).getOrderId()); + HashMap resultData = resultCaptor.getValue(); + assertEquals(fromBillingResult(billingResult), resultData.get("billingResult")); + assertEquals(fromPurchasesList(purchasesList), resultData.get("purchasesList")); } @Test public void consumeAsync() { - establishConnectedBillingClient(); - BillingResult billingResult = buildBillingResult(); - final String token = "mockToken"; + establishConnectedBillingClient(null, null); + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); + HashMap arguments = new HashMap<>(); + arguments.put("purchaseToken", "mockToken"); + arguments.put("developerPayload", "mockPayload"); ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ConsumeResponseListener.class); - methodChannelHandler.consumeAsync(token, platformBillingResult); + methodChannelHandler.onMethodCall(new MethodCall(CONSUME_PURCHASE_ASYNC, arguments), result); - ConsumeParams params = ConsumeParams.newBuilder().setPurchaseToken(token).build(); + ConsumeParams params = ConsumeParams.newBuilder().setPurchaseToken("mockToken").build(); // Verify we pass the data to result verify(mockBillingClient).consumeAsync(refEq(params), listenerCaptor.capture()); - listenerCaptor.getValue().onConsumeResponse(billingResult, token); + listenerCaptor.getValue().onConsumeResponse(billingResult, "mockToken"); + verify(result).success(resultCaptor.capture()); // Verify we pass the response code to result - verify(platformBillingResult, never()).error(any()); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(PlatformBillingResult.class); - verify(platformBillingResult, times(1)).success(resultCaptor.capture()); - assertResultsMatch(resultCaptor.getValue(), billingResult); + verify(result, never()).error(any(), any(), any()); + verify(result, times(1)).success(fromBillingResult(billingResult)); } @Test public void acknowledgePurchase() { - establishConnectedBillingClient(); - BillingResult billingResult = buildBillingResult(); - final String purchaseToken = "mockToken"; + establishConnectedBillingClient(null, null); + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); + HashMap arguments = new HashMap<>(); + arguments.put("purchaseToken", "mockToken"); + arguments.put("developerPayload", "mockPayload"); ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(AcknowledgePurchaseResponseListener.class); - methodChannelHandler.acknowledgePurchase(purchaseToken, platformBillingResult); + methodChannelHandler.onMethodCall(new MethodCall(ACKNOWLEDGE_PURCHASE, arguments), result); AcknowledgePurchaseParams params = - AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchaseToken).build(); + AcknowledgePurchaseParams.newBuilder().setPurchaseToken("mockToken").build(); // Verify we pass the data to result verify(mockBillingClient).acknowledgePurchase(refEq(params), listenerCaptor.capture()); listenerCaptor.getValue().onAcknowledgePurchaseResponse(billingResult); + verify(result).success(resultCaptor.capture()); // Verify we pass the response code to result - verify(platformBillingResult, never()).error(any()); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(PlatformBillingResult.class); - verify(platformBillingResult, times(1)).success(resultCaptor.capture()); - assertResultsMatch(resultCaptor.getValue(), billingResult); + verify(result, never()).error(any(), any(), any()); + verify(result, times(1)).success(fromBillingResult(billingResult)); } @Test @@ -1022,64 +796,77 @@ public class MethodCallHandlerTest { public void isFutureSupported_true() { mockStartConnection(); final String feature = "subscriptions"; + Map arguments = new HashMap<>(); + arguments.put("feature", feature); - BillingResult billingResult = buildBillingResult(BillingClient.BillingResponseCode.OK); - when(mockBillingClient.isFeatureSupported(feature)).thenReturn(billingResult); + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(BillingClient.BillingResponseCode.OK) + .setDebugMessage("dummy debug message") + .build(); - assertTrue(methodChannelHandler.isFeatureSupported(feature)); + MethodCall call = new MethodCall(IS_FEATURE_SUPPORTED, arguments); + when(mockBillingClient.isFeatureSupported(feature)).thenReturn(billingResult); + methodChannelHandler.onMethodCall(call, result); + verify(result).success(true); } @Test public void isFutureSupported_false() { mockStartConnection(); final String feature = "subscriptions"; + Map arguments = new HashMap<>(); + arguments.put("feature", feature); - BillingResult billingResult = buildBillingResult(BillingResponseCode.FEATURE_NOT_SUPPORTED); - when(mockBillingClient.isFeatureSupported(feature)).thenReturn(billingResult); + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED) + .setDebugMessage("dummy debug message") + .build(); - assertFalse(methodChannelHandler.isFeatureSupported(feature)); + MethodCall call = new MethodCall(IS_FEATURE_SUPPORTED, arguments); + when(mockBillingClient.isFeatureSupported(feature)).thenReturn(billingResult); + methodChannelHandler.onMethodCall(call, result); + verify(result).success(false); } - /** - * Call {@link MethodCallHandlerImpl#startConnection(Long, PlatformBillingChoiceMode, - * Messages.Result)} with startup params. - * - *

Defaults to play billing only which is the default. - */ private ArgumentCaptor mockStartConnection() { - return mockStartConnection(PlatformBillingChoiceMode.PLAY_BILLING_ONLY); - } - - /** - * Call {@link MethodCallHandlerImpl#startConnection(Long, PlatformBillingChoiceMode, - * Messages.Result)} with startup params. - */ - private ArgumentCaptor mockStartConnection( - PlatformBillingChoiceMode billingChoiceMode) { + Map arguments = new HashMap<>(); + arguments.put("handle", 1); + MethodCall call = new MethodCall(START_CONNECTION, arguments); ArgumentCaptor captor = ArgumentCaptor.forClass(BillingClientStateListener.class); doNothing().when(mockBillingClient).startConnection(captor.capture()); - methodChannelHandler.startConnection(DEFAULT_HANDLE, billingChoiceMode, platformBillingResult); + methodChannelHandler.onMethodCall(call, result); return captor; } - private void establishConnectedBillingClient() { - @SuppressWarnings("unchecked") - final Messages.Result mockResult = mock(Messages.Result.class); - methodChannelHandler.startConnection( - DEFAULT_HANDLE, PlatformBillingChoiceMode.PLAY_BILLING_ONLY, mockResult); + private void establishConnectedBillingClient( + @Nullable Map arguments, @Nullable Result result) { + if (arguments == null) { + arguments = new HashMap<>(); + arguments.put("handle", 1); + } + if (result == null) { + result = mock(Result.class); + } + + MethodCall connectCall = new MethodCall(START_CONNECTION, arguments); + methodChannelHandler.onMethodCall(connectCall, result); } private void queryForProducts(List productIdList) { // Set up the query method call - establishConnectedBillingClient(); - List productsIds = asList("id1", "id2"); - final List productList = - buildProductList(productsIds, PlatformProductType.INAPP); + establishConnectedBillingClient(/* arguments= */ null, /* result= */ null); + HashMap arguments = new HashMap<>(); + String productType = BillingClient.ProductType.INAPP; + List> productList = buildProductMap(productIdList, productType); + arguments.put("productList", productList); + MethodCall queryCall = new MethodCall(QUERY_PRODUCT_DETAILS, arguments); // Call the method. - methodChannelHandler.queryProductDetailsAsync(productList, platformProductDetailsResult); + methodChannelHandler.onMethodCall(queryCall, mock(Result.class)); // Respond to the call with a matching set of product details. ArgumentCaptor listenerCaptor = @@ -1088,17 +875,21 @@ public class MethodCallHandlerTest { List productDetailsResponse = productIdList.stream().map(this::buildProductDetails).collect(toList()); - BillingResult billingResult = buildBillingResult(); + BillingResult billingResult = + BillingResult.newBuilder() + .setResponseCode(100) + .setDebugMessage("dummy debug message") + .build(); listenerCaptor.getValue().onProductDetailsResponse(billingResult, productDetailsResponse); } - private List buildProductList( - List productIds, PlatformProductType productType) { - List productList = new ArrayList<>(); + private List> buildProductMap(List productIds, String productType) { + List> productList = new ArrayList<>(); for (String productId : productIds) { - PlatformQueryProduct.Builder builder = - new PlatformQueryProduct.Builder().setProductId(productId).setProductType(productType); - productList.add(builder.build()); + Map productMap = new HashMap<>(); + productMap.put("productId", productId); + productMap.put("productType", productType); + productList.add(productMap); } return productList; } @@ -1129,65 +920,12 @@ public class MethodCallHandlerTest { private Purchase buildPurchase(String orderId) { Purchase purchase = mock(Purchase.class); when(purchase.getOrderId()).thenReturn(orderId); - when(purchase.getPurchaseState()).thenReturn(Purchase.PurchaseState.UNSPECIFIED_STATE); - when(purchase.getQuantity()).thenReturn(1); - when(purchase.getPurchaseTime()).thenReturn(0L); - when(purchase.getDeveloperPayload()).thenReturn(""); - when(purchase.getOriginalJson()).thenReturn(""); - when(purchase.getPackageName()).thenReturn(""); - when(purchase.getPurchaseToken()).thenReturn(""); - when(purchase.getSignature()).thenReturn(""); - when(purchase.getProducts()).thenReturn(Collections.emptyList()); return purchase; } private PurchaseHistoryRecord buildPurchaseHistoryRecord(String purchaseToken) { PurchaseHistoryRecord purchase = mock(PurchaseHistoryRecord.class); when(purchase.getPurchaseToken()).thenReturn(purchaseToken); - when(purchase.getQuantity()).thenReturn(1); - when(purchase.getPurchaseTime()).thenReturn(0L); - when(purchase.getDeveloperPayload()).thenReturn(""); - when(purchase.getOriginalJson()).thenReturn(""); - when(purchase.getSignature()).thenReturn(""); - when(purchase.getProducts()).thenReturn(Collections.emptyList()); return purchase; } - - private BillingResult buildBillingResult() { - return buildBillingResult(100); - } - - private BillingResult buildBillingResult(int responseCode) { - return BillingResult.newBuilder() - .setResponseCode(responseCode) - .setDebugMessage("dummy debug message") - .build(); - } - - private void assertResultsMatch(PlatformBillingResult pigeonResult, BillingResult nativeResult) { - assertEquals(pigeonResult.getResponseCode().longValue(), nativeResult.getResponseCode()); - assertEquals(pigeonResult.getDebugMessage(), nativeResult.getDebugMessage()); - } - - private void assertDetailListsMatch( - List expected, List actual) { - assertEquals(expected.size(), actual.size()); - for (int i = 0; i < expected.size(); i++) { - assertDetailsMatch(expected.get(i), actual.get(i)); - } - } - - private void assertDetailsMatch(ProductDetails expected, Messages.PlatformProductDetails actual) { - assertEquals(expected.getDescription(), actual.getDescription()); - assertEquals(expected.getName(), actual.getName()); - assertEquals(expected.getProductId(), actual.getProductId()); - assertEquals(expected.getTitle(), actual.getTitle()); - // This doesn't do a deep match; TranslatorTest covers that. This is just a sanity check. - assertEquals( - expected.getOneTimePurchaseOfferDetails() == null, - actual.getOneTimePurchaseOfferDetails() == null); - assertEquals( - expected.getSubscriptionOfferDetails() == null, - actual.getSubscriptionOfferDetails() == null); - } } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java index fff2ef2ff..aa32afe2e 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java @@ -21,9 +21,10 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; -import java.util.Objects; +import java.util.Map; import org.json.JSONException; import org.junit.Before; import org.junit.Test; @@ -34,7 +35,7 @@ public class TranslatorTest { private static final String IN_APP_PRODUCT_DETAIL_EXAMPLE_JSON = "{\"title\":\"Example title\",\"description\":\"Example description\",\"productId\":\"Example id\",\"type\":\"inapp\",\"name\":\"Example name\",\"oneTimePurchaseOfferDetails\":{\"priceAmountMicros\":990000,\"priceCurrencyCode\":\"USD\",\"formattedPrice\":\"$0.99\"}}"; private static final String SUBS_PRODUCT_DETAIL_EXAMPLE_JSON = - "{\"title\":\"Example title 2\",\"description\":\"Example description 2\",\"productId\":\"Example id 2\",\"type\":\"subs\",\"name\":\"Example name 2\",\"subscriptionOfferDetails\":[{\"offerId\":\"Example offer id\",\"basePlanId\":\"Example base plan id\",\"offerTags\":[\"Example offer tag\"],\"offerIdToken\":\"Example offer token\",\"pricingPhases\":[{\"formattedPrice\":\"$0.99\",\"priceCurrencyCode\":\"USD\",\"priceAmountMicros\":990000,\"billingCycleCount\":4,\"billingPeriod\":\"Example billing period\",\"recurrenceMode\":1}]}]}"; + "{\"title\":\"Example title 2\",\"description\":\"Example description 2\",\"productId\":\"Example id 2\",\"type\":\"subs\",\"name\":\"Example name 2\",\"subscriptionOfferDetails\":[{\"offerId\":\"Example offer id\",\"basePlanId\":\"Example base plan id\",\"offerTags\":[\"Example offer tag\"],\"offerIdToken\":\"Example offer token\",\"pricingPhases\":[{\"formattedPrice\":\"$0.99\",\"priceCurrencyCode\":\"USD\",\"priceAmountMicros\":990000,\"billingCycleCount\":4,\"billingPeriod\":\"Example billing period\",\"recurrenceMode\":0}]}]}"; Constructor productDetailsConstructor; @@ -53,7 +54,7 @@ public class TranslatorTest { final ProductDetails expected = productDetailsConstructor.newInstance(IN_APP_PRODUCT_DETAIL_EXAMPLE_JSON); - Messages.PlatformProductDetails serialized = Translator.fromProductDetail(expected); + Map serialized = Translator.fromProductDetail(expected); assertSerialized(expected, serialized); } @@ -64,7 +65,7 @@ public class TranslatorTest { final ProductDetails expected = productDetailsConstructor.newInstance(SUBS_PRODUCT_DETAIL_EXAMPLE_JSON); - Messages.PlatformProductDetails serialized = Translator.fromProductDetail(expected); + Map serialized = Translator.fromProductDetail(expected); assertSerialized(expected, serialized); } @@ -77,8 +78,7 @@ public class TranslatorTest { productDetailsConstructor.newInstance(IN_APP_PRODUCT_DETAIL_EXAMPLE_JSON), productDetailsConstructor.newInstance(SUBS_PRODUCT_DETAIL_EXAMPLE_JSON)); - final List serialized = - Translator.fromProductDetailsList(expected); + final List> serialized = Translator.fromProductDetailsList(expected); assertEquals(expected.size(), serialized.size()); assertSerialized(expected.get(0), serialized.get(0)); @@ -100,9 +100,10 @@ public class TranslatorTest { public void fromPurchaseWithoutAccountIds() throws JSONException { final Purchase expected = new PurchaseWithoutAccountIdentifiers(PURCHASE_EXAMPLE_JSON, "signature"); - Messages.PlatformPurchase serialized = Translator.fromPurchase(expected); - assertNotNull(serialized.getOrderId()); - assertNull(serialized.getAccountIdentifiers()); + Map serialized = Translator.fromPurchase(expected); + assertNotNull(serialized.get("orderId")); + assertNull(serialized.get("obfuscatedProfileId")); + assertNull(serialized.get("obfuscatedAccountId")); } @Test @@ -122,7 +123,7 @@ public class TranslatorTest { new PurchaseHistoryRecord(PURCHASE_EXAMPLE_JSON, signature), new PurchaseHistoryRecord(purchase2Json, signature)); - final List serialized = + final List> serialized = Translator.fromPurchaseHistoryRecordList(expected); assertEquals(expected.size(), serialized.size()); @@ -144,7 +145,7 @@ public class TranslatorTest { Arrays.asList( new Purchase(PURCHASE_EXAMPLE_JSON, signature), new Purchase(purchase2Json, signature)); - final List serialized = Translator.fromPurchasesList(expected); + final List> serialized = Translator.fromPurchasesList(expected); assertEquals(expected.size(), serialized.size()); assertSerialized(expected.get(0), serialized.get(0)); @@ -163,20 +164,20 @@ public class TranslatorTest { .setDebugMessage("dummy debug message") .setResponseCode(BillingClient.BillingResponseCode.OK) .build(); - Messages.PlatformBillingResult platformResult = Translator.fromBillingResult(newBillingResult); + Map billingResultMap = Translator.fromBillingResult(newBillingResult); - assertEquals(platformResult.getResponseCode().longValue(), newBillingResult.getResponseCode()); - assertEquals(platformResult.getDebugMessage(), newBillingResult.getDebugMessage()); + assertEquals(billingResultMap.get("responseCode"), newBillingResult.getResponseCode()); + assertEquals(billingResultMap.get("debugMessage"), newBillingResult.getDebugMessage()); } @Test public void fromBillingResult_debugMessageNull() { BillingResult newBillingResult = BillingResult.newBuilder().setResponseCode(BillingClient.BillingResponseCode.OK).build(); - Messages.PlatformBillingResult platformResult = Translator.fromBillingResult(newBillingResult); + Map billingResultMap = Translator.fromBillingResult(newBillingResult); - assertEquals(platformResult.getResponseCode().longValue(), newBillingResult.getResponseCode()); - assertEquals(platformResult.getDebugMessage(), newBillingResult.getDebugMessage()); + assertEquals(billingResultMap.get("responseCode"), newBillingResult.getResponseCode()); + assertEquals(billingResultMap.get("debugMessage"), newBillingResult.getDebugMessage()); } @Test @@ -190,146 +191,118 @@ public class TranslatorTest { } } - private void assertSerialized( - ProductDetails expected, Messages.PlatformProductDetails serialized) { - assertEquals(expected.getTitle(), serialized.getTitle()); - assertEquals(expected.getName(), serialized.getName()); - assertEquals(expected.getProductId(), serialized.getProductId()); - assertEquals(expected.getProductType(), productTypeFromPlatform(serialized.getProductType())); + private void assertSerialized(ProductDetails expected, Map serialized) { + assertEquals(expected.getDescription(), serialized.get("description")); + assertEquals(expected.getTitle(), serialized.get("title")); + assertEquals(expected.getName(), serialized.get("name")); + assertEquals(expected.getProductId(), serialized.get("productId")); + assertEquals(expected.getProductType(), serialized.get("productType")); ProductDetails.OneTimePurchaseOfferDetails expectedOneTimePurchaseOfferDetails = expected.getOneTimePurchaseOfferDetails(); - Messages.PlatformOneTimePurchaseOfferDetails oneTimePurchaseOfferDetails = - serialized.getOneTimePurchaseOfferDetails(); - assertEquals(expectedOneTimePurchaseOfferDetails == null, oneTimePurchaseOfferDetails == null); - if (expectedOneTimePurchaseOfferDetails != null && oneTimePurchaseOfferDetails != null) { - assertSerialized(expectedOneTimePurchaseOfferDetails, oneTimePurchaseOfferDetails); + Object oneTimePurchaseOfferDetailsObject = serialized.get("oneTimePurchaseOfferDetails"); + assertEquals( + expectedOneTimePurchaseOfferDetails == null, oneTimePurchaseOfferDetailsObject == null); + if (expectedOneTimePurchaseOfferDetails != null && oneTimePurchaseOfferDetailsObject != null) { + @SuppressWarnings(value = "unchecked") + Map oneTimePurchaseOfferDetailsMap = + (Map) oneTimePurchaseOfferDetailsObject; + assertSerialized(expectedOneTimePurchaseOfferDetails, oneTimePurchaseOfferDetailsMap); } List expectedSubscriptionOfferDetailsList = expected.getSubscriptionOfferDetails(); - List subscriptionOfferDetailsList = - serialized.getSubscriptionOfferDetails(); + Object subscriptionOfferDetailsListObject = serialized.get("subscriptionOfferDetails"); assertEquals( - expectedSubscriptionOfferDetailsList == null, subscriptionOfferDetailsList == null); - if (expectedSubscriptionOfferDetailsList != null && subscriptionOfferDetailsList != null) { - assertSerialized(expectedSubscriptionOfferDetailsList, subscriptionOfferDetailsList); + expectedSubscriptionOfferDetailsList == null, subscriptionOfferDetailsListObject == null); + if (expectedSubscriptionOfferDetailsList != null + && subscriptionOfferDetailsListObject != null) { + @SuppressWarnings(value = "unchecked") + List subscriptionOfferDetailsListList = + (List) subscriptionOfferDetailsListObject; + assertSerialized(expectedSubscriptionOfferDetailsList, subscriptionOfferDetailsListList); } } private void assertSerialized( - ProductDetails.OneTimePurchaseOfferDetails expected, - Messages.PlatformOneTimePurchaseOfferDetails serialized) { - assertEquals(expected.getPriceAmountMicros(), serialized.getPriceAmountMicros().longValue()); - assertEquals(expected.getPriceCurrencyCode(), serialized.getPriceCurrencyCode()); - assertEquals(expected.getFormattedPrice(), serialized.getFormattedPrice()); + ProductDetails.OneTimePurchaseOfferDetails expected, Map serialized) { + assertEquals(expected.getPriceAmountMicros(), serialized.get("priceAmountMicros")); + assertEquals(expected.getPriceCurrencyCode(), serialized.get("priceCurrencyCode")); + assertEquals(expected.getFormattedPrice(), serialized.get("formattedPrice")); } private void assertSerialized( - List expected, - List serialized) { + List expected, List serialized) { assertEquals(expected.size(), serialized.size()); for (int i = 0; i < expected.size(); i++) { - assertSerialized(expected.get(i), serialized.get(i)); + @SuppressWarnings(value = "unchecked") + Map serializedMap = (Map) serialized.get(i); + assertSerialized(expected.get(i), serializedMap); } } private void assertSerialized( - ProductDetails.SubscriptionOfferDetails expected, - Messages.PlatformSubscriptionOfferDetails serialized) { - assertEquals(expected.getBasePlanId(), serialized.getBasePlanId()); - assertEquals(expected.getOfferId(), serialized.getOfferId()); - assertEquals(expected.getOfferTags(), serialized.getOfferTags()); - assertEquals(expected.getOfferToken(), serialized.getOfferToken()); - assertSerialized(expected.getPricingPhases(), serialized.getPricingPhases()); + ProductDetails.SubscriptionOfferDetails expected, Map serialized) { + assertEquals(expected.getBasePlanId(), serialized.get("basePlanId")); + assertEquals(expected.getOfferId(), serialized.get("offerId")); + assertEquals(expected.getOfferTags(), serialized.get("offerTags")); + assertEquals(expected.getOfferToken(), serialized.get("offerIdToken")); + + @SuppressWarnings(value = "unchecked") + List serializedPricingPhases = (List) serialized.get("pricingPhases"); + assertNotNull(serializedPricingPhases); + assertSerialized(expected.getPricingPhases(), serializedPricingPhases); } - private void assertSerialized( - ProductDetails.PricingPhases expected, List serialized) { + private void assertSerialized(ProductDetails.PricingPhases expected, List serialized) { List expectedPhases = expected.getPricingPhaseList(); assertEquals(expectedPhases.size(), serialized.size()); for (int i = 0; i < serialized.size(); i++) { - assertSerialized(expectedPhases.get(i), serialized.get(i)); + @SuppressWarnings(value = "unchecked") + Map pricingPhaseMap = (Map) serialized.get(i); + assertSerialized(expectedPhases.get(i), pricingPhaseMap); } expected.getPricingPhaseList(); } private void assertSerialized( - ProductDetails.PricingPhase expected, Messages.PlatformPricingPhase serialized) { - assertEquals(expected.getFormattedPrice(), serialized.getFormattedPrice()); - assertEquals(expected.getPriceCurrencyCode(), serialized.getPriceCurrencyCode()); - assertEquals(expected.getPriceAmountMicros(), serialized.getPriceAmountMicros().longValue()); - assertEquals(expected.getBillingCycleCount(), serialized.getBillingCycleCount().intValue()); - assertEquals(expected.getBillingPeriod(), serialized.getBillingPeriod()); - assertEquals( - expected.getRecurrenceMode(), recurrenceModeFromPlatform(serialized.getRecurrenceMode())); + ProductDetails.PricingPhase expected, Map serialized) { + assertEquals(expected.getFormattedPrice(), serialized.get("formattedPrice")); + assertEquals(expected.getPriceCurrencyCode(), serialized.get("priceCurrencyCode")); + assertEquals(expected.getPriceAmountMicros(), serialized.get("priceAmountMicros")); + assertEquals(expected.getBillingCycleCount(), serialized.get("billingCycleCount")); + assertEquals(expected.getBillingPeriod(), serialized.get("billingPeriod")); + assertEquals(expected.getRecurrenceMode(), serialized.get("recurrenceMode")); } - private void assertSerialized(Purchase expected, Messages.PlatformPurchase serialized) { - assertEquals(expected.getOrderId(), serialized.getOrderId()); - assertEquals(expected.getPackageName(), serialized.getPackageName()); - assertEquals(expected.getPurchaseTime(), serialized.getPurchaseTime().longValue()); - assertEquals(expected.getPurchaseToken(), serialized.getPurchaseToken()); - assertEquals(expected.getSignature(), serialized.getSignature()); - assertEquals(expected.getOriginalJson(), serialized.getOriginalJson()); - assertEquals(expected.getProducts(), serialized.getProducts()); - assertEquals(expected.getDeveloperPayload(), serialized.getDeveloperPayload()); - assertEquals(expected.isAcknowledged(), serialized.getIsAcknowledged()); - assertEquals(expected.getPurchaseState(), stateFromPlatform(serialized.getPurchaseState())); - assertNotNull( - Objects.requireNonNull(expected.getAccountIdentifiers()).getObfuscatedAccountId()); + private void assertSerialized(Purchase expected, Map serialized) { + assertEquals(expected.getOrderId(), serialized.get("orderId")); + assertEquals(expected.getPackageName(), serialized.get("packageName")); + assertEquals(expected.getPurchaseTime(), serialized.get("purchaseTime")); + assertEquals(expected.getPurchaseToken(), serialized.get("purchaseToken")); + assertEquals(expected.getSignature(), serialized.get("signature")); + assertEquals(expected.getOriginalJson(), serialized.get("originalJson")); + assertEquals(expected.getProducts(), serialized.get("products")); + assertEquals(expected.getDeveloperPayload(), serialized.get("developerPayload")); + assertEquals(expected.isAcknowledged(), serialized.get("isAcknowledged")); + assertEquals(expected.getPurchaseState(), serialized.get("purchaseState")); + assertNotNull(expected.getAccountIdentifiers().getObfuscatedAccountId()); assertEquals( expected.getAccountIdentifiers().getObfuscatedAccountId(), - Objects.requireNonNull(serialized.getAccountIdentifiers()).getObfuscatedAccountId()); + serialized.get("obfuscatedAccountId")); assertNotNull(expected.getAccountIdentifiers().getObfuscatedProfileId()); assertEquals( expected.getAccountIdentifiers().getObfuscatedProfileId(), - Objects.requireNonNull(serialized.getAccountIdentifiers()).getObfuscatedProfileId()); - } - - private String productTypeFromPlatform(Messages.PlatformProductType type) { - switch (type) { - case INAPP: - return BillingClient.ProductType.INAPP; - case SUBS: - return BillingClient.ProductType.SUBS; - } - throw new IllegalStateException("Unhandled type"); + serialized.get("obfuscatedProfileId")); } - private int stateFromPlatform(Messages.PlatformPurchaseState state) { - switch (state) { - case UNSPECIFIED: - return Purchase.PurchaseState.UNSPECIFIED_STATE; - case PURCHASED: - return Purchase.PurchaseState.PURCHASED; - case PENDING: - return Purchase.PurchaseState.PENDING; - } - throw new IllegalStateException("Unhandled state"); - } - - private int recurrenceModeFromPlatform(Messages.PlatformRecurrenceMode mode) { - switch (mode) { - case FINITE_RECURRING: - return ProductDetails.RecurrenceMode.FINITE_RECURRING; - case INFINITE_RECURRING: - return ProductDetails.RecurrenceMode.INFINITE_RECURRING; - case NON_RECURRING: - return ProductDetails.RecurrenceMode.NON_RECURRING; - } - throw new IllegalStateException("Unhandled mode"); - } - - private void assertSerialized( - PurchaseHistoryRecord expected, Messages.PlatformPurchaseHistoryRecord serialized) { - assertEquals(expected.getPurchaseTime(), serialized.getPurchaseTime().longValue()); - assertEquals(expected.getPurchaseToken(), serialized.getPurchaseToken()); - assertEquals(expected.getSignature(), serialized.getSignature()); - assertEquals(expected.getOriginalJson(), serialized.getOriginalJson()); - assertEquals(expected.getProducts(), serialized.getProducts()); - assertEquals(expected.getDeveloperPayload(), serialized.getDeveloperPayload()); - assertEquals(expected.getQuantity(), serialized.getQuantity().intValue()); + private void assertSerialized(PurchaseHistoryRecord expected, Map serialized) { + assertEquals(expected.getPurchaseTime(), serialized.get("purchaseTime")); + assertEquals(expected.getPurchaseToken(), serialized.get("purchaseToken")); + assertEquals(expected.getSignature(), serialized.get("signature")); + assertEquals(expected.getOriginalJson(), serialized.get("originalJson")); + assertEquals(expected.getProducts(), serialized.get("products")); + assertEquals(expected.getDeveloperPayload(), serialized.get("developerPayload")); } } diff --git a/packages/in_app_purchase/in_app_purchase_android/example/README.md b/packages/in_app_purchase/in_app_purchase_android/example/README.md index a820209c2..96b8bb17d 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/README.md +++ b/packages/in_app_purchase/in_app_purchase_android/example/README.md @@ -7,58 +7,3 @@ package. Unless you are making changes to this implementation package, this example is very unlikely to be relevant. - -# In App Purchase Example - -### Preparation - -There's a significant amount of setup required for testing in-app purchases -successfully, including registering new app IDs and store entries to use for -testing in the Play Developer Console. Google Play requires developers to -configure an app with in-app items for purchase to call their in-app-purchase -APIs. The Google Play Store has extensive documentation on how to do this, and -we've also included a high level guide below. - -* [Google Play Billing Overview](https://developer.android.com/google/play/billing/billing_overview) - -### Android - -1. Create a new app in the [Play Developer - Console](https://play.google.com/apps/publish/) (PDC). - -2. Sign up for a merchant's account in the PDC. - -3. Create IAPs in the PDC available for purchase in the app. The example assumes - the following SKU IDs exist: - - - `consumable`: A managed product. - - `upgrade`: A managed product. - - `subscription_silver`: A lower level subscription. - - `subscription_gold`: A higher level subscription. - - Make sure that all of the products are set to `ACTIVE`. - -4. Update `APP_ID` in `example/android/app/build.gradle` to match your package - ID in the PDC. - -5. Create an `example/android/keystore.properties` file with all your signing - information. `keystore.example.properties` exists as an example to follow. - It's impossible to use any of the `BillingClient` APIs from an unsigned APK. - See - [keystore](https://developer.android.com/studio/publish/app-signing#secure-shared-keystore) - and [signing](https://developer.android.com/studio/publish/app-signing#sign-apk) and - [flutter-android](https://docs.flutter.dev/deployment/android#signing-the-app) - for more information. - -6. Build a signed apk. `flutter build apk` will work for this, the gradle files - in this project have been configured to sign even debug builds. - -7. Upload the signed APK from step 6 to the PDC, and publish that to the alpha - test channel. Add your test account as an approved tester. The - `BillingClient` APIs won't work unless the app has been fully published to - the alpha channel and is being used by an authorized test account. See - [here](https://support.google.com/googleplay/android-developer/answer/3131213) - for more info. - -8. Sign in to the test device with the test account from step #7. Then use - `flutter run` to install the app to the device and test like normal. diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/.gitignore b/packages/in_app_purchase/in_app_purchase_android/example/android/.gitignore deleted file mode 100644 index de72ca0c8..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/example/android/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Keystore files -*.jks diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle b/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle index 9279ec8b2..dd0c26c8a 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle +++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle @@ -55,7 +55,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { namespace 'io.flutter.plugins.inapppurchaseexample' - compileSdk flutter.compileSdkVersion + compileSdkVersion flutter.compileSdkVersion signingConfigs { release { @@ -70,7 +70,7 @@ android { defaultConfig { applicationId project.APP_ID minSdkVersion flutter.minSdkVersion - targetSdkVersion 34 + targetSdkVersion 30 versionCode project.VERSION_CODE versionName project.VERSION_NAME testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -107,10 +107,10 @@ flutter { } dependencies { - implementation 'com.android.billingclient:billing:6.1.0' + implementation 'com.android.billingclient:billing:5.0.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:5.1.1' - testImplementation 'org.json:json:20240303' + testImplementation 'org.json:json:20231013' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/AndroidManifest.xml b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/AndroidManifest.xml index 43a74d844..1185a05b3 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/AndroidManifest.xml +++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/AndroidManifest.xml @@ -14,7 +14,6 @@ android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density" android:hardwareAccelerated="true" - android:exported="true" android:windowSoftInputMode="adjustResize"> diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/keystore.example.properties b/packages/in_app_purchase/in_app_purchase_android/example/android/keystore.example.properties index 8357b4beb..ccbbb3653 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/android/keystore.example.properties +++ b/packages/in_app_purchase/in_app_purchase_android/example/android/keystore.example.properties @@ -1,8 +1,7 @@ -# https://developer.android.com/studio/publish/app-signing#generate-key storePassword=??? keyPassword=??? keyAlias=??? storeFile=??? appId=io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE versionCode=1 -versionName=0.0.1 +versionName=0.0.1 \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_android/example/integration_test/in_app_purchase_test.dart b/packages/in_app_purchase/in_app_purchase_android/example/integration_test/in_app_purchase_test.dart index 8ed5de2ce..568becd71 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/integration_test/in_app_purchase_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/example/integration_test/in_app_purchase_test.dart @@ -27,8 +27,7 @@ void main() { late final BillingClient billingClient; setUpAll(() { - billingClient = BillingClient( - (PurchasesResultWrapper _) {}, (UserChoiceDetailsWrapper _) {}); + billingClient = BillingClient((PurchasesResultWrapper _) {}); }); testWidgets('BillingClient.acknowledgePurchase', diff --git a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart index 89883a156..6377d2dc5 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart +++ b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart @@ -44,20 +44,14 @@ class _MyAppState extends State<_MyApp> { final InAppPurchasePlatform _inAppPurchasePlatform = InAppPurchasePlatform.instance; late StreamSubscription> _subscription; - late StreamSubscription _userChoiceDetailsStream; List _notFoundIds = []; List _products = []; List _purchases = []; List _consumables = []; - String _countryCode = ''; - String _isAlternativeBillingOnlyAvailableResponseCode = ''; - String _showAlternativeBillingOnlyDialogResponseCode = ''; - String _alternativeBillingOnlyReportingDetailsToken = ''; bool _isAvailable = false; bool _purchasePending = false; bool _loading = true; String? _queryProductError; - final List _userChoiceDetailsList = []; @override void initState() { @@ -72,19 +66,6 @@ class _MyAppState extends State<_MyApp> { // handle error here. }); initStoreInfo(); - final InAppPurchaseAndroidPlatformAddition addition = - InAppPurchasePlatformAddition.instance! - as InAppPurchaseAndroidPlatformAddition; - final Stream userChoiceDetailsUpdated = - addition.userChoiceDetailsStream; - _userChoiceDetailsStream = - userChoiceDetailsUpdated.listen((GooglePlayUserChoiceDetails details) { - deliverUserChoiceDetails(details); - }, onDone: () { - _userChoiceDetailsStream.cancel(); - }, onError: (Object error) { - // handle error here. - }); super.initState(); } @@ -149,8 +130,6 @@ class _MyAppState extends State<_MyApp> { @override void dispose() { _subscription.cancel(); - _userChoiceDetailsStream.cancel(); - _userChoiceDetailsList.clear(); super.dispose(); } @@ -165,8 +144,6 @@ class _MyAppState extends State<_MyApp> { _buildProductList(), _buildConsumableBox(), const _FeatureCard(), - _buildFetchButtons(), - _buildUserChoiceDetailsDisplay(), ], ), ); @@ -231,139 +208,6 @@ class _MyAppState extends State<_MyApp> { return Card(child: Column(children: children)); } - Card _buildFetchButtons() { - const ListTile header = ListTile(title: Text('AlternativeBilling Info')); - final List entries = []; - entries.add(ListTile( - title: Text('User Country Code', - style: TextStyle(color: ThemeData.light().colorScheme.primary)), - subtitle: Text(_countryCode))); - entries.add(ListTile( - title: Text('isAlternativeBillingOnlyAvailable response code', - style: TextStyle(color: ThemeData.light().colorScheme.primary)), - subtitle: Text(_isAlternativeBillingOnlyAvailableResponseCode))); - entries.add(ListTile( - title: Text('showAlternativeBillingOnlyDialog response code', - style: TextStyle(color: ThemeData.light().colorScheme.primary)), - subtitle: Text(_showAlternativeBillingOnlyDialogResponseCode))); - entries.add(ListTile( - title: Text('createAlternativeBillingOnlyReportingDetails contents', - style: TextStyle(color: ThemeData.light().colorScheme.primary)), - subtitle: Text(_alternativeBillingOnlyReportingDetailsToken))); - - final List buttons = []; - buttons.add(ListTile( - title: TextButton( - style: TextButton.styleFrom( - backgroundColor: Colors.green[800], - foregroundColor: Colors.white, - ), - onPressed: () { - final InAppPurchaseAndroidPlatformAddition addition = - InAppPurchasePlatformAddition.instance! - as InAppPurchaseAndroidPlatformAddition; - unawaited(deliverCountryCode(addition.getCountryCode())); - }, - child: const Text('Fetch Country Code'), - ), - )); - buttons.add(ListTile( - title: TextButton( - style: TextButton.styleFrom( - backgroundColor: Colors.green[800], - foregroundColor: Colors.white, - ), - onPressed: () { - final InAppPurchaseAndroidPlatformAddition addition = - InAppPurchasePlatformAddition.instance! - as InAppPurchaseAndroidPlatformAddition; - unawaited(deliverIsAlternativeBillingOnlyAvailable( - addition.isAlternativeBillingOnlyAvailable())); - }, - child: const Text('isAlternativeBillingOnlyAvailable'), - ), - )); - buttons.add(ListTile( - title: TextButton( - style: TextButton.styleFrom( - backgroundColor: Colors.green[800], - foregroundColor: Colors.white, - ), - onPressed: () { - final InAppPurchaseAndroidPlatformAddition addition = - InAppPurchasePlatformAddition.instance! - as InAppPurchaseAndroidPlatformAddition; - unawaited(deliverShowAlternativeBillingOnlyInformationDialogResult( - addition.showAlternativeBillingOnlyInformationDialog())); - }, - child: const Text('showAlternativeBillingOnlyInformationDialog'), - ), - )); - buttons.add(ListTile( - title: TextButton( - style: TextButton.styleFrom( - backgroundColor: Colors.green[800], - foregroundColor: Colors.white, - ), - onPressed: () { - final InAppPurchaseAndroidPlatformAddition addition = - InAppPurchasePlatformAddition.instance! - as InAppPurchaseAndroidPlatformAddition; - unawaited(addition - .setBillingChoice(BillingChoiceMode.alternativeBillingOnly)); - }, - child: const Text('setBillingChoice alternativeBillingOnly'), - ), - )); - buttons.add(ListTile( - title: TextButton( - style: TextButton.styleFrom( - backgroundColor: Colors.green[800], - foregroundColor: Colors.white, - ), - onPressed: () { - final InAppPurchaseAndroidPlatformAddition addition = - InAppPurchasePlatformAddition.instance! - as InAppPurchaseAndroidPlatformAddition; - unawaited(deliverCreateAlternativeBillingOnlyReportingDetails( - addition.createAlternativeBillingOnlyReportingDetails())); - }, - child: const Text('createAlternativeBillingOnlyReportingDetails'), - ), - )); - return Card( - child: Column( - children: [ - header, - const Divider(), - ...entries, - const Divider(), - ...buttons, - ], - ), - ); - } - - Card _buildUserChoiceDetailsDisplay() { - const ListTile header = ListTile(title: Text('UserChoiceDetails')); - final List entries = []; - for (final String item in _userChoiceDetailsList) { - entries.add(ListTile( - title: Text(item, - style: TextStyle(color: ThemeData.light().colorScheme.primary)), - subtitle: Text(_countryCode))); - } - return Card( - child: Column( - children: [ - header, - const Divider(), - ...entries, - ], - ), - ); - } - Card _buildProductList() { if (_loading) { return const Card( @@ -502,46 +346,6 @@ class _MyAppState extends State<_MyApp> { }); } - Future deliverCountryCode(Future countryCodeFuture) async { - final String countryCode = await countryCodeFuture; - setState(() { - _countryCode = countryCode; - }); - } - - Future deliverIsAlternativeBillingOnlyAvailable( - Future billingOnly) async { - final BillingResultWrapper wrapper = await billingOnly; - setState(() { - _isAlternativeBillingOnlyAvailableResponseCode = - wrapper.responseCode.name; - }); - } - - Future deliverShowAlternativeBillingOnlyInformationDialogResult( - Future billingResult) async { - final BillingResultWrapper wrapper = await billingResult; - setState(() { - _showAlternativeBillingOnlyDialogResponseCode = wrapper.responseCode.name; - }); - } - - Future deliverCreateAlternativeBillingOnlyReportingDetails( - Future - futureWrapper) async { - final AlternativeBillingOnlyReportingDetailsWrapper wrapper = - await futureWrapper; - setState(() { - if (wrapper.responseCode == BillingResponse.ok) { - _alternativeBillingOnlyReportingDetailsToken = - wrapper.externalTransactionToken; - } else { - _alternativeBillingOnlyReportingDetailsToken = - wrapper.responseCode.name; - } - }); - } - Future deliverProduct(PurchaseDetails purchaseDetails) async { // IMPORTANT!! Always verify purchase details before delivering the product. if (purchaseDetails.productID == _kConsumableId) { @@ -575,24 +379,12 @@ class _MyAppState extends State<_MyApp> { // handle invalid purchase here if _verifyPurchase` failed. } - Future deliverUserChoiceDetails( - GooglePlayUserChoiceDetails details) async { - final String detailDescription = - '${details.externalTransactionToken}, ${details.originalExternalTransactionId}, ${details.products.length}'; - setState(() { - _userChoiceDetailsList.add(detailDescription); - }); - } - Future _listenToPurchaseUpdated( List purchaseDetailsList) async { for (final PurchaseDetails purchaseDetails in purchaseDetailsList) { if (purchaseDetails.status == PurchaseStatus.pending) { showPendingUI(); } else { - final InAppPurchaseAndroidPlatformAddition addition = - InAppPurchasePlatformAddition.instance! - as InAppPurchaseAndroidPlatformAddition; if (purchaseDetails.status == PurchaseStatus.error) { handleError(purchaseDetails.error!); } else if (purchaseDetails.status == PurchaseStatus.purchased || @@ -607,6 +399,10 @@ class _MyAppState extends State<_MyApp> { } if (!_kAutoConsume && purchaseDetails.productID == _kConsumableId) { + final InAppPurchaseAndroidPlatformAddition addition = + InAppPurchasePlatformAddition.instance! + as InAppPurchaseAndroidPlatformAddition; + await addition.consumePurchase(purchaseDetails); } diff --git a/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml index 9239f83a0..ed0e009ea 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml @@ -3,8 +3,8 @@ description: Demonstrates how to use the in_app_purchase_android plugin. publish_to: none environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" dependencies: flutter: diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/billing_client_wrappers.dart b/packages/in_app_purchase/in_app_purchase_android/lib/billing_client_wrappers.dart index 508b80e05..31133424a 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/billing_client_wrappers.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/billing_client_wrappers.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -export 'src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.dart'; export 'src/billing_client_wrappers/billing_client_manager.dart'; export 'src/billing_client_wrappers/billing_client_wrapper.dart'; export 'src/billing_client_wrappers/billing_response_wrapper.dart'; @@ -11,4 +10,3 @@ export 'src/billing_client_wrappers/product_details_wrapper.dart'; export 'src/billing_client_wrappers/product_wrapper.dart'; export 'src/billing_client_wrappers/purchase_wrapper.dart'; export 'src/billing_client_wrappers/subscription_offer_details_wrapper.dart'; -export 'src/billing_client_wrappers/user_choice_details_wrapper.dart'; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.dart deleted file mode 100644 index bbabd1477..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.dart +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/material.dart'; -import 'package:json_annotation/json_annotation.dart'; - -import '../../billing_client_wrappers.dart'; - -// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the -// below generated file. Run `flutter packages pub run build_runner watch` to -// rebuild and watch for further changes. -part 'alternative_billing_only_reporting_details_wrapper.g.dart'; - -/// The error message shown when the map representing details is invalid from method channel. -/// -/// This usually indicates a serious underlying code issue in the plugin. -@visibleForTesting -const String kInvalidAlternativeBillingReportingDetailsErrorMessage = - 'Invalid AlternativeBillingReportingDetails map from method channel.'; - -/// Params containing the response code and the debug message from the Play Billing API response. -@JsonSerializable() -@BillingResponseConverter() -@immutable -class AlternativeBillingOnlyReportingDetailsWrapper - implements HasBillingResponse { - /// Constructs the object with [responseCode] and [debugMessage]. - const AlternativeBillingOnlyReportingDetailsWrapper( - {required this.responseCode, - this.debugMessage, - this.externalTransactionToken = ''}); - - /// Constructs an instance of this from a key value map of data. - /// - /// The map needs to have named string keys with values matching the names and - /// types of all of the members on this class. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory AlternativeBillingOnlyReportingDetailsWrapper.fromJson( - Map? map) { - if (map == null || map.isEmpty) { - return const AlternativeBillingOnlyReportingDetailsWrapper( - responseCode: BillingResponse.error, - debugMessage: kInvalidAlternativeBillingReportingDetailsErrorMessage, - ); - } - return _$AlternativeBillingOnlyReportingDetailsWrapperFromJson(map); - } - - /// Response code returned in the Play Billing API calls. - @override - final BillingResponse responseCode; - - /// Debug message returned in the Play Billing API calls. - /// - /// Defaults to `null`. - /// This message uses an en-US locale and should not be shown to users. - @JsonKey(defaultValue: '') - final String? debugMessage; - - /// https://developer.android.com/reference/com/android/billingclient/api/AlternativeBillingOnlyReportingDetails#getExternalTransactionToken() - @JsonKey(defaultValue: '') - final String externalTransactionToken; - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) { - return false; - } - - return other is AlternativeBillingOnlyReportingDetailsWrapper && - other.responseCode == responseCode && - other.debugMessage == debugMessage && - other.externalTransactionToken == externalTransactionToken; - } - - @override - int get hashCode => - Object.hash(responseCode, debugMessage, externalTransactionToken); -} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.g.dart deleted file mode 100644 index 46b82645d..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.g.dart +++ /dev/null @@ -1,17 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'alternative_billing_only_reporting_details_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -AlternativeBillingOnlyReportingDetailsWrapper - _$AlternativeBillingOnlyReportingDetailsWrapperFromJson(Map json) => - AlternativeBillingOnlyReportingDetailsWrapper( - responseCode: const BillingResponseConverter() - .fromJson(json['responseCode'] as int?), - debugMessage: json['debugMessage'] as String? ?? '', - externalTransactionToken: - json['externalTransactionToken'] as String? ?? '', - ); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart index 8e265dbdc..0eca29606 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart @@ -4,12 +4,10 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'billing_client_wrapper.dart'; import 'purchase_wrapper.dart'; -import 'user_choice_details_wrapper.dart'; /// Abstraction of result of [BillingClient] operation that includes /// a [BillingResponse]. @@ -18,13 +16,6 @@ abstract class HasBillingResponse { abstract final BillingResponse responseCode; } -/// Factory for creating BillingClient instances, to allow injection of -/// custom billing clients in tests. -@visibleForTesting -typedef BillingClientFactory = BillingClient Function( - PurchasesUpdatedListener onPurchasesUpdated, - UserSelectedAlternativeBillingListener? alternativeBillingListener); - /// Utility class that manages a [BillingClient] connection. /// /// Connection is initialized on creation of [BillingClientManager]. @@ -41,20 +32,10 @@ class BillingClientManager { /// Creates the [BillingClientManager]. /// /// Immediately initializes connection to the underlying [BillingClient]. - BillingClientManager( - {@visibleForTesting BillingClientFactory? billingClientFactory}) - : _billingChoiceMode = BillingChoiceMode.playBillingOnly, - _billingClientFactory = billingClientFactory ?? _createBillingClient { + BillingClientManager() { _connect(); } - /// Stream of `userSelectedAlternativeBilling` events from the [BillingClient]. - /// - /// This is a broadcast stream, so it can be listened to multiple times. - /// A "done" event will be sent after [dispose] is called. - late final Stream userChoiceDetailsStream = - _userChoiceAlternativeBillingController.stream; - /// Stream of `onPurchasesUpdated` events from the [BillingClient]. /// /// This is a broadcast stream, so it can be listened to multiple times. @@ -67,24 +48,11 @@ class BillingClientManager { /// In order to access the [BillingClient], use [runWithClient] /// and [runWithClientNonRetryable] methods. @visibleForTesting - late final BillingClient client = _billingClientFactory( - _onPurchasesUpdated, onUserChoiceAlternativeBilling); - - // Default (non-test) implementation of _billingClientFactory. - static BillingClient _createBillingClient( - PurchasesUpdatedListener onPurchasesUpdated, - UserSelectedAlternativeBillingListener? onUserChoiceAlternativeBilling) { - return BillingClient(onPurchasesUpdated, onUserChoiceAlternativeBilling); - } + late final BillingClient client = BillingClient(_onPurchasesUpdated); final StreamController _purchasesUpdatedController = StreamController.broadcast(); - final StreamController - _userChoiceAlternativeBillingController = - StreamController.broadcast(); - BillingChoiceMode _billingChoiceMode; - final BillingClientFactory _billingClientFactory; bool _isConnecting = false; bool _isDisposed = false; @@ -143,27 +111,12 @@ class BillingClientManager { /// After calling [dispose]: /// - Further connection attempts will not be made. /// - [purchasesUpdatedStream] will be closed. - /// - [userChoiceDetailsStream] will be closed. /// - Calls to [runWithClient] and [runWithClientNonRetryable] will throw. void dispose() { _debugAssertNotDisposed(); _isDisposed = true; client.endConnection(); _purchasesUpdatedController.close(); - _userChoiceAlternativeBillingController.close(); - } - - /// Ends connection to [BillingClient] and reconnects with [billingChoiceMode]. - /// - /// Callers need to check if [BillingChoiceMode.alternativeBillingOnly] is - /// available by calling [BillingClientWrapper.isAlternativeBillingOnlyAvailable] - /// first. - Future reconnectWithBillingChoiceMode( - BillingChoiceMode billingChoiceMode) async { - _billingChoiceMode = billingChoiceMode; - // Ends connection and triggers OnBillingServiceDisconnected, which causes reconnect. - await client.endConnection(); - await _connect(); } // If disposed, does nothing. @@ -178,9 +131,7 @@ class BillingClientManager { } _isConnecting = true; _readyFuture = Future.sync(() async { - await client.startConnection( - onBillingServiceDisconnected: _connect, - billingChoiceMode: _billingChoiceMode); + await client.startConnection(onBillingServiceDisconnected: _connect); _isConnecting = false; }); return _readyFuture; @@ -200,14 +151,4 @@ class BillingClientManager { 'called dispose() on a BillingClientManager, it can no longer be used.', ); } - - /// Callback passed to [BillingClient] to use when customer chooses - /// alternative billing. - @visibleForTesting - void onUserChoiceAlternativeBilling(UserChoiceDetailsWrapper event) { - if (_isDisposed) { - return; - } - _userChoiceAlternativeBillingController.add(event); - } } diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart index b9417c6b2..12067d514 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart @@ -5,15 +5,21 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; import 'package:json_annotation/json_annotation.dart'; import '../../billing_client_wrappers.dart'; -import '../messages.g.dart'; -import '../pigeon_converters.dart'; -import 'billing_config_wrapper.dart'; +import '../channel.dart'; part 'billing_client_wrapper.g.dart'; +/// Method identifier for the OnPurchaseUpdated method channel method. +@visibleForTesting +const String kOnPurchasesUpdated = + 'PurchasesUpdatedListener#onPurchasesUpdated(BillingResult, List)'; +const String _kOnBillingServiceDisconnected = + 'BillingClientStateListener#onBillingServiceDisconnected()'; + /// Callback triggered by Play in response to purchase activity. /// /// This callback is triggered in response to all purchase activity while an @@ -33,10 +39,6 @@ part 'billing_client_wrapper.g.dart'; typedef PurchasesUpdatedListener = void Function( PurchasesResultWrapper purchasesResult); -/// Wraps a [UserChoiceBillingListener](https://developer.android.com/reference/com/android/billingclient/api/UserChoiceBillingListener) -typedef UserSelectedAlternativeBillingListener = void Function( - UserChoiceDetailsWrapper userChoiceDetailsWrapper); - /// This class can be used directly instead of [InAppPurchaseConnection] to call /// Play-specific billing APIs. /// @@ -57,28 +59,30 @@ typedef UserSelectedAlternativeBillingListener = void Function( /// transparently. class BillingClient { /// Creates a billing client. - BillingClient( - PurchasesUpdatedListener onPurchasesUpdated, - UserSelectedAlternativeBillingListener? alternativeBillingListener, { - @visibleForTesting InAppPurchaseApi? api, - }) : _hostApi = api ?? InAppPurchaseApi(), - hostCallbackHandler = HostBillingClientCallbackHandler( - onPurchasesUpdated, alternativeBillingListener) { - InAppPurchaseCallbackApi.setup(hostCallbackHandler); + BillingClient(PurchasesUpdatedListener onPurchasesUpdated) { + channel.setMethodCallHandler(callHandler); + _callbacks[kOnPurchasesUpdated] = [ + onPurchasesUpdated + ]; } - /// Interface for calling host-side code. - final InAppPurchaseApi _hostApi; - - /// Handlers for calls from the host-side code. - @visibleForTesting - final HostBillingClientCallbackHandler hostCallbackHandler; + // Occasionally methods in the native layer require a Dart callback to be + // triggered in response to a Java callback. For example, + // [startConnection] registers an [OnBillingServiceDisconnected] callback. + // This list of names to callbacks is used to trigger Dart callbacks in + // response to those Java callbacks. Dart sends the Java layer a handle to the + // matching callback here to remember, and then once its twin is triggered it + // sends the handle back over the platform channel. We then access that handle + // in this array and call it in Dart code. See also [_callHandler]. + final Map> _callbacks = >{}; /// Calls /// [`BillingClient#isReady()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#isReady()) /// to get the ready status of the BillingClient instance. Future isReady() async { - return _hostApi.isReady(); + final bool? ready = + await channel.invokeMethod('BillingClient#isReady()'); + return ready ?? false; } /// Enable the [BillingClientWrapper] to handle pending purchases. @@ -104,13 +108,18 @@ class BillingClient { /// This triggers the creation of a new `BillingClient` instance in Java if /// one doesn't already exist. Future startConnection( - {required OnBillingServiceDisconnected onBillingServiceDisconnected, - BillingChoiceMode billingChoiceMode = - BillingChoiceMode.playBillingOnly}) async { - hostCallbackHandler.disconnectCallbacks.add(onBillingServiceDisconnected); - return resultWrapperFromPlatform(await _hostApi.startConnection( - hostCallbackHandler.disconnectCallbacks.length - 1, - platformBillingChoiceMode(billingChoiceMode))); + {required OnBillingServiceDisconnected + onBillingServiceDisconnected}) async { + final List disconnectCallbacks = + _callbacks[_kOnBillingServiceDisconnected] ??= []; + disconnectCallbacks.add(onBillingServiceDisconnected); + return BillingResultWrapper.fromJson((await channel + .invokeMapMethod( + 'BillingClient#startConnection(BillingClientStateListener)', + { + 'handle': disconnectCallbacks.length - 1, + })) ?? + {}); } /// Calls @@ -121,7 +130,7 @@ class BillingClient { /// /// This triggers the destruction of the `BillingClient` instance in Java. Future endConnection() async { - return _hostApi.endConnection(); + return channel.invokeMethod('BillingClient#endConnection()'); } /// Returns a list of [ProductDetailsResponseWrapper]s that have @@ -137,11 +146,16 @@ class BillingClient { Future queryProductDetails({ required List productList, }) async { - return productDetailsResponseWrapperFromPlatform( - await _hostApi.queryProductDetailsAsync(productList - .map((ProductWrapper product) => - platformQueryProductFromWrapper(product)) - .toList())); + final Map arguments = { + 'productList': + productList.map((ProductWrapper product) => product.toJson()).toList() + }; + return ProductDetailsResponseWrapper.fromJson( + (await channel.invokeMapMethod( + 'BillingClient#queryProductDetailsAsync(QueryProductDetailsParams, ProductDetailsResponseListener)', + arguments, + )) ?? + {}); } /// Attempt to launch the Play Billing Flow for a given [productDetails]. @@ -193,17 +207,21 @@ class BillingClient { ProrationMode? prorationMode}) async { assert((oldProduct == null) == (purchaseToken == null), 'oldProduct and purchaseToken must both be set, or both be null.'); - return resultWrapperFromPlatform( - await _hostApi.launchBillingFlow(PlatformBillingFlowParams( - product: product, - prorationMode: const ProrationModeConverter().toJson(prorationMode ?? - ProrationMode.unknownSubscriptionUpgradeDowngradePolicy), - offerToken: offerToken, - accountId: accountId, - obfuscatedProfileId: obfuscatedProfileId, - oldProduct: oldProduct, - purchaseToken: purchaseToken, - ))); + final Map arguments = { + 'product': product, + 'offerToken': offerToken, + 'accountId': accountId, + 'obfuscatedProfileId': obfuscatedProfileId, + 'oldProduct': oldProduct, + 'purchaseToken': purchaseToken, + 'prorationMode': const ProrationModeConverter().toJson(prorationMode ?? + ProrationMode.unknownSubscriptionUpgradeDowngradePolicy) + }; + return BillingResultWrapper.fromJson( + (await channel.invokeMapMethod( + 'BillingClient#launchBillingFlow(Activity, BillingFlowParams)', + arguments)) ?? + {}); } /// Fetches recent purchases for the given [ProductType]. @@ -218,24 +236,14 @@ class BillingClient { /// This wraps /// [`BillingClient#queryPurchasesAsync(QueryPurchaseParams, PurchaseResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryPurchasesAsync(com.android.billingclient.api.QueryPurchasesParams,%20com.android.billingclient.api.PurchasesResponseListener)). Future queryPurchases(ProductType productType) async { - // TODO(stuartmorgan): Investigate whether forceOkResponseCode is actually - // correct. This code preserves the behavior of the pre-Pigeon-conversion - // Java code, but the way this field is treated in PurchasesResultWrapper is - // inconsistent with ProductDetailsResponseWrapper and - // PurchasesHistoryResult, which have a getter for - // billingResult.responseCode instead of having a separate field, and the - // other use of PurchasesResultWrapper (onPurchasesUpdated) was using - // billingResult.getResponseCode() for responseCode instead of hard-coding - // OK. Several Dart unit tests had to be removed when the hard-coding logic - // was moved from Java to here because they were testing a case that the - // plugin could never actually generate, and it may well be that those tests - // were correct and the functionality they were intended to test had been - // broken by the original change to hard-code this on the Java side (instead - // of making it a forwarding getter on the Dart side). - return purchasesResultWrapperFromPlatform( - await _hostApi - .queryPurchasesAsync(platformProductTypeFromWrapper(productType)), - forceOkResponseCode: true); + return PurchasesResultWrapper.fromJson( + (await channel.invokeMapMethod( + 'BillingClient#queryPurchasesAsync(QueryPurchaseParams, PurchaseResponseListener)', + { + 'productType': const ProductTypeConverter().toJson(productType) + }, + )) ?? + {}); } /// Fetches purchase history for the given [ProductType]. @@ -252,9 +260,13 @@ class BillingClient { /// [`BillingClient#queryPurchaseHistoryAsync(QueryPurchaseHistoryParams, PurchaseHistoryResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryPurchaseHistoryAsync(com.android.billingclient.api.QueryPurchaseHistoryParams,%20com.android.billingclient.api.PurchaseHistoryResponseListener)). Future queryPurchaseHistory( ProductType productType) async { - return purchaseHistoryResultFromPlatform( - await _hostApi.queryPurchaseHistoryAsync( - platformProductTypeFromWrapper(productType))); + return PurchasesHistoryResult.fromJson((await channel.invokeMapMethod< + String, dynamic>( + 'BillingClient#queryPurchaseHistoryAsync(QueryPurchaseHistoryParams, PurchaseHistoryResponseListener)', + { + 'productType': const ProductTypeConverter().toJson(productType) + })) ?? + {}); } /// Consumes a given in-app product. @@ -265,8 +277,13 @@ class BillingClient { /// This wraps /// [`BillingClient#consumeAsync(ConsumeParams, ConsumeResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#consumeAsync(java.lang.String,%20com.android.billingclient.api.ConsumeResponseListener)) Future consumeAsync(String purchaseToken) async { - return resultWrapperFromPlatform( - await _hostApi.consumeAsync(purchaseToken)); + return BillingResultWrapper.fromJson((await channel.invokeMapMethod( + 'BillingClient#consumeAsync(ConsumeParams, ConsumeResponseListener)', + { + 'purchaseToken': purchaseToken, + })) ?? + {}); } /// Acknowledge an in-app purchase. @@ -288,81 +305,44 @@ class BillingClient { /// This wraps /// [`BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#acknowledgePurchase(com.android.billingclient.api.AcknowledgePurchaseParams,%20com.android.billingclient.api.AcknowledgePurchaseResponseListener)) Future acknowledgePurchase(String purchaseToken) async { - return resultWrapperFromPlatform( - await _hostApi.acknowledgePurchase(purchaseToken)); + return BillingResultWrapper.fromJson((await channel.invokeMapMethod( + 'BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)', + { + 'purchaseToken': purchaseToken, + })) ?? + {}); } /// Checks if the specified feature or capability is supported by the Play Store. /// Call this to check if a [BillingClientFeature] is supported by the device. Future isFeatureSupported(BillingClientFeature feature) async { - return _hostApi.isFeatureSupported( - const BillingClientFeatureConverter().toJson(feature)); - } - - /// Fetches billing config info into a [BillingConfigWrapper] object. - Future getBillingConfig() async { - return billingConfigWrapperFromPlatform( - await _hostApi.getBillingConfigAsync()); + final bool? result = await channel.invokeMethod( + 'BillingClient#isFeatureSupported(String)', { + 'feature': const BillingClientFeatureConverter().toJson(feature), + }); + return result ?? false; } - /// Checks if "AlterntitiveBillingOnly" feature is available. - Future isAlternativeBillingOnlyAvailable() async { - return resultWrapperFromPlatform( - await _hostApi.isAlternativeBillingOnlyAvailableAsync()); - } - - /// Shows the alternative billing only information dialog on top of the calling app. - Future - showAlternativeBillingOnlyInformationDialog() async { - return resultWrapperFromPlatform( - await _hostApi.showAlternativeBillingOnlyInformationDialog()); - } - - /// The details used to report transactions made via alternative billing - /// without user choice to use Google Play billing. - Future - createAlternativeBillingOnlyReportingDetails() async { - return alternativeBillingOnlyReportingDetailsWrapperFromPlatform( - await _hostApi.createAlternativeBillingOnlyReportingDetailsAsync()); - } -} - -/// Implementation of InAppPurchaseCallbackApi, for use by [BillingClient]. -/// -/// Actual Dart callback functions are stored here, indexed by the handle -/// provided to the host side when setting up the connection in non-singleton -/// cases. When a callback is triggered from the host side, the corresponding -/// Dart function is invoked. -@visibleForTesting -class HostBillingClientCallbackHandler implements InAppPurchaseCallbackApi { - /// Creates a new handler with the given singleton handlers, and no - /// per-connection handlers. - HostBillingClientCallbackHandler( - this.purchasesUpdatedCallback, this.alternativeBillingListener); - - /// The handler for PurchasesUpdatedListener#onPurchasesUpdated. - final PurchasesUpdatedListener purchasesUpdatedCallback; - - /// The handler for UserChoiceBillingListener#userSelectedAlternativeBilling. - UserSelectedAlternativeBillingListener? alternativeBillingListener; - - /// Handlers for onBillingServiceDisconnected, indexed by handle identifier. - final List disconnectCallbacks = - []; - - @override - void onBillingServiceDisconnected(int callbackHandle) { - disconnectCallbacks[callbackHandle](); - } - - @override - void onPurchasesUpdated(PlatformPurchasesResponse update) { - purchasesUpdatedCallback(purchasesResultWrapperFromPlatform(update)); - } - - @override - void userSelectedalternativeBilling(PlatformUserChoiceDetails details) { - alternativeBillingListener!(userChoiceDetailsFromPlatform(details)); + /// The method call handler for [channel]. + @visibleForTesting + Future callHandler(MethodCall call) async { + switch (call.method) { + case kOnPurchasesUpdated: + // The purchases updated listener is a singleton. + assert(_callbacks[kOnPurchasesUpdated]!.length == 1); + final PurchasesUpdatedListener listener = + _callbacks[kOnPurchasesUpdated]!.first as PurchasesUpdatedListener; + listener(PurchasesResultWrapper.fromJson( + (call.arguments as Map).cast())); + case _kOnBillingServiceDisconnected: + final int handle = + (call.arguments as Map)['handle']! as int; + final List onDisconnected = + _callbacks[_kOnBillingServiceDisconnected]! + .cast(); + onDisconnected[handle](); + } } } @@ -393,7 +373,7 @@ enum BillingResponse { @JsonValue(-2) featureNotSupported, - /// The Play Store service is not connected now - potentially transient state. + /// The play Store service is not connected now - potentially transient state. @JsonValue(-1) serviceDisconnected, @@ -438,54 +418,6 @@ enum BillingResponse { networkError, } -/// Plugin concept to cover billing modes. -/// -/// [playBillingOnly] (google Play billing only). -/// [alternativeBillingOnly] (app provided billing with reporting to Play). -@JsonEnum(alwaysCreate: true) -enum BillingChoiceMode { - // WARNING: Changes to this class need to be reflected in our generated code. - // Run `flutter packages pub run build_runner watch` to rebuild and watch for - // further changes. - // Values must match what is used in - // in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java - - /// Billing through google Play. Default state. - @JsonValue(0) - playBillingOnly, - - /// Billing through app provided flow. - @JsonValue(1) - alternativeBillingOnly, - - /// Users can choose Play billing or alternative billing. - @JsonValue(2) - userChoiceBilling, -} - -/// Serializer for [BillingChoiceMode]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@BillingChoiceModeConverter()`. -class BillingChoiceModeConverter - implements JsonConverter { - /// Default const constructor. - const BillingChoiceModeConverter(); - - @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - BillingChoiceMode fromJson(int? json) { - if (json == null) { - return BillingChoiceMode.playBillingOnly; - } - return $enumDecode(_$BillingChoiceModeEnumMap, json); - } - - @override - int toJson(BillingChoiceMode object) => _$BillingChoiceModeEnumMap[object]!; -} - /// Serializer for [BillingResponse]. /// /// Use these in `@JsonSerializable()` classes by annotating them with @@ -495,8 +427,6 @@ class BillingResponseConverter implements JsonConverter { const BillingResponseConverter(); @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') BillingResponse fromJson(int? json) { if (json == null) { return BillingResponse.error; @@ -537,8 +467,6 @@ class ProductTypeConverter implements JsonConverter { const ProductTypeConverter(); @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') ProductType fromJson(String? json) { if (json == null) { return ProductType.inapp; @@ -609,8 +537,6 @@ class ProrationModeConverter implements JsonConverter { const ProrationModeConverter(); @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') ProrationMode fromJson(int? json) { if (json == null) { return ProrationMode.unknownSubscriptionUpgradeDowngradePolicy; @@ -666,8 +592,6 @@ class BillingClientFeatureConverter const BillingClientFeatureConverter(); @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') BillingClientFeature fromJson(String json) { return $enumDecode( _$BillingClientFeatureEnumMap.cast(), diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart index 0768c35de..f0f99ee55 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart @@ -22,12 +22,6 @@ const _$BillingResponseEnumMap = { BillingResponse.networkError: 12, }; -const _$BillingChoiceModeEnumMap = { - BillingChoiceMode.playBillingOnly: 0, - BillingChoiceMode.alternativeBillingOnly: 1, - BillingChoiceMode.userChoiceBilling: 2, -}; - const _$ProductTypeEnumMap = { ProductType.inapp: 'inapp', ProductType.subs: 'subs', diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.dart deleted file mode 100644 index 3020a120e..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.dart +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/material.dart'; -import 'package:json_annotation/json_annotation.dart'; - -import '../../billing_client_wrappers.dart'; - -// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the -// below generated file. Run `flutter packages pub run build_runner watch` to -// rebuild and watch for further changes. -part 'billing_config_wrapper.g.dart'; - -/// The error message shown when the map represents billing config is invalid from method channel. -/// -/// This usually indicates a serious underlining code issue in the plugin. -@visibleForTesting -const String kInvalidBillingConfigErrorMessage = - 'Invalid billing config map from method channel.'; - -/// Params containing the response code and the debug message from the Play Billing API response. -@JsonSerializable() -@BillingResponseConverter() -@immutable -class BillingConfigWrapper implements HasBillingResponse { - /// Constructs the object with [responseCode] and [debugMessage]. - const BillingConfigWrapper( - {required this.responseCode, this.debugMessage, this.countryCode = ''}); - - /// Constructs an instance of this from a key value map of data. - /// - /// The map needs to have named string keys with values matching the names and - /// types of all of the members on this class. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory BillingConfigWrapper.fromJson(Map? map) { - if (map == null || map.isEmpty) { - return const BillingConfigWrapper( - responseCode: BillingResponse.error, - debugMessage: kInvalidBillingConfigErrorMessage, - ); - } - return _$BillingConfigWrapperFromJson(map); - } - - /// Response code returned in the Play Billing API calls. - @override - final BillingResponse responseCode; - - /// Debug message returned in the Play Billing API calls. - /// - /// Defaults to `null`. - /// This message uses an en-US locale and should not be shown to users. - @JsonKey(defaultValue: '') - final String? debugMessage; - - /// https://developer.android.com/reference/com/android/billingclient/api/BillingConfig#getCountryCode() - @JsonKey(defaultValue: '') - final String countryCode; - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) { - return false; - } - - return other is BillingConfigWrapper && - other.responseCode == responseCode && - other.debugMessage == debugMessage && - other.countryCode == countryCode; - } - - @override - int get hashCode => Object.hash(responseCode, debugMessage, countryCode); -} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.g.dart deleted file mode 100644 index 21f98577d..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.g.dart +++ /dev/null @@ -1,15 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'billing_config_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -BillingConfigWrapper _$BillingConfigWrapperFromJson(Map json) => - BillingConfigWrapper( - responseCode: const BillingResponseConverter() - .fromJson(json['responseCode'] as int?), - debugMessage: json['debugMessage'] as String? ?? '', - countryCode: json['countryCode'] as String? ?? '', - ); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.dart index 5faf19764..62887b00d 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.dart @@ -31,8 +31,6 @@ class BillingResultWrapper implements HasBillingResponse { /// /// The map needs to have named string keys with values matching the names and /// types of all of the members on this class. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') factory BillingResultWrapper.fromJson(Map? map) { if (map == null || map.isEmpty) { return const BillingResultWrapper( @@ -50,8 +48,6 @@ class BillingResultWrapper implements HasBillingResponse { /// /// Defaults to `null`. /// This message uses an en-US locale and should not be shown to users. - // TODO(stuartmorgan): Make this non-nullable, since the underlying native - // object's property is annotated as @NonNull. final String? debugMessage; @override diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.dart index f22184958..f5ceed22e 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.dart @@ -17,6 +17,7 @@ part 'one_time_purchase_offer_details_wrapper.g.dart'; @immutable class OneTimePurchaseOfferDetailsWrapper { /// Creates a [OneTimePurchaseOfferDetailsWrapper]. + @visibleForTesting const OneTimePurchaseOfferDetailsWrapper({ required this.formattedPrice, required this.priceAmountMicros, @@ -25,8 +26,6 @@ class OneTimePurchaseOfferDetailsWrapper { /// Factory for creating a [OneTimePurchaseOfferDetailsWrapper] from a [Map] /// with the offer details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') factory OneTimePurchaseOfferDetailsWrapper.fromJson( Map map) => _$OneTimePurchaseOfferDetailsWrapperFromJson(map); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.dart index 43303d4d2..2a7c279b3 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.dart @@ -21,6 +21,7 @@ part 'product_details_wrapper.g.dart'; @immutable class ProductDetailsWrapper { /// Creates a [ProductDetailsWrapper] with the given purchase details. + @visibleForTesting const ProductDetailsWrapper({ required this.description, required this.name, @@ -33,8 +34,6 @@ class ProductDetailsWrapper { /// Factory for creating a [ProductDetailsWrapper] from a [Map] with the /// product details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') factory ProductDetailsWrapper.fromJson(Map map) => _$ProductDetailsWrapperFromJson(map); @@ -124,8 +123,6 @@ class ProductDetailsResponseWrapper implements HasBillingResponse { /// /// The map needs to have named string keys with values matching the names and /// types of all of the members on this class. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') factory ProductDetailsResponseWrapper.fromJson(Map map) => _$ProductDetailsResponseWrapperFromJson(map); @@ -181,8 +178,6 @@ class RecurrenceModeConverter implements JsonConverter { const RecurrenceModeConverter(); @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') RecurrenceMode fromJson(int? json) { if (json == null) { return RecurrenceMode.nonRecurring; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart index 8d17d81c9..97fde8a87 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart @@ -25,6 +25,7 @@ part 'purchase_wrapper.g.dart'; @immutable class PurchaseWrapper { /// Creates a purchase wrapper with the given purchase details. + @visibleForTesting const PurchaseWrapper({ required this.orderId, required this.packageName, @@ -42,8 +43,6 @@ class PurchaseWrapper { }); /// Factory for creating a [PurchaseWrapper] from a [Map] with the purchase details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') factory PurchaseWrapper.fromJson(Map map) => _$PurchaseWrapperFromJson(map); @@ -173,6 +172,7 @@ class PurchaseWrapper { @immutable class PurchaseHistoryRecordWrapper { /// Creates a [PurchaseHistoryRecordWrapper] with the given record details. + @visibleForTesting const PurchaseHistoryRecordWrapper({ required this.purchaseTime, required this.purchaseToken, @@ -183,8 +183,6 @@ class PurchaseHistoryRecordWrapper { }); /// Factory for creating a [PurchaseHistoryRecordWrapper] from a [Map] with the record details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') factory PurchaseHistoryRecordWrapper.fromJson(Map map) => _$PurchaseHistoryRecordWrapperFromJson(map); @@ -265,8 +263,6 @@ class PurchasesResultWrapper implements HasBillingResponse { required this.purchasesList}); /// Factory for creating a [PurchaseResultWrapper] from a [Map] with the result details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') factory PurchasesResultWrapper.fromJson(Map map) => _$PurchasesResultWrapperFromJson(map); @@ -317,8 +313,6 @@ class PurchasesHistoryResult implements HasBillingResponse { {required this.billingResult, required this.purchaseHistoryRecordList}); /// Factory for creating a [PurchasesHistoryResult] from a [Map] with the history result details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') factory PurchasesHistoryResult.fromJson(Map map) => _$PurchasesHistoryResultFromJson(map); @@ -393,8 +387,6 @@ class PurchaseStateConverter const PurchaseStateConverter(); @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') PurchaseStateWrapper fromJson(int? json) { if (json == null) { return PurchaseStateWrapper.unspecified_state; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.dart index 4b5642ed0..aa5688eb6 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.dart @@ -20,6 +20,7 @@ part 'subscription_offer_details_wrapper.g.dart'; @immutable class SubscriptionOfferDetailsWrapper { /// Creates a [SubscriptionOfferDetailsWrapper]. + @visibleForTesting const SubscriptionOfferDetailsWrapper({ required this.basePlanId, this.offerId, @@ -30,8 +31,6 @@ class SubscriptionOfferDetailsWrapper { /// Factory for creating a [SubscriptionOfferDetailsWrapper] from a [Map] /// with the offer details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') factory SubscriptionOfferDetailsWrapper.fromJson(Map map) => _$SubscriptionOfferDetailsWrapperFromJson(map); @@ -91,6 +90,7 @@ class SubscriptionOfferDetailsWrapper { @immutable class PricingPhaseWrapper { /// Creates a new [PricingPhaseWrapper] from the supplied info. + @visibleForTesting const PricingPhaseWrapper({ required this.billingCycleCount, required this.billingPeriod, @@ -101,8 +101,6 @@ class PricingPhaseWrapper { }); /// Factory for creating a [PricingPhaseWrapper] from a [Map] with the phase details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') factory PricingPhaseWrapper.fromJson(Map map) => _$PricingPhaseWrapperFromJson(map); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.dart deleted file mode 100644 index 8b23058c1..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.dart +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/foundation.dart'; -import 'package:json_annotation/json_annotation.dart'; - -import '../../billing_client_wrappers.dart'; - -// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the -// below generated file. Run `flutter packages pub run build_runner watch` to -// rebuild and watch for further changes. -part 'user_choice_details_wrapper.g.dart'; - -/// This wraps [`com.android.billingclient.api.UserChoiceDetails`](https://developer.android.com/reference/com/android/billingclient/api/UserChoiceDetails) -// See https://docs.flutter.dev/data-and-backend/serialization/json#generating-code-for-nested-classes -// for explination for why this uses explicitToJson. -@JsonSerializable(createToJson: true, explicitToJson: true) -@immutable -class UserChoiceDetailsWrapper { - /// Creates a purchase wrapper with the given purchase details. - const UserChoiceDetailsWrapper({ - required this.originalExternalTransactionId, - required this.externalTransactionToken, - required this.products, - }); - - /// Factory for creating a [UserChoiceDetailsWrapper] from a [Map] with - /// the user choice details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory UserChoiceDetailsWrapper.fromJson(Map map) => - _$UserChoiceDetailsWrapperFromJson(map); - - /// Creates a JSON representation of this product. - Map toJson() => _$UserChoiceDetailsWrapperToJson(this); - - @override - bool operator ==(Object other) { - if (identical(other, this)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - return other is UserChoiceDetailsWrapper && - other.originalExternalTransactionId == originalExternalTransactionId && - other.externalTransactionToken == externalTransactionToken && - listEquals(other.products, products); - } - - @override - int get hashCode => Object.hash( - originalExternalTransactionId, - externalTransactionToken, - products.hashCode, - ); - - /// Returns the external transaction Id of the originating subscription, if - /// the purchase is a subscription upgrade/downgrade. - @JsonKey(defaultValue: '') - final String originalExternalTransactionId; - - /// Returns a token that represents the user's prospective purchase via - /// user choice alternative billing. - @JsonKey(defaultValue: '') - final String externalTransactionToken; - - /// Returns a list of [UserChoiceDetailsProductWrapper] to be purchased in - /// the user choice alternative billing flow. - @JsonKey(defaultValue: []) - final List products; -} - -/// Data structure representing a UserChoiceDetails product. -/// -/// This wraps [`com.android.billingclient.api.UserChoiceDetails.Product`](https://developer.android.com/reference/com/android/billingclient/api/UserChoiceDetails.Product) -// -// See https://docs.flutter.dev/data-and-backend/serialization/json#generating-code-for-nested-classes -// for explination for why this uses explicitToJson. -@JsonSerializable(createToJson: true, explicitToJson: true) -@ProductTypeConverter() -@immutable -class UserChoiceDetailsProductWrapper { - /// Creates a [UserChoiceDetailsProductWrapper] with the given record details. - const UserChoiceDetailsProductWrapper({ - required this.id, - required this.offerToken, - required this.productType, - }); - - /// Factory for creating a [UserChoiceDetailsProductWrapper] from a [Map] with the record details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory UserChoiceDetailsProductWrapper.fromJson(Map map) => - _$UserChoiceDetailsProductWrapperFromJson(map); - - /// Creates a JSON representation of this product. - Map toJson() => - _$UserChoiceDetailsProductWrapperToJson(this); - - /// Returns the id of the product being purchased. - @JsonKey(defaultValue: '') - final String id; - - /// Returns the offer token that was passed in launchBillingFlow to purchase the product. - @JsonKey(defaultValue: '') - final String offerToken; - - /// Returns the [ProductType] of the product being purchased. - final ProductType productType; - - @override - bool operator ==(Object other) { - if (identical(other, this)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - return other is UserChoiceDetailsProductWrapper && - other.id == id && - other.offerToken == offerToken && - other.productType == productType; - } - - @override - int get hashCode => Object.hash( - id, - offerToken, - productType, - ); -} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.g.dart deleted file mode 100644 index 669d26358..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.g.dart +++ /dev/null @@ -1,45 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'user_choice_details_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -UserChoiceDetailsWrapper _$UserChoiceDetailsWrapperFromJson(Map json) => - UserChoiceDetailsWrapper( - originalExternalTransactionId: - json['originalExternalTransactionId'] as String? ?? '', - externalTransactionToken: - json['externalTransactionToken'] as String? ?? '', - products: (json['products'] as List?) - ?.map((e) => UserChoiceDetailsProductWrapper.fromJson( - Map.from(e as Map))) - .toList() ?? - [], - ); - -Map _$UserChoiceDetailsWrapperToJson( - UserChoiceDetailsWrapper instance) => - { - 'originalExternalTransactionId': instance.originalExternalTransactionId, - 'externalTransactionToken': instance.externalTransactionToken, - 'products': instance.products.map((e) => e.toJson()).toList(), - }; - -UserChoiceDetailsProductWrapper _$UserChoiceDetailsProductWrapperFromJson( - Map json) => - UserChoiceDetailsProductWrapper( - id: json['id'] as String? ?? '', - offerToken: json['offerToken'] as String? ?? '', - productType: - const ProductTypeConverter().fromJson(json['productType'] as String?), - ); - -Map _$UserChoiceDetailsProductWrapperToJson( - UserChoiceDetailsProductWrapper instance) => - { - 'id': instance.id, - 'offerToken': instance.offerToken, - 'productType': const ProductTypeConverter().toJson(instance.productType), - }; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/channel.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/channel.dart new file mode 100644 index 000000000..f8ab4d48b --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/channel.dart @@ -0,0 +1,9 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; + +/// Method channel for the plugin's platform<-->Dart calls. +const MethodChannel channel = + MethodChannel('plugins.flutter.io/in_app_purchase'); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart index 9b9e2f1b1..2e4ed5b0f 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart @@ -4,14 +4,12 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../billing_client_wrappers.dart'; import '../in_app_purchase_android.dart'; -import 'billing_client_wrappers/billing_config_wrapper.dart'; /// [IAPError.code] code for failed purchases. const String kPurchaseErrorCode = 'purchase_error'; @@ -30,12 +28,7 @@ const String kIAPSource = 'google_play'; /// This translates various `BillingClient` calls and responses into the /// generic plugin API. class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { - /// Creates a new InAppPurchaseAndroidPlatform instance, and configures it - /// for use. - @visibleForTesting - InAppPurchaseAndroidPlatform( - {@visibleForTesting BillingClientManager? manager}) - : billingClientManager = manager ?? BillingClientManager() { + InAppPurchaseAndroidPlatform._() { // Register [InAppPurchaseAndroidPlatformAddition]. InAppPurchasePlatformAddition.instance = InAppPurchaseAndroidPlatformAddition(billingClientManager); @@ -49,7 +42,7 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { static void registerPlatform() { // Register the platform instance with the plugin platform // interface. - InAppPurchasePlatform.instance = InAppPurchaseAndroidPlatform(); + InAppPurchasePlatform.instance = InAppPurchaseAndroidPlatform._(); } final StreamController> _purchaseUpdatedController = @@ -63,7 +56,7 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { /// /// This field should not be used out of test code. @visibleForTesting - final BillingClientManager billingClientManager; + final BillingClientManager billingClientManager = BillingClientManager(); static final Set _productIdsToConsume = {}; @@ -312,14 +305,4 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { ]; } } - - /// Returns Play billing country code based on ISO-3166-1 alpha2 format. - /// - /// See: https://developer.android.com/reference/com/android/billingclient/api/BillingConfig - /// See: https://unicode.org/cldr/charts/latest/supplemental/territory_containment_un_m_49.html - Future getCountryCode() async { - final BillingConfigWrapper billingConfig = await billingClientManager - .runWithClient((BillingClient client) => client.getBillingConfig()); - return billingConfig.countryCode; - } } diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart index c0ca171ad..e3050f888 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart @@ -2,34 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - import 'package:flutter/services.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../billing_client_wrappers.dart'; import '../in_app_purchase_android.dart'; -import 'billing_client_wrappers/billing_config_wrapper.dart'; -import 'types/translator.dart'; /// Contains InApp Purchase features that are only available on PlayStore. class InAppPurchaseAndroidPlatformAddition extends InAppPurchasePlatformAddition { /// Creates a [InAppPurchaseAndroidPlatformAddition] which uses the supplied /// `BillingClientManager` to provide Android specific features. - InAppPurchaseAndroidPlatformAddition(this._billingClientManager) { - _billingClientManager.userChoiceDetailsStream - .map(Translator.convertToUserChoiceDetails) - .listen(_userChoiceDetailsStreamController.add); - } - - final StreamController - _userChoiceDetailsStreamController = - StreamController.broadcast(); - - /// [GooglePlayUserChoiceDetails] emits each time user selects alternative billing. - late final Stream userChoiceDetailsStream = - _userChoiceDetailsStreamController.stream; + InAppPurchaseAndroidPlatformAddition(this._billingClientManager); /// Whether pending purchase is enabled. /// @@ -162,62 +146,4 @@ class InAppPurchaseAndroidPlatformAddition (BillingClient client) => client.isFeatureSupported(feature), ); } - - /// Returns Play billing country code based on ISO-3166-1 alpha2 format. - /// - /// See: https://developer.android.com/reference/com/android/billingclient/api/BillingConfig - /// See: https://unicode.org/cldr/charts/latest/supplemental/territory_containment_un_m_49.html - @Deprecated('Use InAppPurchasePlatfrom.getCountryCode') - Future getCountryCode() async { - final BillingConfigWrapper billingConfig = await _billingClientManager - .runWithClient((BillingClient client) => client.getBillingConfig()); - return billingConfig.countryCode; - } - - /// Returns if the caller can use alternative billing only without giving the - /// user a choice to use Play billing. - /// - /// See: https://developer.android.com/reference/com/android/billingclient/api/BillingClient#isAlternativeBillingOnlyAvailableAsync(com.android.billingclient.api.AlternativeBillingOnlyAvailabilityListener) - Future isAlternativeBillingOnlyAvailable() async { - final BillingResultWrapper wrapper = - await _billingClientManager.runWithClient((BillingClient client) => - client.isAlternativeBillingOnlyAvailable()); - return wrapper; - } - - /// Shows the alternative billing only information dialog on top of the calling app. - /// - /// See: https://developer.android.com/reference/com/android/billingclient/api/BillingClient#showAlternativeBillingOnlyInformationDialog(android.app.Activity,%20com.android.billingclient.api.AlternativeBillingOnlyInformationDialogListener) - Future - showAlternativeBillingOnlyInformationDialog() async { - final BillingResultWrapper wrapper = - await _billingClientManager.runWithClient((BillingClient client) => - client.showAlternativeBillingOnlyInformationDialog()); - return wrapper; - } - - /// The details used to report transactions made via alternative billing - /// without user choice to use Google Play billing. - /// - /// See: https://developer.android.com/reference/com/android/billingclient/api/AlternativeBillingOnlyReportingDetails - Future - createAlternativeBillingOnlyReportingDetails() async { - final AlternativeBillingOnlyReportingDetailsWrapper wrapper = - await _billingClientManager.runWithClient((BillingClient client) => - client.createAlternativeBillingOnlyReportingDetails()); - return wrapper; - } - - /// Disconnects, sets AlternativeBillingOnly to true, and reconnects to - /// the [BillingClient]. - /// - /// [BillingChoiceMode.playBillingOnly] is the default state used. - /// [BillingChoiceMode.alternativeBillingOnly] will enable alternative billing only. - /// - /// Play apis have requirements for when this method can be called. - /// See: https://developer.android.com/google/play/billing/alternative/alternative-billing-without-user-choice-in-app - Future setBillingChoice(BillingChoiceMode billingChoiceMode) { - return _billingClientManager - .reconnectWithBillingChoiceMode(billingChoiceMode); - } } diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/messages.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/messages.g.dart deleted file mode 100644 index 1b5753f80..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/messages.g.dart +++ /dev/null @@ -1,1407 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// Autogenerated from Pigeon (v17.1.2), do not edit directly. -// See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers - -import 'dart:async'; -import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; - -import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; -import 'package:flutter/services.dart'; - -PlatformException _createConnectionError(String channelName) { - return PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel: "$channelName".', - ); -} - -List wrapResponse( - {Object? result, PlatformException? error, bool empty = false}) { - if (empty) { - return []; - } - if (error == null) { - return [result]; - } - return [error.code, error.message, error.details]; -} - -/// Pigeon version of Java BillingClient.ProductType. -enum PlatformProductType { - inapp, - subs, -} - -/// Pigeon version of billing_client_wrapper.dart's BillingChoiceMode. -enum PlatformBillingChoiceMode { - /// Billing through google play. - /// - /// Default state. - playBillingOnly, - - /// Billing through app provided flow. - alternativeBillingOnly, - - /// Users can choose Play billing or alternative billing. - userChoiceBilling, -} - -/// Pigeon version of Java Purchase.PurchaseState. -enum PlatformPurchaseState { - unspecified, - purchased, - pending, -} - -/// Pigeon version of Java ProductDetails.RecurrenceMode. -enum PlatformRecurrenceMode { - finiteRecurring, - infiniteRecurring, - nonRecurring, -} - -/// Pigeon version of Java QueryProductDetailsParams.Product. -class PlatformQueryProduct { - PlatformQueryProduct({ - required this.productId, - required this.productType, - }); - - String productId; - - PlatformProductType productType; - - Object encode() { - return [ - productId, - productType.index, - ]; - } - - static PlatformQueryProduct decode(Object result) { - result as List; - return PlatformQueryProduct( - productId: result[0]! as String, - productType: PlatformProductType.values[result[1]! as int], - ); - } -} - -/// Pigeon version of Java AccountIdentifiers. -class PlatformAccountIdentifiers { - PlatformAccountIdentifiers({ - this.obfuscatedAccountId, - this.obfuscatedProfileId, - }); - - String? obfuscatedAccountId; - - String? obfuscatedProfileId; - - Object encode() { - return [ - obfuscatedAccountId, - obfuscatedProfileId, - ]; - } - - static PlatformAccountIdentifiers decode(Object result) { - result as List; - return PlatformAccountIdentifiers( - obfuscatedAccountId: result[0] as String?, - obfuscatedProfileId: result[1] as String?, - ); - } -} - -/// Pigeon version of Java BillingResult. -class PlatformBillingResult { - PlatformBillingResult({ - required this.responseCode, - required this.debugMessage, - }); - - int responseCode; - - String debugMessage; - - Object encode() { - return [ - responseCode, - debugMessage, - ]; - } - - static PlatformBillingResult decode(Object result) { - result as List; - return PlatformBillingResult( - responseCode: result[0]! as int, - debugMessage: result[1]! as String, - ); - } -} - -/// Pigeon version of Java ProductDetails.OneTimePurchaseOfferDetails. -class PlatformOneTimePurchaseOfferDetails { - PlatformOneTimePurchaseOfferDetails({ - required this.priceAmountMicros, - required this.formattedPrice, - required this.priceCurrencyCode, - }); - - int priceAmountMicros; - - String formattedPrice; - - String priceCurrencyCode; - - Object encode() { - return [ - priceAmountMicros, - formattedPrice, - priceCurrencyCode, - ]; - } - - static PlatformOneTimePurchaseOfferDetails decode(Object result) { - result as List; - return PlatformOneTimePurchaseOfferDetails( - priceAmountMicros: result[0]! as int, - formattedPrice: result[1]! as String, - priceCurrencyCode: result[2]! as String, - ); - } -} - -/// Pigeon version of Java ProductDetails. -class PlatformProductDetails { - PlatformProductDetails({ - required this.description, - required this.name, - required this.productId, - required this.productType, - required this.title, - this.oneTimePurchaseOfferDetails, - this.subscriptionOfferDetails, - }); - - String description; - - String name; - - String productId; - - PlatformProductType productType; - - String title; - - PlatformOneTimePurchaseOfferDetails? oneTimePurchaseOfferDetails; - - List? subscriptionOfferDetails; - - Object encode() { - return [ - description, - name, - productId, - productType.index, - title, - oneTimePurchaseOfferDetails?.encode(), - subscriptionOfferDetails, - ]; - } - - static PlatformProductDetails decode(Object result) { - result as List; - return PlatformProductDetails( - description: result[0]! as String, - name: result[1]! as String, - productId: result[2]! as String, - productType: PlatformProductType.values[result[3]! as int], - title: result[4]! as String, - oneTimePurchaseOfferDetails: result[5] != null - ? PlatformOneTimePurchaseOfferDetails.decode( - result[5]! as List) - : null, - subscriptionOfferDetails: (result[6] as List?) - ?.cast(), - ); - } -} - -/// Pigeon version of ProductDetailsResponseWrapper, which contains the -/// components of the Java ProductDetailsResponseListener callback. -class PlatformProductDetailsResponse { - PlatformProductDetailsResponse({ - required this.billingResult, - required this.productDetails, - }); - - PlatformBillingResult billingResult; - - List productDetails; - - Object encode() { - return [ - billingResult.encode(), - productDetails, - ]; - } - - static PlatformProductDetailsResponse decode(Object result) { - result as List; - return PlatformProductDetailsResponse( - billingResult: PlatformBillingResult.decode(result[0]! as List), - productDetails: - (result[1] as List?)!.cast(), - ); - } -} - -/// Pigeon version of AlternativeBillingOnlyReportingDetailsWrapper, which -/// contains the components of the Java -/// AlternativeBillingOnlyReportingDetailsListener callback. -class PlatformAlternativeBillingOnlyReportingDetailsResponse { - PlatformAlternativeBillingOnlyReportingDetailsResponse({ - required this.billingResult, - required this.externalTransactionToken, - }); - - PlatformBillingResult billingResult; - - String externalTransactionToken; - - Object encode() { - return [ - billingResult.encode(), - externalTransactionToken, - ]; - } - - static PlatformAlternativeBillingOnlyReportingDetailsResponse decode( - Object result) { - result as List; - return PlatformAlternativeBillingOnlyReportingDetailsResponse( - billingResult: PlatformBillingResult.decode(result[0]! as List), - externalTransactionToken: result[1]! as String, - ); - } -} - -/// Pigeon version of BillingConfigWrapper, which contains the components of the -/// Java BillingConfigResponseListener callback. -class PlatformBillingConfigResponse { - PlatformBillingConfigResponse({ - required this.billingResult, - required this.countryCode, - }); - - PlatformBillingResult billingResult; - - String countryCode; - - Object encode() { - return [ - billingResult.encode(), - countryCode, - ]; - } - - static PlatformBillingConfigResponse decode(Object result) { - result as List; - return PlatformBillingConfigResponse( - billingResult: PlatformBillingResult.decode(result[0]! as List), - countryCode: result[1]! as String, - ); - } -} - -/// Pigeon version of Java BillingFlowParams. -class PlatformBillingFlowParams { - PlatformBillingFlowParams({ - required this.product, - required this.prorationMode, - this.offerToken, - this.accountId, - this.obfuscatedProfileId, - this.oldProduct, - this.purchaseToken, - }); - - String product; - - int prorationMode; - - String? offerToken; - - String? accountId; - - String? obfuscatedProfileId; - - String? oldProduct; - - String? purchaseToken; - - Object encode() { - return [ - product, - prorationMode, - offerToken, - accountId, - obfuscatedProfileId, - oldProduct, - purchaseToken, - ]; - } - - static PlatformBillingFlowParams decode(Object result) { - result as List; - return PlatformBillingFlowParams( - product: result[0]! as String, - prorationMode: result[1]! as int, - offerToken: result[2] as String?, - accountId: result[3] as String?, - obfuscatedProfileId: result[4] as String?, - oldProduct: result[5] as String?, - purchaseToken: result[6] as String?, - ); - } -} - -/// Pigeon version of Java ProductDetails.PricingPhase. -class PlatformPricingPhase { - PlatformPricingPhase({ - required this.billingCycleCount, - required this.recurrenceMode, - required this.priceAmountMicros, - required this.billingPeriod, - required this.formattedPrice, - required this.priceCurrencyCode, - }); - - int billingCycleCount; - - PlatformRecurrenceMode recurrenceMode; - - int priceAmountMicros; - - String billingPeriod; - - String formattedPrice; - - String priceCurrencyCode; - - Object encode() { - return [ - billingCycleCount, - recurrenceMode.index, - priceAmountMicros, - billingPeriod, - formattedPrice, - priceCurrencyCode, - ]; - } - - static PlatformPricingPhase decode(Object result) { - result as List; - return PlatformPricingPhase( - billingCycleCount: result[0]! as int, - recurrenceMode: PlatformRecurrenceMode.values[result[1]! as int], - priceAmountMicros: result[2]! as int, - billingPeriod: result[3]! as String, - formattedPrice: result[4]! as String, - priceCurrencyCode: result[5]! as String, - ); - } -} - -/// Pigeon version of Java Purchase. -/// -/// See also PurchaseWrapper on the Dart side. -class PlatformPurchase { - PlatformPurchase({ - this.orderId, - required this.packageName, - required this.purchaseTime, - required this.purchaseToken, - required this.signature, - required this.products, - required this.isAutoRenewing, - required this.originalJson, - required this.developerPayload, - required this.isAcknowledged, - required this.quantity, - required this.purchaseState, - this.accountIdentifiers, - }); - - String? orderId; - - String packageName; - - int purchaseTime; - - String purchaseToken; - - String signature; - - List products; - - bool isAutoRenewing; - - String originalJson; - - String developerPayload; - - bool isAcknowledged; - - int quantity; - - PlatformPurchaseState purchaseState; - - PlatformAccountIdentifiers? accountIdentifiers; - - Object encode() { - return [ - orderId, - packageName, - purchaseTime, - purchaseToken, - signature, - products, - isAutoRenewing, - originalJson, - developerPayload, - isAcknowledged, - quantity, - purchaseState.index, - accountIdentifiers?.encode(), - ]; - } - - static PlatformPurchase decode(Object result) { - result as List; - return PlatformPurchase( - orderId: result[0] as String?, - packageName: result[1]! as String, - purchaseTime: result[2]! as int, - purchaseToken: result[3]! as String, - signature: result[4]! as String, - products: (result[5] as List?)!.cast(), - isAutoRenewing: result[6]! as bool, - originalJson: result[7]! as String, - developerPayload: result[8]! as String, - isAcknowledged: result[9]! as bool, - quantity: result[10]! as int, - purchaseState: PlatformPurchaseState.values[result[11]! as int], - accountIdentifiers: result[12] != null - ? PlatformAccountIdentifiers.decode(result[12]! as List) - : null, - ); - } -} - -/// Pigeon version of PurchaseHistoryRecord. -/// -/// See also PurchaseHistoryRecordWrapper on the Dart side. -class PlatformPurchaseHistoryRecord { - PlatformPurchaseHistoryRecord({ - required this.quantity, - required this.purchaseTime, - this.developerPayload, - required this.originalJson, - required this.purchaseToken, - required this.signature, - required this.products, - }); - - int quantity; - - int purchaseTime; - - String? developerPayload; - - String originalJson; - - String purchaseToken; - - String signature; - - List products; - - Object encode() { - return [ - quantity, - purchaseTime, - developerPayload, - originalJson, - purchaseToken, - signature, - products, - ]; - } - - static PlatformPurchaseHistoryRecord decode(Object result) { - result as List; - return PlatformPurchaseHistoryRecord( - quantity: result[0]! as int, - purchaseTime: result[1]! as int, - developerPayload: result[2] as String?, - originalJson: result[3]! as String, - purchaseToken: result[4]! as String, - signature: result[5]! as String, - products: (result[6] as List?)!.cast(), - ); - } -} - -/// Pigeon version of PurchasesHistoryResult, which contains the components of -/// the Java PurchaseHistoryResponseListener callback. -class PlatformPurchaseHistoryResponse { - PlatformPurchaseHistoryResponse({ - required this.billingResult, - required this.purchases, - }); - - PlatformBillingResult billingResult; - - List purchases; - - Object encode() { - return [ - billingResult.encode(), - purchases, - ]; - } - - static PlatformPurchaseHistoryResponse decode(Object result) { - result as List; - return PlatformPurchaseHistoryResponse( - billingResult: PlatformBillingResult.decode(result[0]! as List), - purchases: - (result[1] as List?)!.cast(), - ); - } -} - -/// Pigeon version of PurchasesResultWrapper, which contains the components of -/// the Java PurchasesResponseListener callback. -class PlatformPurchasesResponse { - PlatformPurchasesResponse({ - required this.billingResult, - required this.purchases, - }); - - PlatformBillingResult billingResult; - - List purchases; - - Object encode() { - return [ - billingResult.encode(), - purchases, - ]; - } - - static PlatformPurchasesResponse decode(Object result) { - result as List; - return PlatformPurchasesResponse( - billingResult: PlatformBillingResult.decode(result[0]! as List), - purchases: (result[1] as List?)!.cast(), - ); - } -} - -/// Pigeon version of Java ProductDetails.SubscriptionOfferDetails. -class PlatformSubscriptionOfferDetails { - PlatformSubscriptionOfferDetails({ - required this.basePlanId, - this.offerId, - required this.offerToken, - required this.offerTags, - required this.pricingPhases, - }); - - String basePlanId; - - String? offerId; - - String offerToken; - - List offerTags; - - List pricingPhases; - - Object encode() { - return [ - basePlanId, - offerId, - offerToken, - offerTags, - pricingPhases, - ]; - } - - static PlatformSubscriptionOfferDetails decode(Object result) { - result as List; - return PlatformSubscriptionOfferDetails( - basePlanId: result[0]! as String, - offerId: result[1] as String?, - offerToken: result[2]! as String, - offerTags: (result[3] as List?)!.cast(), - pricingPhases: - (result[4] as List?)!.cast(), - ); - } -} - -/// Pigeon version of UserChoiceDetailsWrapper and Java UserChoiceDetails. -class PlatformUserChoiceDetails { - PlatformUserChoiceDetails({ - this.originalExternalTransactionId, - required this.externalTransactionToken, - required this.products, - }); - - String? originalExternalTransactionId; - - String externalTransactionToken; - - List products; - - Object encode() { - return [ - originalExternalTransactionId, - externalTransactionToken, - products, - ]; - } - - static PlatformUserChoiceDetails decode(Object result) { - result as List; - return PlatformUserChoiceDetails( - originalExternalTransactionId: result[0] as String?, - externalTransactionToken: result[1]! as String, - products: - (result[2] as List?)!.cast(), - ); - } -} - -/// Pigeon version of UserChoiseDetails.Product. -class PlatformUserChoiceProduct { - PlatformUserChoiceProduct({ - required this.id, - this.offerToken, - required this.type, - }); - - String id; - - String? offerToken; - - PlatformProductType type; - - Object encode() { - return [ - id, - offerToken, - type.index, - ]; - } - - static PlatformUserChoiceProduct decode(Object result) { - result as List; - return PlatformUserChoiceProduct( - id: result[0]! as String, - offerToken: result[1] as String?, - type: PlatformProductType.values[result[2]! as int], - ); - } -} - -class _InAppPurchaseApiCodec extends StandardMessageCodec { - const _InAppPurchaseApiCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is PlatformAccountIdentifiers) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value - is PlatformAlternativeBillingOnlyReportingDetailsResponse) { - buffer.putUint8(129); - writeValue(buffer, value.encode()); - } else if (value is PlatformBillingConfigResponse) { - buffer.putUint8(130); - writeValue(buffer, value.encode()); - } else if (value is PlatformBillingFlowParams) { - buffer.putUint8(131); - writeValue(buffer, value.encode()); - } else if (value is PlatformBillingResult) { - buffer.putUint8(132); - writeValue(buffer, value.encode()); - } else if (value is PlatformOneTimePurchaseOfferDetails) { - buffer.putUint8(133); - writeValue(buffer, value.encode()); - } else if (value is PlatformPricingPhase) { - buffer.putUint8(134); - writeValue(buffer, value.encode()); - } else if (value is PlatformProductDetails) { - buffer.putUint8(135); - writeValue(buffer, value.encode()); - } else if (value is PlatformProductDetailsResponse) { - buffer.putUint8(136); - writeValue(buffer, value.encode()); - } else if (value is PlatformPurchase) { - buffer.putUint8(137); - writeValue(buffer, value.encode()); - } else if (value is PlatformPurchaseHistoryRecord) { - buffer.putUint8(138); - writeValue(buffer, value.encode()); - } else if (value is PlatformPurchaseHistoryResponse) { - buffer.putUint8(139); - writeValue(buffer, value.encode()); - } else if (value is PlatformPurchasesResponse) { - buffer.putUint8(140); - writeValue(buffer, value.encode()); - } else if (value is PlatformQueryProduct) { - buffer.putUint8(141); - writeValue(buffer, value.encode()); - } else if (value is PlatformSubscriptionOfferDetails) { - buffer.putUint8(142); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 128: - return PlatformAccountIdentifiers.decode(readValue(buffer)!); - case 129: - return PlatformAlternativeBillingOnlyReportingDetailsResponse.decode( - readValue(buffer)!); - case 130: - return PlatformBillingConfigResponse.decode(readValue(buffer)!); - case 131: - return PlatformBillingFlowParams.decode(readValue(buffer)!); - case 132: - return PlatformBillingResult.decode(readValue(buffer)!); - case 133: - return PlatformOneTimePurchaseOfferDetails.decode(readValue(buffer)!); - case 134: - return PlatformPricingPhase.decode(readValue(buffer)!); - case 135: - return PlatformProductDetails.decode(readValue(buffer)!); - case 136: - return PlatformProductDetailsResponse.decode(readValue(buffer)!); - case 137: - return PlatformPurchase.decode(readValue(buffer)!); - case 138: - return PlatformPurchaseHistoryRecord.decode(readValue(buffer)!); - case 139: - return PlatformPurchaseHistoryResponse.decode(readValue(buffer)!); - case 140: - return PlatformPurchasesResponse.decode(readValue(buffer)!); - case 141: - return PlatformQueryProduct.decode(readValue(buffer)!); - case 142: - return PlatformSubscriptionOfferDetails.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - -class InAppPurchaseApi { - /// Constructor for [InAppPurchaseApi]. The [binaryMessenger] named argument is - /// available for dependency injection. If it is left null, the default - /// BinaryMessenger will be used which routes to the host platform. - InAppPurchaseApi({BinaryMessenger? binaryMessenger}) - : __pigeon_binaryMessenger = binaryMessenger; - final BinaryMessenger? __pigeon_binaryMessenger; - - static const MessageCodec pigeonChannelCodec = - _InAppPurchaseApiCodec(); - - /// Wraps BillingClient#isReady. - Future isReady() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.isReady'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] as bool?)!; - } - } - - /// Wraps BillingClient#startConnection(BillingClientStateListener). - Future startConnection( - int callbackHandle, PlatformBillingChoiceMode billingMode) async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.startConnection'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = await __pigeon_channel - .send([callbackHandle, billingMode.index]) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] as PlatformBillingResult?)!; - } - } - - /// Wraps BillingClient#endConnection(BillingClientStateListener). - Future endConnection() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.endConnection'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else { - return; - } - } - - /// Wraps BillingClient#getBillingConfigAsync(GetBillingConfigParams, BillingConfigResponseListener). - Future getBillingConfigAsync() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.getBillingConfigAsync'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] as PlatformBillingConfigResponse?)!; - } - } - - /// Wraps BillingClient#launchBillingFlow(Activity, BillingFlowParams). - Future launchBillingFlow( - PlatformBillingFlowParams params) async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.launchBillingFlow'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send([params]) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] as PlatformBillingResult?)!; - } - } - - /// Wraps BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener). - Future acknowledgePurchase( - String purchaseToken) async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.acknowledgePurchase'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send([purchaseToken]) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] as PlatformBillingResult?)!; - } - } - - /// Wraps BillingClient#consumeAsync(ConsumeParams, ConsumeResponseListener). - Future consumeAsync(String purchaseToken) async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.consumeAsync'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send([purchaseToken]) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] as PlatformBillingResult?)!; - } - } - - /// Wraps BillingClient#queryPurchasesAsync(QueryPurchaseParams, PurchaseResponseListener). - Future queryPurchasesAsync( - PlatformProductType productType) async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.queryPurchasesAsync'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = await __pigeon_channel - .send([productType.index]) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] as PlatformPurchasesResponse?)!; - } - } - - /// Wraps BillingClient#queryPurchaseHistoryAsync(QueryPurchaseHistoryParams, PurchaseHistoryResponseListener). - Future queryPurchaseHistoryAsync( - PlatformProductType productType) async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.queryPurchaseHistoryAsync'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = await __pigeon_channel - .send([productType.index]) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] as PlatformPurchaseHistoryResponse?)!; - } - } - - /// Wraps BillingClient#queryProductDetailsAsync(QueryProductDetailsParams, ProductDetailsResponseListener). - Future queryProductDetailsAsync( - List products) async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.queryProductDetailsAsync'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send([products]) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] as PlatformProductDetailsResponse?)!; - } - } - - /// Wraps BillingClient#isFeatureSupported(String). - Future isFeatureSupported(String feature) async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.isFeatureSupported'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send([feature]) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] as bool?)!; - } - } - - /// Wraps BillingClient#isAlternativeBillingOnlyAvailableAsync(). - Future isAlternativeBillingOnlyAvailableAsync() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.isAlternativeBillingOnlyAvailableAsync'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] as PlatformBillingResult?)!; - } - } - - /// Wraps BillingClient#showAlternativeBillingOnlyInformationDialog(). - Future - showAlternativeBillingOnlyInformationDialog() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.showAlternativeBillingOnlyInformationDialog'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] as PlatformBillingResult?)!; - } - } - - /// Wraps BillingClient#createAlternativeBillingOnlyReportingDetailsAsync(AlternativeBillingOnlyReportingDetailsListener). - Future - createAlternativeBillingOnlyReportingDetailsAsync() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.createAlternativeBillingOnlyReportingDetailsAsync'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] - as PlatformAlternativeBillingOnlyReportingDetailsResponse?)!; - } - } -} - -class _InAppPurchaseCallbackApiCodec extends StandardMessageCodec { - const _InAppPurchaseCallbackApiCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is PlatformAccountIdentifiers) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is PlatformBillingResult) { - buffer.putUint8(129); - writeValue(buffer, value.encode()); - } else if (value is PlatformPurchase) { - buffer.putUint8(130); - writeValue(buffer, value.encode()); - } else if (value is PlatformPurchasesResponse) { - buffer.putUint8(131); - writeValue(buffer, value.encode()); - } else if (value is PlatformUserChoiceDetails) { - buffer.putUint8(132); - writeValue(buffer, value.encode()); - } else if (value is PlatformUserChoiceProduct) { - buffer.putUint8(133); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 128: - return PlatformAccountIdentifiers.decode(readValue(buffer)!); - case 129: - return PlatformBillingResult.decode(readValue(buffer)!); - case 130: - return PlatformPurchase.decode(readValue(buffer)!); - case 131: - return PlatformPurchasesResponse.decode(readValue(buffer)!); - case 132: - return PlatformUserChoiceDetails.decode(readValue(buffer)!); - case 133: - return PlatformUserChoiceProduct.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - -abstract class InAppPurchaseCallbackApi { - static const MessageCodec pigeonChannelCodec = - _InAppPurchaseCallbackApiCodec(); - - /// Called for BillingClientStateListener#onBillingServiceDisconnected(). - void onBillingServiceDisconnected(int callbackHandle); - - /// Called for PurchasesUpdatedListener#onPurchasesUpdated(BillingResult, List). - void onPurchasesUpdated(PlatformPurchasesResponse update); - - /// Called for UserChoiceBillingListener#userSelectedAlternativeBilling(UserChoiceDetails). - void userSelectedalternativeBilling(PlatformUserChoiceDetails details); - - static void setup(InAppPurchaseCallbackApi? api, - {BinaryMessenger? binaryMessenger}) { - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseCallbackApi.onBillingServiceDisconnected', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - __pigeon_channel.setMessageHandler(null); - } else { - __pigeon_channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseCallbackApi.onBillingServiceDisconnected was null.'); - final List args = (message as List?)!; - final int? arg_callbackHandle = (args[0] as int?); - assert(arg_callbackHandle != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseCallbackApi.onBillingServiceDisconnected was null, expected non-null int.'); - try { - api.onBillingServiceDisconnected(arg_callbackHandle!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseCallbackApi.onPurchasesUpdated', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - __pigeon_channel.setMessageHandler(null); - } else { - __pigeon_channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseCallbackApi.onPurchasesUpdated was null.'); - final List args = (message as List?)!; - final PlatformPurchasesResponse? arg_update = - (args[0] as PlatformPurchasesResponse?); - assert(arg_update != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseCallbackApi.onPurchasesUpdated was null, expected non-null PlatformPurchasesResponse.'); - try { - api.onPurchasesUpdated(arg_update!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseCallbackApi.userSelectedalternativeBilling', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - __pigeon_channel.setMessageHandler(null); - } else { - __pigeon_channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseCallbackApi.userSelectedalternativeBilling was null.'); - final List args = (message as List?)!; - final PlatformUserChoiceDetails? arg_details = - (args[0] as PlatformUserChoiceDetails?); - assert(arg_details != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseCallbackApi.userSelectedalternativeBilling was null, expected non-null PlatformUserChoiceDetails.'); - try { - api.userSelectedalternativeBilling(arg_details!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - } -} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/pigeon_converters.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/pigeon_converters.dart deleted file mode 100644 index 7fe8a3c92..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/pigeon_converters.dart +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import '../billing_client_wrappers.dart'; -import 'billing_client_wrappers/billing_config_wrapper.dart'; -import 'messages.g.dart'; - -/// Converts a [BillingChoiceMode] to the Pigeon equivalent. -PlatformBillingChoiceMode platformBillingChoiceMode(BillingChoiceMode mode) { - return switch (mode) { - BillingChoiceMode.playBillingOnly => - PlatformBillingChoiceMode.playBillingOnly, - BillingChoiceMode.alternativeBillingOnly => - PlatformBillingChoiceMode.alternativeBillingOnly, - BillingChoiceMode.userChoiceBilling => - PlatformBillingChoiceMode.userChoiceBilling, - }; -} - -/// Creates a [BillingResultWrapper] from the Pigeon equivalent. -BillingResultWrapper resultWrapperFromPlatform(PlatformBillingResult result) { - return BillingResultWrapper( - responseCode: - const BillingResponseConverter().fromJson(result.responseCode), - debugMessage: result.debugMessage); -} - -/// Creates a [ProductDetailsResponseWrapper] from the Pigeon equivalent. -ProductDetailsResponseWrapper productDetailsResponseWrapperFromPlatform( - PlatformProductDetailsResponse response) { - return ProductDetailsResponseWrapper( - billingResult: resultWrapperFromPlatform(response.billingResult), - productDetailsList: response.productDetails - // See TODOs in messages.dart for why casting away nullability is safe. - .map((PlatformProductDetails? p) => p!) - .map(productDetailsWrapperFromPlatform) - .toList()); -} - -/// Creates a [ProductDetailsWrapper] from the Pigeon equivalent. -ProductDetailsWrapper productDetailsWrapperFromPlatform( - PlatformProductDetails product) { - return ProductDetailsWrapper( - description: product.description, - name: product.name, - productId: product.productId, - productType: productTypeFromPlatform(product.productType), - title: product.title, - oneTimePurchaseOfferDetails: oneTimePurchaseOfferDetailsWrapperFromPlatform( - product.oneTimePurchaseOfferDetails), - subscriptionOfferDetails: product.subscriptionOfferDetails - // See comment in messages.dart for why casting away nullability is safe. - ?.map((PlatformSubscriptionOfferDetails? o) => o!) - .map(subscriptionOfferDetailsWrapperFromPlatform) - .toList(), - ); -} - -/// Creates a [OneTimePurchaseOfferDetailsWrapper] from the Pigeon equivalent. -OneTimePurchaseOfferDetailsWrapper? - oneTimePurchaseOfferDetailsWrapperFromPlatform( - PlatformOneTimePurchaseOfferDetails? details) { - if (details == null) { - return null; - } - return OneTimePurchaseOfferDetailsWrapper( - formattedPrice: details.formattedPrice, - priceAmountMicros: details.priceAmountMicros, - priceCurrencyCode: details.priceCurrencyCode, - ); -} - -/// Creates a [PurchaseHistoryResult] from the Pigeon equivalent. -PurchasesHistoryResult purchaseHistoryResultFromPlatform( - PlatformPurchaseHistoryResponse response) { - return PurchasesHistoryResult( - billingResult: resultWrapperFromPlatform(response.billingResult), - purchaseHistoryRecordList: response.purchases - // See comment in messages.dart for why casting away nullability is safe. - .map((PlatformPurchaseHistoryRecord? r) => r!) - .map(purchaseHistoryRecordWrapperFromPlatform) - .toList(), - ); -} - -/// Creates a [PurchaseHistoryRecordWrapper] from the Pigeon equivalent. -PurchaseHistoryRecordWrapper purchaseHistoryRecordWrapperFromPlatform( - PlatformPurchaseHistoryRecord record) { - return PurchaseHistoryRecordWrapper( - purchaseTime: record.purchaseTime, - purchaseToken: record.purchaseToken, - signature: record.signature, - // See comment in messages.dart for why casting away nullability is safe. - products: record.products.map((String? s) => s!).toList(), - originalJson: record.originalJson, - developerPayload: record.developerPayload, - ); -} - -/// Creates a [PurchasesResultWrapper] from the Pigeon equivalent. -PurchasesResultWrapper purchasesResultWrapperFromPlatform( - PlatformPurchasesResponse response, - {bool forceOkResponseCode = false}) { - return PurchasesResultWrapper( - billingResult: resultWrapperFromPlatform(response.billingResult), - purchasesList: response.purchases - // See TODOs in messages.dart for why casting away nullability is safe. - .map((PlatformPurchase? p) => p!) - .map(purchaseWrapperFromPlatform) - .toList(), - responseCode: forceOkResponseCode - ? BillingResponse.ok - : const BillingResponseConverter() - .fromJson(response.billingResult.responseCode), - ); -} - -/// Creates an [AlternativeBillingOnlyReportingDetailsWrapper] from the Pigeon -/// equivalent. -AlternativeBillingOnlyReportingDetailsWrapper - alternativeBillingOnlyReportingDetailsWrapperFromPlatform( - PlatformAlternativeBillingOnlyReportingDetailsResponse response) { - return AlternativeBillingOnlyReportingDetailsWrapper( - responseCode: const BillingResponseConverter() - .fromJson(response.billingResult.responseCode), - debugMessage: response.billingResult.debugMessage, - externalTransactionToken: response.externalTransactionToken, - ); -} - -/// Creates a [BillingConfigWrapper] from the Pigeon equivalent. -BillingConfigWrapper billingConfigWrapperFromPlatform( - PlatformBillingConfigResponse response) { - return BillingConfigWrapper( - responseCode: const BillingResponseConverter() - .fromJson(response.billingResult.responseCode), - debugMessage: response.billingResult.debugMessage, - countryCode: response.countryCode, - ); -} - -/// Creates a Pigeon [PlatformProduct] from a [ProductWrapper]. -PlatformQueryProduct platformQueryProductFromWrapper(ProductWrapper product) { - return PlatformQueryProduct( - productId: product.productId, - productType: platformProductTypeFromWrapper(product.productType), - ); -} - -/// Converts a [ProductType] to its Pigeon equivalent. -PlatformProductType platformProductTypeFromWrapper(ProductType type) { - return switch (type) { - ProductType.inapp => PlatformProductType.inapp, - ProductType.subs => PlatformProductType.subs, - }; -} - -/// Creates a [PricingPhaseWrapper] from its Pigeon equivalent. -PricingPhaseWrapper pricingPhaseWrapperFromPlatform( - PlatformPricingPhase phase) { - return PricingPhaseWrapper( - billingCycleCount: phase.billingCycleCount, - billingPeriod: phase.billingPeriod, - formattedPrice: phase.formattedPrice, - priceAmountMicros: phase.priceAmountMicros, - priceCurrencyCode: phase.priceCurrencyCode, - recurrenceMode: recurrenceModeFromPlatform(phase.recurrenceMode), - ); -} - -/// Converts a Pigeon [PlatformProductType] to its public API equivalent. -ProductType productTypeFromPlatform(PlatformProductType type) { - return switch (type) { - PlatformProductType.inapp => ProductType.inapp, - PlatformProductType.subs => ProductType.subs, - }; -} - -/// Creates a [PurchaseWrapper] from the Pigeon equivalent. -PurchaseWrapper purchaseWrapperFromPlatform(PlatformPurchase purchase) { - return PurchaseWrapper( - orderId: purchase.orderId ?? '', - packageName: purchase.packageName, - purchaseTime: purchase.purchaseTime, - purchaseToken: purchase.purchaseToken, - signature: purchase.signature, - // See comment in messages.dart for why casting away nullability is safe. - products: purchase.products.map((String? s) => s!).toList(), - isAutoRenewing: purchase.isAutoRenewing, - originalJson: purchase.originalJson, - isAcknowledged: purchase.isAcknowledged, - purchaseState: purchaseStateWrapperFromPlatform(purchase.purchaseState), - developerPayload: purchase.developerPayload, - obfuscatedAccountId: purchase.accountIdentifiers?.obfuscatedAccountId, - obfuscatedProfileId: purchase.accountIdentifiers?.obfuscatedProfileId, - ); -} - -/// Creates a [PurchaseStateWrapper] from the Pigeon equivalent. -PurchaseStateWrapper purchaseStateWrapperFromPlatform( - PlatformPurchaseState state) { - return switch (state) { - PlatformPurchaseState.unspecified => PurchaseStateWrapper.unspecified_state, - PlatformPurchaseState.purchased => PurchaseStateWrapper.purchased, - PlatformPurchaseState.pending => PurchaseStateWrapper.pending, - }; -} - -/// Creates a [RecurrenceMode] from the Pigeon equivalent. -RecurrenceMode recurrenceModeFromPlatform(PlatformRecurrenceMode mode) { - return switch (mode) { - PlatformRecurrenceMode.finiteRecurring => RecurrenceMode.finiteRecurring, - PlatformRecurrenceMode.infiniteRecurring => - RecurrenceMode.infiniteRecurring, - PlatformRecurrenceMode.nonRecurring => RecurrenceMode.nonRecurring, - }; -} - -/// Creates a [SubscriptionOfferDetailsWrapper] from the Pigeon equivalent. -SubscriptionOfferDetailsWrapper subscriptionOfferDetailsWrapperFromPlatform( - PlatformSubscriptionOfferDetails offer) { - return SubscriptionOfferDetailsWrapper( - basePlanId: offer.basePlanId, - offerId: offer.offerId, - // See comment in messages.dart for why casting away nullability is safe. - offerTags: offer.offerTags.map((String? s) => s!).toList(), - offerIdToken: offer.offerToken, - pricingPhases: offer.pricingPhases - // See comment in messages.dart for why casting away nullability is safe. - .map((PlatformPricingPhase? p) => p!) - .map(pricingPhaseWrapperFromPlatform) - .toList(), - ); -} - -/// Creates a [UserChoiceDetailsWrapper] from the Pigeon equivalent. -UserChoiceDetailsWrapper userChoiceDetailsFromPlatform( - PlatformUserChoiceDetails details) { - return UserChoiceDetailsWrapper( - originalExternalTransactionId: details.originalExternalTransactionId ?? '', - externalTransactionToken: details.externalTransactionToken, - products: details.products - // See comment in messages.dart for why casting away nullability is safe. - .map((PlatformUserChoiceProduct? p) => p!) - .map(userChoiceDetailsProductFromPlatform) - .toList(), - ); -} - -/// Creates a [UserChoiceDetailsProductWrapper] from the Pigeon equivalent. -UserChoiceDetailsProductWrapper userChoiceDetailsProductFromPlatform( - PlatformUserChoiceProduct product) { - return UserChoiceDetailsProductWrapper( - id: product.id, - offerToken: product.offerToken ?? '', - productType: productTypeFromPlatform(product.type), - ); -} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_user_choice_details.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_user_choice_details.dart deleted file mode 100644 index 97553b345..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_user_choice_details.dart +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/foundation.dart'; - -/// Data structure representing a UserChoiceDetails. -/// -/// This wraps [`com.android.billingclient.api.UserChoiceDetails`](https://developer.android.com/reference/com/android/billingclient/api/UserChoiceDetails) -@immutable -class GooglePlayUserChoiceDetails { - /// Creates a new Google Play specific user choice billing details object with - /// the provided details. - const GooglePlayUserChoiceDetails({ - required this.originalExternalTransactionId, - required this.externalTransactionToken, - required this.products, - }); - - /// Returns the external transaction Id of the originating subscription, if - /// the purchase is a subscription upgrade/downgrade. - final String originalExternalTransactionId; - - /// Returns a token that represents the user's prospective purchase via - /// user choice alternative billing. - final String externalTransactionToken; - - /// Returns a list of [GooglePlayUserChoiceDetailsProduct] to be purchased in - /// the user choice alternative billing flow. - final List products; - - @override - bool operator ==(Object other) { - if (identical(other, this)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - return other is GooglePlayUserChoiceDetails && - other.originalExternalTransactionId == originalExternalTransactionId && - other.externalTransactionToken == externalTransactionToken && - listEquals(other.products, products); - } - - @override - int get hashCode => Object.hash( - originalExternalTransactionId, - externalTransactionToken, - products.hashCode, - ); -} - -/// Data structure representing a UserChoiceDetails product. -/// -/// This wraps [`com.android.billingclient.api.UserChoiceDetails.Product`](https://developer.android.com/reference/com/android/billingclient/api/UserChoiceDetails.Product) -@immutable -class GooglePlayUserChoiceDetailsProduct { - /// Creates UserChoiceDetailsProduct. - const GooglePlayUserChoiceDetailsProduct( - {required this.id, required this.offerToken, required this.productType}); - - /// Returns the id of the product being purchased. - final String id; - - /// Returns the offer token that was passed in launchBillingFlow to purchase the product. - final String offerToken; - - /// Returns the [GooglePlayProductType] of the product being purchased. - final GooglePlayProductType productType; - - @override - bool operator ==(Object other) { - if (identical(other, this)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - return other is GooglePlayUserChoiceDetailsProduct && - other.id == id && - other.offerToken == offerToken && - other.productType == productType; - } - - @override - int get hashCode => Object.hash( - id, - offerToken, - productType, - ); -} - -/// This wraps [`com.android.billingclient.api.BillingClient.ProductType`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.ProductType) -enum GooglePlayProductType { - /// A Product type for Android apps in-app products. - inapp, - - /// A Product type for Android apps subscriptions. - subs -} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/translator.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/translator.dart deleted file mode 100644 index f6461afc2..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/translator.dart +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/foundation.dart'; - -import '../../billing_client_wrappers.dart'; -import 'google_play_user_choice_details.dart'; - -/// Class used to convert cross process object into api expose objects. -class Translator { - Translator._(); - - /// Converts from [UserChoiceDetailsWrapper] to [GooglePlayUserChoiceDetails]. - static GooglePlayUserChoiceDetails convertToUserChoiceDetails( - UserChoiceDetailsWrapper detailsWrapper) { - return GooglePlayUserChoiceDetails( - originalExternalTransactionId: - detailsWrapper.originalExternalTransactionId, - externalTransactionToken: detailsWrapper.externalTransactionToken, - products: detailsWrapper.products - .map((UserChoiceDetailsProductWrapper e) => - convertToUserChoiceDetailsProduct(e)) - .toList()); - } - - /// Converts from [UserChoiceDetailsProductWrapper] to [GooglePlayUserChoiceDetailsProduct]. - @visibleForTesting - static GooglePlayUserChoiceDetailsProduct convertToUserChoiceDetailsProduct( - UserChoiceDetailsProductWrapper productWrapper) { - return GooglePlayUserChoiceDetailsProduct( - id: productWrapper.id, - offerToken: productWrapper.offerToken, - productType: convertToPlayProductType(productWrapper.productType)); - } - - /// Coverts from [ProductType] to [GooglePlayProductType]. - @visibleForTesting - static GooglePlayProductType convertToPlayProductType(ProductType type) { - switch (type) { - case ProductType.inapp: - return GooglePlayProductType.inapp; - case ProductType.subs: - return GooglePlayProductType.subs; - } - } -} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/types.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/types.dart index 5116ac54e..0a43425f6 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/types.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/types.dart @@ -6,5 +6,4 @@ export 'change_subscription_param.dart'; export 'google_play_product_details.dart'; export 'google_play_purchase_details.dart'; export 'google_play_purchase_param.dart'; -export 'google_play_user_choice_details.dart'; export 'query_purchase_details_response.dart'; diff --git a/packages/in_app_purchase/in_app_purchase_android/pigeons/copyright.txt b/packages/in_app_purchase/in_app_purchase_android/pigeons/copyright.txt deleted file mode 100644 index 1236b63ca..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/pigeons/copyright.txt +++ /dev/null @@ -1,3 +0,0 @@ -Copyright 2013 The Flutter Authors. All rights reserved. -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. diff --git a/packages/in_app_purchase/in_app_purchase_android/pigeons/messages.dart b/packages/in_app_purchase/in_app_purchase_android/pigeons/messages.dart deleted file mode 100644 index 2921c0907..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/pigeons/messages.dart +++ /dev/null @@ -1,416 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:pigeon/pigeon.dart'; - -@ConfigurePigeon(PigeonOptions( - dartOut: 'lib/src/messages.g.dart', - javaOptions: JavaOptions(package: 'io.flutter.plugins.inapppurchase'), - javaOut: - 'android/src/main/java/io/flutter/plugins/inapppurchase/Messages.java', - copyrightHeader: 'pigeons/copyright.txt', -)) - -/// Pigeon version of Java QueryProductDetailsParams.Product. -class PlatformQueryProduct { - PlatformQueryProduct({required this.productId, required this.productType}); - - final String productId; - final PlatformProductType productType; -} - -/// Pigeon version of Java AccountIdentifiers. -class PlatformAccountIdentifiers { - PlatformAccountIdentifiers({ - required this.obfuscatedAccountId, - required this.obfuscatedProfileId, - }); - - final String? obfuscatedAccountId; - final String? obfuscatedProfileId; -} - -/// Pigeon version of Java BillingResult. -class PlatformBillingResult { - PlatformBillingResult( - {required this.responseCode, required this.debugMessage}); - final int responseCode; - final String debugMessage; -} - -/// Pigeon version of Java ProductDetails.OneTimePurchaseOfferDetails. -class PlatformOneTimePurchaseOfferDetails { - PlatformOneTimePurchaseOfferDetails({ - required this.priceAmountMicros, - required this.formattedPrice, - required this.priceCurrencyCode, - }); - - final int priceAmountMicros; - final String formattedPrice; - final String priceCurrencyCode; -} - -/// Pigeon version of Java ProductDetails. -class PlatformProductDetails { - PlatformProductDetails({ - required this.description, - required this.name, - required this.productId, - required this.productType, - required this.title, - required this.oneTimePurchaseOfferDetails, - required this.subscriptionOfferDetails, - }); - - final String description; - final String name; - final String productId; - final PlatformProductType productType; - final String title; - final PlatformOneTimePurchaseOfferDetails? oneTimePurchaseOfferDetails; - // TODO(stuartmorgan): Make the generic type non-nullable once supported. - // https://github.com/flutter/flutter/issues/97848 - // The consuming code treats it (the entries, not the list itself) as - // non-nullable. - final List? subscriptionOfferDetails; -} - -/// Pigeon version of ProductDetailsResponseWrapper, which contains the -/// components of the Java ProductDetailsResponseListener callback. -class PlatformProductDetailsResponse { - PlatformProductDetailsResponse({ - required this.billingResult, - required this.productDetails, - }); - - final PlatformBillingResult billingResult; - // TODO(stuartmorgan): Make the generic type non-nullable once supported. - // https://github.com/flutter/flutter/issues/97848 - // The consuming code treats it as non-nullable. - final List productDetails; -} - -/// Pigeon version of AlternativeBillingOnlyReportingDetailsWrapper, which -/// contains the components of the Java -/// AlternativeBillingOnlyReportingDetailsListener callback. -class PlatformAlternativeBillingOnlyReportingDetailsResponse { - PlatformAlternativeBillingOnlyReportingDetailsResponse( - {required this.billingResult, required this.externalTransactionToken}); - - final PlatformBillingResult billingResult; - final String externalTransactionToken; -} - -/// Pigeon version of BillingConfigWrapper, which contains the components of the -/// Java BillingConfigResponseListener callback. -class PlatformBillingConfigResponse { - PlatformBillingConfigResponse( - {required this.billingResult, required this.countryCode}); - - final PlatformBillingResult billingResult; - final String countryCode; -} - -/// Pigeon version of Java BillingFlowParams. -class PlatformBillingFlowParams { - PlatformBillingFlowParams({ - required this.product, - required this.prorationMode, - required this.offerToken, - required this.accountId, - required this.obfuscatedProfileId, - required this.oldProduct, - required this.purchaseToken, - }); - - final String product; - // Ideally this would be replaced with an enum on the dart side that maps - // to constants on the Java side, but it's deprecated anyway so that will be - // resolved during the update to the new API. - final int prorationMode; - final String? offerToken; - final String? accountId; - final String? obfuscatedProfileId; - final String? oldProduct; - final String? purchaseToken; -} - -/// Pigeon version of Java ProductDetails.PricingPhase. -class PlatformPricingPhase { - PlatformPricingPhase({ - required this.billingCycleCount, - required this.recurrenceMode, - required this.priceAmountMicros, - required this.billingPeriod, - required this.formattedPrice, - required this.priceCurrencyCode, - }); - - final int billingCycleCount; - final PlatformRecurrenceMode recurrenceMode; - final int priceAmountMicros; - final String billingPeriod; - final String formattedPrice; - final String priceCurrencyCode; -} - -/// Pigeon version of Java Purchase. -/// -/// See also PurchaseWrapper on the Dart side. -class PlatformPurchase { - const PlatformPurchase({ - required this.orderId, - required this.packageName, - required this.purchaseTime, - required this.purchaseToken, - required this.signature, - required this.products, - required this.isAutoRenewing, - required this.originalJson, - required this.developerPayload, - required this.isAcknowledged, - required this.quantity, - required this.purchaseState, - required this.accountIdentifiers, - }); - - final String? orderId; - final String packageName; - final int purchaseTime; - final String purchaseToken; - final String signature; - // TODO(stuartmorgan): Make the type non-nullable once supported. - // https://github.com/flutter/flutter/issues/97848 - // The consuming code treats it as non-nullable. - final List products; - final bool isAutoRenewing; - final String originalJson; - final String developerPayload; - final bool isAcknowledged; - final int quantity; - final PlatformPurchaseState purchaseState; - final PlatformAccountIdentifiers? accountIdentifiers; -} - -/// Pigeon version of PurchaseHistoryRecord. -/// -/// See also PurchaseHistoryRecordWrapper on the Dart side. -class PlatformPurchaseHistoryRecord { - PlatformPurchaseHistoryRecord({ - required this.quantity, - required this.purchaseTime, - required this.developerPayload, - required this.originalJson, - required this.purchaseToken, - required this.signature, - required this.products, - }); - - final int quantity; - final int purchaseTime; - final String? developerPayload; - final String originalJson; - final String purchaseToken; - final String signature; - // TODO(stuartmorgan): Make the type non-nullable once supported. - // https://github.com/flutter/flutter/issues/97848 - // The consuming code treats it as non-nullable. - final List products; -} - -/// Pigeon version of PurchasesHistoryResult, which contains the components of -/// the Java PurchaseHistoryResponseListener callback. -class PlatformPurchaseHistoryResponse { - PlatformPurchaseHistoryResponse({ - required this.billingResult, - required this.purchases, - }); - - final PlatformBillingResult billingResult; - // TODO(stuartmorgan): Make the type non-nullable once supported. - // https://github.com/flutter/flutter/issues/97848 - // The consuming code treats it as non-nullable. - final List purchases; -} - -/// Pigeon version of PurchasesResultWrapper, which contains the components of -/// the Java PurchasesResponseListener callback. -class PlatformPurchasesResponse { - PlatformPurchasesResponse({ - required this.billingResult, - required this.purchases, - }); - - final PlatformBillingResult billingResult; - // TODO(stuartmorgan): Make the generic type non-nullable once supported. - // https://github.com/flutter/flutter/issues/97848 - // The consuming code treats it as non-nullable. - final List purchases; -} - -/// Pigeon version of Java ProductDetails.SubscriptionOfferDetails. -class PlatformSubscriptionOfferDetails { - PlatformSubscriptionOfferDetails({ - required this.basePlanId, - required this.offerId, - required this.offerToken, - required this.offerTags, - required this.pricingPhases, - }); - - final String basePlanId; - final String? offerId; - final String offerToken; - // TODO(stuartmorgan): Make the generic type non-nullable once supported. - // https://github.com/flutter/flutter/issues/97848 - // The consuming code treats it as non-nullable. - final List offerTags; - // On the native side this is actually a class called PricingPhases, - // which contains nothing but a List. Since this is an - // internal API, we can always add that indirection later if we need it, - // so for now this bypasses that unnecessary wrapper. - // TODO(stuartmorgan): Make the generic type non-nullable once supported. - // https://github.com/flutter/flutter/issues/97848 - // The consuming code treats it as non-nullable. - final List pricingPhases; -} - -/// Pigeon version of UserChoiceDetailsWrapper and Java UserChoiceDetails. -class PlatformUserChoiceDetails { - PlatformUserChoiceDetails({ - required this.originalExternalTransactionId, - required this.externalTransactionToken, - required this.products, - }); - - final String? originalExternalTransactionId; - final String externalTransactionToken; - // TODO(stuartmorgan): Make the generic type non-nullable once supported. - // https://github.com/flutter/flutter/issues/97848 - // The consuming code treats it as non-nullable. - final List products; -} - -/// Pigeon version of UserChoiseDetails.Product. -class PlatformUserChoiceProduct { - PlatformUserChoiceProduct({ - required this.id, - required this.offerToken, - required this.type, - }); - - final String id; - final String? offerToken; - final PlatformProductType type; -} - -/// Pigeon version of Java BillingClient.ProductType. -enum PlatformProductType { - inapp, - subs, -} - -/// Pigeon version of billing_client_wrapper.dart's BillingChoiceMode. -enum PlatformBillingChoiceMode { - /// Billing through google play. - /// - /// Default state. - playBillingOnly, - - /// Billing through app provided flow. - alternativeBillingOnly, - - /// Users can choose Play billing or alternative billing. - userChoiceBilling, -} - -/// Pigeon version of Java Purchase.PurchaseState. -enum PlatformPurchaseState { - unspecified, - purchased, - pending, -} - -/// Pigeon version of Java ProductDetails.RecurrenceMode. -enum PlatformRecurrenceMode { - finiteRecurring, - infiniteRecurring, - nonRecurring, -} - -@HostApi() -abstract class InAppPurchaseApi { - /// Wraps BillingClient#isReady. - bool isReady(); - - /// Wraps BillingClient#startConnection(BillingClientStateListener). - @async - PlatformBillingResult startConnection( - int callbackHandle, PlatformBillingChoiceMode billingMode); - - /// Wraps BillingClient#endConnection(BillingClientStateListener). - void endConnection(); - - /// Wraps BillingClient#getBillingConfigAsync(GetBillingConfigParams, BillingConfigResponseListener). - @async - PlatformBillingConfigResponse getBillingConfigAsync(); - - /// Wraps BillingClient#launchBillingFlow(Activity, BillingFlowParams). - PlatformBillingResult launchBillingFlow(PlatformBillingFlowParams params); - - /// Wraps BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener). - @async - PlatformBillingResult acknowledgePurchase(String purchaseToken); - - /// Wraps BillingClient#consumeAsync(ConsumeParams, ConsumeResponseListener). - @async - PlatformBillingResult consumeAsync(String purchaseToken); - - /// Wraps BillingClient#queryPurchasesAsync(QueryPurchaseParams, PurchaseResponseListener). - @async - PlatformPurchasesResponse queryPurchasesAsync( - PlatformProductType productType); - - /// Wraps BillingClient#queryPurchaseHistoryAsync(QueryPurchaseHistoryParams, PurchaseHistoryResponseListener). - @async - PlatformPurchaseHistoryResponse queryPurchaseHistoryAsync( - PlatformProductType productType); - - /// Wraps BillingClient#queryProductDetailsAsync(QueryProductDetailsParams, ProductDetailsResponseListener). - @async - PlatformProductDetailsResponse queryProductDetailsAsync( - List products); - - /// Wraps BillingClient#isFeatureSupported(String). - // TODO(stuartmorgan): Consider making this take a enum, and converting the - // enum value to string constants on the native side, so that magic strings - // from the Play Billing API aren't duplicated in Dart code. - bool isFeatureSupported(String feature); - - /// Wraps BillingClient#isAlternativeBillingOnlyAvailableAsync(). - @async - PlatformBillingResult isAlternativeBillingOnlyAvailableAsync(); - - /// Wraps BillingClient#showAlternativeBillingOnlyInformationDialog(). - @async - PlatformBillingResult showAlternativeBillingOnlyInformationDialog(); - - /// Wraps BillingClient#createAlternativeBillingOnlyReportingDetailsAsync(AlternativeBillingOnlyReportingDetailsListener). - @async - PlatformAlternativeBillingOnlyReportingDetailsResponse - createAlternativeBillingOnlyReportingDetailsAsync(); -} - -@FlutterApi() -abstract class InAppPurchaseCallbackApi { - /// Called for BillingClientStateListener#onBillingServiceDisconnected(). - void onBillingServiceDisconnected(int callbackHandle); - - /// Called for PurchasesUpdatedListener#onPurchasesUpdated(BillingResult, List). - void onPurchasesUpdated(PlatformPurchasesResponse update); - - /// Called for UserChoiceBillingListener#userSelectedAlternativeBilling(UserChoiceDetails). - void userSelectedalternativeBilling(PlatformUserChoiceDetails details); -} diff --git a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml index 6950533f1..1a9f24c75 100644 --- a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml @@ -2,12 +2,11 @@ name: in_app_purchase_android description: An implementation for the Android platform of the Flutter `in_app_purchase` plugin. This uses the Android BillingClient APIs. repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 - -version: 0.3.4 +version: 0.3.0+16 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" flutter: plugin: @@ -21,7 +20,7 @@ dependencies: collection: ^1.15.0 flutter: sdk: flutter - in_app_purchase_platform_interface: ^1.4.0 + in_app_purchase_platform_interface: ^1.3.0 json_annotation: ^4.8.0 dev_dependencies: @@ -30,7 +29,6 @@ dev_dependencies: sdk: flutter json_serializable: ^6.3.1 mockito: 5.4.4 - pigeon: ^17.1.1 test: ^1.16.0 topics: diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart index 1c72d02f9..11efbd165 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart @@ -4,44 +4,54 @@ import 'dart:async'; +import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_android/billing_client_wrappers.dart'; -import 'package:in_app_purchase_android/src/messages.g.dart'; -import 'package:mockito/mockito.dart'; +import 'package:in_app_purchase_android/src/channel.dart'; -import 'billing_client_wrapper_test.mocks.dart'; +import '../stub_in_app_purchase_platform.dart'; +import 'purchase_wrapper_test.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - late MockInAppPurchaseApi mockApi; + final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); late BillingClientManager manager; + late Completer connectedCompleter; + + const String startConnectionCall = + 'BillingClient#startConnection(BillingClientStateListener)'; + const String endConnectionCall = 'BillingClient#endConnection()'; + const String onBillingServiceDisconnectedCallback = + 'BillingClientStateListener#onBillingServiceDisconnected()'; + + setUpAll(() => _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, stubPlatform.fakeMethodCallHandler)); setUp(() { WidgetsFlutterBinding.ensureInitialized(); - mockApi = MockInAppPurchaseApi(); - when(mockApi.startConnection(any, any)).thenAnswer( - (_) async => PlatformBillingResult(responseCode: 0, debugMessage: '')); - manager = BillingClientManager( - billingClientFactory: (PurchasesUpdatedListener listener, - UserSelectedAlternativeBillingListener? - alternativeBillingListener) => - BillingClient(listener, alternativeBillingListener, api: mockApi)); + connectedCompleter = Completer.sync(); + stubPlatform.addResponse( + name: startConnectionCall, + value: buildBillingResultMap( + const BillingResultWrapper(responseCode: BillingResponse.ok), + ), + additionalStepBeforeReturn: (dynamic _) => connectedCompleter.future, + ); + stubPlatform.addResponse(name: endConnectionCall); + manager = BillingClientManager(); }); + tearDown(() => stubPlatform.reset()); + group('BillingClientWrapper', () { test('connects on initialization', () { - verify(mockApi.startConnection(any, any)).called(1); + expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(1)); }); test('waits for connection before executing the operations', () async { - final Completer connectedCompleter = Completer(); - when(mockApi.startConnection(any, any)).thenAnswer((_) async { - connectedCompleter.complete(); - return PlatformBillingResult(responseCode: 0, debugMessage: ''); - }); - final Completer calledCompleter1 = Completer(); final Completer calledCompleter2 = Completer(); unawaited(manager.runWithClient((BillingClient _) async { @@ -60,39 +70,21 @@ void main() { test('re-connects when client sends onBillingServiceDisconnected', () async { + connectedCompleter.complete(); // Ensures all asynchronous connected code finishes. await manager.runWithClientNonRetryable((_) async {}); - manager.client.hostCallbackHandler.onBillingServiceDisconnected(0); - verify(mockApi.startConnection(any, any)).called(2); - }); - - test('re-connects when host calls reconnectWithBillingChoiceMode', - () async { - // Ensures all asynchronous connected code finishes. - await manager.runWithClientNonRetryable((_) async {}); - - await manager.reconnectWithBillingChoiceMode( - BillingChoiceMode.alternativeBillingOnly); - // Verify that connection was ended. - verify(mockApi.endConnection()).called(1); - - clearInteractions(mockApi); - - /// Fake the disconnect that we would expect from a endConnectionCall. - manager.client.hostCallbackHandler.onBillingServiceDisconnected(0); - // Verify that after connection ended reconnect was called. - final VerificationResult result = - verify(mockApi.startConnection(any, captureAny)); - expect(result.captured.single, - PlatformBillingChoiceMode.alternativeBillingOnly); + await manager.client.callHandler( + const MethodCall(onBillingServiceDisconnectedCallback, + {'handle': 0}), + ); + expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(2)); }); test( 're-connects when operation returns BillingResponse.serviceDisconnected', () async { - clearInteractions(mockApi); - + connectedCompleter.complete(); int timesCalled = 0; final BillingResultWrapper result = await manager.runWithClient( (BillingClient _) async { @@ -104,43 +96,23 @@ void main() { ); }, ); - verify(mockApi.startConnection(any, any)).called(1); + expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(2)); expect(timesCalled, equals(2)); expect(result.responseCode, equals(BillingResponse.ok)); }, ); test('does not re-connect when disposed', () { - clearInteractions(mockApi); + connectedCompleter.complete(); manager.dispose(); - verifyNever(mockApi.startConnection(any, any)); - verify(mockApi.endConnection()).called(1); - }); - - test( - 'Emits UserChoiceDetailsWrapper when onUserChoiceAlternativeBilling is called', - () async { - // Ensures all asynchronous connected code finishes. - await manager.runWithClientNonRetryable((_) async {}); - - const UserChoiceDetailsWrapper expected = UserChoiceDetailsWrapper( - originalExternalTransactionId: 'TransactionId', - externalTransactionToken: 'TransactionToken', - products: [ - UserChoiceDetailsProductWrapper( - id: 'id1', - offerToken: 'offerToken1', - productType: ProductType.inapp), - UserChoiceDetailsProductWrapper( - id: 'id2', - offerToken: 'offerToken2', - productType: ProductType.inapp), - ], - ); - final Future detailsFuture = - manager.userChoiceDetailsStream.first; - manager.onUserChoiceAlternativeBilling(expected); - expect(await detailsFuture, expected); + expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(1)); + expect(stubPlatform.countPreviousCalls(endConnectionCall), equals(1)); }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart index 0d94ddc7b..a840bd2d0 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart @@ -2,17 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_android/billing_client_wrappers.dart'; -import 'package:in_app_purchase_android/src/billing_client_wrappers/billing_config_wrapper.dart'; -import 'package:in_app_purchase_android/src/messages.g.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; +import 'package:in_app_purchase_android/src/channel.dart'; -import '../test_conversion_utils.dart'; -import 'billing_client_wrapper_test.mocks.dart'; +import '../stub_in_app_purchase_platform.dart'; import 'product_details_wrapper_test.dart'; import 'purchase_wrapper_test.dart'; @@ -30,30 +25,29 @@ const PurchaseWrapper dummyOldPurchase = PurchaseWrapper( purchaseState: PurchaseStateWrapper.purchased, ); -@GenerateNiceMocks(>[MockSpec()]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); - late MockInAppPurchaseApi mockApi; + final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); late BillingClient billingClient; + setUpAll(() => _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, stubPlatform.fakeMethodCallHandler)); + setUp(() { - mockApi = MockInAppPurchaseApi(); - when(mockApi.startConnection(any, any)).thenAnswer( - (_) async => PlatformBillingResult(responseCode: 0, debugMessage: '')); - billingClient = BillingClient( - (PurchasesResultWrapper _) {}, (UserChoiceDetailsWrapper _) {}, - api: mockApi); + billingClient = BillingClient((PurchasesResultWrapper _) {}); + stubPlatform.reset(); }); group('isReady', () { test('true', () async { - when(mockApi.isReady()).thenAnswer((_) async => true); + stubPlatform.addResponse(name: 'BillingClient#isReady()', value: true); expect(await billingClient.isReady(), isTrue); }); test('false', () async { - when(mockApi.isReady()).thenAnswer((_) async => false); + stubPlatform.addResponse(name: 'BillingClient#isReady()', value: false); expect(await billingClient.isReady(), isFalse); }); }); @@ -78,14 +72,17 @@ void main() { }); group('startConnection', () { + const String methodName = + 'BillingClient#startConnection(BillingClientStateListener)'; test('returns BillingResultWrapper', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.developerError; - when(mockApi.startConnection(any, any)).thenAnswer( - (_) async => PlatformBillingResult( - responseCode: const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage, - ), + stubPlatform.addResponse( + name: methodName, + value: { + 'responseCode': const BillingResponseConverter().toJson(responseCode), + 'debugMessage': debugMessage, + }, ); const BillingResultWrapper billingResult = BillingResultWrapper( @@ -96,115 +93,57 @@ void main() { equals(billingResult)); }); - test('passes default values to onBillingServiceDisconnected', () async { + test('passes handle to onBillingServiceDisconnected', () async { + const String debugMessage = 'dummy message'; + const BillingResponse responseCode = BillingResponse.developerError; + stubPlatform.addResponse( + name: methodName, + value: { + 'responseCode': const BillingResponseConverter().toJson(responseCode), + 'debugMessage': debugMessage, + }, + ); await billingClient.startConnection(onBillingServiceDisconnected: () {}); - - final VerificationResult result = - verify(mockApi.startConnection(captureAny, captureAny)); - expect(result.captured[0], 0); - expect(result.captured[1], PlatformBillingChoiceMode.playBillingOnly); + final MethodCall call = stubPlatform.previousCallMatching(methodName); + expect(call.arguments, equals({'handle': 0})); }); - test('passes billingChoiceMode alternativeBillingOnly when set', () async { - await billingClient.startConnection( - onBillingServiceDisconnected: () {}, - billingChoiceMode: BillingChoiceMode.alternativeBillingOnly); - - expect(verify(mockApi.startConnection(any, captureAny)).captured.first, - PlatformBillingChoiceMode.alternativeBillingOnly); - }); - - test('passes billingChoiceMode userChoiceBilling when set', () async { - final Completer completer = - Completer(); - billingClient = BillingClient((PurchasesResultWrapper _) {}, - (UserChoiceDetailsWrapper details) => completer.complete(details), - api: mockApi); - - await billingClient.startConnection( - onBillingServiceDisconnected: () {}, - billingChoiceMode: BillingChoiceMode.alternativeBillingOnly); - - expect(verify(mockApi.startConnection(any, captureAny)).captured.first, - PlatformBillingChoiceMode.alternativeBillingOnly); - - const UserChoiceDetailsWrapper expected = UserChoiceDetailsWrapper( - originalExternalTransactionId: 'TransactionId', - externalTransactionToken: 'TransactionToken', - products: [ - UserChoiceDetailsProductWrapper( - id: 'id1', - offerToken: 'offerToken1', - productType: ProductType.inapp), - UserChoiceDetailsProductWrapper( - id: 'id2', - offerToken: 'offerToken2', - productType: ProductType.inapp), - ], + test('handles method channel returning null', () async { + stubPlatform.addResponse( + name: methodName, ); - billingClient.hostCallbackHandler.alternativeBillingListener!(expected); - expect(completer.isCompleted, isTrue); - expect(await completer.future, expected); - }); - test('UserChoiceDetailsWrapper searilization check', () async { - // Test ensures that changes to UserChoiceDetailsWrapper#toJson are - // compatible with code in Translator.java. - const String transactionIdKey = 'originalExternalTransactionId'; - const String transactionTokenKey = 'externalTransactionToken'; - const String productsKey = 'products'; - const String productIdKey = 'id'; - const String productOfferTokenKey = 'offerToken'; - const String productTypeKey = 'productType'; - - const UserChoiceDetailsProductWrapper expectedProduct1 = - UserChoiceDetailsProductWrapper( - id: 'id1', - offerToken: 'offerToken1', - productType: ProductType.inapp); - const UserChoiceDetailsProductWrapper expectedProduct2 = - UserChoiceDetailsProductWrapper( - id: 'id2', - offerToken: 'offerToken2', - productType: ProductType.inapp); - const UserChoiceDetailsWrapper expected = UserChoiceDetailsWrapper( - originalExternalTransactionId: 'TransactionId', - externalTransactionToken: 'TransactionToken', - products: [ - expectedProduct1, - expectedProduct2, - ], - ); - final Map detailsJson = expected.toJson(); - expect(detailsJson.keys, contains(transactionIdKey)); - expect(detailsJson.keys, contains(transactionTokenKey)); - expect(detailsJson.keys, contains(productsKey)); - - final Map productJson = expectedProduct1.toJson(); - expect(productJson, contains(productIdKey)); - expect(productJson, contains(productOfferTokenKey)); - expect(productJson, contains(productTypeKey)); + expect( + await billingClient.startConnection( + onBillingServiceDisconnected: () {}), + equals(const BillingResultWrapper( + responseCode: BillingResponse.error, + debugMessage: kInvalidBillingResultErrorMessage))); }); }); test('endConnection', () async { - verifyNever(mockApi.endConnection()); + const String endConnectionName = 'BillingClient#endConnection()'; + expect(stubPlatform.countPreviousCalls(endConnectionName), equals(0)); + stubPlatform.addResponse(name: endConnectionName); await billingClient.endConnection(); - verify(mockApi.endConnection()).called(1); + expect(stubPlatform.countPreviousCalls(endConnectionName), equals(1)); }); group('queryProductDetails', () { + const String queryMethodName = + 'BillingClient#queryProductDetailsAsync(QueryProductDetailsParams, ProductDetailsResponseListener)'; + test('handles empty productDetails', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.developerError; - when(mockApi.queryProductDetailsAsync(any)) - .thenAnswer((_) async => PlatformProductDetailsResponse( - billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), - productDetails: [], - )); + stubPlatform.addResponse(name: queryMethodName, value: { + 'billingResult': { + 'responseCode': const BillingResponseConverter().toJson(responseCode), + 'debugMessage': debugMessage, + }, + 'productDetailsList': >[] + }); final ProductDetailsResponseWrapper response = await billingClient .queryProductDetails(productList: [ @@ -221,16 +160,15 @@ void main() { test('returns ProductDetailsResponseWrapper', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; - when(mockApi.queryProductDetailsAsync(any)) - .thenAnswer((_) async => PlatformProductDetailsResponse( - billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), - productDetails: [ - convertToPigeonProductDetails(dummyOneTimeProductDetails) - ], - )); + stubPlatform.addResponse(name: queryMethodName, value: { + 'billingResult': { + 'responseCode': const BillingResponseConverter().toJson(responseCode), + 'debugMessage': debugMessage, + }, + 'productDetailsList': >[ + buildProductMap(dummyOneTimeProductDetails) + ], + }); final ProductDetailsResponseWrapper response = await billingClient.queryProductDetails( @@ -245,16 +183,39 @@ void main() { expect(response.billingResult, equals(billingResult)); expect(response.productDetailsList, contains(dummyOneTimeProductDetails)); }); + + test('handles null method channel response', () async { + stubPlatform.addResponse(name: queryMethodName); + + final ProductDetailsResponseWrapper response = + await billingClient.queryProductDetails( + productList: [ + const ProductWrapper( + productId: 'invalid', productType: ProductType.inapp), + ], + ); + + const BillingResultWrapper billingResult = BillingResultWrapper( + responseCode: BillingResponse.error, + debugMessage: kInvalidBillingResultErrorMessage); + expect(response.billingResult, equals(billingResult)); + expect(response.productDetailsList, isEmpty); + }); }); group('launchBillingFlow', () { + const String launchMethodName = + 'BillingClient#launchBillingFlow(Activity, BillingFlowParams)'; + test('serializes and deserializes data', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); - when(mockApi.launchBillingFlow(any)).thenAnswer( - (_) async => convertToPigeonResult(expectedBillingResult)); + stubPlatform.addResponse( + name: launchMethodName, + value: buildBillingResultMap(expectedBillingResult), + ); const ProductDetailsWrapper productDetails = dummyOneTimeProductDetails; const String accountId = 'hashedAccountId'; const String profileId = 'hashedProfileId'; @@ -265,19 +226,25 @@ void main() { accountId: accountId, obfuscatedProfileId: profileId), equals(expectedBillingResult)); - - final VerificationResult result = - verify(mockApi.launchBillingFlow(captureAny)); - final PlatformBillingFlowParams params = - result.captured.single as PlatformBillingFlowParams; - expect(params.product, equals(productDetails.productId)); - expect(params.accountId, equals(accountId)); - expect(params.obfuscatedProfileId, equals(profileId)); + final Map arguments = stubPlatform + .previousCallMatching(launchMethodName) + .arguments as Map; + expect(arguments['product'], equals(productDetails.productId)); + expect(arguments['accountId'], equals(accountId)); + expect(arguments['obfuscatedProfileId'], equals(profileId)); }); test( 'Change subscription throws assertion error `oldProduct` and `purchaseToken` has different nullability', () async { + const String debugMessage = 'dummy message'; + const BillingResponse responseCode = BillingResponse.ok; + const BillingResultWrapper expectedBillingResult = BillingResultWrapper( + responseCode: responseCode, debugMessage: debugMessage); + stubPlatform.addResponse( + name: launchMethodName, + value: buildBillingResultMap(expectedBillingResult), + ); const ProductDetailsWrapper productDetails = dummyOneTimeProductDetails; const String accountId = 'hashedAccountId'; const String profileId = 'hashedProfileId'; @@ -306,8 +273,10 @@ void main() { const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); - when(mockApi.launchBillingFlow(any)).thenAnswer( - (_) async => convertToPigeonResult(expectedBillingResult)); + stubPlatform.addResponse( + name: launchMethodName, + value: buildBillingResultMap(expectedBillingResult), + ); const ProductDetailsWrapper productDetails = dummyOneTimeProductDetails; const String accountId = 'hashedAccountId'; const String profileId = 'hashedProfileId'; @@ -320,15 +289,15 @@ void main() { oldProduct: dummyOldPurchase.products.first, purchaseToken: dummyOldPurchase.purchaseToken), equals(expectedBillingResult)); - final VerificationResult result = - verify(mockApi.launchBillingFlow(captureAny)); - final PlatformBillingFlowParams params = - result.captured.single as PlatformBillingFlowParams; - expect(params.product, equals(productDetails.productId)); - expect(params.accountId, equals(accountId)); - expect(params.oldProduct, equals(dummyOldPurchase.products.first)); - expect(params.purchaseToken, equals(dummyOldPurchase.purchaseToken)); - expect(params.obfuscatedProfileId, equals(profileId)); + final Map arguments = stubPlatform + .previousCallMatching(launchMethodName) + .arguments as Map; + expect(arguments['product'], equals(productDetails.productId)); + expect(arguments['accountId'], equals(accountId)); + expect(arguments['oldProduct'], equals(dummyOldPurchase.products.first)); + expect( + arguments['purchaseToken'], equals(dummyOldPurchase.purchaseToken)); + expect(arguments['obfuscatedProfileId'], equals(profileId)); }); test( @@ -338,8 +307,10 @@ void main() { const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); - when(mockApi.launchBillingFlow(any)).thenAnswer( - (_) async => convertToPigeonResult(expectedBillingResult)); + stubPlatform.addResponse( + name: launchMethodName, + value: buildBillingResultMap(expectedBillingResult), + ); const ProductDetailsWrapper productDetails = dummyOneTimeProductDetails; const String accountId = 'hashedAccountId'; const String profileId = 'hashedProfileId'; @@ -355,16 +326,16 @@ void main() { prorationMode: prorationMode, purchaseToken: dummyOldPurchase.purchaseToken), equals(expectedBillingResult)); - final VerificationResult result = - verify(mockApi.launchBillingFlow(captureAny)); - final PlatformBillingFlowParams params = - result.captured.single as PlatformBillingFlowParams; - expect(params.product, equals(productDetails.productId)); - expect(params.accountId, equals(accountId)); - expect(params.oldProduct, equals(dummyOldPurchase.products.first)); - expect(params.obfuscatedProfileId, equals(profileId)); - expect(params.purchaseToken, equals(dummyOldPurchase.purchaseToken)); - expect(params.prorationMode, + final Map arguments = stubPlatform + .previousCallMatching(launchMethodName) + .arguments as Map; + expect(arguments['product'], equals(productDetails.productId)); + expect(arguments['accountId'], equals(accountId)); + expect(arguments['oldProduct'], equals(dummyOldPurchase.products.first)); + expect(arguments['obfuscatedProfileId'], equals(profileId)); + expect( + arguments['purchaseToken'], equals(dummyOldPurchase.purchaseToken)); + expect(arguments['prorationMode'], const ProrationModeConverter().toJson(prorationMode)); }); @@ -375,8 +346,10 @@ void main() { const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); - when(mockApi.launchBillingFlow(any)).thenAnswer( - (_) async => convertToPigeonResult(expectedBillingResult)); + stubPlatform.addResponse( + name: launchMethodName, + value: buildBillingResultMap(expectedBillingResult), + ); const ProductDetailsWrapper productDetails = dummyOneTimeProductDetails; const String accountId = 'hashedAccountId'; const String profileId = 'hashedProfileId'; @@ -392,16 +365,16 @@ void main() { prorationMode: prorationMode, purchaseToken: dummyOldPurchase.purchaseToken), equals(expectedBillingResult)); - final VerificationResult result = - verify(mockApi.launchBillingFlow(captureAny)); - final PlatformBillingFlowParams params = - result.captured.single as PlatformBillingFlowParams; - expect(params.product, equals(productDetails.productId)); - expect(params.accountId, equals(accountId)); - expect(params.oldProduct, equals(dummyOldPurchase.products.first)); - expect(params.obfuscatedProfileId, equals(profileId)); - expect(params.purchaseToken, equals(dummyOldPurchase.purchaseToken)); - expect(params.prorationMode, + final Map arguments = stubPlatform + .previousCallMatching(launchMethodName) + .arguments as Map; + expect(arguments['product'], equals(productDetails.productId)); + expect(arguments['accountId'], equals(accountId)); + expect(arguments['oldProduct'], equals(dummyOldPurchase.products.first)); + expect(arguments['obfuscatedProfileId'], equals(profileId)); + expect( + arguments['purchaseToken'], equals(dummyOldPurchase.purchaseToken)); + expect(arguments['prorationMode'], const ProrationModeConverter().toJson(prorationMode)); }); @@ -410,24 +383,41 @@ void main() { const BillingResponse responseCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: responseCode, debugMessage: debugMessage); - when(mockApi.launchBillingFlow(any)).thenAnswer( - (_) async => convertToPigeonResult(expectedBillingResult)); + stubPlatform.addResponse( + name: launchMethodName, + value: buildBillingResultMap(expectedBillingResult), + ); const ProductDetailsWrapper productDetails = dummyOneTimeProductDetails; expect( await billingClient.launchBillingFlow( product: productDetails.productId), equals(expectedBillingResult)); - final VerificationResult result = - verify(mockApi.launchBillingFlow(captureAny)); - final PlatformBillingFlowParams params = - result.captured.single as PlatformBillingFlowParams; - expect(params.product, equals(productDetails.productId)); - expect(params.accountId, isNull); + final Map arguments = stubPlatform + .previousCallMatching(launchMethodName) + .arguments as Map; + expect(arguments['product'], equals(productDetails.productId)); + expect(arguments['accountId'], isNull); + }); + + test('handles method channel returning null', () async { + stubPlatform.addResponse( + name: launchMethodName, + ); + const ProductDetailsWrapper productDetails = dummyOneTimeProductDetails; + expect( + await billingClient.launchBillingFlow( + product: productDetails.productId), + equals(const BillingResultWrapper( + responseCode: BillingResponse.error, + debugMessage: kInvalidBillingResultErrorMessage))); }); }); group('queryPurchases', () { + const String queryPurchasesMethodName = + 'BillingClient#queryPurchasesAsync(QueryPurchaseParams, PurchaseResponseListener)'; + test('serializes and deserializes data', () async { const BillingResponse expectedCode = BillingResponse.ok; final List expectedList = [ @@ -436,17 +426,14 @@ void main() { const String debugMessage = 'dummy message'; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); - when(mockApi.queryPurchasesAsync(any)) - .thenAnswer((_) async => PlatformPurchasesResponse( - billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(expectedCode), - debugMessage: debugMessage), - purchases: expectedList - .map((PurchaseWrapper purchase) => - convertToPigeonPurchase(purchase)) - .toList(), - )); + stubPlatform + .addResponse(name: queryPurchasesMethodName, value: { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'responseCode': const BillingResponseConverter().toJson(expectedCode), + 'purchasesList': expectedList + .map((PurchaseWrapper purchase) => buildPurchaseMap(purchase)) + .toList(), + }); final PurchasesResultWrapper response = await billingClient.queryPurchases(ProductType.inapp); @@ -461,27 +448,42 @@ void main() { const String debugMessage = 'dummy message'; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); - when(mockApi.queryPurchasesAsync(any)) - .thenAnswer((_) async => PlatformPurchasesResponse( - billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(expectedCode), - debugMessage: debugMessage), - purchases: [], - )); + stubPlatform + .addResponse(name: queryPurchasesMethodName, value: { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'responseCode': const BillingResponseConverter().toJson(expectedCode), + 'purchasesList': [], + }); final PurchasesResultWrapper response = await billingClient.queryPurchases(ProductType.inapp); expect(response.billingResult, equals(expectedBillingResult)); - // The top-level response code is hard-coded to "ok", as the underlying - // API no longer returns it. - expect(response.responseCode, BillingResponse.ok); + expect(response.responseCode, equals(expectedCode)); + expect(response.purchasesList, isEmpty); + }); + + test('handles method channel returning null', () async { + stubPlatform.addResponse( + name: queryPurchasesMethodName, + ); + final PurchasesResultWrapper response = + await billingClient.queryPurchases(ProductType.inapp); + + expect( + response.billingResult, + equals(const BillingResultWrapper( + responseCode: BillingResponse.error, + debugMessage: kInvalidBillingResultErrorMessage))); + expect(response.responseCode, BillingResponse.error); expect(response.purchasesList, isEmpty); }); }); group('queryPurchaseHistory', () { + const String queryPurchaseHistoryMethodName = + 'BillingClient#queryPurchaseHistoryAsync(QueryPurchaseHistoryParams, PurchaseHistoryResponseListener)'; + test('serializes and deserializes data', () async { const BillingResponse expectedCode = BillingResponse.ok; final List expectedList = @@ -491,16 +493,15 @@ void main() { const String debugMessage = 'dummy message'; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); - when(mockApi.queryPurchaseHistoryAsync(any)) - .thenAnswer((_) async => PlatformPurchaseHistoryResponse( - billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(expectedCode), - debugMessage: debugMessage), - purchases: expectedList - .map(platformPurchaseHistoryRecordFromWrapper) - .toList(), - )); + stubPlatform.addResponse( + name: queryPurchaseHistoryMethodName, + value: { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'purchaseHistoryRecordList': expectedList + .map((PurchaseHistoryRecordWrapper purchaseHistoryRecord) => + buildPurchaseHistoryRecordMap(purchaseHistoryRecord)) + .toList(), + }); final PurchasesHistoryResult response = await billingClient.queryPurchaseHistory(ProductType.inapp); @@ -513,14 +514,12 @@ void main() { const String debugMessage = 'dummy message'; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); - when(mockApi.queryPurchaseHistoryAsync(any)) - .thenAnswer((_) async => PlatformPurchaseHistoryResponse( - billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(expectedCode), - debugMessage: debugMessage), - purchases: [], - )); + stubPlatform.addResponse( + name: queryPurchaseHistoryMethodName, + value: { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'purchaseHistoryRecordList': [], + }); final PurchasesHistoryResult response = await billingClient.queryPurchaseHistory(ProductType.inapp); @@ -528,153 +527,124 @@ void main() { expect(response.billingResult, equals(expectedBillingResult)); expect(response.purchaseHistoryRecordList, isEmpty); }); + + test('handles method channel returning null', () async { + stubPlatform.addResponse( + name: queryPurchaseHistoryMethodName, + ); + final PurchasesHistoryResult response = + await billingClient.queryPurchaseHistory(ProductType.inapp); + + expect( + response.billingResult, + equals(const BillingResultWrapper( + responseCode: BillingResponse.error, + debugMessage: kInvalidBillingResultErrorMessage))); + expect(response.purchaseHistoryRecordList, isEmpty); + }); }); group('consume purchases', () { + const String consumeMethodName = + 'BillingClient#consumeAsync(ConsumeParams, ConsumeResponseListener)'; test('consume purchase async success', () async { - const String token = 'dummy token'; const BillingResponse expectedCode = BillingResponse.ok; const String debugMessage = 'dummy message'; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); - when(mockApi.consumeAsync(token)).thenAnswer( - (_) async => convertToPigeonResult(expectedBillingResult)); + stubPlatform.addResponse( + name: consumeMethodName, + value: buildBillingResultMap(expectedBillingResult)); final BillingResultWrapper billingResult = - await billingClient.consumeAsync(token); + await billingClient.consumeAsync('dummy token'); expect(billingResult, equals(expectedBillingResult)); }); + + test('handles method channel returning null', () async { + stubPlatform.addResponse( + name: consumeMethodName, + ); + final BillingResultWrapper billingResult = + await billingClient.consumeAsync('dummy token'); + + expect( + billingResult, + equals(const BillingResultWrapper( + responseCode: BillingResponse.error, + debugMessage: kInvalidBillingResultErrorMessage))); + }); }); group('acknowledge purchases', () { + const String acknowledgeMethodName = + 'BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)'; test('acknowledge purchase success', () async { - const String token = 'dummy token'; const BillingResponse expectedCode = BillingResponse.ok; const String debugMessage = 'dummy message'; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); - when(mockApi.acknowledgePurchase(token)).thenAnswer( - (_) async => convertToPigeonResult(expectedBillingResult)); + stubPlatform.addResponse( + name: acknowledgeMethodName, + value: buildBillingResultMap(expectedBillingResult)); final BillingResultWrapper billingResult = - await billingClient.acknowledgePurchase(token); + await billingClient.acknowledgePurchase('dummy token'); expect(billingResult, equals(expectedBillingResult)); }); + + test('handles method channel returning null', () async { + stubPlatform.addResponse( + name: acknowledgeMethodName, + ); + final BillingResultWrapper billingResult = + await billingClient.acknowledgePurchase('dummy token'); + + expect( + billingResult, + equals(const BillingResultWrapper( + responseCode: BillingResponse.error, + debugMessage: kInvalidBillingResultErrorMessage))); + }); }); group('isFeatureSupported', () { + const String isFeatureSupportedMethodName = + 'BillingClient#isFeatureSupported(String)'; test('isFeatureSupported returns false', () async { - when(mockApi.isFeatureSupported('subscriptions')) - .thenAnswer((_) async => false); + late Map arguments; + stubPlatform.addResponse( + name: isFeatureSupportedMethodName, + value: false, + additionalStepBeforeReturn: (dynamic value) => + arguments = value as Map, + ); final bool isSupported = await billingClient .isFeatureSupported(BillingClientFeature.subscriptions); expect(isSupported, isFalse); + expect(arguments['feature'], equals('subscriptions')); }); test('isFeatureSupported returns true', () async { - when(mockApi.isFeatureSupported('subscriptions')) - .thenAnswer((_) async => true); + late Map arguments; + stubPlatform.addResponse( + name: isFeatureSupportedMethodName, + value: true, + additionalStepBeforeReturn: (dynamic value) => + arguments = value as Map, + ); final bool isSupported = await billingClient .isFeatureSupported(BillingClientFeature.subscriptions); expect(isSupported, isTrue); + expect(arguments['feature'], equals('subscriptions')); }); }); - - group('billingConfig', () { - test('billingConfig returns object', () async { - const BillingConfigWrapper expected = BillingConfigWrapper( - countryCode: 'US', - responseCode: BillingResponse.ok, - debugMessage: ''); - when(mockApi.getBillingConfigAsync()) - .thenAnswer((_) async => platformBillingConfigFromWrapper(expected)); - final BillingConfigWrapper result = - await billingClient.getBillingConfig(); - expect(result.countryCode, 'US'); - expect(result, expected); - }); - }); - - group('isAlternativeBillingOnlyAvailable', () { - test('returns object', () async { - const BillingResultWrapper expected = BillingResultWrapper( - responseCode: BillingResponse.ok, debugMessage: 'message'); - when(mockApi.isAlternativeBillingOnlyAvailableAsync()).thenAnswer( - (_) async => PlatformBillingResult( - responseCode: 0, debugMessage: expected.debugMessage!)); - final BillingResultWrapper result = - await billingClient.isAlternativeBillingOnlyAvailable(); - expect(result, expected); - }); - }); - - group('createAlternativeBillingOnlyReportingDetails', () { - test('returns object', () async { - const AlternativeBillingOnlyReportingDetailsWrapper expected = - AlternativeBillingOnlyReportingDetailsWrapper( - responseCode: BillingResponse.ok, - debugMessage: 'debug', - externalTransactionToken: 'abc123youandme'); - when(mockApi.createAlternativeBillingOnlyReportingDetailsAsync()) - .thenAnswer((_) async => - platformAlternativeBillingOnlyReportingDetailsFromWrapper( - expected)); - final AlternativeBillingOnlyReportingDetailsWrapper result = - await billingClient.createAlternativeBillingOnlyReportingDetails(); - expect(result, equals(expected)); - }); - }); - - group('showAlternativeBillingOnlyInformationDialog', () { - test('returns object', () async { - const BillingResultWrapper expected = BillingResultWrapper( - responseCode: BillingResponse.ok, debugMessage: 'message'); - when(mockApi.showAlternativeBillingOnlyInformationDialog()).thenAnswer( - (_) async => PlatformBillingResult( - responseCode: 0, debugMessage: expected.debugMessage!)); - final BillingResultWrapper result = - await billingClient.showAlternativeBillingOnlyInformationDialog(); - expect(result, expected); - }); - }); -} - -PlatformBillingConfigResponse platformBillingConfigFromWrapper( - BillingConfigWrapper original) { - return PlatformBillingConfigResponse( - billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(original.responseCode), - debugMessage: original.debugMessage!, - ), - countryCode: original.countryCode); } -PlatformAlternativeBillingOnlyReportingDetailsResponse - platformAlternativeBillingOnlyReportingDetailsFromWrapper( - AlternativeBillingOnlyReportingDetailsWrapper original) { - return PlatformAlternativeBillingOnlyReportingDetailsResponse( - billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(original.responseCode), - debugMessage: original.debugMessage!, - ), - externalTransactionToken: original.externalTransactionToken); -} - -PlatformPurchaseHistoryRecord platformPurchaseHistoryRecordFromWrapper( - PurchaseHistoryRecordWrapper wrapper) { - return PlatformPurchaseHistoryRecord( - // For some reason quantity is not currently exposed in - // PurchaseHistoryRecordWrapper. - quantity: 99, - purchaseTime: wrapper.purchaseTime, - originalJson: wrapper.originalJson, - purchaseToken: wrapper.purchaseToken, - signature: wrapper.signature, - products: wrapper.products, - developerPayload: wrapper.developerPayload, - ); -} +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.mocks.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.mocks.dart deleted file mode 100644 index 1fd6c3382..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.mocks.dart +++ /dev/null @@ -1,428 +0,0 @@ -// Mocks generated by Mockito 5.4.4 from annotations -// in in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i3; - -import 'package:in_app_purchase_android/src/messages.g.dart' as _i2; -import 'package:mockito/mockito.dart' as _i1; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: deprecated_member_use -// ignore_for_file: deprecated_member_use_from_same_package -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types -// ignore_for_file: subtype_of_sealed_class - -class _FakePlatformBillingResult_0 extends _i1.SmartFake - implements _i2.PlatformBillingResult { - _FakePlatformBillingResult_0( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakePlatformBillingConfigResponse_1 extends _i1.SmartFake - implements _i2.PlatformBillingConfigResponse { - _FakePlatformBillingConfigResponse_1( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakePlatformPurchasesResponse_2 extends _i1.SmartFake - implements _i2.PlatformPurchasesResponse { - _FakePlatformPurchasesResponse_2( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakePlatformPurchaseHistoryResponse_3 extends _i1.SmartFake - implements _i2.PlatformPurchaseHistoryResponse { - _FakePlatformPurchaseHistoryResponse_3( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakePlatformProductDetailsResponse_4 extends _i1.SmartFake - implements _i2.PlatformProductDetailsResponse { - _FakePlatformProductDetailsResponse_4( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakePlatformAlternativeBillingOnlyReportingDetailsResponse_5 - extends _i1.SmartFake - implements _i2.PlatformAlternativeBillingOnlyReportingDetailsResponse { - _FakePlatformAlternativeBillingOnlyReportingDetailsResponse_5( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -/// A class which mocks [InAppPurchaseApi]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockInAppPurchaseApi extends _i1.Mock implements _i2.InAppPurchaseApi { - @override - _i3.Future isReady() => (super.noSuchMethod( - Invocation.method( - #isReady, - [], - ), - returnValue: _i3.Future.value(false), - returnValueForMissingStub: _i3.Future.value(false), - ) as _i3.Future); - - @override - _i3.Future<_i2.PlatformBillingResult> startConnection( - int? callbackHandle, - _i2.PlatformBillingChoiceMode? billingMode, - ) => - (super.noSuchMethod( - Invocation.method( - #startConnection, - [ - callbackHandle, - billingMode, - ], - ), - returnValue: _i3.Future<_i2.PlatformBillingResult>.value( - _FakePlatformBillingResult_0( - this, - Invocation.method( - #startConnection, - [ - callbackHandle, - billingMode, - ], - ), - )), - returnValueForMissingStub: _i3.Future<_i2.PlatformBillingResult>.value( - _FakePlatformBillingResult_0( - this, - Invocation.method( - #startConnection, - [ - callbackHandle, - billingMode, - ], - ), - )), - ) as _i3.Future<_i2.PlatformBillingResult>); - - @override - _i3.Future endConnection() => (super.noSuchMethod( - Invocation.method( - #endConnection, - [], - ), - returnValue: _i3.Future.value(), - returnValueForMissingStub: _i3.Future.value(), - ) as _i3.Future); - - @override - _i3.Future<_i2.PlatformBillingConfigResponse> getBillingConfigAsync() => - (super.noSuchMethod( - Invocation.method( - #getBillingConfigAsync, - [], - ), - returnValue: _i3.Future<_i2.PlatformBillingConfigResponse>.value( - _FakePlatformBillingConfigResponse_1( - this, - Invocation.method( - #getBillingConfigAsync, - [], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.PlatformBillingConfigResponse>.value( - _FakePlatformBillingConfigResponse_1( - this, - Invocation.method( - #getBillingConfigAsync, - [], - ), - )), - ) as _i3.Future<_i2.PlatformBillingConfigResponse>); - - @override - _i3.Future<_i2.PlatformBillingResult> launchBillingFlow( - _i2.PlatformBillingFlowParams? params) => - (super.noSuchMethod( - Invocation.method( - #launchBillingFlow, - [params], - ), - returnValue: _i3.Future<_i2.PlatformBillingResult>.value( - _FakePlatformBillingResult_0( - this, - Invocation.method( - #launchBillingFlow, - [params], - ), - )), - returnValueForMissingStub: _i3.Future<_i2.PlatformBillingResult>.value( - _FakePlatformBillingResult_0( - this, - Invocation.method( - #launchBillingFlow, - [params], - ), - )), - ) as _i3.Future<_i2.PlatformBillingResult>); - - @override - _i3.Future<_i2.PlatformBillingResult> acknowledgePurchase( - String? purchaseToken) => - (super.noSuchMethod( - Invocation.method( - #acknowledgePurchase, - [purchaseToken], - ), - returnValue: _i3.Future<_i2.PlatformBillingResult>.value( - _FakePlatformBillingResult_0( - this, - Invocation.method( - #acknowledgePurchase, - [purchaseToken], - ), - )), - returnValueForMissingStub: _i3.Future<_i2.PlatformBillingResult>.value( - _FakePlatformBillingResult_0( - this, - Invocation.method( - #acknowledgePurchase, - [purchaseToken], - ), - )), - ) as _i3.Future<_i2.PlatformBillingResult>); - - @override - _i3.Future<_i2.PlatformBillingResult> consumeAsync(String? purchaseToken) => - (super.noSuchMethod( - Invocation.method( - #consumeAsync, - [purchaseToken], - ), - returnValue: _i3.Future<_i2.PlatformBillingResult>.value( - _FakePlatformBillingResult_0( - this, - Invocation.method( - #consumeAsync, - [purchaseToken], - ), - )), - returnValueForMissingStub: _i3.Future<_i2.PlatformBillingResult>.value( - _FakePlatformBillingResult_0( - this, - Invocation.method( - #consumeAsync, - [purchaseToken], - ), - )), - ) as _i3.Future<_i2.PlatformBillingResult>); - - @override - _i3.Future<_i2.PlatformPurchasesResponse> queryPurchasesAsync( - _i2.PlatformProductType? productType) => - (super.noSuchMethod( - Invocation.method( - #queryPurchasesAsync, - [productType], - ), - returnValue: _i3.Future<_i2.PlatformPurchasesResponse>.value( - _FakePlatformPurchasesResponse_2( - this, - Invocation.method( - #queryPurchasesAsync, - [productType], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.PlatformPurchasesResponse>.value( - _FakePlatformPurchasesResponse_2( - this, - Invocation.method( - #queryPurchasesAsync, - [productType], - ), - )), - ) as _i3.Future<_i2.PlatformPurchasesResponse>); - - @override - _i3.Future<_i2.PlatformPurchaseHistoryResponse> queryPurchaseHistoryAsync( - _i2.PlatformProductType? productType) => - (super.noSuchMethod( - Invocation.method( - #queryPurchaseHistoryAsync, - [productType], - ), - returnValue: _i3.Future<_i2.PlatformPurchaseHistoryResponse>.value( - _FakePlatformPurchaseHistoryResponse_3( - this, - Invocation.method( - #queryPurchaseHistoryAsync, - [productType], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.PlatformPurchaseHistoryResponse>.value( - _FakePlatformPurchaseHistoryResponse_3( - this, - Invocation.method( - #queryPurchaseHistoryAsync, - [productType], - ), - )), - ) as _i3.Future<_i2.PlatformPurchaseHistoryResponse>); - - @override - _i3.Future<_i2.PlatformProductDetailsResponse> queryProductDetailsAsync( - List<_i2.PlatformQueryProduct?>? products) => - (super.noSuchMethod( - Invocation.method( - #queryProductDetailsAsync, - [products], - ), - returnValue: _i3.Future<_i2.PlatformProductDetailsResponse>.value( - _FakePlatformProductDetailsResponse_4( - this, - Invocation.method( - #queryProductDetailsAsync, - [products], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.PlatformProductDetailsResponse>.value( - _FakePlatformProductDetailsResponse_4( - this, - Invocation.method( - #queryProductDetailsAsync, - [products], - ), - )), - ) as _i3.Future<_i2.PlatformProductDetailsResponse>); - - @override - _i3.Future isFeatureSupported(String? feature) => (super.noSuchMethod( - Invocation.method( - #isFeatureSupported, - [feature], - ), - returnValue: _i3.Future.value(false), - returnValueForMissingStub: _i3.Future.value(false), - ) as _i3.Future); - - @override - _i3.Future<_i2.PlatformBillingResult> - isAlternativeBillingOnlyAvailableAsync() => (super.noSuchMethod( - Invocation.method( - #isAlternativeBillingOnlyAvailableAsync, - [], - ), - returnValue: _i3.Future<_i2.PlatformBillingResult>.value( - _FakePlatformBillingResult_0( - this, - Invocation.method( - #isAlternativeBillingOnlyAvailableAsync, - [], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.PlatformBillingResult>.value( - _FakePlatformBillingResult_0( - this, - Invocation.method( - #isAlternativeBillingOnlyAvailableAsync, - [], - ), - )), - ) as _i3.Future<_i2.PlatformBillingResult>); - - @override - _i3.Future<_i2.PlatformBillingResult> - showAlternativeBillingOnlyInformationDialog() => (super.noSuchMethod( - Invocation.method( - #showAlternativeBillingOnlyInformationDialog, - [], - ), - returnValue: _i3.Future<_i2.PlatformBillingResult>.value( - _FakePlatformBillingResult_0( - this, - Invocation.method( - #showAlternativeBillingOnlyInformationDialog, - [], - ), - )), - returnValueForMissingStub: - _i3.Future<_i2.PlatformBillingResult>.value( - _FakePlatformBillingResult_0( - this, - Invocation.method( - #showAlternativeBillingOnlyInformationDialog, - [], - ), - )), - ) as _i3.Future<_i2.PlatformBillingResult>); - - @override - _i3.Future<_i2.PlatformAlternativeBillingOnlyReportingDetailsResponse> - createAlternativeBillingOnlyReportingDetailsAsync() => - (super.noSuchMethod( - Invocation.method( - #createAlternativeBillingOnlyReportingDetailsAsync, - [], - ), - returnValue: _i3.Future< - _i2 - .PlatformAlternativeBillingOnlyReportingDetailsResponse>.value( - _FakePlatformAlternativeBillingOnlyReportingDetailsResponse_5( - this, - Invocation.method( - #createAlternativeBillingOnlyReportingDetailsAsync, - [], - ), - )), - returnValueForMissingStub: _i3.Future< - _i2 - .PlatformAlternativeBillingOnlyReportingDetailsResponse>.value( - _FakePlatformAlternativeBillingOnlyReportingDetailsResponse_5( - this, - Invocation.method( - #createAlternativeBillingOnlyReportingDetailsAsync, - [], - ), - )), - ) as _i3.Future< - _i2.PlatformAlternativeBillingOnlyReportingDetailsResponse>); -} diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart index a9c76ab5a..cae939643 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart @@ -7,44 +7,53 @@ import 'package:flutter/widgets.dart' as widgets; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:in_app_purchase_android/in_app_purchase_android.dart'; -import 'package:in_app_purchase_android/src/billing_client_wrappers/billing_config_wrapper.dart'; -import 'package:in_app_purchase_android/src/messages.g.dart'; -import 'package:in_app_purchase_android/src/types/translator.dart'; -import 'package:mockito/mockito.dart'; +import 'package:in_app_purchase_android/src/channel.dart'; -import 'billing_client_wrappers/billing_client_wrapper_test.dart'; -import 'billing_client_wrappers/billing_client_wrapper_test.mocks.dart'; import 'billing_client_wrappers/purchase_wrapper_test.dart'; -import 'test_conversion_utils.dart'; +import 'stub_in_app_purchase_platform.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - late MockInAppPurchaseApi mockApi; + final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); late InAppPurchaseAndroidPlatformAddition iapAndroidPlatformAddition; - late BillingClientManager manager; + const String startConnectionCall = + 'BillingClient#startConnection(BillingClientStateListener)'; + const String endConnectionCall = 'BillingClient#endConnection()'; + + setUpAll(() { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, stubPlatform.fakeMethodCallHandler); + }); setUp(() { widgets.WidgetsFlutterBinding.ensureInitialized(); - mockApi = MockInAppPurchaseApi(); - when(mockApi.startConnection(any, any)).thenAnswer( - (_) async => PlatformBillingResult(responseCode: 0, debugMessage: '')); - manager = BillingClientManager( - billingClientFactory: (PurchasesUpdatedListener listener, - UserSelectedAlternativeBillingListener? - alternativeBillingListener) => - BillingClient(listener, alternativeBillingListener, api: mockApi)); - iapAndroidPlatformAddition = InAppPurchaseAndroidPlatformAddition(manager); + + const String debugMessage = 'dummy message'; + const BillingResponse responseCode = BillingResponse.ok; + const BillingResultWrapper expectedBillingResult = BillingResultWrapper( + responseCode: responseCode, debugMessage: debugMessage); + stubPlatform.addResponse( + name: startConnectionCall, + value: buildBillingResultMap(expectedBillingResult)); + stubPlatform.addResponse(name: endConnectionCall); + iapAndroidPlatformAddition = + InAppPurchaseAndroidPlatformAddition(BillingClientManager()); }); group('consume purchases', () { + const String consumeMethodName = + 'BillingClient#consumeAsync(ConsumeParams, ConsumeResponseListener)'; test('consume purchase async success', () async { const BillingResponse expectedCode = BillingResponse.ok; const String debugMessage = 'dummy message'; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); - when(mockApi.consumeAsync(any)).thenAnswer( - (_) async => convertToPigeonResult(expectedBillingResult)); + stubPlatform.addResponse( + name: consumeMethodName, + value: buildBillingResultMap(expectedBillingResult), + ); final BillingResultWrapper billingResultWrapper = await iapAndroidPlatformAddition.consumePurchase( GooglePlayPurchaseDetails.fromPurchase(dummyPurchase).first); @@ -53,101 +62,45 @@ void main() { }); }); - group('billingConfig', () { - test('getCountryCode success', () async { - const String expectedCountryCode = 'US'; - const BillingConfigWrapper expected = BillingConfigWrapper( - countryCode: expectedCountryCode, - responseCode: BillingResponse.ok, - debugMessage: 'dummy message'); - - when(mockApi.getBillingConfigAsync()) - .thenAnswer((_) async => platformBillingConfigFromWrapper(expected)); - final String countryCode = - await iapAndroidPlatformAddition.getCountryCode(); - - expect(countryCode, equals(expectedCountryCode)); - }); - }); - - group('setBillingChoice', () { - test('setAlternativeBillingOnlyState', () async { - clearInteractions(mockApi); - await iapAndroidPlatformAddition - .setBillingChoice(BillingChoiceMode.alternativeBillingOnly); - - // Fake the disconnect that we would expect from a endConnectionCall. - manager.client.hostCallbackHandler.onBillingServiceDisconnected(0); - // Verify that after connection ended reconnect was called. - final VerificationResult result = - verify(mockApi.startConnection(any, captureAny)); - expect(result.callCount, equals(2)); - expect(result.captured.last, - PlatformBillingChoiceMode.alternativeBillingOnly); - }); - - test('setPlayBillingState', () async { - clearInteractions(mockApi); - await iapAndroidPlatformAddition - .setBillingChoice(BillingChoiceMode.playBillingOnly); - - // Fake the disconnect that we would expect from a endConnectionCall. - manager.client.hostCallbackHandler.onBillingServiceDisconnected(0); - // Verify that after connection ended reconnect was called. - final VerificationResult result = - verify(mockApi.startConnection(any, captureAny)); - expect(result.callCount, equals(2)); - expect(result.captured.last, PlatformBillingChoiceMode.playBillingOnly); - }); - }); - - group('isAlternativeBillingOnlyAvailable', () { - test('isAlternativeBillingOnlyAvailable success', () async { - const BillingResultWrapper expected = BillingResultWrapper( - responseCode: BillingResponse.ok, debugMessage: 'dummy message'); - when(mockApi.isAlternativeBillingOnlyAvailableAsync()).thenAnswer( - (_) async => PlatformBillingResult( - responseCode: 0, debugMessage: expected.debugMessage!)); - - final BillingResultWrapper result = - await iapAndroidPlatformAddition.isAlternativeBillingOnlyAvailable(); - - expect(result, equals(expected)); - }); - }); - - group('showAlternativeBillingOnlyInformationDialog', () { - test('showAlternativeBillingOnlyInformationDialog success', () async { - const BillingResultWrapper expected = BillingResultWrapper( - responseCode: BillingResponse.ok, debugMessage: 'dummy message'); - - when(mockApi.isAlternativeBillingOnlyAvailableAsync()) - .thenAnswer((_) async => convertToPigeonResult(expected)); - when(mockApi.showAlternativeBillingOnlyInformationDialog()) - .thenAnswer((_) async => convertToPigeonResult(expected)); - final BillingResultWrapper result = - await iapAndroidPlatformAddition.isAlternativeBillingOnlyAvailable(); - - expect(result, equals(expected)); - }); - }); - group('queryPastPurchase', () { group('queryPurchaseDetails', () { + const String queryMethodName = + 'BillingClient#queryPurchasesAsync(QueryPurchaseParams, PurchaseResponseListener)'; + test('handles error', () async { + const String debugMessage = 'dummy message'; + const BillingResponse responseCode = BillingResponse.developerError; + const BillingResultWrapper expectedBillingResult = BillingResultWrapper( + responseCode: responseCode, debugMessage: debugMessage); + + stubPlatform + .addResponse(name: queryMethodName, value: { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'responseCode': const BillingResponseConverter().toJson(responseCode), + 'purchasesList': >[] + }); + final QueryPurchaseDetailsResponse response = + await iapAndroidPlatformAddition.queryPastPurchases(); + expect(response.pastPurchases, isEmpty); + expect(response.error, isNotNull); + expect( + response.error!.message, BillingResponse.developerError.toString()); + expect(response.error!.source, kIAPSource); + }); + test('returns ProductDetailsResponseWrapper', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; - - when(mockApi.queryPurchasesAsync(any)) - .thenAnswer((_) async => PlatformPurchasesResponse( - billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), - purchases: [ - convertToPigeonPurchase(dummyPurchase), - ], - )); + const BillingResultWrapper expectedBillingResult = BillingResultWrapper( + responseCode: responseCode, debugMessage: debugMessage); + + stubPlatform + .addResponse(name: queryMethodName, value: { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'responseCode': const BillingResponseConverter().toJson(responseCode), + 'purchasesList': >[ + buildPurchaseMap(dummyPurchase), + ] + }); // Since queryPastPurchases makes 2 platform method calls (one for each ProductType), the result will contain 2 dummyWrapper instead // of 1. @@ -158,13 +111,26 @@ void main() { }); test('should store platform exception in the response', () async { - when(mockApi.queryPurchasesAsync(any)).thenAnswer((_) async { - throw PlatformException( - code: 'error_code', - message: 'error_message', - details: {'info': 'error_info'}, - ); - }); + const String debugMessage = 'dummy message'; + + const BillingResponse responseCode = BillingResponse.developerError; + const BillingResultWrapper expectedBillingResult = BillingResultWrapper( + responseCode: responseCode, debugMessage: debugMessage); + stubPlatform.addResponse( + name: queryMethodName, + value: { + 'responseCode': + const BillingResponseConverter().toJson(responseCode), + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'purchasesList': >[] + }, + additionalStepBeforeReturn: (dynamic _) { + throw PlatformException( + code: 'error_code', + message: 'error_message', + details: {'info': 'error_info'}, + ); + }); final QueryPurchaseDetailsResponse response = await iapAndroidPlatformAddition.queryPastPurchases(); expect(response.pastPurchases, isEmpty); @@ -178,44 +144,40 @@ void main() { }); group('isFeatureSupported', () { + const String isFeatureSupportedMethodName = + 'BillingClient#isFeatureSupported(String)'; test('isFeatureSupported returns false', () async { - when(mockApi.isFeatureSupported('subscriptions')) - .thenAnswer((_) async => false); + late Map arguments; + stubPlatform.addResponse( + name: isFeatureSupportedMethodName, + value: false, + additionalStepBeforeReturn: (dynamic value) => + arguments = value as Map, + ); final bool isSupported = await iapAndroidPlatformAddition .isFeatureSupported(BillingClientFeature.subscriptions); expect(isSupported, isFalse); + expect(arguments['feature'], equals('subscriptions')); }); test('isFeatureSupported returns true', () async { - when(mockApi.isFeatureSupported('subscriptions')) - .thenAnswer((_) async => true); + late Map arguments; + stubPlatform.addResponse( + name: isFeatureSupportedMethodName, + value: true, + additionalStepBeforeReturn: (dynamic value) => + arguments = value as Map, + ); final bool isSupported = await iapAndroidPlatformAddition .isFeatureSupported(BillingClientFeature.subscriptions); expect(isSupported, isTrue); - }); - }); - - group('userChoiceDetails', () { - test('called', () async { - final Future futureDetails = - iapAndroidPlatformAddition.userChoiceDetailsStream.first; - const UserChoiceDetailsWrapper expected = UserChoiceDetailsWrapper( - originalExternalTransactionId: 'TransactionId', - externalTransactionToken: 'TransactionToken', - products: [ - UserChoiceDetailsProductWrapper( - id: 'id1', - offerToken: 'offerToken1', - productType: ProductType.inapp), - UserChoiceDetailsProductWrapper( - id: 'id2', - offerToken: 'offerToken2', - productType: ProductType.inapp), - ], - ); - manager.onUserChoiceAlternativeBilling(expected); - expect( - await futureDetails, Translator.convertToUserChoiceDetails(expected)); + expect(arguments['feature'], equals('subscriptions')); }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart index c5e5d628e..330e68cff 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart @@ -9,105 +9,125 @@ import 'package:flutter/widgets.dart' as widgets; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:in_app_purchase_android/in_app_purchase_android.dart'; -import 'package:in_app_purchase_android/src/billing_client_wrappers/billing_config_wrapper.dart'; -import 'package:in_app_purchase_android/src/messages.g.dart'; +import 'package:in_app_purchase_android/src/channel.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; -import 'package:mockito/mockito.dart'; -import 'billing_client_wrappers/billing_client_wrapper_test.dart'; -import 'billing_client_wrappers/billing_client_wrapper_test.mocks.dart'; import 'billing_client_wrappers/product_details_wrapper_test.dart'; import 'billing_client_wrappers/purchase_wrapper_test.dart'; -import 'test_conversion_utils.dart'; +import 'stub_in_app_purchase_platform.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - late MockInAppPurchaseApi mockApi; + final StubInAppPurchasePlatform stubPlatform = StubInAppPurchasePlatform(); late InAppPurchaseAndroidPlatform iapAndroidPlatform; + const String startConnectionCall = + 'BillingClient#startConnection(BillingClientStateListener)'; + const String endConnectionCall = 'BillingClient#endConnection()'; + const String acknowledgePurchaseCall = + 'BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)'; + const String onBillingServiceDisconnectedCallback = + 'BillingClientStateListener#onBillingServiceDisconnected()'; + + setUpAll(() { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, stubPlatform.fakeMethodCallHandler); + }); setUp(() { widgets.WidgetsFlutterBinding.ensureInitialized(); - mockApi = MockInAppPurchaseApi(); - when(mockApi.startConnection(any, any)).thenAnswer( - (_) async => PlatformBillingResult(responseCode: 0, debugMessage: '')); - iapAndroidPlatform = InAppPurchaseAndroidPlatform( - manager: BillingClientManager( - billingClientFactory: (PurchasesUpdatedListener listener, - UserSelectedAlternativeBillingListener? - alternativeBillingListener) => - BillingClient(listener, alternativeBillingListener, - api: mockApi))); - InAppPurchasePlatform.instance = iapAndroidPlatform; + const String debugMessage = 'dummy message'; + const BillingResponse responseCode = BillingResponse.ok; + const BillingResultWrapper expectedBillingResult = BillingResultWrapper( + responseCode: responseCode, debugMessage: debugMessage); + stubPlatform.addResponse( + name: startConnectionCall, + value: buildBillingResultMap(expectedBillingResult)); + stubPlatform.addResponse(name: endConnectionCall); + + InAppPurchaseAndroidPlatform.registerPlatform(); + iapAndroidPlatform = + InAppPurchasePlatform.instance as InAppPurchaseAndroidPlatform; + }); + + tearDown(() { + stubPlatform.reset(); }); group('connection management', () { test('connects on initialization', () { //await iapAndroidPlatform.isAvailable(); - verify(mockApi.startConnection(any, any)).called(1); + expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(1)); }); test('re-connects when client sends onBillingServiceDisconnected', () { - iapAndroidPlatform.billingClientManager.client.hostCallbackHandler - .onBillingServiceDisconnected(0); - verify(mockApi.startConnection(any, any)).called(2); + iapAndroidPlatform.billingClientManager.client.callHandler( + const MethodCall(onBillingServiceDisconnectedCallback, + {'handle': 0}), + ); + expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(2)); }); test( 're-connects when operation returns BillingResponse.clientDisconnected', () async { - when(mockApi.acknowledgePurchase(any)).thenAnswer( - (_) async => PlatformBillingResult( - responseCode: const BillingResponseConverter() - .toJson(BillingResponse.serviceDisconnected), - debugMessage: 'disconnected'), + final Map okValue = buildBillingResultMap( + const BillingResultWrapper(responseCode: BillingResponse.ok)); + stubPlatform.addResponse( + name: acknowledgePurchaseCall, + value: buildBillingResultMap( + const BillingResultWrapper( + responseCode: BillingResponse.serviceDisconnected, + ), + ), + ); + stubPlatform.addResponse( + name: startConnectionCall, + value: okValue, + additionalStepBeforeReturn: (dynamic _) => stubPlatform.addResponse( + name: acknowledgePurchaseCall, value: okValue), ); - when(mockApi.startConnection(any, any)).thenAnswer((_) async { - // Change the acknowledgePurchase response to success for the next call. - when(mockApi.acknowledgePurchase(any)).thenAnswer( - (_) async => PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(BillingResponse.ok), - debugMessage: 'disconnected'), - ); - return PlatformBillingResult(responseCode: 0, debugMessage: ''); - }); final PurchaseDetails purchase = GooglePlayPurchaseDetails.fromPurchase(dummyUnacknowledgedPurchase) .first; final BillingResultWrapper result = await iapAndroidPlatform.completePurchase(purchase); - verify(mockApi.acknowledgePurchase(any)).called(2); - verify(mockApi.startConnection(any, any)).called(2); + expect( + stubPlatform.countPreviousCalls(acknowledgePurchaseCall), + equals(2), + ); + expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(2)); expect(result.responseCode, equals(BillingResponse.ok)); }); }); group('isAvailable', () { test('true', () async { - when(mockApi.isReady()).thenAnswer((_) async => true); + stubPlatform.addResponse(name: 'BillingClient#isReady()', value: true); expect(await iapAndroidPlatform.isAvailable(), isTrue); }); test('false', () async { - when(mockApi.isReady()).thenAnswer((_) async => false); + stubPlatform.addResponse(name: 'BillingClient#isReady()', value: false); expect(await iapAndroidPlatform.isAvailable(), isFalse); }); }); group('queryProductDetails', () { + const String queryMethodName = + 'BillingClient#queryProductDetailsAsync(QueryProductDetailsParams, ProductDetailsResponseListener)'; + test('handles empty productDetails', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; - when(mockApi.queryProductDetailsAsync(any)) - .thenAnswer((_) async => PlatformProductDetailsResponse( - billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), - productDetails: [], - )); + const BillingResultWrapper expectedBillingResult = BillingResultWrapper( + responseCode: responseCode, debugMessage: debugMessage); + stubPlatform.addResponse(name: queryMethodName, value: { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'productDetailsList': >[], + }); final ProductDetailsResponse response = await iapAndroidPlatform.queryProductDetails({''}); @@ -117,16 +137,14 @@ void main() { test('should get correct product details', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; - when(mockApi.queryProductDetailsAsync(any)) - .thenAnswer((_) async => PlatformProductDetailsResponse( - billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), - productDetails: [ - convertToPigeonProductDetails(dummyOneTimeProductDetails) - ], - )); + const BillingResultWrapper expectedBillingResult = BillingResultWrapper( + responseCode: responseCode, debugMessage: debugMessage); + stubPlatform.addResponse(name: queryMethodName, value: { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'productDetailsList': >[ + buildProductMap(dummyOneTimeProductDetails) + ] + }); // Since queryProductDetails makes 2 platform method calls (one for each ProductType), the result will contain 2 dummyWrapper instead // of 1. final ProductDetailsResponse response = @@ -145,16 +163,14 @@ void main() { test('should get the correct notFoundIDs', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; - when(mockApi.queryProductDetailsAsync(any)) - .thenAnswer((_) async => PlatformProductDetailsResponse( - billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), - productDetails: [ - convertToPigeonProductDetails(dummyOneTimeProductDetails) - ], - )); + const BillingResultWrapper expectedBillingResult = BillingResultWrapper( + responseCode: responseCode, debugMessage: debugMessage); + stubPlatform.addResponse(name: queryMethodName, value: { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'productDetailsList': >[ + buildProductMap(dummyOneTimeProductDetails) + ] + }); // Since queryProductDetails makes 2 platform method calls (one for each ProductType), the result will contain 2 dummyWrapper instead // of 1. final ProductDetailsResponse response = @@ -165,13 +181,23 @@ void main() { test( 'should have error stored in the response when platform exception is thrown', () async { - when(mockApi.queryProductDetailsAsync(any)).thenAnswer((_) async { - throw PlatformException( - code: 'error_code', - message: 'error_message', - details: {'info': 'error_info'}, - ); - }); + const BillingResponse responseCode = BillingResponse.ok; + stubPlatform.addResponse( + name: queryMethodName, + value: { + 'responseCode': + const BillingResponseConverter().toJson(responseCode), + 'productDetailsList': >[ + buildProductMap(dummyOneTimeProductDetails) + ] + }, + additionalStepBeforeReturn: (dynamic _) { + throw PlatformException( + code: 'error_code', + message: 'error_message', + details: {'info': 'error_info'}, + ); + }); // Since queryProductDetails makes 2 platform method calls (one for each ProductType), the result will contain 2 dummyWrapper instead // of 1. final ProductDetailsResponse response = @@ -187,15 +213,56 @@ void main() { }); group('restorePurchases', () { - test('should store platform exception in the response', () async { - when(mockApi.queryPurchasesAsync(any)).thenAnswer((_) async { - throw PlatformException( - code: 'error_code', - message: 'error_message', - details: {'info': 'error_info'}, - ); + const String queryMethodName = + 'BillingClient#queryPurchasesAsync(QueryPurchaseParams, PurchaseResponseListener)'; + test('handles error', () async { + const String debugMessage = 'dummy message'; + const BillingResponse responseCode = BillingResponse.developerError; + const BillingResultWrapper expectedBillingResult = BillingResultWrapper( + responseCode: responseCode, debugMessage: debugMessage); + + stubPlatform.addResponse(name: queryMethodName, value: { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'responseCode': const BillingResponseConverter().toJson(responseCode), + 'purchasesList': >[] }); + expect( + iapAndroidPlatform.restorePurchases(), + throwsA( + isA() + .having( + (InAppPurchaseException e) => e.source, 'source', kIAPSource) + .having((InAppPurchaseException e) => e.code, 'code', + kRestoredPurchaseErrorCode) + .having((InAppPurchaseException e) => e.message, 'message', + responseCode.toString()), + ), + ); + }); + + test('should store platform exception in the response', () async { + const String debugMessage = 'dummy message'; + + const BillingResponse responseCode = BillingResponse.developerError; + const BillingResultWrapper expectedBillingResult = BillingResultWrapper( + responseCode: responseCode, debugMessage: debugMessage); + stubPlatform.addResponse( + name: queryMethodName, + value: { + 'responseCode': + const BillingResponseConverter().toJson(responseCode), + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'purchasesList': >[] + }, + additionalStepBeforeReturn: (dynamic _) { + throw PlatformException( + code: 'error_code', + message: 'error_message', + details: {'info': 'error_info'}, + ); + }); + expect( iapAndroidPlatform.restorePurchases(), throwsA( @@ -225,17 +292,16 @@ void main() { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.ok; - - when(mockApi.queryPurchasesAsync(any)) - .thenAnswer((_) async => PlatformPurchasesResponse( - billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), - purchases: [ - convertToPigeonPurchase(dummyPurchase), - ], - )); + const BillingResultWrapper expectedBillingResult = BillingResultWrapper( + responseCode: responseCode, debugMessage: debugMessage); + + stubPlatform.addResponse(name: queryMethodName, value: { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'responseCode': const BillingResponseConverter().toJson(responseCode), + 'purchasesList': >[ + buildPurchaseMap(dummyPurchase), + ] + }); // Since queryPastPurchases makes 2 platform method calls (one for each // ProductType), the result will contain 2 dummyPurchase instances instead @@ -263,6 +329,11 @@ void main() { }); group('make payment', () { + const String launchMethodName = + 'BillingClient#launchBillingFlow(Activity, BillingFlowParams)'; + const String consumeMethodName = + 'BillingClient#consumeAsync(ConsumeParams, ConsumeResponseListener)'; + test('buy non consumable, serializes and deserializes data', () async { const ProductDetailsWrapper productDetails = dummyOneTimeProductDetails; const String accountId = 'hashedAccountId'; @@ -271,31 +342,33 @@ void main() { const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); - when(mockApi.launchBillingFlow(any)).thenAnswer((_) async { - // Mock java update purchase callback. - iapAndroidPlatform.billingClientManager.client.hostCallbackHandler - .onPurchasesUpdated(PlatformPurchasesResponse( - billingResult: convertToPigeonResult(expectedBillingResult), - purchases: [ - PlatformPurchase( - orderId: 'orderID1', - products: [productDetails.productId], - isAutoRenewing: false, - packageName: 'package', - purchaseTime: 1231231231, - purchaseToken: 'token', - signature: 'sign', - originalJson: 'json', - developerPayload: 'dummy payload', - isAcknowledged: true, - purchaseState: PlatformPurchaseState.purchased, - quantity: 1, - ) - ], - )); - - return convertToPigeonResult(expectedBillingResult); - }); + stubPlatform.addResponse( + name: launchMethodName, + value: buildBillingResultMap(expectedBillingResult), + additionalStepBeforeReturn: (dynamic _) { + // Mock java update purchase callback. + final MethodCall call = + MethodCall(kOnPurchasesUpdated, { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'responseCode': const BillingResponseConverter().toJson(sentCode), + 'purchasesList': [ + { + 'orderId': 'orderID1', + 'products': [productDetails.productId], + 'isAutoRenewing': false, + 'packageName': 'package', + 'purchaseTime': 1231231231, + 'purchaseToken': 'token', + 'signature': 'sign', + 'originalJson': 'json', + 'developerPayload': 'dummy payload', + 'isAcknowledged': true, + 'purchaseState': 1, + } + ] + }); + iapAndroidPlatform.billingClientManager.client.callHandler(call); + }); final Completer completer = Completer(); PurchaseDetails purchaseDetails; final Stream> purchaseStream = @@ -328,16 +401,19 @@ void main() { const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); - when(mockApi.launchBillingFlow(any)).thenAnswer((_) async { - // Mock java update purchase callback. - iapAndroidPlatform.billingClientManager.client.hostCallbackHandler - .onPurchasesUpdated(PlatformPurchasesResponse( - billingResult: convertToPigeonResult(expectedBillingResult), - purchases: [], - )); - - return convertToPigeonResult(expectedBillingResult); - }); + stubPlatform.addResponse( + name: launchMethodName, + value: buildBillingResultMap(expectedBillingResult), + additionalStepBeforeReturn: (dynamic _) { + // Mock java update purchase callback. + final MethodCall call = + MethodCall(kOnPurchasesUpdated, { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'responseCode': const BillingResponseConverter().toJson(sentCode), + 'purchasesList': const [] + }); + iapAndroidPlatform.billingClientManager.client.callHandler(call); + }); final Completer completer = Completer(); PurchaseDetails purchaseDetails; final Stream> purchaseStream = @@ -370,43 +446,47 @@ void main() { const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); - when(mockApi.launchBillingFlow(any)).thenAnswer((_) async { - // Mock java update purchase callback. - iapAndroidPlatform.billingClientManager.client.hostCallbackHandler - .onPurchasesUpdated(PlatformPurchasesResponse( - billingResult: convertToPigeonResult(expectedBillingResult), - purchases: [ - PlatformPurchase( - orderId: 'orderID1', - products: [productDetails.productId], - isAutoRenewing: false, - packageName: 'package', - purchaseTime: 1231231231, - purchaseToken: 'token', - signature: 'sign', - originalJson: 'json', - developerPayload: 'dummy payload', - isAcknowledged: true, - purchaseState: PlatformPurchaseState.purchased, - quantity: 1, - ) - ], - )); - - return convertToPigeonResult(expectedBillingResult); - }); + stubPlatform.addResponse( + name: launchMethodName, + value: buildBillingResultMap(expectedBillingResult), + additionalStepBeforeReturn: (dynamic _) { + // Mock java update purchase callback. + final MethodCall call = + MethodCall(kOnPurchasesUpdated, { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'responseCode': const BillingResponseConverter().toJson(sentCode), + 'purchasesList': [ + { + 'orderId': 'orderID1', + 'products': [productDetails.productId], + 'isAutoRenewing': false, + 'packageName': 'package', + 'purchaseTime': 1231231231, + 'purchaseToken': 'token', + 'signature': 'sign', + 'originalJson': 'json', + 'developerPayload': 'dummy payload', + 'isAcknowledged': true, + 'purchaseState': 1, + } + ] + }); + iapAndroidPlatform.billingClientManager.client.callHandler(call); + }); final Completer consumeCompleter = Completer(); // adding call back for consume purchase const BillingResponse expectedCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResultForConsume = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); - when(mockApi.consumeAsync(any)).thenAnswer((Invocation invocation) async { - final String purchaseToken = - invocation.positionalArguments.first as String; - consumeCompleter.complete(purchaseToken); - return convertToPigeonResult(expectedBillingResultForConsume); - }); + stubPlatform.addResponse( + name: consumeMethodName, + value: buildBillingResultMap(expectedBillingResultForConsume), + additionalStepBeforeReturn: (dynamic args) { + final String purchaseToken = + (args as Map)['purchaseToken']! as String; + consumeCompleter.complete(purchaseToken); + }); final Completer completer = Completer(); PurchaseDetails purchaseDetails; @@ -442,8 +522,9 @@ void main() { const BillingResponse sentCode = BillingResponse.error; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); - when(mockApi.launchBillingFlow(any)).thenAnswer( - (_) async => convertToPigeonResult(expectedBillingResult)); + stubPlatform.addResponse( + name: launchMethodName, + value: buildBillingResultMap(expectedBillingResult)); final bool result = await iapAndroidPlatform.buyNonConsumable( purchaseParam: GooglePlayPurchaseParam( @@ -461,8 +542,10 @@ void main() { const BillingResponse sentCode = BillingResponse.developerError; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); - when(mockApi.launchBillingFlow(any)).thenAnswer( - (_) async => convertToPigeonResult(expectedBillingResult)); + stubPlatform.addResponse( + name: launchMethodName, + value: buildBillingResultMap(expectedBillingResult), + ); final bool result = await iapAndroidPlatform.buyConsumable( purchaseParam: GooglePlayPurchaseParam( @@ -481,43 +564,47 @@ void main() { const BillingResponse sentCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); - when(mockApi.launchBillingFlow(any)).thenAnswer((_) async { - // Mock java update purchase callback. - iapAndroidPlatform.billingClientManager.client.hostCallbackHandler - .onPurchasesUpdated(PlatformPurchasesResponse( - billingResult: convertToPigeonResult(expectedBillingResult), - purchases: [ - PlatformPurchase( - orderId: 'orderID1', - products: [productDetails.productId], - isAutoRenewing: false, - packageName: 'package', - purchaseTime: 1231231231, - purchaseToken: 'token', - signature: 'sign', - originalJson: 'json', - developerPayload: 'dummy payload', - isAcknowledged: true, - purchaseState: PlatformPurchaseState.purchased, - quantity: 1, - ) - ], - )); - - return convertToPigeonResult(expectedBillingResult); - }); + stubPlatform.addResponse( + name: launchMethodName, + value: buildBillingResultMap(expectedBillingResult), + additionalStepBeforeReturn: (dynamic _) { + // Mock java update purchase callback. + final MethodCall call = + MethodCall(kOnPurchasesUpdated, { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'responseCode': const BillingResponseConverter().toJson(sentCode), + 'purchasesList': [ + { + 'orderId': 'orderID1', + 'products': [productDetails.productId], + 'isAutoRenewing': false, + 'packageName': 'package', + 'purchaseTime': 1231231231, + 'purchaseToken': 'token', + 'signature': 'sign', + 'originalJson': 'json', + 'developerPayload': 'dummy payload', + 'isAcknowledged': true, + 'purchaseState': 1, + } + ] + }); + iapAndroidPlatform.billingClientManager.client.callHandler(call); + }); final Completer consumeCompleter = Completer(); // adding call back for consume purchase const BillingResponse expectedCode = BillingResponse.error; const BillingResultWrapper expectedBillingResultForConsume = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); - when(mockApi.consumeAsync(any)).thenAnswer((Invocation invocation) async { - final String purchaseToken = - invocation.positionalArguments.first as String; - consumeCompleter.complete(purchaseToken); - return convertToPigeonResult(expectedBillingResultForConsume); - }); + stubPlatform.addResponse( + name: consumeMethodName, + value: buildBillingResultMap(expectedBillingResultForConsume), + additionalStepBeforeReturn: (dynamic args) { + final String purchaseToken = + (args as Map)['purchaseToken']! as String; + consumeCompleter.complete(purchaseToken); + }); final Completer completer = Completer(); PurchaseDetails purchaseDetails; @@ -556,43 +643,47 @@ void main() { const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); - when(mockApi.launchBillingFlow(any)).thenAnswer((_) async { - // Mock java update purchase callback. - iapAndroidPlatform.billingClientManager.client.hostCallbackHandler - .onPurchasesUpdated(PlatformPurchasesResponse( - billingResult: convertToPigeonResult(expectedBillingResult), - purchases: [ - PlatformPurchase( - orderId: 'orderID1', - products: [productDetails.productId], - isAutoRenewing: false, - packageName: 'package', - purchaseTime: 1231231231, - purchaseToken: 'token', - signature: 'sign', - originalJson: 'json', - developerPayload: 'dummy payload', - isAcknowledged: true, - purchaseState: PlatformPurchaseState.purchased, - quantity: 1, - ) - ], - )); - - return convertToPigeonResult(expectedBillingResult); - }); + stubPlatform.addResponse( + name: launchMethodName, + value: buildBillingResultMap(expectedBillingResult), + additionalStepBeforeReturn: (dynamic _) { + // Mock java update purchase callback. + final MethodCall call = + MethodCall(kOnPurchasesUpdated, { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'responseCode': const BillingResponseConverter().toJson(sentCode), + 'purchasesList': [ + { + 'orderId': 'orderID1', + 'products': [productDetails.productId], + 'isAutoRenewing': false, + 'packageName': 'package', + 'purchaseTime': 1231231231, + 'purchaseToken': 'token', + 'signature': 'sign', + 'originalJson': 'json', + 'developerPayload': 'dummy payload', + 'isAcknowledged': true, + 'purchaseState': 1, + } + ] + }); + iapAndroidPlatform.billingClientManager.client.callHandler(call); + }); final Completer consumeCompleter = Completer(); // adding call back for consume purchase const BillingResponse expectedCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResultForConsume = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); - when(mockApi.consumeAsync(any)).thenAnswer((Invocation invocation) async { - final String purchaseToken = - invocation.positionalArguments.first as String; - consumeCompleter.complete(purchaseToken); - return convertToPigeonResult(expectedBillingResultForConsume); - }); + stubPlatform.addResponse( + name: consumeMethodName, + value: buildBillingResultMap(expectedBillingResultForConsume), + additionalStepBeforeReturn: (dynamic args) { + final String purchaseToken = + (args as Map)['purchaseToken']! as String; + consumeCompleter.complete(purchaseToken); + }); final Stream> purchaseStream = iapAndroidPlatform.purchaseStream; @@ -619,43 +710,47 @@ void main() { const BillingResponse sentCode = BillingResponse.userCanceled; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); - when(mockApi.launchBillingFlow(any)).thenAnswer((_) async { - // Mock java update purchase callback. - iapAndroidPlatform.billingClientManager.client.hostCallbackHandler - .onPurchasesUpdated(PlatformPurchasesResponse( - billingResult: convertToPigeonResult(expectedBillingResult), - purchases: [ - PlatformPurchase( - orderId: 'orderID1', - products: [productDetails.productId], - isAutoRenewing: false, - packageName: 'package', - purchaseTime: 1231231231, - purchaseToken: 'token', - signature: 'sign', - originalJson: 'json', - developerPayload: 'dummy payload', - isAcknowledged: true, - purchaseState: PlatformPurchaseState.purchased, - quantity: 1, - ) - ], - )); - - return convertToPigeonResult(expectedBillingResult); - }); + stubPlatform.addResponse( + name: launchMethodName, + value: buildBillingResultMap(expectedBillingResult), + additionalStepBeforeReturn: (dynamic _) { + // Mock java update purchase callback. + final MethodCall call = + MethodCall(kOnPurchasesUpdated, { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'responseCode': const BillingResponseConverter().toJson(sentCode), + 'purchasesList': [ + { + 'orderId': 'orderID1', + 'products': [productDetails.productId], + 'isAutoRenewing': false, + 'packageName': 'package', + 'purchaseTime': 1231231231, + 'purchaseToken': 'token', + 'signature': 'sign', + 'originalJson': 'json', + 'developerPayload': 'dummy payload', + 'isAcknowledged': true, + 'purchaseState': 1, + } + ] + }); + iapAndroidPlatform.billingClientManager.client.callHandler(call); + }); final Completer consumeCompleter = Completer(); // adding call back for consume purchase const BillingResponse expectedCode = BillingResponse.userCanceled; const BillingResultWrapper expectedBillingResultForConsume = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); - when(mockApi.consumeAsync(any)).thenAnswer((Invocation invocation) async { - final String purchaseToken = - invocation.positionalArguments.first as String; - consumeCompleter.complete(purchaseToken); - return convertToPigeonResult(expectedBillingResultForConsume); - }); + stubPlatform.addResponse( + name: consumeMethodName, + value: buildBillingResultMap(expectedBillingResultForConsume), + additionalStepBeforeReturn: (dynamic args) { + final String purchaseToken = + (args as Map)['purchaseToken']! as String; + consumeCompleter.complete(purchaseToken); + }); final Completer completer = Completer(); PurchaseDetails purchaseDetails; @@ -688,16 +783,19 @@ void main() { const BillingResponse sentCode = BillingResponse.ok; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: sentCode, debugMessage: debugMessage); - when(mockApi.launchBillingFlow(any)).thenAnswer((_) async { - // Mock java update purchase callback. - iapAndroidPlatform.billingClientManager.client.hostCallbackHandler - .onPurchasesUpdated(PlatformPurchasesResponse( - billingResult: convertToPigeonResult(expectedBillingResult), - purchases: [], - )); - - return convertToPigeonResult(expectedBillingResult); - }); + stubPlatform.addResponse( + name: launchMethodName, + value: buildBillingResultMap(expectedBillingResult), + additionalStepBeforeReturn: (dynamic _) { + // Mock java update purchase callback. + final MethodCall call = + MethodCall(kOnPurchasesUpdated, { + 'billingResult': buildBillingResultMap(expectedBillingResult), + 'responseCode': const BillingResponseConverter().toJson(sentCode), + 'purchasesList': const [] + }); + iapAndroidPlatform.billingClientManager.client.callHandler(call); + }); final Completer completer = Completer(); PurchaseDetails purchaseDetails; @@ -727,13 +825,17 @@ void main() { }); group('complete purchase', () { + const String completeMethodName = + 'BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)'; test('complete purchase success', () async { const BillingResponse expectedCode = BillingResponse.ok; const String debugMessage = 'dummy message'; const BillingResultWrapper expectedBillingResult = BillingResultWrapper( responseCode: expectedCode, debugMessage: debugMessage); - when(mockApi.acknowledgePurchase(any)).thenAnswer( - (_) async => convertToPigeonResult(expectedBillingResult)); + stubPlatform.addResponse( + name: completeMethodName, + value: buildBillingResultMap(expectedBillingResult), + ); final PurchaseDetails purchaseDetails = GooglePlayPurchaseDetails.fromPurchase(dummyUnacknowledgedPurchase) .first; @@ -749,20 +851,10 @@ void main() { expect(await completer.future, equals(expectedBillingResult)); }); }); - - group('billingConfig', () { - test('getCountryCode success', () async { - const String expectedCountryCode = 'US'; - const BillingConfigWrapper expected = BillingConfigWrapper( - countryCode: expectedCountryCode, - responseCode: BillingResponse.ok, - debugMessage: 'dummy message'); - - when(mockApi.getBillingConfigAsync()) - .thenAnswer((_) async => platformBillingConfigFromWrapper(expected)); - final String countryCode = await iapAndroidPlatform.getCountryCode(); - - expect(countryCode, equals(expectedCountryCode)); - }); - }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_android/test/stub_in_app_purchase_platform.dart b/packages/in_app_purchase/in_app_purchase_android/test/stub_in_app_purchase_platform.dart new file mode 100644 index 000000000..35e2807bc --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_android/test/stub_in_app_purchase_platform.dart @@ -0,0 +1,48 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'package:flutter/services.dart'; + +// `FutureOr` instead of `FutureOr` to avoid +// "don't assign to void" warnings. +typedef AdditionalSteps = FutureOr Function(dynamic args); + +class StubInAppPurchasePlatform { + final Map _expectedCalls = {}; + final Map _additionalSteps = + {}; + void addResponse( + {required String name, + dynamic value, + AdditionalSteps? additionalStepBeforeReturn}) { + _additionalSteps[name] = additionalStepBeforeReturn; + _expectedCalls[name] = value; + } + + final List _previousCalls = []; + List get previousCalls => _previousCalls; + MethodCall previousCallMatching(String name) => + _previousCalls.firstWhere((MethodCall call) => call.method == name); + int countPreviousCalls(String name) => + _previousCalls.where((MethodCall call) => call.method == name).length; + + void reset() { + _expectedCalls.clear(); + _previousCalls.clear(); + _additionalSteps.clear(); + } + + Future fakeMethodCallHandler(MethodCall call) async { + _previousCalls.add(call); + if (_expectedCalls.containsKey(call.method)) { + if (_additionalSteps[call.method] != null) { + await _additionalSteps[call.method]!(call.arguments); + } + return Future.sync(() => _expectedCalls[call.method]); + } else { + return Future.sync(() => null); + } + } +} diff --git a/packages/in_app_purchase/in_app_purchase_android/test/test_conversion_utils.dart b/packages/in_app_purchase/in_app_purchase_android/test/test_conversion_utils.dart deleted file mode 100644 index 483c7ee25..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/test/test_conversion_utils.dart +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:in_app_purchase_android/billing_client_wrappers.dart'; -import 'package:in_app_purchase_android/src/messages.g.dart'; -import 'package:in_app_purchase_android/src/pigeon_converters.dart'; - -/// Creates the [PlatformBillingResult] to return from a mock to get -/// [targetResult]. -/// -/// Since [PlatformBillingResult] returns a non-nullable debug string, the -/// target must have a non-null string as well. -PlatformBillingResult convertToPigeonResult(BillingResultWrapper targetResult) { - return PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(targetResult.responseCode), - debugMessage: targetResult.debugMessage!, - ); -} - -/// Creates a [PlatformPurchase] from the corresponding [PurchaseWrapper]. -PlatformPurchase convertToPigeonPurchase(PurchaseWrapper purchase) { - return PlatformPurchase( - orderId: purchase.orderId, - packageName: purchase.packageName, - purchaseTime: purchase.purchaseTime, - purchaseToken: purchase.purchaseToken, - signature: purchase.signature, - products: purchase.products, - isAutoRenewing: purchase.isAutoRenewing, - originalJson: purchase.originalJson, - developerPayload: purchase.developerPayload ?? '', - isAcknowledged: purchase.isAcknowledged, - purchaseState: _convertToPigeonPurchaseState(purchase.purchaseState), - // For some reason quantity is not in PurchaseWrapper. - quantity: 99, - accountIdentifiers: purchase.obfuscatedAccountId != null || - purchase.obfuscatedProfileId != null - ? PlatformAccountIdentifiers( - obfuscatedAccountId: purchase.obfuscatedAccountId, - obfuscatedProfileId: purchase.obfuscatedProfileId, - ) - : null); -} - -/// Creates a [PlatformProductDetails] from the corresponding [ProductDetailsWrapper]. -PlatformProductDetails convertToPigeonProductDetails( - ProductDetailsWrapper details) { - return PlatformProductDetails( - description: details.description, - name: details.name, - productId: details.productId, - productType: platformProductTypeFromWrapper(details.productType), - title: details.title, - oneTimePurchaseOfferDetails: _convertToPigeonOneTimePurchaseOfferDetails( - details.oneTimePurchaseOfferDetails), - subscriptionOfferDetails: details.subscriptionOfferDetails - ?.map(convertToPigeonSubscriptionOfferDetails) - .toList()); -} - -PlatformSubscriptionOfferDetails convertToPigeonSubscriptionOfferDetails( - SubscriptionOfferDetailsWrapper details) { - return PlatformSubscriptionOfferDetails( - basePlanId: details.basePlanId, - offerId: details.offerId, - offerToken: details.offerIdToken, - offerTags: details.offerTags, - pricingPhases: - details.pricingPhases.map(convertToPigeonPricingPhase).toList()); -} - -PlatformPricingPhase convertToPigeonPricingPhase(PricingPhaseWrapper phase) { - return PlatformPricingPhase( - billingCycleCount: phase.billingCycleCount, - recurrenceMode: _convertToPigeonRecurrenceMode(phase.recurrenceMode), - priceAmountMicros: phase.priceAmountMicros, - billingPeriod: phase.billingPeriod, - formattedPrice: phase.formattedPrice, - priceCurrencyCode: phase.priceCurrencyCode); -} - -PlatformOneTimePurchaseOfferDetails? - _convertToPigeonOneTimePurchaseOfferDetails( - OneTimePurchaseOfferDetailsWrapper? offer) { - if (offer == null) { - return null; - } - return PlatformOneTimePurchaseOfferDetails( - priceAmountMicros: offer.priceAmountMicros, - formattedPrice: offer.formattedPrice, - priceCurrencyCode: offer.priceCurrencyCode); -} - -/// Creates a [PlatformPurchaseState] from the Dart wrapper equivalent. -PlatformPurchaseState _convertToPigeonPurchaseState( - PurchaseStateWrapper state) { - return switch (state) { - PurchaseStateWrapper.unspecified_state => PlatformPurchaseState.unspecified, - PurchaseStateWrapper.purchased => PlatformPurchaseState.purchased, - PurchaseStateWrapper.pending => PlatformPurchaseState.pending, - }; -} - -PlatformRecurrenceMode _convertToPigeonRecurrenceMode(RecurrenceMode mode) { - return switch (mode) { - RecurrenceMode.finiteRecurring => PlatformRecurrenceMode.finiteRecurring, - RecurrenceMode.infiniteRecurring => - PlatformRecurrenceMode.infiniteRecurring, - RecurrenceMode.nonRecurring => PlatformRecurrenceMode.nonRecurring, - }; -} diff --git a/packages/in_app_purchase/in_app_purchase_android/test/types/translator_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/types/translator_test.dart deleted file mode 100644 index 7e19c4b41..000000000 --- a/packages/in_app_purchase/in_app_purchase_android/test/types/translator_test.dart +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:in_app_purchase_android/billing_client_wrappers.dart'; -import 'package:in_app_purchase_android/src/types/google_play_user_choice_details.dart'; -import 'package:in_app_purchase_android/src/types/translator.dart'; -import 'package:test/test.dart'; - -void main() { - group('Translator ', () { - test('convertToPlayProductType', () { - expect(Translator.convertToPlayProductType(ProductType.inapp), - GooglePlayProductType.inapp); - expect(Translator.convertToPlayProductType(ProductType.subs), - GooglePlayProductType.subs); - expect(GooglePlayProductType.values.length, ProductType.values.length); - }); - - test('convertToUserChoiceDetailsProduct', () { - const GooglePlayUserChoiceDetailsProduct expected = - GooglePlayUserChoiceDetailsProduct( - id: 'id', - offerToken: 'offerToken', - productType: GooglePlayProductType.inapp); - expect( - Translator.convertToUserChoiceDetailsProduct( - UserChoiceDetailsProductWrapper( - id: expected.id, - offerToken: expected.offerToken, - productType: ProductType.inapp)), - expected); - }); - test('convertToUserChoiceDetailsProduct', () { - const GooglePlayUserChoiceDetailsProduct expectedProduct1 = - GooglePlayUserChoiceDetailsProduct( - id: 'id1', - offerToken: 'offerToken1', - productType: GooglePlayProductType.inapp); - const GooglePlayUserChoiceDetailsProduct expectedProduct2 = - GooglePlayUserChoiceDetailsProduct( - id: 'id2', - offerToken: 'offerToken2', - productType: GooglePlayProductType.subs); - const GooglePlayUserChoiceDetails expected = GooglePlayUserChoiceDetails( - originalExternalTransactionId: 'originalExternalTransactionId', - externalTransactionToken: 'externalTransactionToken', - products: [ - expectedProduct1, - expectedProduct2 - ]); - - expect( - Translator.convertToUserChoiceDetails(UserChoiceDetailsWrapper( - originalExternalTransactionId: - expected.originalExternalTransactionId, - externalTransactionToken: expected.externalTransactionToken, - products: [ - UserChoiceDetailsProductWrapper( - id: expectedProduct1.id, - offerToken: expectedProduct1.offerToken, - productType: ProductType.inapp), - UserChoiceDetailsProductWrapper( - id: expectedProduct2.id, - offerToken: expectedProduct2.offerToken, - productType: ProductType.subs), - ])), - expected); - }); - }); -} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/AUTHORS b/packages/in_app_purchase/in_app_purchase_ohos/AUTHORS new file mode 100644 index 000000000..78f9e5ad9 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/AUTHORS @@ -0,0 +1,67 @@ +# Below is a list of people and organizations that have contributed +# to the Flutter project. Names should be added to the list like so: +# +# Name/Organization + +Google Inc. +The Chromium Authors +German Saprykin +Benjamin Sauer +larsenthomasj@gmail.com +Ali Bitek +Pol Batlló +Anatoly Pulyaevskiy +Hayden Flinner +Stefano Rodriguez +Salvatore Giordano +Brian Armstrong +Paul DeMarco +Fabricio Nogueira +Simon Lightfoot +Ashton Thomas +Thomas Danner +Diego Velásquez +Hajime Nakamura +Tuyển Vũ Xuân +Miguel Ruivo +Sarthak Verma +Mike Diarmid +Invertase +Elliot Hesp +Vince Varga +Aawaz Gyawali +EUI Limited +Katarina Sheremet +Thomas Stockx +Sarbagya Dhaubanjar +Ozkan Eksi +Rishab Nayak +ko2ic +Jonathan Younger +Jose Sanchez +Debkanchan Samadder +Audrius Karosevicius +Lukasz Piliszczuk +SoundReply Solutions GmbH +Rafal Wachol +Pau Picas +Christian Weder +Alexandru Tuca +Christian Weder +Rhodes Davis Jr. +Luigi Agosti +Quentin Le Guennec +Koushik Ravikumar +Nissim Dsilva +Giancarlo Rocha +Ryo Miyake +Théo Champion +Kazuki Yamaguchi +Eitan Schwartz +Chris Rutkowski +Juan Alvarez +Aleksandr Yurkovskiy +Anton Borries +Alex Li +Rahul Raj <64.rahulraj@gmail.com> +Maurits van Beusekom diff --git a/packages/in_app_purchase/in_app_purchase_ohos/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_ohos/CHANGELOG.md new file mode 100644 index 000000000..fd91ecfdf --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +* Support OpenHarmony diff --git a/packages/in_app_purchase/in_app_purchase_ohos/LICENSE b/packages/in_app_purchase/in_app_purchase_ohos/LICENSE new file mode 100644 index 000000000..c6823b81e --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/LICENSE @@ -0,0 +1,25 @@ +Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/in_app_purchase/in_app_purchase_ohos/README.md b/packages/in_app_purchase/in_app_purchase_ohos/README.md new file mode 100644 index 000000000..971c114cb --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/README.md @@ -0,0 +1,24 @@ +# in\_app\_purchase\_ohos + +The Ohos implementation of [`in_app_purchase`][1]. + +## Usage + +This package is [endorsed][2], which means you can simply use `in_app_purchase` +normally. This package will be automatically included in your app when you do, +so you do not need to add it to your `pubspec.yaml`. + +## Contributing + +This plugin utilizes +[json_serializable](https://pub.dev/packages/json_serializable) for the +numerous data structures communicated between the underlying platform layers and Dart. After +modifying any of the serialized data structures, regenerate the serializers by executing +`flutter pub run build_runner build --delete-conflicting-outputs`. +`flutter pub run build_runner watch --delete-conflicting-outputs` will supervise the filesystem for changes. + +If you are interested in contributing to the plugin, please consult our +[contribution guidelines](https://github.com/flutter/plugins/blob/main/CONTRIBUTING.md). + +[1]: https://pub.dev/packages/in_app_purchase +[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin diff --git a/packages/in_app_purchase/in_app_purchase_ohos/build.yaml b/packages/in_app_purchase/in_app_purchase_ohos/build.yaml new file mode 100644 index 000000000..651a557fc --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/build.yaml @@ -0,0 +1,8 @@ +# See https://pub.dev/packages/build_config +targets: + $default: + builders: + json_serializable: + options: + any_map: true + create_to_json: false diff --git a/packages/in_app_purchase/in_app_purchase_ohos/example/README.md b/packages/in_app_purchase/in_app_purchase_ohos/example/README.md new file mode 100644 index 000000000..96b8bb17d --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/example/README.md @@ -0,0 +1,9 @@ +# Platform Implementation Test App + +This is a test app for manual testing and automated integration testing +of this platform implementation. It is not intended to demonstrate actual use of +this package, since the intent is that plugin clients use the app-facing +package. + +Unless you are making changes to this implementation package, this example is +very unlikely to be relevant. diff --git a/packages/in_app_purchase/in_app_purchase_ohos/example/integration_test/in_app_purchase_test.dart b/packages/in_app_purchase/in_app_purchase_ohos/example/integration_test/in_app_purchase_test.dart new file mode 100644 index 000000000..28f99f748 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/example/integration_test/in_app_purchase_test.dart @@ -0,0 +1,20 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:in_app_purchase_ohos/in_app_purchase_ohos.dart'; +import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; +import 'package:integration_test/integration_test.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('Can create InAppPurchaseStoreKit instance', + (WidgetTester tester) async { + InAppPurchaseOhosPlatform.registerPlatform(); + final InAppPurchasePlatform androidPlatform = + InAppPurchasePlatform.instance; + expect(androidPlatform, isNotNull); + }); +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/example/lib/consumable_store.dart b/packages/in_app_purchase/in_app_purchase_ohos/example/lib/consumable_store.dart new file mode 100644 index 000000000..f8791d3b1 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/example/lib/consumable_store.dart @@ -0,0 +1,52 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'package:shared_preferences/shared_preferences.dart'; + +// ignore: avoid_classes_with_only_static_members +/// A store of consumable items. +/// +/// This is a development prototype that stores consumables in the shared +/// preferences. Do not use this in real world apps. +class ConsumableStore { + static const String _kPrefKey = 'consumables'; + static Future _writes = Future.value(); + + /// Adds a consumable with ID `id` to the store. + /// + /// The consumable is only added after the returned Future is complete. + static Future save(String id) { + _writes = _writes.then((void _) => _doSave(id)); + return _writes; + } + + /// Consumes a consumable with ID `id` from the store. + /// + /// The consumable was only consumed after the returned Future is complete. + static Future consume(String id) { + _writes = _writes.then((void _) => _doConsume(id)); + return _writes; + } + + /// Returns the list of consumables from the store. + static Future> load() async { + return (await SharedPreferences.getInstance()).getStringList(_kPrefKey) ?? + []; + } + + static Future _doSave(String id) async { + final List cached = await load(); + final SharedPreferences prefs = await SharedPreferences.getInstance(); + cached.add(id); + await prefs.setStringList(_kPrefKey, cached); + } + + static Future _doConsume(String id) async { + final List cached = await load(); + final SharedPreferences prefs = await SharedPreferences.getInstance(); + cached.remove(id); + await prefs.setStringList(_kPrefKey, cached); + } +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase_ohos/example/lib/main.dart new file mode 100644 index 000000000..d79a39189 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/example/lib/main.dart @@ -0,0 +1,414 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:in_app_purchase_ohos/in_app_purchase_ohos.dart'; +import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; + +import 'consumable_store.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + + // When using the Ohos plugin directly it is mandatory to register + // the plugin as default instance as part of initializing the app. + InAppPurchaseOhosPlatform.registerPlatform(); + + runApp(_MyApp()); +} + +const String _kConsumableId = 'consumable'; +const String _kUpgradeId = 'upgrade'; +const String _kSilverSubscriptionId = 'subscription_silver'; +const String _kGoldSubscriptionId = 'subscription_gold'; +const List _kProductIds = [ + _kConsumableId, + _kUpgradeId, + _kSilverSubscriptionId, + _kGoldSubscriptionId, +]; + +class _MyApp extends StatefulWidget { + @override + State<_MyApp> createState() => _MyAppState(); +} + +class _MyAppState extends State<_MyApp> { + final InAppPurchaseOhosPlatform _iapStoreKitPlatform = + InAppPurchasePlatform.instance as InAppPurchaseOhosPlatform; + late StreamSubscription> _subscription; + List _notFoundIds = []; + List _products = []; + List _purchases = []; + List _consumables = []; + bool _isAvailable = false; + bool _purchasePending = false; + bool _loading = true; + String? _queryProductError; + + @override + void initState() { + final Stream> purchaseUpdated = + _iapStoreKitPlatform.purchaseStream; + _subscription = + purchaseUpdated.listen((List purchaseDetailsList) { + _listenToPurchaseUpdated(purchaseDetailsList); + }, onDone: () { + _subscription.cancel(); + }, onError: (Object error) { + // handle error here. + }); + + initStoreInfo(); + super.initState(); + } + + Future initStoreInfo() async { + final bool isAvailable = await _iapStoreKitPlatform.isAvailable(); + if (!isAvailable) { + setState(() { + _isAvailable = isAvailable; + _products = []; + _purchases = []; + _notFoundIds = []; + _consumables = []; + _purchasePending = false; + _loading = false; + }); + return; + } + final ProductDetailsResponse productDetailResponse = + await _iapStoreKitPlatform.queryProductDetails(_kProductIds.toSet()); + if (productDetailResponse.error != null) { + setState(() { + _queryProductError = productDetailResponse.error!.message; + _isAvailable = isAvailable; + _products = productDetailResponse.productDetails; + _purchases = []; + _notFoundIds = productDetailResponse.notFoundIDs; + _consumables = []; + _purchasePending = false; + _loading = false; + }); + return; + } + + if (productDetailResponse.productDetails.isEmpty) { + setState(() { + _queryProductError = null; + _isAvailable = isAvailable; + _products = productDetailResponse.productDetails; + _purchases = []; + _notFoundIds = productDetailResponse.notFoundIDs; + _consumables = []; + _purchasePending = false; + _loading = false; + }); + return; + } + + final List consumables = await ConsumableStore.load(); + setState(() { + _isAvailable = isAvailable; + _products = productDetailResponse.productDetails; + _notFoundIds = productDetailResponse.notFoundIDs; + _consumables = consumables; + _purchasePending = false; + _loading = false; + }); + } + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final List stack = []; + if (_queryProductError == null) { + stack.add( + ListView( + children: [ + _buildConnectionCheckTile(), + _buildProductList(), + _buildConsumableBox(), + // _buildRestoreButton(), + ], + ), + ); + } else { + stack.add(Center( + child: Text(_queryProductError!), + )); + } + if (_purchasePending) { + stack.add( + // const Stack( + Stack( + children: [ + Opacity( + opacity: 0.3, + child: ModalBarrier(dismissible: false, color: Colors.grey), + ), + Center( + child: CircularProgressIndicator(), + ), + ], + ), + ); + } + + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('IAP Example'), + ), + body: Stack( + children: stack, + ), + ), + ); + } + + Card _buildConnectionCheckTile() { + if (_loading) { + return const Card(child: ListTile(title: Text('Trying to connect...'))); + } + final Widget storeHeader = ListTile( + leading: Icon(_isAvailable ? Icons.check : Icons.block, + color: _isAvailable + ? Colors.green + : ThemeData.light().colorScheme.error), + title: + Text('The store is ${_isAvailable ? 'available' : 'unavailable'}.'), + ); + final List children = [storeHeader]; + + if (!_isAvailable) { + children.addAll([ + const Divider(), + ListTile( + title: Text('Not connected', + style: TextStyle(color: ThemeData.light().colorScheme.error)), + subtitle: const Text( + 'Unable to connect to the payments processor. Has this app been configured correctly? See the example README for instructions.'), + ), + ]); + } + return Card(child: Column(children: children)); + } + + Card _buildProductList() { + if (_loading) { + return const Card( + child: ListTile( + leading: CircularProgressIndicator(), + title: Text('Fetching products...'))); + } + if (!_isAvailable) { + return const Card(); + } + const ListTile productHeader = ListTile(title: Text('Products for Sale')); + final List productList = []; + if (_notFoundIds.isNotEmpty) { + productList.add(ListTile( + title: Text('[${_notFoundIds.join(", ")}] not found', + style: TextStyle(color: ThemeData.light().colorScheme.error)), + subtitle: const Text( + 'This app needs special configuration to run. Please see example/README.md for instructions.'))); + } + + // This loading previous purchases code is just a demo. Please do not use this as it is. + // In your app you should always verify the purchase data using the `verificationData` inside the [PurchaseDetails] object before trusting it. + // We recommend that you use your own server to verify the purchase data. + final Map purchases = + Map.fromEntries( + _purchases.map((PurchaseDetails purchase) { + if (purchase.pendingCompletePurchase) { + _iapStoreKitPlatform.completePurchase(purchase); + } + return MapEntry(purchase.productID, purchase); + })); + productList.addAll(_products.map( + (ProductDetails productDetails) { + final PurchaseDetails? previousPurchase = purchases[productDetails.id]; + return ListTile( + title: Text( + productDetails.title, + ), + subtitle: Text( + productDetails.description, + ), + trailing: previousPurchase != null + ? SizedBox.shrink() + : TextButton( + style: TextButton.styleFrom( + backgroundColor: Colors.green[800], + foregroundColor: Colors.white, + ), + onPressed: () { + final PurchaseParam purchaseParam = PurchaseParam( + productDetails: productDetails, + ); + if (productDetails.id == _kConsumableId) { + _iapStoreKitPlatform.buyConsumable( + purchaseParam: purchaseParam); + } else { + _iapStoreKitPlatform.buyNonConsumable( + purchaseParam: purchaseParam); + } + }, + child: Text(productDetails.price), + )); + }, + )); + + return Card( + child: Column( + children: [productHeader, const Divider()] + productList)); + } + + Card _buildConsumableBox() { + if (_loading) { + return const Card( + child: ListTile( + leading: CircularProgressIndicator(), + title: Text('Fetching consumables...'))); + } + if (!_isAvailable || _notFoundIds.contains(_kConsumableId)) { + return const Card(); + } + const ListTile consumableHeader = + ListTile(title: Text('Purchased consumables')); + final List tokens = _consumables.map((String id) { + return GridTile( + child: IconButton( + icon: const Icon( + Icons.stars, + size: 42.0, + color: Colors.orange, + ), + splashColor: Colors.yellowAccent, + onPressed: () => consume(id), + ), + ); + }).toList(); + return Card( + child: Column(children: [ + consumableHeader, + const Divider(), + GridView.count( + crossAxisCount: 5, + shrinkWrap: true, + padding: const EdgeInsets.all(16.0), + children: tokens, + ) + ])); + } + + Widget _buildRestoreButton() { + if (_loading) { + return Container(); + } + + return Padding( + padding: const EdgeInsets.all(4.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + style: TextButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Colors.white, + ), + onPressed: () => _iapStoreKitPlatform.restorePurchases(), + child: const Text('Restore purchases'), + ), + ], + ), + ); + } + + Future consume(String id) async { + await ConsumableStore.consume(id); + final List consumables = await ConsumableStore.load(); + setState(() { + _consumables = consumables; + }); + } + + void showPendingUI() { + setState(() { + _purchasePending = true; + }); + } + + Future deliverProduct(PurchaseDetails purchaseDetails) async { + // IMPORTANT!! Always verify purchase details before delivering the product. + if (purchaseDetails.productID == _kConsumableId) { + await ConsumableStore.save(purchaseDetails.purchaseID!); + final List consumables = await ConsumableStore.load(); + setState(() { + _purchasePending = false; + _consumables = consumables; + }); + } else { + setState(() { + _purchases.add(purchaseDetails); + _purchasePending = false; + }); + } + } + + void handleError(IAPError error) { + setState(() { + _purchasePending = false; + }); + } + + Future _verifyPurchase(PurchaseDetails purchaseDetails) { + // IMPORTANT!! Always verify a purchase before delivering the product. + // For the purpose of an example, we directly return true. + return Future.value(true); + } + + void _handleInvalidPurchase(PurchaseDetails purchaseDetails) { + // handle invalid purchase here if _verifyPurchase` failed. + } + + void _listenToPurchaseUpdated(List purchaseDetailsList) { + purchaseDetailsList.forEach(_handleReportedPurchaseState); + } + + Future _handleReportedPurchaseState( + PurchaseDetails purchaseDetails) async { + print('InAppPurchasePlugin purchaseDetails.status =$purchaseDetails'); + if (purchaseDetails.status == PurchaseStatus.pending) { + showPendingUI(); + } else { + if (purchaseDetails.status == PurchaseStatus.error) { + handleError(purchaseDetails.error!); + } else if (purchaseDetails.status == PurchaseStatus.purchased || + purchaseDetails.status == PurchaseStatus.restored) { + final bool valid = await _verifyPurchase(purchaseDetails); + if (valid) { + await deliverProduct(purchaseDetails); + } else { + _handleInvalidPurchase(purchaseDetails); + return; + } + }else if(purchaseDetails.status == PurchaseStatus.canceled){ + + } + + if (purchaseDetails.pendingCompletePurchase) { + await _iapStoreKitPlatform.completePurchase(purchaseDetails); + } + } + } +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/.gitignore b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/.gitignore new file mode 100644 index 000000000..6ca13b317 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/.gitignore @@ -0,0 +1,19 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +*.har +**/BuildProfile.ets +**/oh-package-lock.json5 + +**/src/main/resources/rawfile/flutter_assets/ +**/libs/arm64-v8a/libapp.so +**/libs/arm64-v8a/libflutter.so +**/libs/arm64-v8a/libvmservice_snapshot.so diff --git a/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/AppScope/app.json5 b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/AppScope/app.json5 new file mode 100644 index 000000000..a13c8bfdc --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.example.in_app_purchase_ohos", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/AppScope/resources/base/element/string.json b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/AppScope/resources/base/element/string.json new file mode 100644 index 000000000..810f4a362 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "example" + } + ] +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/AppScope/resources/base/media/app_icon.png b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 zcmX|G1ymHk)?T_}Vd;>R?p|tHQo6fg38|$UVM!6BLrPFWk?s;$LOP{GmJpBl$qoSA!PUg~PA65-S00{{S`XKG6NkG0RgjEntPrmV+?0|00mu7;+5 zrdpa{2QLqPJ4Y{j7=Mrl{BaxrkdY69+c~(w{Fv-v&aR%aEI&JYSeRTLWm!zbv;?)_ ziZB;fwGbbeL5Q}YLx`J$lp~A09KK8t_z}PZ=4ZzgdeKtgoc+o5EvN9A1K1_<>M?MBqb#!ASf&# zEX?<)!RH(7>1P+j=jqG(58}TVN-$psA6K}atCuI!KTJD&FMmH-78ZejBm)0qc{ESp z|LuG1{QnBUJRg_E=h1#XMWt2%fcoN@l7eAS!Es?Q+;XsRNPhiiE=@AqlLkJzF`O18 zbsbSmKN=aaq8k3NFYZfDWpKmM!coBU0(XnL8R{4=i|wi{!uWYM2je{U{B*K2PVdu&=E zTq*-XsEsJ$u5H4g6DIm2Y!DN`>^v|AqlwuCD;w45K0@eqauiqWf7l&o)+YLHm~|L~ z7$0v5mkobriU!H<@mVJHLlmQqzQ3d6Rh_-|%Yy2li*tHO>_vcnuZ7OR_xkAIuIU&x z-|8Y0wj|6|a6_I(v91y%k_kNw6pnkNdxjqG8!%Vz_d%c_!X+6-;1`GC9_FpjoHev5fEV7RhJ>r=mh-jp$fqbqRJ=obwdgLDVP5+s zy1=_DWG0Y-Jb3t^WXmkr(d9~08k-|#Ly zaNOmT(^9tIb&eb4%CzIT zAm3CUtWSr1t4?h1kk#NBi{U|pJslvME{q|_eS^3En>SOqSxyuN1x;Is@8~m?*>}** znrRFArP!K_52RpX*&JHMR<^lVdm8ypJ}0R(SD(51j;6@ni$6bQ+2XL+R^|NnSp5}(kzvMZ^(@4fD_{QVu$(&K6H|C37TG1Am9Re{<<3gd zh@`>;BqkXMW&p0T6rt|iB$)~CvFe(XC)F9WgAZn*0@t$oZo;!*}r@_`h?KKH&6A@3= zISXoQB+~`op>NP-buiA*^0n{@i{_?MRG)&k)c)k_F+-2Lud!S9pc+i`s74NpBCaGF zXN+pHkubw*msGBTY27BKHv)RRh3;nMg4&$fD_6X9Vt~;_4D+5XPH~#Kn-yjcy!$}1 zigv#FNY>TqMhtIBb@UoF!cE~Q8~;!Pek>SQQwHnHuWKoVBosAiOr}q>!>aE*Krc)V zBUMEcJ5NU0g8}-h6i1zpMY9>m4ne?=U2~`w7K7Q0gB_=p@$5K7p6}thw z-~3dMj?YNX2X$lZ+7ngQ$=s}3mizNN@kE%OtB)?c&i~2L55z8^=yz;xMHLmlY>&Q# zJj?!)M#q_SyfkQh)k?j8IfLtB)ZCp|*vf4_B zos?73yd^h-Ac+;?E4*bpf=o*^3x3-`TVjbY4n6!EN10K6o@fxdyps05Vo3PU)otB} z`3kR+2w7_C#8Z!q`J)p{Vh!+m9-UP!$STp+Hb}}#@#_u^SsUQg<}59< zTvH3%XS4G+6FF^(m6bVF&nSUIXcl;nw{=H$%fgeJ>CgDYiLdpDXr{;-AnG z8dvcrHYVMI&`R6;GWekI@Ir3!uo)oz4^{6q0m^}@f2tM9&=YHNi6-?rh0-{+k@cQm zdp`g#YdQn%MDVg2GR>wZ`n2<0l4)9nx1Wfr&!Dvz=bPwU!h2S?ez6MVc5APE4-xLB zi&W9Q8k2@0w!C53g?iAIQ}~p*3O(@zja6KQ=M3zfW*_6o5SwR-)6VBh~m7{^-=MC-owYH5-u40a}a0liho3QZZ5L{bS_xM1)4}19)zTU$$MY zq3eZML1WC{K%YFd`Be0M-rkO^l?h{kM{$2oK1*A@HVJ57*yhDkUF!2WZ&oA4Y-sK( zCY69%#`mBCi6>6uw(x4gbFaP0+FD*JKJ-q!F1E?vLJ+d35!I5d7@^eU?(CS|C^tmI5?lv@s{{*|1F zFg|OzNpZ0hxljdjaW%45O0MOttRrd(Z?h{HYbB-KFUx&9GfFL3b8NwZ$zNu)WbBD` zYkj$^UB5%3Pj1MDr>S2Ejr9pUcgA!;ZG!@{uAy12)vG=*^9-|dNQBc8&`oxBlU~#y zs!anJX&T?57Jdr^sb>e+V`MVfY>Y0ESg7MG<7W0g&bR-ZYzzZ%2H&Etcp zcd6QeXO1D!5A#zM0lx*GH}`M)2~ZFLE;sP^RSB5wVMNfiZXPd(cmO>j=OSA3`o5r& zna(|^jGXbdN7PK)U8b7^zYtYkkeb%<%F~=OqB~kXMQkq}ii|skh@WSRt>5za;cjP0 zZ~nD%6)wzedqE}BMLt~qKwlvTr33))#uP~xyw#*Eaa|DbMQ_%mG0U8numf8)0DX`r zRoG2bM;#g|p-8gWnwRV5SCW0tLjLO&9Z?K>FImeIxlGUgo0Zk`9Qzhj1eco~7XZy+hXc@YF&ZQ=? zn*^1O56yK^x{y}q`j7}blGCx%dydV!c7)g~tJzmHhV=W~jbWRRR{1<^oDK+1clprm zz$eCy7y9+?{E|YgkW~}}iB#I4XoJ*xr8R?i_Hv$=Cof5bo-Nj~f`-DLebH}&0% zfQj9@WGd4;N~Y?mzQsHJTJq6!Qzl^-vwol(+fMt#Pl=Wh#lI5Vmu@QM0=_r+1wHt` z+8WZ~c2}KQQ+q)~2Ki77QvV&`xb|xVcTms99&cD$Zz4+-^R4kvUBxG8gDk7Y`K*)JZ^2rL(+ZWV~%W(@6 z)0bPArG#BROa_PHs~&WplQ_UIrpd)1N1QGPfv!J(Z9jNT#i%H?CE6|pPZb9hJ1JW4 z^q;ft#!HRNV0YgPojzIYT`8LuET2rUe-J|c!9l4`^*;4WtY@Ew@pL>wkjmMgGfN7 ze}}GtmU0@<_#08~I-Suk=^*9GLW=H4xhsml;vAV{%hy5Eegl@!6qKqbG024%n2HHw zCc@ivW_$@5ZoHP70(7D+(`PvgjW1Pd`wsiuv-aCukMrafwDm)B!xXVy*j2opohhoU zcJz%ADmj>i3`-3-$7nQKBQQuGY;2Qt&+(L~C>vSGFj5{Mlv?T_^dql;{zkpe4R1}R z%XfZyQ}wr*sr>jrKgm*PWLjuVc%6&&`Kbf1SuFpHPN&>W)$GmqC;pIoBC`=4-hPY8 zT*>%I2fP}vGW;R=^!1be?ta2UQd2>alOFFbVl;(SQJ4Jk#)4Z0^wpWEVvY4=vyDk@ zqlModi@iVPMC+{?rm=4(n+<;|lmUO@UKYA>EPTS~AndtK^Wy^%#3<;(dQdk3WaUkRtzSMC9}7x2||CNpF#(3T4C)@ z$~RWs`BNABKX|{cmBt>Q=&gkXl&x!!NK_%5hW0LS)Z4PB>%sV?F-{Wyj#s7W%$F{D zXdK^Fp3wvy+48+GP6F_|^PCRx=ddcTO3sG;B23A49~Qaw31SZ0Rc~`r4qqt%#OGW{ zCA_(LG5^N>yzUn&kAgVmxb=EA8s&tBXC}S1CZ(KoW)(%^JjLTPo^fs`Va;`=YlVPgmB$!yB}<(4ym6OeZ3xAJJ#;)2+B%p3P1Wt+d$eo`vz`T zXfUP2))kBDPoscH;Jc7I3NU<({|@wM$&GaDt`n7WLgIY3IA7A6-_R?z8N3mz|}*i z(zl5ot--Oq@f2-nv{X(ujT2T(k1vY_qh93pK@>H-qc%2Xta)IP0Q%zt%bqYgI`o!wv!0QerB`nCN^1n|@$sVOQ!V0teVG!I z_fD%JvfDeT1cK#-{o6Gv7}& zY0#NWin~kVaf$aufV&;63Hbs|`QVZWpDX6IMk1Hj2G}fiH9e-^6u2zf^FIr^BwD<6zjw63+{yUe8PUFvk8v{sJ=R{d#`O!sz`Q13~< zPT$JS(w=yQfU2`zPCNfSw=&zup@DXc(98afjhv@1w_f!m2Z>rMJ19AB&dB%P#Ls3b z=lK7OILM+SQ&VEd=1GN6o&>YVVtIzoZ%=Z_SdqJN2}E43{bE`>w+A;=y->@^k{oCC z$F*WTY&?34;kfyFV?b*Xb1Pq`Z=%OgwEg)Rz)tx=`f%5#w_INP=x&z5!jI;#;N$ma zhO)+MDm;SxOEVL15; zGq(v2pL3&P1Sl)8P*;G-fd{l1QJsv@e@d8)1PK4w2m*M%V3j-V~L^$i|&C@b?D?9tfwE{B^}Z$k8e5FmQ>v7Xz)sG32g9t}YBt zyR$+*_00RmPx+0mW+vVG4mxd(n$(eQf3-w>JPl2UJpafrPaL5@2j}%{VE-) zBI%6Qpj*dsdH<;g!S!avA~bv^0E+ zfyJbSjPb+j;J52U)<|cIcntQBI2T#>2;tOxu{%D?kML476AErF(qN9hPva5Nkc@BF zC-tLF@3ZFb%Kpj)M<{)x*l|*Ia@ECeXo2E4h2f!aV=cHAhi_E_mfUth(sM4^hJq7B zQsGWqdZUm9S%F`$nQ*_#NcuD`&)Ek%_s{&^78{9Hm ztri&rYLOxgFdG>O@+XHy z9#;|&vBCPXH5Mon^I`jSuR$&~ZWtyB67ujzFSj!51>#C}C17~TffQ{c-!QFQkTQ%! zIR^b1`zHx|*1GU?tbBx23weFLz5H?y_Q%N&t$}k?w+``2A=aotj0;2v$~AL z{scF-cL{wsdrmPvf#a9OHyYLcwQD4Kcm)`LLwMh4WT~p29f7M!iafJSU`IV}QY5Wa z(n44-9oA}?J{a+ah*@31WTs#&J#o1`H98#6IQf;Wv0N_!);f&9g7o-k(lW5rWnDUR zQBFIRG+X=6NnsI@mxnwm;tf5;_Uxg?jZ8m-m0}&6+DA!qam(p$mN5R})yA_7m$q@| zFEd|dpS595rxQr-n#GjI5i-AhnUE>Cr;jpCqSrD~EwK_DqI^7%3#p5)%T_od!t3SOmH9MyXeeGO2(UQL;ax|x?Ncixmeo1=$ z{-);Au{*tfzOG?KQ~K|ak8-HQ?`Pekhe2WM(8s{xv-p>Zmu_6{G!-oE$7$mY`MOJorI=+mMx?H;`pr!;fVYz?5~yXBACruWB`Ph zZM}90_<^OBxIhyZ9BW$`>6JvO;%VFpqVr8|7t3~AmxYak6?`Pp#c;**_SYmi`&z23 z`p6_~ePvH)C6x-G9$hgL=eVALq`-AiamN>!3~Lxw&{H(b{B(7xSRm6<3<{%{yXiH# zos5Rv1L+8fUKJLo%P>4I&$}yR?p|tHQo6fg38|$UVM!6BLrPFWk?s;$LOP{GmJpBl$qoSA!PUg~PA65-S00{{S`XKG6NkG0RgjEntPrmV+?0|00mu7;+5 zrdpa{2QLqPJ4Y{j7=Mrl{BaxrkdY69+c~(w{Fv-v&aR%aEI&JYSeRTLWm!zbv;?)_ ziZB;fwGbbeL5Q}YLx`J$lp~A09KK8t_z}PZ=4ZzgdeKtgoc+o5EvN9A1K1_<>M?MBqb#!ASf&# zEX?<)!RH(7>1P+j=jqG(58}TVN-$psA6K}atCuI!KTJD&FMmH-78ZejBm)0qc{ESp z|LuG1{QnBUJRg_E=h1#XMWt2%fcoN@l7eAS!Es?Q+;XsRNPhiiE=@AqlLkJzF`O18 zbsbSmKN=aaq8k3NFYZfDWpKmM!coBU0(XnL8R{4=i|wi{!uWYM2je{U{B*K2PVdu&=E zTq*-XsEsJ$u5H4g6DIm2Y!DN`>^v|AqlwuCD;w45K0@eqauiqWf7l&o)+YLHm~|L~ z7$0v5mkobriU!H<@mVJHLlmQqzQ3d6Rh_-|%Yy2li*tHO>_vcnuZ7OR_xkAIuIU&x z-|8Y0wj|6|a6_I(v91y%k_kNw6pnkNdxjqG8!%Vz_d%c_!X+6-;1`GC9_FpjoHev5fEV7RhJ>r=mh-jp$fqbqRJ=obwdgLDVP5+s zy1=_DWG0Y-Jb3t^WXmkr(d9~08k-|#Ly zaNOmT(^9tIb&eb4%CzIT zAm3CUtWSr1t4?h1kk#NBi{U|pJslvME{q|_eS^3En>SOqSxyuN1x;Is@8~m?*>}** znrRFArP!K_52RpX*&JHMR<^lVdm8ypJ}0R(SD(51j;6@ni$6bQ+2XL+R^|NnSp5}(kzvMZ^(@4fD_{QVu$(&K6H|C37TG1Am9Re{<<3gd zh@`>;BqkXMW&p0T6rt|iB$)~CvFe(XC)F9WgAZn*0@t$oZo;!*}r@_`h?KKH&6A@3= zISXoQB+~`op>NP-buiA*^0n{@i{_?MRG)&k)c)k_F+-2Lud!S9pc+i`s74NpBCaGF zXN+pHkubw*msGBTY27BKHv)RRh3;nMg4&$fD_6X9Vt~;_4D+5XPH~#Kn-yjcy!$}1 zigv#FNY>TqMhtIBb@UoF!cE~Q8~;!Pek>SQQwHnHuWKoVBosAiOr}q>!>aE*Krc)V zBUMEcJ5NU0g8}-h6i1zpMY9>m4ne?=U2~`w7K7Q0gB_=p@$5K7p6}thw z-~3dMj?YNX2X$lZ+7ngQ$=s}3mizNN@kE%OtB)?c&i~2L55z8^=yz;xMHLmlY>&Q# zJj?!)M#q_SyfkQh)k?j8IfLtB)ZCp|*vf4_B zos?73yd^h-Ac+;?E4*bpf=o*^3x3-`TVjbY4n6!EN10K6o@fxdyps05Vo3PU)otB} z`3kR+2w7_C#8Z!q`J)p{Vh!+m9-UP!$STp+Hb}}#@#_u^SsUQg<}59< zTvH3%XS4G+6FF^(m6bVF&nSUIXcl;nw{=H$%fgeJ>CgDYiLdpDXr{;-AnG z8dvcrHYVMI&`R6;GWekI@Ir3!uo)oz4^{6q0m^}@f2tM9&=YHNi6-?rh0-{+k@cQm zdp`g#YdQn%MDVg2GR>wZ`n2<0l4)9nx1Wfr&!Dvz=bPwU!h2S?ez6MVc5APE4-xLB zi&W9Q8k2@0w!C53g?iAIQ}~p*3O(@zja6KQ=M3zfW*_6o5SwR-)6VBh~m7{^-=MC-owYH5-u40a}a0liho3QZZ5L{bS_xM1)4}19)zTU$$MY zq3eZML1WC{K%YFd`Be0M-rkO^l?h{kM{$2oK1*A@HVJ57*yhDkUF!2WZ&oA4Y-sK( zCY69%#`mBCi6>6uw(x4gbFaP0+FD*JKJ-q!F1E?vLJ+d35!I5d7@^eU?(CS|C^tmI5?lv@s{{*|1F zFg|OzNpZ0hxljdjaW%45O0MOttRrd(Z?h{HYbB-KFUx&9GfFL3b8NwZ$zNu)WbBD` zYkj$^UB5%3Pj1MDr>S2Ejr9pUcgA!;ZG!@{uAy12)vG=*^9-|dNQBc8&`oxBlU~#y zs!anJX&T?57Jdr^sb>e+V`MVfY>Y0ESg7MG<7W0g&bR-ZYzzZ%2H&Etcp zcd6QeXO1D!5A#zM0lx*GH}`M)2~ZFLE;sP^RSB5wVMNfiZXPd(cmO>j=OSA3`o5r& zna(|^jGXbdN7PK)U8b7^zYtYkkeb%<%F~=OqB~kXMQkq}ii|skh@WSRt>5za;cjP0 zZ~nD%6)wzedqE}BMLt~qKwlvTr33))#uP~xyw#*Eaa|DbMQ_%mG0U8numf8)0DX`r zRoG2bM;#g|p-8gWnwRV5SCW0tLjLO&9Z?K>FImeIxlGUgo0Zk`9Qzhj1eco~7XZy+hXc@YF&ZQ=? zn*^1O56yK^x{y}q`j7}blGCx%dydV!c7)g~tJzmHhV=W~jbWRRR{1<^oDK+1clprm zz$eCy7y9+?{E|YgkW~}}iB#I4XoJ*xr8R?i_Hv$=Cof5bo-Nj~f`-DLebH}&0% zfQj9@WGd4;N~Y?mzQsHJTJq6!Qzl^-vwol(+fMt#Pl=Wh#lI5Vmu@QM0=_r+1wHt` z+8WZ~c2}KQQ+q)~2Ki77QvV&`xb|xVcTms99&cD$Zz4+-^R4kvUBxG8gDk7Y`K*)JZ^2rL(+ZWV~%W(@6 z)0bPArG#BROa_PHs~&WplQ_UIrpd)1N1QGPfv!J(Z9jNT#i%H?CE6|pPZb9hJ1JW4 z^q;ft#!HRNV0YgPojzIYT`8LuET2rUe-J|c!9l4`^*;4WtY@Ew@pL>wkjmMgGfN7 ze}}GtmU0@<_#08~I-Suk=^*9GLW=H4xhsml;vAV{%hy5Eegl@!6qKqbG024%n2HHw zCc@ivW_$@5ZoHP70(7D+(`PvgjW1Pd`wsiuv-aCukMrafwDm)B!xXVy*j2opohhoU zcJz%ADmj>i3`-3-$7nQKBQQuGY;2Qt&+(L~C>vSGFj5{Mlv?T_^dql;{zkpe4R1}R z%XfZyQ}wr*sr>jrKgm*PWLjuVc%6&&`Kbf1SuFpHPN&>W)$GmqC;pIoBC`=4-hPY8 zT*>%I2fP}vGW;R=^!1be?ta2UQd2>alOFFbVl;(SQJ4Jk#)4Z0^wpWEVvY4=vyDk@ zqlModi@iVPMC+{?rm=4(n+<;|lmUO@UKYA>EPTS~AndtK^Wy^%#3<;(dQdk3WaUkRtzSMC9}7x2||CNpF#(3T4C)@ z$~RWs`BNABKX|{cmBt>Q=&gkXl&x!!NK_%5hW0LS)Z4PB>%sV?F-{Wyj#s7W%$F{D zXdK^Fp3wvy+48+GP6F_|^PCRx=ddcTO3sG;B23A49~Qaw31SZ0Rc~`r4qqt%#OGW{ zCA_(LG5^N>yzUn&kAgVmxb=EA8s&tBXC}S1CZ(KoW)(%^JjLTPo^fs`Va;`=YlVPgmB$!yB}<(4ym6OeZ3xAJJ#;)2+B%p3P1Wt+d$eo`vz`T zXfUP2))kBDPoscH;Jc7I3NU<({|@wM$&GaDt`n7WLgIY3IA7A6-_R?z8N3mz|}*i z(zl5ot--Oq@f2-nv{X(ujT2T(k1vY_qh93pK@>H-qc%2Xta)IP0Q%zt%bqYgI`o!wv!0QerB`nCN^1n|@$sVOQ!V0teVG!I z_fD%JvfDeT1cK#-{o6Gv7}& zY0#NWin~kVaf$aufV&;63Hbs|`QVZWpDX6IMk1Hj2G}fiH9e-^6u2zf^FIr^BwD<6zjw63+{yUe8PUFvk8v{sJ=R{d#`O!sz`Q13~< zPT$JS(w=yQfU2`zPCNfSw=&zup@DXc(98afjhv@1w_f!m2Z>rMJ19AB&dB%P#Ls3b z=lK7OILM+SQ&VEd=1GN6o&>YVVtIzoZ%=Z_SdqJN2}E43{bE`>w+A;=y->@^k{oCC z$F*WTY&?34;kfyFV?b*Xb1Pq`Z=%OgwEg)Rz)tx=`f%5#w_INP=x&z5!jI;#;N$ma zhO)+MDm;SxOEVL15; zGq(v2pL3&P1Sl)8P*;G-fd{l1QJsv@e@d8)1PK4w2m*M%V3j-V~L^$i|&C@b?D?9tfwE{B^}Z$k8e5FmQ>v7Xz)sG32g9t}YBt zyR$+*_00RmPx+0mW+vVG4mxd(n$(eQf3-w>JPl2UJpafrPaL5@2j}%{VE-) zBI%6Qpj*dsdH<;g!S!avA~bv^0E+ zfyJbSjPb+j;J52U)<|cIcntQBI2T#>2;tOxu{%D?kML476AErF(qN9hPva5Nkc@BF zC-tLF@3ZFb%Kpj)M<{)x*l|*Ia@ECeXo2E4h2f!aV=cHAhi_E_mfUth(sM4^hJq7B zQsGWqdZUm9S%F`$nQ*_#NcuD`&)Ek%_s{&^78{9Hm ztri&rYLOxgFdG>O@+XHy z9#;|&vBCPXH5Mon^I`jSuR$&~ZWtyB67ujzFSj!51>#C}C17~TffQ{c-!QFQkTQ%! zIR^b1`zHx|*1GU?tbBx23weFLz5H?y_Q%N&t$}k?w+``2A=aotj0;2v$~AL z{scF-cL{wsdrmPvf#a9OHyYLcwQD4Kcm)`LLwMh4WT~p29f7M!iafJSU`IV}QY5Wa z(n44-9oA}?J{a+ah*@31WTs#&J#o1`H98#6IQf;Wv0N_!);f&9g7o-k(lW5rWnDUR zQBFIRG+X=6NnsI@mxnwm;tf5;_Uxg?jZ8m-m0}&6+DA!qam(p$mN5R})yA_7m$q@| zFEd|dpS595rxQr-n#GjI5i-AhnUE>Cr;jpCqSrD~EwK_DqI^7%3#p5)%T_od!t3SOmH9MyXeeGO2(UQL;ax|x?Ncixmeo1=$ z{-);Au{*tfzOG?KQ~K|ak8-HQ?`Pekhe2WM(8s{xv-p>Zmu_6{G!-oE$7$mY`MOJorI=+mMx?H;`pr!;fVYz?5~yXBACruWB`Ph zZM}90_<^OBxIhyZ9BW$`>6JvO;%VFpqVr8|7t3~AmxYak6?`Pp#c;**_SYmi`&z23 z`p6_~ePvH)C6x-G9$hgL=eVALq`-AiamN>!3~Lxw&{H(b{B(7xSRm6<3<{%{yXiH# zos5Rv1L+8fUKJLo%P>4I&$}y { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', + JSON.stringify(data) ?? ''); + }); + } + + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } + + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } + + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets new file mode 100644 index 000000000..bdf1e5f90 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import hilog from '@ohos.hilog'; + +@Entry +@Component +struct Index { + aboutToAppear() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility index aboutToAppear'); + } + @State message: string = 'Hello World' + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + Button() { + Text('next page') + .fontSize(20) + .fontWeight(FontWeight.Bold) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .width('35%') + .height('5%') + .onClick(()=>{ + }) + } + .width('100%') + } + .height('100%') + } + } \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts new file mode 100644 index 000000000..58d9c312f --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts @@ -0,0 +1,64 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import hilog from '@ohos.hilog'; +import TestRunner from '@ohos.application.testRunner'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +var abilityDelegator = undefined +var abilityDelegatorArguments = undefined + +async function onAbilityCreateCallback() { + hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback'); +} + +async function addAbilityMonitorCallback(err: any) { + hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); +} + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare '); + } + + async onRun() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility' + let lMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + }; + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName + var debug = abilityDelegatorArguments.parameters['-D'] + if (debug == 'true') + { + cmd += ' -D' + } + hilog.info(0x0000, 'testTag', 'cmd : %{public}s', cmd); + abilityDelegator.executeShellCommand(cmd, + (err: any, d: any) => { + hilog.info(0x0000, 'testTag', 'executeShellCommand : err : %{public}s', JSON.stringify(err) ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.stdResult ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.exitCode ?? ''); + }) + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/module.json5 b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/module.json5 new file mode 100644 index 000000000..fab77ce2e --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/module.json5 @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:test_pages", + "abilities": [ + { + "name": "TestAbility", + "srcEntry": "./ets/testability/TestAbility.ets", + "description": "$string:TestAbility_desc", + "icon": "$media:icon", + "label": "$string:TestAbility_label", + "exported": true, + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "skills": [ + { + "actions": [ + "action.system.home" + ], + "entities": [ + "entity.system.home" + ] + } + ] + } + ] + } +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/resources/base/element/color.json b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/resources/base/element/color.json new file mode 100644 index 000000000..3c712962d --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/resources/base/element/string.json b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/resources/base/element/string.json new file mode 100644 index 000000000..65d8fa5a7 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + } + ] +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/resources/base/media/icon.png b/packages/in_app_purchase/in_app_purchase_ohos/example/ohos/entry/src/ohosTest/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 zcmX|G1ymHk)?T_}Vd;>R?p|tHQo6fg38|$UVM!6BLrPFWk?s;$LOP{GmJpBl$qoSA!PUg~PA65-S00{{S`XKG6NkG0RgjEntPrmV+?0|00mu7;+5 zrdpa{2QLqPJ4Y{j7=Mrl{BaxrkdY69+c~(w{Fv-v&aR%aEI&JYSeRTLWm!zbv;?)_ ziZB;fwGbbeL5Q}YLx`J$lp~A09KK8t_z}PZ=4ZzgdeKtgoc+o5EvN9A1K1_<>M?MBqb#!ASf&# zEX?<)!RH(7>1P+j=jqG(58}TVN-$psA6K}atCuI!KTJD&FMmH-78ZejBm)0qc{ESp z|LuG1{QnBUJRg_E=h1#XMWt2%fcoN@l7eAS!Es?Q+;XsRNPhiiE=@AqlLkJzF`O18 zbsbSmKN=aaq8k3NFYZfDWpKmM!coBU0(XnL8R{4=i|wi{!uWYM2je{U{B*K2PVdu&=E zTq*-XsEsJ$u5H4g6DIm2Y!DN`>^v|AqlwuCD;w45K0@eqauiqWf7l&o)+YLHm~|L~ z7$0v5mkobriU!H<@mVJHLlmQqzQ3d6Rh_-|%Yy2li*tHO>_vcnuZ7OR_xkAIuIU&x z-|8Y0wj|6|a6_I(v91y%k_kNw6pnkNdxjqG8!%Vz_d%c_!X+6-;1`GC9_FpjoHev5fEV7RhJ>r=mh-jp$fqbqRJ=obwdgLDVP5+s zy1=_DWG0Y-Jb3t^WXmkr(d9~08k-|#Ly zaNOmT(^9tIb&eb4%CzIT zAm3CUtWSr1t4?h1kk#NBi{U|pJslvME{q|_eS^3En>SOqSxyuN1x;Is@8~m?*>}** znrRFArP!K_52RpX*&JHMR<^lVdm8ypJ}0R(SD(51j;6@ni$6bQ+2XL+R^|NnSp5}(kzvMZ^(@4fD_{QVu$(&K6H|C37TG1Am9Re{<<3gd zh@`>;BqkXMW&p0T6rt|iB$)~CvFe(XC)F9WgAZn*0@t$oZo;!*}r@_`h?KKH&6A@3= zISXoQB+~`op>NP-buiA*^0n{@i{_?MRG)&k)c)k_F+-2Lud!S9pc+i`s74NpBCaGF zXN+pHkubw*msGBTY27BKHv)RRh3;nMg4&$fD_6X9Vt~;_4D+5XPH~#Kn-yjcy!$}1 zigv#FNY>TqMhtIBb@UoF!cE~Q8~;!Pek>SQQwHnHuWKoVBosAiOr}q>!>aE*Krc)V zBUMEcJ5NU0g8}-h6i1zpMY9>m4ne?=U2~`w7K7Q0gB_=p@$5K7p6}thw z-~3dMj?YNX2X$lZ+7ngQ$=s}3mizNN@kE%OtB)?c&i~2L55z8^=yz;xMHLmlY>&Q# zJj?!)M#q_SyfkQh)k?j8IfLtB)ZCp|*vf4_B zos?73yd^h-Ac+;?E4*bpf=o*^3x3-`TVjbY4n6!EN10K6o@fxdyps05Vo3PU)otB} z`3kR+2w7_C#8Z!q`J)p{Vh!+m9-UP!$STp+Hb}}#@#_u^SsUQg<}59< zTvH3%XS4G+6FF^(m6bVF&nSUIXcl;nw{=H$%fgeJ>CgDYiLdpDXr{;-AnG z8dvcrHYVMI&`R6;GWekI@Ir3!uo)oz4^{6q0m^}@f2tM9&=YHNi6-?rh0-{+k@cQm zdp`g#YdQn%MDVg2GR>wZ`n2<0l4)9nx1Wfr&!Dvz=bPwU!h2S?ez6MVc5APE4-xLB zi&W9Q8k2@0w!C53g?iAIQ}~p*3O(@zja6KQ=M3zfW*_6o5SwR-)6VBh~m7{^-=MC-owYH5-u40a}a0liho3QZZ5L{bS_xM1)4}19)zTU$$MY zq3eZML1WC{K%YFd`Be0M-rkO^l?h{kM{$2oK1*A@HVJ57*yhDkUF!2WZ&oA4Y-sK( zCY69%#`mBCi6>6uw(x4gbFaP0+FD*JKJ-q!F1E?vLJ+d35!I5d7@^eU?(CS|C^tmI5?lv@s{{*|1F zFg|OzNpZ0hxljdjaW%45O0MOttRrd(Z?h{HYbB-KFUx&9GfFL3b8NwZ$zNu)WbBD` zYkj$^UB5%3Pj1MDr>S2Ejr9pUcgA!;ZG!@{uAy12)vG=*^9-|dNQBc8&`oxBlU~#y zs!anJX&T?57Jdr^sb>e+V`MVfY>Y0ESg7MG<7W0g&bR-ZYzzZ%2H&Etcp zcd6QeXO1D!5A#zM0lx*GH}`M)2~ZFLE;sP^RSB5wVMNfiZXPd(cmO>j=OSA3`o5r& zna(|^jGXbdN7PK)U8b7^zYtYkkeb%<%F~=OqB~kXMQkq}ii|skh@WSRt>5za;cjP0 zZ~nD%6)wzedqE}BMLt~qKwlvTr33))#uP~xyw#*Eaa|DbMQ_%mG0U8numf8)0DX`r zRoG2bM;#g|p-8gWnwRV5SCW0tLjLO&9Z?K>FImeIxlGUgo0Zk`9Qzhj1eco~7XZy+hXc@YF&ZQ=? zn*^1O56yK^x{y}q`j7}blGCx%dydV!c7)g~tJzmHhV=W~jbWRRR{1<^oDK+1clprm zz$eCy7y9+?{E|YgkW~}}iB#I4XoJ*xr8R?i_Hv$=Cof5bo-Nj~f`-DLebH}&0% zfQj9@WGd4;N~Y?mzQsHJTJq6!Qzl^-vwol(+fMt#Pl=Wh#lI5Vmu@QM0=_r+1wHt` z+8WZ~c2}KQQ+q)~2Ki77QvV&`xb|xVcTms99&cD$Zz4+-^R4kvUBxG8gDk7Y`K*)JZ^2rL(+ZWV~%W(@6 z)0bPArG#BROa_PHs~&WplQ_UIrpd)1N1QGPfv!J(Z9jNT#i%H?CE6|pPZb9hJ1JW4 z^q;ft#!HRNV0YgPojzIYT`8LuET2rUe-J|c!9l4`^*;4WtY@Ew@pL>wkjmMgGfN7 ze}}GtmU0@<_#08~I-Suk=^*9GLW=H4xhsml;vAV{%hy5Eegl@!6qKqbG024%n2HHw zCc@ivW_$@5ZoHP70(7D+(`PvgjW1Pd`wsiuv-aCukMrafwDm)B!xXVy*j2opohhoU zcJz%ADmj>i3`-3-$7nQKBQQuGY;2Qt&+(L~C>vSGFj5{Mlv?T_^dql;{zkpe4R1}R z%XfZyQ}wr*sr>jrKgm*PWLjuVc%6&&`Kbf1SuFpHPN&>W)$GmqC;pIoBC`=4-hPY8 zT*>%I2fP}vGW;R=^!1be?ta2UQd2>alOFFbVl;(SQJ4Jk#)4Z0^wpWEVvY4=vyDk@ zqlModi@iVPMC+{?rm=4(n+<;|lmUO@UKYA>EPTS~AndtK^Wy^%#3<;(dQdk3WaUkRtzSMC9}7x2||CNpF#(3T4C)@ z$~RWs`BNABKX|{cmBt>Q=&gkXl&x!!NK_%5hW0LS)Z4PB>%sV?F-{Wyj#s7W%$F{D zXdK^Fp3wvy+48+GP6F_|^PCRx=ddcTO3sG;B23A49~Qaw31SZ0Rc~`r4qqt%#OGW{ zCA_(LG5^N>yzUn&kAgVmxb=EA8s&tBXC}S1CZ(KoW)(%^JjLTPo^fs`Va;`=YlVPgmB$!yB}<(4ym6OeZ3xAJJ#;)2+B%p3P1Wt+d$eo`vz`T zXfUP2))kBDPoscH;Jc7I3NU<({|@wM$&GaDt`n7WLgIY3IA7A6-_R?z8N3mz|}*i z(zl5ot--Oq@f2-nv{X(ujT2T(k1vY_qh93pK@>H-qc%2Xta)IP0Q%zt%bqYgI`o!wv!0QerB`nCN^1n|@$sVOQ!V0teVG!I z_fD%JvfDeT1cK#-{o6Gv7}& zY0#NWin~kVaf$aufV&;63Hbs|`QVZWpDX6IMk1Hj2G}fiH9e-^6u2zf^FIr^BwD<6zjw63+{yUe8PUFvk8v{sJ=R{d#`O!sz`Q13~< zPT$JS(w=yQfU2`zPCNfSw=&zup@DXc(98afjhv@1w_f!m2Z>rMJ19AB&dB%P#Ls3b z=lK7OILM+SQ&VEd=1GN6o&>YVVtIzoZ%=Z_SdqJN2}E43{bE`>w+A;=y->@^k{oCC z$F*WTY&?34;kfyFV?b*Xb1Pq`Z=%OgwEg)Rz)tx=`f%5#w_INP=x&z5!jI;#;N$ma zhO)+MDm;SxOEVL15; zGq(v2pL3&P1Sl)8P*;G-fd{l1QJsv@e@d8)1PK4w2m*M%V3j-V~L^$i|&C@b?D?9tfwE{B^}Z$k8e5FmQ>v7Xz)sG32g9t}YBt zyR$+*_00RmPx+0mW+vVG4mxd(n$(eQf3-w>JPl2UJpafrPaL5@2j}%{VE-) zBI%6Qpj*dsdH<;g!S!avA~bv^0E+ zfyJbSjPb+j;J52U)<|cIcntQBI2T#>2;tOxu{%D?kML476AErF(qN9hPva5Nkc@BF zC-tLF@3ZFb%Kpj)M<{)x*l|*Ia@ECeXo2E4h2f!aV=cHAhi_E_mfUth(sM4^hJq7B zQsGWqdZUm9S%F`$nQ*_#NcuD`&)Ek%_s{&^78{9Hm ztri&rYLOxgFdG>O@+XHy z9#;|&vBCPXH5Mon^I`jSuR$&~ZWtyB67ujzFSj!51>#C}C17~TffQ{c-!QFQkTQ%! zIR^b1`zHx|*1GU?tbBx23weFLz5H?y_Q%N&t$}k?w+``2A=aotj0;2v$~AL z{scF-cL{wsdrmPvf#a9OHyYLcwQD4Kcm)`LLwMh4WT~p29f7M!iafJSU`IV}QY5Wa z(n44-9oA}?J{a+ah*@31WTs#&J#o1`H98#6IQf;Wv0N_!);f&9g7o-k(lW5rWnDUR zQBFIRG+X=6NnsI@mxnwm;tf5;_Uxg?jZ8m-m0}&6+DA!qam(p$mN5R})yA_7m$q@| zFEd|dpS595rxQr-n#GjI5i-AhnUE>Cr;jpCqSrD~EwK_DqI^7%3#p5)%T_od!t3SOmH9MyXeeGO2(UQL;ax|x?Ncixmeo1=$ z{-);Au{*tfzOG?KQ~K|ak8-HQ?`Pekhe2WM(8s{xv-p>Zmu_6{G!-oE$7$mY`MOJorI=+mMx?H;`pr!;fVYz?5~yXBACruWB`Ph zZM}90_<^OBxIhyZ9BW$`>6JvO;%VFpqVr8|7t3~AmxYak6?`Pp#c;**_SYmi`&z23 z`p6_~ePvH)C6x-G9$hgL=eVALq`-AiamN>!3~Lxw&{H(b{B(7xSRm6<3<{%{yXiH# zos5Rv1L+8fUKJLo%P>4I&$}y=3.0.0 <4.0.0" + flutter: ">=3.10.0" + +dependencies: + flutter: + sdk: flutter + in_app_purchase_ohos: + # When depending on this package from a real application you should use: + # in_app_purchase_ohos: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + in_app_purchase_platform_interface: ^1.0.0 + shared_preferences: + git: + url: https://gitee.com/openharmony-sig/flutter_packages.git + path: packages/shared_preferences/shared_preferences + +dev_dependencies: + # build_runner: ^2.4.5 + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + +flutter: + uses-material-design: true diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/iap_kit_wrappers.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/iap_kit_wrappers.dart new file mode 100644 index 000000000..eade8d181 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/iap_kit_wrappers.dart @@ -0,0 +1,9 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'src/iap_kit_wrappers/ik_payment_queue_wrapper.dart'; +export 'src/iap_kit_wrappers/ik_payment_transaction_wrappers.dart'; +export 'src/iap_kit_wrappers/ik_product_wrapper.dart'; +export 'src/iap_kit_wrappers/ik_receipt_manager.dart'; +export 'src/iap_kit_wrappers/ik_request_maker.dart'; diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/in_app_purchase_ohos.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/in_app_purchase_ohos.dart new file mode 100644 index 000000000..bee9e1c9a --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/in_app_purchase_ohos.dart @@ -0,0 +1,7 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +export 'src/in_app_purchase_ohos_platform.dart'; +export 'src/in_app_purchase_ohos_platform_addition.dart'; +export 'src/types/types.dart'; diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/channel.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/channel.dart new file mode 100644 index 000000000..c8b9d647a --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/channel.dart @@ -0,0 +1,9 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/services.dart'; + +/// Method channel for the plugin's platform<-->Dart calls. +const MethodChannel channel = + MethodChannel('plugins.flutter.io/in_app_purchase'); \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/README.md b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/README.md new file mode 100644 index 000000000..8a99aab65 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/README.md @@ -0,0 +1,5 @@ +# iap_kit_wrappers + +This exposes Dart endpoints through to the +IAP Kit APIs. It offers functionality +as an alternative to [in_app_purchase](../in_app_purchase/README.md). \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/enum_converters.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/enum_converters.dart new file mode 100644 index 000000000..76e193235 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/enum_converters.dart @@ -0,0 +1,143 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; +import 'package:json_annotation/json_annotation.dart'; + +import '../../iap_kit_wrappers.dart'; + +part 'enum_converters.g.dart'; + +/// Serializer for [IKPaymentTransactionStateWrapper]. +/// +/// Use these in `@JsonSerializable()` classes by annotating them with +/// `@IKTransactionStatusConverter()`. +class IKTransactionStatusConverter + implements JsonConverter { + /// Default const constructor. + const IKTransactionStatusConverter(); + + @override + IKPaymentTransactionStateWrapper fromJson(int? json) { + if (json == null) { + return IKPaymentTransactionStateWrapper.unspecified; + } + return $enumDecode( + _$IKPaymentTransactionStateWrapperEnumMap + .cast(), + json); + } + + /// Converts an [IKPaymentTransactionStateWrapper] to a [PurchaseStatus]. + PurchaseStatus toPurchaseStatus( + IKPaymentTransactionStateWrapper object, IKError? error) { + print('MethodCallHandlerImpl toPurchaseStatus. $object'); + switch (object) { + case IKPaymentTransactionStateWrapper.purchasing: + case IKPaymentTransactionStateWrapper.deferred: + return PurchaseStatus.pending; + case IKPaymentTransactionStateWrapper.purchased: + return PurchaseStatus.purchased; + case IKPaymentTransactionStateWrapper.restored: + return PurchaseStatus.restored; + case IKPaymentTransactionStateWrapper.failed: + // According to the Apple documentation the error code "2" indicates + // the user cancelled the payment (IKErrorPaymentCancelled) and error + // code "15" indicates the cancellation of the overlay (IKErrorOverlayCancelled). + if (error != null && (error.code == 2 || error.code == 15)) { + return PurchaseStatus.canceled; + } + return PurchaseStatus.error; + case IKPaymentTransactionStateWrapper.unspecified: + return PurchaseStatus.error; + } + } + + @override + int toJson(IKPaymentTransactionStateWrapper object) => + _$IKPaymentTransactionStateWrapperEnumMap[object]!; +} + +/// Serializer for [IKSubscriptionPeriodUnit]. +/// +/// Use these in `@JsonSerializable()` classes by annotating them with +/// `@IKSubscriptionPeriodUnitConverter()`. +class IKSubscriptionPeriodUnitConverter + implements JsonConverter { + /// Default const constructor. + const IKSubscriptionPeriodUnitConverter(); + + @override + IKSubscriptionPeriodUnit fromJson(int? json) { + if (json == null) { + return IKSubscriptionPeriodUnit.day; + } + return $enumDecode( + _$IKSubscriptionPeriodUnitEnumMap + .cast(), + json); + } + + @override + int toJson(IKSubscriptionPeriodUnit object) => + _$IKSubscriptionPeriodUnitEnumMap[object]!; +} + +/// Serializer for [IKProductDiscountPaymentMode]. +/// +/// Use these in `@JsonSerializable()` classes by annotating them with +/// `@IKProductDiscountPaymentModeConverter()`. +class IKProductDiscountPaymentModeConverter + implements JsonConverter { + /// Default const constructor. + const IKProductDiscountPaymentModeConverter(); + + @override + IKProductDiscountPaymentMode fromJson(int? json) { + if (json == null) { + return IKProductDiscountPaymentMode.payAsYouGo; + } + return $enumDecode( + _$IKProductDiscountPaymentModeEnumMap + .cast(), + json); + } + + @override + int toJson(IKProductDiscountPaymentMode object) => + _$IKProductDiscountPaymentModeEnumMap[object]!; +} + +// Define a class so we generate serializer helper methods for the enums +// See https://github.com/google/json_serializable.dart/issues/778 +@JsonSerializable() +class _SerializedEnums { + late IKPaymentTransactionStateWrapper response; + late IKSubscriptionPeriodUnit unit; + late IKProductDiscountPaymentMode discountPaymentMode; +} + +/// Serializer for [IKProductDiscountType]. +/// +/// Use these in `@JsonSerializable()` classes by annotating them with +/// `@IKProductDiscountTypeConverter()`. +class IKProductDiscountTypeConverter + implements JsonConverter { + /// Default const constructor. + const IKProductDiscountTypeConverter(); + + @override + IKProductDiscountType fromJson(int? json) { + if (json == null) { + return IKProductDiscountType.introductory; + } + return $enumDecode( + _$IKProductDiscountTypeEnumMap.cast(), + json); + } + + @override + int toJson(IKProductDiscountType object) => + _$IKProductDiscountTypeEnumMap[object]!; +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/enum_converters.g.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/enum_converters.g.dart new file mode 100644 index 000000000..1fd872ac6 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/enum_converters.g.dart @@ -0,0 +1,42 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'enum_converters.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_SerializedEnums _$SerializedEnumsFromJson(Map json) => _SerializedEnums() + ..response = + $enumDecode(_$IKPaymentTransactionStateWrapperEnumMap, json['response']) + ..unit = $enumDecode(_$IKSubscriptionPeriodUnitEnumMap, json['unit']) + ..discountPaymentMode = $enumDecode( + _$IKProductDiscountPaymentModeEnumMap, json['discountPaymentMode']); + +const _$IKPaymentTransactionStateWrapperEnumMap = { + IKPaymentTransactionStateWrapper.purchasing: 0, + IKPaymentTransactionStateWrapper.purchased: 1, + IKPaymentTransactionStateWrapper.failed: 2, + IKPaymentTransactionStateWrapper.restored: 3, + IKPaymentTransactionStateWrapper.deferred: 4, + IKPaymentTransactionStateWrapper.unspecified: -1, +}; + +const _$IKSubscriptionPeriodUnitEnumMap = { + IKSubscriptionPeriodUnit.day: 0, + IKSubscriptionPeriodUnit.week: 1, + IKSubscriptionPeriodUnit.month: 2, + IKSubscriptionPeriodUnit.year: 3, +}; + +const _$IKProductDiscountPaymentModeEnumMap = { + IKProductDiscountPaymentMode.payAsYouGo: 0, + IKProductDiscountPaymentMode.payUpFront: 1, + IKProductDiscountPaymentMode.freeTrail: 2, + IKProductDiscountPaymentMode.unspecified: -1, +}; + +const _$IKProductDiscountTypeEnumMap = { + IKProductDiscountType.introductory: 0, + IKProductDiscountType.subscription: 1, +}; diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_payment_queue_wrapper.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_payment_queue_wrapper.dart new file mode 100644 index 000000000..c4fdc94af --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_payment_queue_wrapper.dart @@ -0,0 +1,264 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:json_annotation/json_annotation.dart'; + +import '../../iap_kit_wrappers.dart'; +import '../channel.dart'; +import '../in_app_purchase_ohos_platform.dart'; + +part 'ik_payment_queue_wrapper.g.dart'; + +class IKPaymentQueueWrapper { + /// Returns the default payment queue. + /// + /// We do not support instantiating a custom payment queue, hence the + /// singleton. However, you can override the observer. + factory IKPaymentQueueWrapper() { + return _singleton; + } + + IKPaymentQueueWrapper._(); + + static final IKPaymentQueueWrapper _singleton = IKPaymentQueueWrapper._(); + + IKTransactionObserverWrapper? _observer; + + Future> transactions() async { + return _getTransactionList((await channel + .invokeListMethod('iap#transactions'))!); + } + + static Future queryEnvironmentStatus() async => + (await channel.invokeMethod('iap#queryEnvironmentStatus')) ?? false; + + void setTransactionObserver(IKTransactionObserverWrapper observer) { + _observer = observer; + channel.setMethodCallHandler(handleObserverCallbacks); + } + + Future startObservingTransactionQueue() => + channel.invokeMethod('iap#startObservingTransactionQueue'); + + Future stopObservingTransactionQueue() => + channel.invokeMethod('iap#stopObservingTransactionQueue'); + + Future addPayment(IKPaymentWrapper payment) async { + assert(_observer != null, + '[in_app_purchase]: Trying to add a payment without an observer. One must be set using `IKPaymentQueueWrapper.setTransactionObserver` before the app launches.'); + final Map requestMap = payment.toMap(); + await channel.invokeMethod( + 'iap#createPurchase', + requestMap, + ); + } + + Future finishTransaction( + IKPaymentTransactionWrapper transaction) async { + final Map requestMap = transaction.toFinishMap(); + await channel.invokeMethod( + 'iap#finishPurchase', + requestMap, + ); + } + + // 暂未实现 + Future restoreTransactions({String? applicationUserName}) async { + await channel.invokeMethod( + 'iap#restoreTransactions', applicationUserName); + } + + /// Triage a method channel call from the platform and triggers the correct observer method. + /// + /// This method is public for testing purposes only and should not be used + /// outside this class. + @visibleForTesting + Future handleObserverCallbacks(MethodCall call) async { + assert(_observer != null, + '[in_app_purchase]: (Fatal)The observer has not been set but we received a purchase transaction notification. Please ensure the observer has been set using `setTransactionObserver`. Make sure the observer is added right at the App Launch.'); + print('MethodCallHandlerImpl handleObserverCallbacks : ' + call.method); + final IKTransactionObserverWrapper observer = _observer!; + switch (call.method) { + case 'updatedTransactions': + { + print('MethodCallHandlerImpl handleObserverCallbacks'); + final List transactions = + _getTransactionList(call.arguments as List); + return Future(() { + observer.updatedTransactions(transactions: transactions); + }); + } + case 'removedTransactions': + { + final List transactions = + _getTransactionList(call.arguments as List); + return Future(() { + observer.removedTransactions(transactions: transactions); + }); + } + default: + break; + } + throw PlatformException( + code: 'no_such_callback', + message: 'Did not recognize the observer callback ${call.method}.'); + } + + // Get transaction wrapper object list from arguments. + List _getTransactionList( + List transactionsData) { + return transactionsData.map((dynamic map) { + return IKPaymentTransactionWrapper.fromJson( + Map.castFrom( + map as Map)); + }).toList(); + } +} + +@immutable +@JsonSerializable() +class IKError { + /// Creates a new [IKError] object with the provided information. + const IKError( + {required this.code, required this.domain, required this.userInfo}); + + /// Constructs an instance of this from a key-value map of data. + /// + /// The map needs to have named string keys with values matching the names and + /// types of all of the members on this class. The `map` parameter must not be + /// null. + factory IKError.fromJson(Map map) { + return _$IKErrorFromJson(map); + } + + /// Error code + @JsonKey(defaultValue: 0) + final int code; + + /// Error + @JsonKey(defaultValue: '') + final String domain; + + /// A map that contains more detailed information about the error. + @JsonKey(defaultValue: {}) + final Map userInfo; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is IKError && + other.code == code && + other.domain == domain && + const DeepCollectionEquality.unordered() + .equals(other.userInfo, userInfo); + } + + @override + int get hashCode => Object.hash( + code, + domain, + userInfo, + ); +} + +/// Dart wrapper around IAP Kit +/// [PurchaseParameter](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/iap-iap-V5#section1340120344598). +/// +/// Used as the parameter to initiate a payment. In general, a developer should +/// not need to create the payment object explicitly; instead, use +/// [IKPaymentQueueWrapper.addPayment] directly with a product identifier to +/// initiate a payment. +@immutable +@JsonSerializable(createToJson: true) +class IKPaymentWrapper { + /// Creates a new [IKPaymentWrapper] with the provided information. + const IKPaymentWrapper({ + required this.productId, + this.productType, + this.developerPayload, + this.reservedInfo, + this.promotionalOfferId, + this.applicationUserName, + this.jwsRepresentation, + }); + + /// Constructs an instance of this from a key value map of data. + /// + /// The map needs to have named string keys with values matching the names and + /// types of all of the members on this class. The `map` parameter must not be + /// null. + factory IKPaymentWrapper.fromJson(Map map) { + return _$IKPaymentWrapperFromJson(map); + } + + /// Creates a Map object describes the payment object. + Map toMap() { + return { + 'productId': productId, + 'productType': productType, + 'developerPayload': developerPayload, + 'reservedInfo': reservedInfo, + 'promotionalOfferId': promotionalOfferId, + 'applicationUserName': applicationUserName, + 'jwsRepresentation': jwsRepresentation, + }; + } + + /// 待支付的商品ID。商品ID来源于开发者在AppGallery Connect中配置商品信息时设置的“商品ID”,具体请参见配置商品信息。 + final String productId; + + /// 需要查询的商品类型 + final ProductType? productType; + + /// 商户侧保留信息 + final String? developerPayload; + + /// 要求JSON String格式,商户可以将额外需要传入的字段以key-value的形式设置在JSON String中,并通过该参数传入。 + final String? reservedInfo; + + /// 优惠ID。优惠ID来源于开发者在AppGallery Connect中配置商品信息时设置的促销优惠标识符, + /// 具体请参见设置促销价格。传递该字段且要生效,需传递jwsRepresentation字段包含促销优惠信息。 + final String? promotionalOfferId; + + /// 用户账户相关联的混淆字符串,唯一标识用户。传递优惠ID场景,可以传递该字段。 + final String? applicationUserName; + + /// 包含购买参数信息的JWS格式签名数据。购买参数,如促销优惠等。 + final String? jwsRepresentation; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is IKPaymentWrapper && + other.productId == productId && + other.productType == productType && + other.developerPayload == developerPayload && + other.reservedInfo == reservedInfo && + other.promotionalOfferId == promotionalOfferId && + other.applicationUserName == applicationUserName && + other.jwsRepresentation == jwsRepresentation; + } + + @override + int get hashCode => Object.hash(productId, productType, developerPayload, + reservedInfo, promotionalOfferId); + + @override + String toString() => _$IKPaymentWrapperToJson(this).toString(); +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_payment_queue_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_payment_queue_wrapper.g.dart new file mode 100644 index 000000000..1cd277f1c --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_payment_queue_wrapper.g.dart @@ -0,0 +1,50 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'ik_payment_queue_wrapper.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +IKError _$IKErrorFromJson(Map json) => IKError( + code: json['code'] as int? ?? 0, + domain: json['domain'] as String? ?? '', + userInfo: (json['userInfo'] as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ) ?? + {}, + ); + +IKPaymentWrapper _$IKPaymentWrapperFromJson(Map json) => IKPaymentWrapper( + productId: json['productId'] as String? ?? '', + productType: _$IKProductTypeFromInt(json['productType'] as int? ?? 0), + developerPayload: json['developerPayload'] as String?, + reservedInfo: json['reservedInfo'] as String?, + promotionalOfferId: json['promotionalOfferId'] as String?, + applicationUserName: json['applicationUserName'] as String?, + jwsRepresentation: json['jwsRepresentation'] as String?, + ); + +Map _$IKPaymentWrapperToJson(IKPaymentWrapper instance) => + { + 'productId': instance.productId, + 'productType': instance.productType, + 'developerPayload': instance.developerPayload, + 'reservedInfo': instance.reservedInfo, + 'promotionalOfferId': instance.promotionalOfferId, + 'applicationUserName': instance.applicationUserName, + 'jwsRepresentation': instance.jwsRepresentation, + }; + +ProductType _$IKProductTypeFromInt(int type) { + switch (type) { + case 0: + return ProductType.CONSUMABLE; + case 1: + return ProductType.NONCONSUMABLE; + case 3: + return ProductType.AUTORENEWABLE; + default: + return ProductType.CONSUMABLE; + } +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_payment_transaction_wrappers.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_payment_transaction_wrappers.dart new file mode 100644 index 000000000..dc34cd955 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_payment_transaction_wrappers.dart @@ -0,0 +1,172 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:json_annotation/json_annotation.dart'; + +import 'enum_converters.dart'; +import 'ik_payment_queue_wrapper.dart'; +import 'ik_product_wrapper.dart'; + +part 'ik_payment_transaction_wrappers.g.dart'; + +/// Callback handlers for transaction status changes. +abstract class IKTransactionObserverWrapper { + /// Triggered when any transactions are updated. + void updatedTransactions( + {required List transactions}); + + /// Triggered when any transactions are removed from the payment queue. + void removedTransactions( + {required List transactions}); +} + +/// The state of a transaction. +enum IKPaymentTransactionStateWrapper { + /// Indicates the transaction is being processed in App Store. + /// + /// You should update your UI to indicate that you are waiting for the + /// transaction to update to another state. Never complete a transaction that + /// is still in a purchasing state. + @JsonValue(0) + purchasing, + + /// The user's payment has been succesfully processed. + /// + /// You should provide the user the content that they purchased. + @JsonValue(1) + purchased, + + /// The transaction failed. + /// + /// Check the [IKPaymentTransactionWrapper.error] property from + /// [IKPaymentTransactionWrapper] for details. + @JsonValue(2) + failed, + + /// This transaction is restoring content previously purchased by the user. + /// + /// The previous transaction information can be obtained in + /// [IKPaymentTransactionWrapper.originalTransaction] from + /// [IKPaymentTransactionWrapper]. + @JsonValue(3) + restored, + + /// The transaction is in the queue but pending external action. Wait for + /// another callback to get the final state. + /// + /// You should update your UI to indicate that you are waiting for the + /// transaction to update to another state. + @JsonValue(4) + deferred, + + /// Indicates the transaction is in an unspecified state. + @JsonValue(-1) + unspecified, +} + +/// Created when a payment is added to the [IKPaymentQueueWrapper]. +/// +/// Transactions are delivered to your app when a payment is finished +/// processing. Completed transactions provide a receipt and a transaction +/// identifier that the app can use to save a permanent record of the processed +/// payment. +@JsonSerializable(createToJson: true) +@immutable +class IKPaymentTransactionWrapper { + /// Creates a new [IKPaymentTransactionWrapper] with the provided information. + // TODO(stuartmorgan): Temporarily ignore const warning in other parts of the + // federated package, and remove this. + // ignore: prefer_const_constructors_in_immutables + IKPaymentTransactionWrapper({ + required this.payment, + required this.transactionState, + this.originalTransaction, + this.transactionTimeStamp, + this.transactionIdentifier, + this.error, + }); + + /// Constructs an instance of this from a key value map of data. + /// + /// The map needs to have named string keys with values matching the names and + /// types of all of the members on this class. The `map` parameter must not be + /// null. + factory IKPaymentTransactionWrapper.fromJson(Map map) { + return _$IKPaymentTransactionWrapperFromJson(map); + } + + /// Current transaction state. + @IKTransactionStatusConverter() + final IKPaymentTransactionStateWrapper transactionState; + + /// The payment that has been created and added to the payment queue which + /// generated this transaction. + final IKPaymentWrapper payment; + + /// The original Transaction. + /// + /// Only available if the [transactionState] is [SKPaymentTransactionStateWrapper.restored]. + /// Otherwise the value is `null`. + /// + /// When the [transactionState] + /// is [IKPaymentTransactionStateWrapper.restored], the current transaction + /// object holds a new [transactionIdentifier]. + final IKPaymentTransactionWrapper? originalTransaction; + + /// The timestamp of the transaction. + /// + /// Seconds since epoch. It is only defined when the [transactionState] is + /// [IKPaymentTransactionStateWrapper.purchased] or + /// [IKPaymentTransactionStateWrapper.restored]. + /// Otherwise, the value is `null`. + final double? transactionTimeStamp; + + /// The unique string identifer of the transaction. + /// + /// It is only defined when the [transactionState] is + /// [IKPaymentTransactionStateWrapper.purchased] or + /// [IKPaymentTransactionStateWrapper.restored]. You may wish to record this + /// string as part of an audit trail for App Store purchases. The value of + /// this string corresponds to the same property in the receipt. + /// + /// The value is `null` if it is an unsuccessful transaction. + final String? transactionIdentifier; + + /// The error object + /// + /// Only available if the [transactionState] is + /// [IKPaymentTransactionStateWrapper.failed]. + final IKError? error; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is IKPaymentTransactionWrapper && + other.payment == payment && + other.transactionState == transactionState && + other.originalTransaction == originalTransaction && + other.transactionTimeStamp == transactionTimeStamp && + other.transactionIdentifier == transactionIdentifier && + other.error == error; + } + + @override + int get hashCode => Object.hash(payment, transactionState, + originalTransaction, transactionTimeStamp, transactionIdentifier, error); + + @override + String toString() => _$IKPaymentTransactionWrapperToJson(this).toString(); + + /// The payload that is used to finish this transaction. + Map toFinishMap() => { + 'transactionIdentifier': transactionIdentifier, + 'productIdentifier': payment.productId, + }; +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_payment_transaction_wrappers.g.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_payment_transaction_wrappers.g.dart new file mode 100644 index 000000000..9a51a4267 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_payment_transaction_wrappers.g.dart @@ -0,0 +1,36 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'ik_payment_transaction_wrappers.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +IKPaymentTransactionWrapper _$IKPaymentTransactionWrapperFromJson(Map json) => + IKPaymentTransactionWrapper( + payment: IKPaymentWrapper.fromJson( + Map.from(json['payment'] as Map)), + transactionState: const IKTransactionStatusConverter() + .fromJson(json['transactionState'] as int?), + originalTransaction: json['originalTransaction'] == null + ? null + : IKPaymentTransactionWrapper.fromJson( + Map.from(json['originalTransaction'] as Map)), + transactionTimeStamp: (json['transactionTimeStamp'] as num?)?.toDouble(), + transactionIdentifier: json['transactionIdentifier'] as String?, + error: json['error'] == null + ? null + : IKError.fromJson(Map.from(json['error'] as Map)), + ); + +Map _$IKPaymentTransactionWrapperToJson( + IKPaymentTransactionWrapper instance) => + { + 'transactionState': const IKTransactionStatusConverter() + .toJson(instance.transactionState), + 'payment': instance.payment, + 'originalTransaction': instance.originalTransaction, + 'transactionTimeStamp': instance.transactionTimeStamp, + 'transactionIdentifier': instance.transactionIdentifier, + 'error': instance.error, + }; diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_product_wrapper.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_product_wrapper.dart new file mode 100644 index 000000000..057e60ac2 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_product_wrapper.dart @@ -0,0 +1,279 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:collection/collection.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'enum_converters.dart'; + +// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the +// below generated file. Run `flutter packages pub run build_runner watch` to +// rebuild and watch for further changes. +part 'ik_product_wrapper.g.dart'; + +/// Represents the response object returned by [IKRequestMaker.startProductRequest]. +/// Contains information about a list of products and a list of invalid product identifiers. +@JsonSerializable() +@immutable +class IKProductResponseWrapper { + /// Creates an [IKProductResponseWrapper] with the given product details. + // TODO(stuartmorgan): Temporarily ignore const warning in other parts of the + // federated package, and remove this. + // ignore: prefer_const_constructors_in_immutables + IKProductResponseWrapper( + {required this.products, required this.invalidProductIdentifiers}); + + /// Constructing an instance from a map from the Objective-C layer. + /// + /// This method should only be used with `map` values returned by [IKRequestMaker.startProductRequest]. + factory IKProductResponseWrapper.fromJson(Map map) { + var maptt = _$SkProductResponseWrapperFromJson(map); + return _$SkProductResponseWrapperFromJson(map); + } + + /// Stores all matching successfully found products. + /// + /// One product in this list matches one valid product identifier passed to the [IKRequestMaker.startProductRequest]. + /// Will be empty if the [IKRequestMaker.startProductRequest] method does not pass any correct product identifier. + @JsonKey(defaultValue: []) + final List products; + + /// Stores product identifiers in the `productIdentifiers` from [IKRequestMaker.startProductRequest] that are not recognized by the AppGallery. + /// Will be empty if all the product identifiers are valid. + @JsonKey(defaultValue: []) + final List invalidProductIdentifiers; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is IKProductResponseWrapper && + const DeepCollectionEquality().equals(other.products, products) && + const DeepCollectionEquality() + .equals(other.invalidProductIdentifiers, invalidProductIdentifiers); + } + + @override + int get hashCode => Object.hash(products, invalidProductIdentifiers); +} + +/// Used as a property in the [IKProductSubscriptionPeriodWrapper]. Minimum is a day and maximum is a year. +// The values of the enum options are matching the [IKProductPeriodUnit]'s values. Should there be an update or addition +// in the [IKProductPeriodUnit], this need to be updated to match. +enum IKSubscriptionPeriodUnit { + /// An interval lasting one day. + @JsonValue(0) + day, + + /// An interval lasting one month. + @JsonValue(1) + + /// An interval lasting one week. + week, + @JsonValue(2) + + /// An interval lasting one month. + month, + + /// An interval lasting one year. + @JsonValue(3) + year, +} + +/// A period is defined by a [numberOfUnits] and a [unit], e.g for a 3 months period [numberOfUnits] is 3 and [unit] is a month. +/// It is used as a property in [IKProductDiscountWrapper] and [IKProductWrapper]. +@JsonSerializable() +@immutable +class IKProductSubscriptionPeriodWrapper { + /// Creates an [IKProductSubscriptionPeriodWrapper] for a `numberOfUnits`x`unit` period. + // TODO(stuartmorgan): Temporarily ignore const warning in other parts of the + // federated package, and remove this. + // ignore: prefer_const_constructors_in_immutables + IKProductSubscriptionPeriodWrapper( + {required this.numberOfUnits, required this.unit}); + + /// Constructing an instance from a map from the Objective-C layer. + /// + /// This method should only be used with `map` values returned by [IKProductDiscountWrapper.fromJson] or [IKProductWrapper.fromJson]. + factory IKProductSubscriptionPeriodWrapper.fromJson( + Map? map) { + if (map == null) { + return IKProductSubscriptionPeriodWrapper( + numberOfUnits: 0, unit: IKSubscriptionPeriodUnit.day); + } + return _$IKProductSubscriptionPeriodWrapperFromJson(map); + } + + /// The number of [unit] units in this period. + /// + /// Must be greater than 0 if the object is valid. + @JsonKey(defaultValue: 0) + final int numberOfUnits; + + /// The time unit used to specify the length of this period. + @IKSubscriptionPeriodUnitConverter() + final IKSubscriptionPeriodUnit unit; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is IKProductSubscriptionPeriodWrapper && + other.numberOfUnits == numberOfUnits && + other.unit == unit; + } + + @override + int get hashCode => Object.hash(numberOfUnits, unit); +} + +/// This is used as a property in the [IKProductDiscountWrapper]. +// The values of the enum options are matching the [IKProductDiscountPaymentMode]'s values. Should there be an update or addition +// in the [IKProductDiscountPaymentMode], this need to be updated to match. +enum IKProductDiscountPaymentMode { + /// Allows user to pay the discounted price at each payment period. + @JsonValue(0) + payAsYouGo, + + /// Allows user to pay the discounted price upfront and receive the product for the rest of time that was paid for. + @JsonValue(1) + payUpFront, + + /// User pays nothing during the discounted period. + @JsonValue(2) + freeTrail, + + /// Unspecified mode. + @JsonValue(-1) + unspecified, +} + + +/// This is used as a property in the [IKProductDiscountWrapper]. +/// The values of the enum options are matching the [IKProductDiscountType]'s +/// values. +/// +/// Values representing the types of discount offers an app can present. +enum IKProductDiscountType { + /// A constant indicating the discount type is an introductory offer. + @JsonValue(0) + introductory, + + /// A constant indicating the discount type is a promotional offer. + @JsonValue(1) + subscription, +} + +/// Dart wrapper around IAP Kit [Product](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/iap-iap-V5#section346874219313). +/// +/// A list of [IKProductWrapper] is returned in the [IKRequestMaker.startProductRequest] method, and +/// should be stored for use when making a payment. +@JsonSerializable() +@immutable +class IKProductWrapper { + /// Creates an [IKProductWrapper] with the given product details. + // TODO(stuartmorgan): Temporarily ignore const warning in other parts of the + // federated package, and remove this. + // ignore: prefer_const_constructors_in_immutables + IKProductWrapper({ + required this.id, + required this.type, + required this.name, + required this.description, + required this.localPrice, + required this.microPrice, + required this.originalLocalPrice, + required this.originalMicroPrice, + required this.currency, + this.status, + // this.subscriptionInfo, + // this.promotionalOffers, + this.jsonRepresentation, + }); + + /// Constructing an instance from a map from the ets layer. + /// + /// This method should only be used with `map` values returned by [IKProductResponseWrapper.fromJson]. + factory IKProductWrapper.fromJson(Map map) { + return _$IKProductWrapperFromJson(map); + } + + /// 商品ID + final String id; + + /// 商品类型 + final ProductType type; + + /// 商品名称,为配置商品信息时配置的名称。用于显示在应用内支付收银台 + final String name; + + /// 商品描述,即配置商品信息时配置的描述信息 + final String description; + + /// 商品的展示价格,包含商品币种和价格,格式为“币种+商品价格”,例如 EUR 0.15。部分国家/地区会返回“货币符号+商品价格”,例如中国大陆返回“¥0.15”。此价格含税。可选。 + final String localPrice; + + /// 商品实际价格乘以1,000,000后的微单位价格。例如某个商品实际价格是1.99美元,则该商品对应的微单位价格为:1.99*1000000=1990000。 + final int microPrice; + + /// 商品的原价,包含商品币种和价格,格式为“币种+商品价格”,例如 EUR 0.15。部分国家/地区会返回“货币符号+商品价格”,例如中国大陆返回“¥0.15”。此价格含税。 + final String originalLocalPrice; + + /// 商品原价的微单位价格。商品原价乘以1,000,000后的微单位价格。例如某个商品原价是1.99美元,则该商品对应的微单位价格为:1.99*1000000=1990000。 + final int originalMicroPrice; + + /// 用于支付该商品的币种,必须符合ISO 4217标准,例如USD、CNY、MYR。 + final String currency; + + /// 商品状态 + final ProductStatus? status; + + /// 自动续期订阅商品相关的信息。可选。 + // final SubscriptionInfo? subscriptionInfo; + + /// 订阅商品支持的优惠信息列表 + // final PromotionalOffer[]? promotionalOffers; + + /// 商品详细信息的原始JSON字符串 + final String? jsonRepresentation; +} + +/// 商品状态枚举 +enum ProductType { + /// 消耗型商品 + @JsonValue(0) + CONSUMABLE, + + /// 非消耗型商品 + @JsonValue(1) + NONCONSUMABLE, + + /// 自动续期订阅商品 + @JsonValue(3) + AUTORENEWABLE, +} + +/// 商品状态枚举 +enum ProductStatus { + /// 有效状态。 + @JsonValue(0) + VALID, + + /// 取消状态,即删除。此状态的商品不可续订,也不可订阅。 + @JsonValue(1) + CANCELED, + + /// 下线状态,不能订阅,但老用户仍可续订。 + @JsonValue(3) + OFFLINE, +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_product_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_product_wrapper.g.dart new file mode 100644 index 000000000..072612e1c --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_product_wrapper.g.dart @@ -0,0 +1,69 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'ik_product_wrapper.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +IKProductResponseWrapper _$SkProductResponseWrapperFromJson(Map json) => + IKProductResponseWrapper( + products: (json['products'] as List?) + ?.map((e) => IKProductWrapper.fromJson( + Map.from(e as Map))) + .toList() ?? + [], + invalidProductIdentifiers: + (json['invalidProductIdentifiers'] as List?) + ?.map((e) => e as String) + .toList() ?? + [], + ); + +IKProductSubscriptionPeriodWrapper _$IKProductSubscriptionPeriodWrapperFromJson( + Map json) => + IKProductSubscriptionPeriodWrapper( + numberOfUnits: json['numberOfUnits'] as int? ?? 0, + unit: const IKSubscriptionPeriodUnitConverter() + .fromJson(json['unit'] as int?), + ); + +IKProductWrapper _$IKProductWrapperFromJson(Map json) => IKProductWrapper( + id: json['id'] as String? ?? '', + type: _$IKProductTypeFromInt(json['type'] as int? ?? 0), + name: json['name'] as String? ?? '', + description: json['description'] as String? ?? '', + localPrice: json['localPrice'] as String? ?? '', + microPrice: json['microPrice'] as int? ?? 0, + originalLocalPrice: json['originalLocalPrice'] as String? ?? '', + originalMicroPrice: json['originalMicroPrice'] as int? ?? 0, + currency: json['currency'] as String? ?? '', + status: _$IKProductStatusFromInt(json['status'] as int? ?? 0), + jsonRepresentation: json['jsonRepresentation'] as String?, + ); + +ProductType _$IKProductTypeFromInt(int type) { + switch (type) { + case 0: + return ProductType.CONSUMABLE; + case 1: + return ProductType.NONCONSUMABLE; + case 3: + return ProductType.AUTORENEWABLE; + default: + return ProductType.CONSUMABLE; + } +} + +ProductStatus _$IKProductStatusFromInt(int type) { + switch (type) { + case 0: + return ProductStatus.VALID; + case 1: + return ProductStatus.CANCELED; + case 3: + return ProductStatus.OFFLINE; + default: + return ProductStatus.VALID; + } +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_receipt_manager.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_receipt_manager.dart new file mode 100644 index 000000000..68feb9e10 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_receipt_manager.dart @@ -0,0 +1,18 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import '../channel.dart'; + +// ignore: avoid_classes_with_only_static_members +/// This class contains static methods to manage StoreKit receipts. +class IKReceiptManager { + /// Retrieve the receipt data from your application's main bundle. + static Future retrieveReceiptData() async { + return (await channel.invokeMethod( + 'iap#retrieveReceiptData')) ?? + ''; + } +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_request_maker.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_request_maker.dart new file mode 100644 index 000000000..0eebfe836 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/iap_kit_wrappers/ik_request_maker.dart @@ -0,0 +1,46 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/services.dart'; + +import '../channel.dart'; +import 'ik_product_wrapper.dart'; + +/// A request maker that handles all the requests made by IKRequest subclasses. +class IKRequestMaker { + /// Fetches product information for a list of given product identifiers. + /// + /// The `productIdentifiers` should contain legitimate product identifiers that you declared for the products in the iTunes Connect. Invalid identifiers + /// will be stored and returned in [SkProductResponseWrapper.invalidProductIdentifiers]. Duplicate values in `productIdentifiers` will be omitted. + /// If `productIdentifiers` is null, an `storekit_invalid_argument` error will be returned. If `productIdentifiers` is empty, a [SkProductResponseWrapper] + /// will still be returned with [SkProductResponseWrapper.products] being null. + /// + /// [SkProductResponseWrapper] is returned if there is no error during the request. + /// A [PlatformException] is thrown if the platform code making the request fails. + Future startProductRequest( + List productIdentifiers) async { + final Map? productResponseMap = + await channel.invokeMapMethod( + 'iap#queryProducts', + productIdentifiers, + ); + if (productResponseMap == null) { + throw PlatformException( + code: 'storekit_no_response', + message: 'StoreKit: Failed to get response from platform.', + ); + } + return IKProductResponseWrapper.fromJson(productResponseMap); + } + + Future startRefreshReceiptRequest( + {Map? receiptProperties}) { + return channel.invokeMethod( + 'iap#refreshReceipt', + receiptProperties, + ); + } +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/in_app_purchase_ohos_platform.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/in_app_purchase_ohos_platform.dart new file mode 100644 index 000000000..2fa17d603 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/in_app_purchase_ohos_platform.dart @@ -0,0 +1,219 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; + +import '../iap_kit_wrappers.dart'; +import '../in_app_purchase_ohos.dart'; + +/// [IAPError.code] code for failed purchases. +const String kPurchaseErrorCode = 'purchase_error'; + +/// Indicates store front is AppGallery. +const String kIAPSource = 'app_gallery'; + +/// An [InAppPurchasePlatform] that wraps IAP Kit. +/// +/// This translates various `IAP Kit` calls and responses into the +/// generic plugin API. +class InAppPurchaseOhosPlatform extends InAppPurchasePlatform { + /// Creates an [InAppPurchaseOhosPlatform] object. + /// + /// This constructor should only be used for testing, for any other purpose + /// get the connection from the [instance] getter. + @visibleForTesting + InAppPurchaseOhosPlatform(); + + static late IKPaymentQueueWrapper _skPaymentQueueWrapper; + static late _TransactionObserver _observer; + + @override + Stream> get purchaseStream => + _observer.purchaseUpdatedController.stream; + + /// Callback handler for transaction status changes. + @visibleForTesting + static IKTransactionObserverWrapper get observer => _observer; + + /// Registers this class as the default instance of [InAppPurchasePlatform]. + static void registerPlatform() { + // Register the [InAppPurchaseOhosPlatformAddition] containing + // IapKit-specific functionality. + InAppPurchasePlatformAddition.instance = + InAppPurchaseOhosPlatformAddition(); + + // Register the platform-specific implementation of the idiomatic + // InAppPurchase API. + InAppPurchasePlatform.instance = InAppPurchaseOhosPlatform(); + + _skPaymentQueueWrapper = IKPaymentQueueWrapper(); + + // Create a purchaseUpdatedController and notify the native side when to + // start of stop sending updates. + final StreamController> updateController = + StreamController>.broadcast( + onListen: () => _skPaymentQueueWrapper.startObservingTransactionQueue(), + onCancel: () => _skPaymentQueueWrapper.stopObservingTransactionQueue(), + ); + _observer = _TransactionObserver(updateController); + _skPaymentQueueWrapper.setTransactionObserver(observer); + } + + @override + Future isAvailable() => IKPaymentQueueWrapper.queryEnvironmentStatus(); + + @override + Future buyNonConsumable({required PurchaseParam purchaseParam}) async { + await _skPaymentQueueWrapper.addPayment( + IKPaymentWrapper(productId: purchaseParam.productDetails.id)); + return true; // There's no error feedback from ohos here to return. + } + + @override + Future buyConsumable( + {required PurchaseParam purchaseParam, bool autoConsume = true}) { + assert(autoConsume == true, 'On ohos, we should always auto consume'); + return buyNonConsumable(purchaseParam: purchaseParam); + } + + @override + Future completePurchase(PurchaseDetails purchase) { + assert( + purchase is AppGalleryPurchaseDetails, + 'On ohos, the `purchase` should always be of type `AppGalleryPurchaseDetails`.', + ); + + return _skPaymentQueueWrapper.finishTransaction( + (purchase as AppGalleryPurchaseDetails).ikPaymentTransaction, + ); + } + + @override + Future restorePurchases({String? applicationUserName}) async { + return _observer + .restoreTransactions( + queue: _skPaymentQueueWrapper, + applicationUserName: applicationUserName) + .whenComplete(() => _observer.cleanUpRestoredTransactions()); + } + + /// Query the product detail list. + /// + /// This method only returns [ProductDetailsResponse]. + /// To get detailed Store Kit product list, use [SkProductResponseWrapper.startProductRequest] + /// to get the [IKProductResponseWrapper]. + @override + Future queryProductDetails( + Set identifiers) async { + final IKRequestMaker requestMaker = IKRequestMaker(); + IKProductResponseWrapper response; + PlatformException? exception; + try { + response = await requestMaker.startProductRequest(identifiers.toList()); + print('InAppPurchasePlugin productDetailResponse response'); + } on PlatformException catch (e) { + exception = e; + response = IKProductResponseWrapper( + products: const [], + invalidProductIdentifiers: identifiers.toList()); + } + List productDetails = + []; + productDetails = response.products + .map((IKProductWrapper productWrapper) => + AppGalleryProductDetails.fromIKProduct(productWrapper)) + .toList(); + List invalidIdentifiers = response.invalidProductIdentifiers; + if (productDetails.isEmpty) { + invalidIdentifiers = identifiers.toList(); + } + final ProductDetailsResponse productDetailsResponse = + ProductDetailsResponse( + productDetails: productDetails, + notFoundIDs: invalidIdentifiers, + error: exception == null + ? null + : IAPError( + source: kIAPSource, + code: exception.code, + message: exception.message ?? '', + details: exception.details), + ); + return productDetailsResponse; + } +} + +enum _TransactionRestoreState { + notRunning, + waitingForTransactions, + receivedTransaction, +} + +class _TransactionObserver implements IKTransactionObserverWrapper { + _TransactionObserver(this.purchaseUpdatedController); + + final StreamController> purchaseUpdatedController; + + Completer? _restoreCompleter; + late String _receiptData; + _TransactionRestoreState _transactionRestoreState = + _TransactionRestoreState.notRunning; + + Future restoreTransactions({ + required IKPaymentQueueWrapper queue, + String? applicationUserName, + }) { + _transactionRestoreState = _TransactionRestoreState.waitingForTransactions; + _restoreCompleter = Completer(); + queue.restoreTransactions(applicationUserName: applicationUserName); + return _restoreCompleter!.future; + } + + void cleanUpRestoredTransactions() { + _restoreCompleter = null; + } + + @override + void updatedTransactions( + {required List transactions}) { + _handleTransationUpdates(transactions); + } + + @override + void removedTransactions( + {required List transactions}) {} + + Future getReceiptData() async { + try { + _receiptData = await IKReceiptManager.retrieveReceiptData(); + } catch (e) { + _receiptData = ''; + } + return _receiptData; + } + + Future _handleTransationUpdates( + List transactions) async { + print('MethodCallHandlerImpl _handleTransationUpdates '); + if (_transactionRestoreState == + _TransactionRestoreState.waitingForTransactions && + transactions.any((IKPaymentTransactionWrapper transaction) => + transaction.transactionState == + IKPaymentTransactionStateWrapper.restored)) { + _transactionRestoreState = _TransactionRestoreState.receivedTransaction; + } + + final String receiptData = await getReceiptData(); + final List purchases = transactions + .map((IKPaymentTransactionWrapper transaction) => + AppGalleryPurchaseDetails.fromIKTransaction( + transaction, receiptData)) + .toList(); + purchaseUpdatedController.add(purchases); + } +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/in_app_purchase_ohos_platform_addition.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/in_app_purchase_ohos_platform_addition.dart new file mode 100644 index 000000000..88a032535 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/in_app_purchase_ohos_platform_addition.dart @@ -0,0 +1,33 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; + +import '../iap_kit_wrappers.dart'; +import '../in_app_purchase_ohos.dart'; + +/// Contains InApp Purchase features that are only available on ohos. +class InAppPurchaseOhosPlatformAddition + extends InAppPurchasePlatformAddition { + + /// Retry loading purchase data after an initial failure. + /// + /// If no results, a `null` value is returned. + Future refreshPurchaseVerificationData() async { + await IKRequestMaker().startRefreshReceiptRequest(); + try { + final String receipt = await IKReceiptManager.retrieveReceiptData(); + return PurchaseVerificationData( + localVerificationData: receipt, + serverVerificationData: receipt, + source: kIAPSource); + } catch (e) { + // ignore: avoid_print + print( + 'Something is wrong while fetching the receipt, this normally happens when the app is ' + 'running on a simulator: $e'); + return null; + } + } +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/types/app_gallery_product_details.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/types/app_gallery_product_details.dart new file mode 100644 index 000000000..907dd3288 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/types/app_gallery_product_details.dart @@ -0,0 +1,43 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; + +import '../../iap_kit_wrappers.dart'; + +/// The class represents the information of a product as registered in the AppGallery Connect +/// AppGallery. +class AppGalleryProductDetails extends ProductDetails { + /// Creates a new AppStore specific product details object with the provided + /// details. + AppGalleryProductDetails({ + required super.id, + required super.title, + required super.description, + required super.price, + required super.rawPrice, + required super.currencyCode, + required this.skProduct, + required super.currencySymbol, + }); + + factory AppGalleryProductDetails.fromIKProduct(IKProductWrapper product) { + return AppGalleryProductDetails( + id: product.id, + title: product.name, + description: product.description, + price: product.localPrice, + rawPrice: product.microPrice / 1000000.0, + currencyCode: product.currency, + currencySymbol: product.localPrice.isNotEmpty + ? product.localPrice.replaceAll(RegExp(r'[0-9.]+'), "") + : product.currency, + skProduct: product, + ); + } + + /// Points back to the [IKProductWrapper] object that was used to generate + /// this [AppGalleryProductDetails] object. + final IKProductWrapper skProduct; +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/types/app_gallery_purchase_details.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/types/app_gallery_purchase_details.dart new file mode 100644 index 000000000..b77ba6d56 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/types/app_gallery_purchase_details.dart @@ -0,0 +1,79 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; + +import '../../iap_kit_wrappers.dart'; +import '../../in_app_purchase_ohos.dart'; +import '../iap_kit_wrappers/enum_converters.dart'; + +/// The class represents the information of a purchase made with the IAP Kit +/// AppGallery. +class AppGalleryPurchaseDetails extends PurchaseDetails { + /// Creates a new AppStore specific purchase details object with the provided + /// details. + AppGalleryPurchaseDetails({ + super.purchaseID, + required super.productID, + required super.verificationData, + required super.transactionDate, + required this.ikPaymentTransaction, + required PurchaseStatus status, + }) : super(status: status) { + this.status = status; + } + + factory AppGalleryPurchaseDetails.fromIKTransaction( + IKPaymentTransactionWrapper transaction, + String base64EncodedReceipt, + ) { + print('MethodCallHandlerImpl AppGalleryPurchaseDetails.fromIKTransaction '); + final AppGalleryPurchaseDetails purchaseDetails = AppGalleryPurchaseDetails( + productID: transaction.payment.productId, + purchaseID: transaction.transactionIdentifier, + ikPaymentTransaction: transaction, + status: const IKTransactionStatusConverter() + .toPurchaseStatus(transaction.transactionState, transaction.error), + transactionDate: transaction.transactionTimeStamp != null + ? (transaction.transactionTimeStamp! * 1000).toInt().toString() + : null, + verificationData: PurchaseVerificationData( + localVerificationData: base64EncodedReceipt, + serverVerificationData: base64EncodedReceipt, + source: kIAPSource), + ); + var statuus = purchaseDetails.status; + if (purchaseDetails.status == PurchaseStatus.error || + purchaseDetails.status == PurchaseStatus.canceled) { + purchaseDetails.error = IAPError( + source: kIAPSource, + code: kPurchaseErrorCode, + message: transaction.error?.domain ?? '', + details: transaction.error?.userInfo, + ); + } + return purchaseDetails; + } + + /// Points back to the [IKPaymentTransactionWrapper] which was used to + /// generate this [AppStorePurchaseDetails] object. + final IKPaymentTransactionWrapper ikPaymentTransaction; + + late PurchaseStatus _status; + + /// The status that this [PurchaseDetails] is currently on. + @override + PurchaseStatus get status => _status; + + @override + set status(PurchaseStatus status) { + _pendingCompletePurchase = status == PurchaseStatus.purchased; + _status = status; + } + + bool _pendingCompletePurchase = false; + + @override + bool get pendingCompletePurchase => _pendingCompletePurchase; +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/types/app_gallery_purchase_param.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/types/app_gallery_purchase_param.dart new file mode 100644 index 000000000..556acab09 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/types/app_gallery_purchase_param.dart @@ -0,0 +1,18 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; + +/// Apple AppStore specific parameter object for generating a purchase. +class AppGalleryPurchaseParam extends PurchaseParam { + /// Creates a new [AppGalleryPurchaseParam] object with the given data. + AppGalleryPurchaseParam({ + required super.productDetails, + super.applicationUserName, + this.quantity = 1, + }); + + /// Quantity of the product user requested to buy. + final int quantity; +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/lib/src/types/types.dart b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/types/types.dart new file mode 100644 index 000000000..1704a2e9c --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/lib/src/types/types.dart @@ -0,0 +1,7 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +export 'app_gallery_product_details.dart'; +export 'app_gallery_purchase_details.dart'; +export 'app_gallery_purchase_param.dart'; diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/.gitignore b/packages/in_app_purchase/in_app_purchase_ohos/ohos/.gitignore new file mode 100644 index 000000000..e2713a277 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/BuildProfile.ets b/packages/in_app_purchase/in_app_purchase_ohos/ohos/BuildProfile.ets new file mode 100644 index 000000000..fde84590b --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/BuildProfile.ets @@ -0,0 +1,32 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.0'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/Index.ets b/packages/in_app_purchase/in_app_purchase_ohos/ohos/Index.ets new file mode 100644 index 000000000..8e39f7c8d --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/Index.ets @@ -0,0 +1,17 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { InAppPurchasePlugin } from './src/main/ets/components/InAppPurchasePlugin' +export default InAppPurchasePlugin \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/build-profile.json5 b/packages/in_app_purchase/in_app_purchase_ohos/ohos/build-profile.json5 new file mode 100644 index 000000000..e6773f9f5 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/build-profile.json5 @@ -0,0 +1,31 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest" + } + ] +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/consumer-rules.txt b/packages/in_app_purchase/in_app_purchase_ohos/ohos/consumer-rules.txt new file mode 100644 index 000000000..e69de29bb diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/hvigorfile.ts b/packages/in_app_purchase/in_app_purchase_ohos/ohos/hvigorfile.ts new file mode 100644 index 000000000..12a327db0 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/hvigorfile.ts @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/obfuscation-rules.txt b/packages/in_app_purchase/in_app_purchase_ohos/ohos/obfuscation-rules.txt new file mode 100644 index 000000000..272efb6ca --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/oh-package.json5 b/packages/in_app_purchase/in_app_purchase_ohos/ohos/oh-package.json5 new file mode 100644 index 000000000..4c93b2c90 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/oh-package.json5 @@ -0,0 +1,11 @@ +{ + "name": "in_app_purchase", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@ohos/flutter_ohos": "file:../libs/flutter.har" + } +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/common/JWTUtil.ts b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/common/JWTUtil.ts new file mode 100644 index 000000000..6f87f4dbc --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/common/JWTUtil.ts @@ -0,0 +1,60 @@ +/* +* Copyright (c) 2024 Huawei Technologies Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import { util } from '@kit.ArkTS'; + +const centerLineRegex: RegExp = new RegExp('-', 'g'); +const underLineRegex: RegExp = new RegExp('_', 'g'); +const textDecoder = util.TextDecoder.create("utf-8", { ignoreBOM: true }); +const base64 = new util.Base64Helper(); +const TAG: string = 'JWTUtil'; + +const BASE64_PAD_MOD = 4; +const BASE64_PAD_INVALID = 1; + +export class JWTUtil { + public static base64Decode(input: string) { + return textDecoder.decodeWithStream(base64.decodeSync(input)); + } + + private static base64UrlDecode(input: string) { + input = input + .replace(centerLineRegex, '+') + .replace(underLineRegex, '/'); + + // Pad out with standard base64 required padding characters + const pad = input.length % BASE64_PAD_MOD; + if (pad) { + if (pad === BASE64_PAD_INVALID) { + throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding'); + } + input += new Array(5 - pad).join('='); + } + return this.base64Decode(input); + } + + public static decodeJwtObj(data: string) { + let jwt: string[] = data.split("."); + let exp: string = ""; + if (jwt.length < 3) { + return exp; + } + try { + exp = JWTUtil.base64UrlDecode(jwt[1]); + } catch (err) { + console.error(TAG, 'decodeJwtObj parse err: ' + JSON.stringify(err)); + } + return exp; + } +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/common/TsUtil.ts b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/common/TsUtil.ts new file mode 100644 index 000000000..17ddb00ee --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/common/TsUtil.ts @@ -0,0 +1,24 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +export function ObjToMap(obj: Object): Map { + const map = new Map(); + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + map.set(key, obj[key]); + } + } + return map +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/InAppPurchasePlugin.ets b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/InAppPurchasePlugin.ets new file mode 100644 index 000000000..acc2a6683 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/InAppPurchasePlugin.ets @@ -0,0 +1,69 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { + AbilityAware, + AbilityPluginBinding, + FlutterPlugin, + FlutterPluginBinding, + BinaryMessenger, + Log, + MethodChannel, +} from '@ohos/flutter_ohos'; +import { common } from '@kit.AbilityKit'; +import { MethodCallHandlerImpl } from './MethodCallHandlerImpl'; + +const TAG = "InAppPurchasePlugin" + +export class InAppPurchasePlugin implements FlutterPlugin, AbilityAware { + private methodChannel: MethodChannel | null = null + private methodCallHandler: MethodCallHandlerImpl | null = null + private context: common.UIAbilityContext | null = null + + onAttachedToEngine(binding: FlutterPluginBinding): void { + this.setUpMethodChannel(binding.getBinaryMessenger(), binding.getApplicationContext()); + } + + onDetachedFromEngine(binding: FlutterPluginBinding): void { + this.teardownMethodChannel(); + } + + onAttachedToAbility(binding: AbilityPluginBinding): void { + this.context = binding.getAbility().context + this.methodCallHandler?.setContext(this.context) + } + + onDetachedFromAbility(): void { + this.methodCallHandler?.setContext(null) + } + + getUniqueClassName(): string { + return TAG + } + + setUpMethodChannel(messenger: BinaryMessenger, context: common.Context) { + this.methodChannel = new MethodChannel(messenger, "plugins.flutter.io/in_app_purchase"); + this.methodCallHandler = + new MethodCallHandlerImpl(context, this.methodChannel); + this.methodChannel.setMethodCallHandler(this.methodCallHandler); + } + + teardownMethodChannel() { + this.methodChannel?.setMethodCallHandler(null); + this.methodChannel = null; + this.methodCallHandler = null; + this.context = null; + } +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/MethodCallHandlerImpl.ets b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/MethodCallHandlerImpl.ets new file mode 100644 index 000000000..28b3aeff9 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/MethodCallHandlerImpl.ets @@ -0,0 +1,288 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { MethodCall, MethodCallHandler, MethodChannel, MethodResult, Log, Any } from '@ohos/flutter_ohos'; +import { MethodNames } from './MethodNames'; +import { iap } from '@kit.IAPKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { common } from '@kit.AbilityKit'; +import { ProductType } from './ProductType'; +import { JWTUtil } from '../common/JWTUtil'; +import { PurchaseOrderPayload, PurchaseSubGroupStatusPayload } from './PurchaseOrderPayload'; +import { HashMap } from '@kit.ArkTS'; +import { TransactionState } from './TransactionState'; +import { PurchaseData } from './PurchaseData'; +import { ObjToMap } from '../common/TsUtil'; + +const TAG = "MethodCallHandlerImpl" + +export class IKError { + code: number = 0 + domain: string = '' + userInfo: Map = new Map() +} + +export class MethodCallHandlerImpl implements MethodCallHandler { + private context: common.UIAbilityContext | null = null + private methodChannel: MethodChannel + private observer: MethodChannel | null = null + private productDetailsMap: HashMap = new HashMap() + + constructor(context: common.Context, methodChannel: MethodChannel) { + this.methodChannel = methodChannel + } + + setContext(context: common.UIAbilityContext | null) { + this.context = context + } + + onMethodCall(call: MethodCall, result: MethodResult): void { + switch (call.method) { + case MethodNames.START_OBSERVING_TRANSACTION_QUEUE: + this.startObserver() + break; + case MethodNames.STOP_OBSERVING_TRANSACTION_QUEUE: + this.stopObserver() + break; + case MethodNames.QUERY_ENVIRONMENT_STATUS: + this.isFeatureSupported(result) + break; + case MethodNames.QUERY_PRODUCTS: + let productList: Array = call.args as Array ?? [] + this.queryProductDetails(productList, result) + break; + case MethodNames.CREATE_PURCHASE: + this.purchaseGoods(call.args, result) + break; + case MethodNames.RETRIEVE_RECEIPT_DATA: + result.success(null) + break; + case MethodNames.FINISH_PURCHASE: + this.iapFinishPurchase(call, result) + break; + default: + result.notImplemented(); + } + } + + isFeatureSupported(result: MethodResult): void { + try { + iap.queryEnvironmentStatus(this.context).then(() => { + result.success(true); + }).catch((err: BusinessError) => { + Log.e(TAG, `Failed to query environment status. Code is ${err.code}, message is ${err.message}`); + result.success(false); + }) + } catch (e) { + Log.e(TAG, `Failed to query environment status. err: ${JSON.stringify(e)}`); + result.success(false); + } + } + + queryProductDetails(list: Array, result: MethodResult) { + let productList: Array> = [] + let count: number = 0 + let validProductsList: Array = [] + let invalidProductIdentifiers: Array = [] + ProductType.forEach((type) => { + const queryProductParam: iap.QueryProductsParameter = { + productType: type, + productIds: list + }; + iap.queryProducts(this.context, queryProductParam).then((result) => { + let resultTemp = result.map((value) => { + validProductsList.push(value.id) + this.productDetailsMap.set(value.id, value) + return ObjToMap(value) + }) + productList.push(...resultTemp) + }).catch((err: BusinessError) => { + Log.e(TAG, `Failed to query products. Code is ${err.code}, message is ${err.message}`); + }).finally(() => { + count++ + if (count === ProductType.length) { + let productDetailsResponse: HashMap = new HashMap() + productDetailsResponse.set('products', productList) + list.forEach((id) => { + if (!validProductsList.includes(id)) { + invalidProductIdentifiers.push(id) + } + }) + productDetailsResponse.set('invalidProductIdentifiers', invalidProductIdentifiers) + result.success(productDetailsResponse) + } + }) + }) + } + + purchaseGoods(args: Any, result: MethodResult) { + let map: Map = args as Map + let id: string = map.get('productId') as string + // 拉起支付接口前更新状态为支付中 + let mapRes: Map = new Map() + mapRes.set('payment', args) + mapRes.set('transactionState', TransactionState.purchasing) + this.observer?.invokeMethod('updatedTransactions', [mapRes]) + this.createPurchase(id, map, result) + } + + createPurchase(id: string, args: Map, result: MethodResult) { + try { + const type = this.productDetailsMap.get(id).type + let createPurchaseParam: iap.PurchaseParameter = { + productId: id, + productType: type, + } + iap.createPurchase(this.context, createPurchaseParam).then(async (createPurchaseResult) => { + let type: number = JSON.parse(createPurchaseResult.purchaseData).type + if (type == iap.ProductType.AUTORENEWABLE) { + let jwsSubscriptionStatus: string = JSON.parse(createPurchaseResult.purchaseData).jwsSubscriptionStatus; + let purchaseStr = JWTUtil.decodeJwtObj(jwsSubscriptionStatus); + let purchaseOrderPayload = JSON.parse(purchaseStr) as PurchaseSubGroupStatusPayload; + let map: Map = new Map(); + map.set('payment', args) + map.set('transactionIdentifier', + purchaseOrderPayload.lastSubscriptionStatus?.lastPurchaseOrder.purchaseOrderId) + map.set('transactionState', TransactionState.purchased) + this.observer?.invokeMethod('updatedTransactions', [map]) + } else if (type == iap.ProductType.CONSUMABLE || type == iap.ProductType.NONCONSUMABLE) { + let jwsPurchaseOrder: string = JSON.parse(createPurchaseResult.purchaseData).jwsPurchaseOrder; + let purchaseStr = JWTUtil.decodeJwtObj(jwsPurchaseOrder); + let purchaseOrderPayload = JSON.parse(purchaseStr) as PurchaseOrderPayload; + let map: Map = new Map(); + map.set('payment', args) + map.set('transactionIdentifier', purchaseOrderPayload.purchaseOrderId) + map.set('transactionState', TransactionState.purchased) + this.observer?.invokeMethod('updatedTransactions', [map]) + } + result.success(null) + }).catch((err: BusinessError) => { + Log.e(TAG, `Failed to create purchase1. Code is ${err.code}, message is ${err.message}`); + this.errorPurchase(args, err); + }) + } catch (err) { + const e: BusinessError = err as BusinessError; + const msg: string = `Failed to create purchase. Code is ${e.code}, message is ${e.message}`; + Log.e(TAG, msg); + this.errorPurchase(args, err); + } + } + + errorPurchase(args: Map, err: BusinessError) { + let map: Map = new Map(); + let iKError: IKError = new IKError() + if (err) { + iKError.code = err.code + iKError.domain = err.message + } + map.set('error', iKError) + map.set('payment', args) + map.set('transactionState', TransactionState.failed) + this.observer?.invokeMethod('updatedTransactions', [map]) + } + + finishPurchase(productType: iap.ProductType, purchaseToken: string, purchaseOrderId: string, result?: MethodResult) { + let finishPurchaseParam: iap.FinishPurchaseParameter = { + productType: productType, + purchaseToken: purchaseToken, + purchaseOrderId: purchaseOrderId + }; + iap.finishPurchase(this.context, finishPurchaseParam).then(() => { + Log.i(TAG, 'Succeeded in finishing purchase.'); + result?.success(true); + }).catch((err: BusinessError) => { + Log.e(TAG, `Failed to finish purchase. Code is ${err.code}, message is ${err.message}`); + result?.error('' + err.code, err.message, err); + }); + } + + queryPurchasesAsync(productType: iap.ProductType, result: MethodResult) { + let param: iap.QueryPurchasesParameter = { + productType: productType, + queryType: iap.PurchaseQueryType.UNFINISHED + }; + iap.queryPurchases(this.context, param).then((res: iap.QueryPurchaseResult) => { + let purchaseDataList: string[] = res.purchaseDataList; + if (purchaseDataList === undefined || purchaseDataList.length <= 0) { + /* 购买成功后, 查询不到购买订单 返回失败. */ + Log.i(TAG, 'queryPurchases, purchaseDataList empty'); + result.error('UNAVAILABLE', 'purchaseDataList empty!', null); + return; + } + this.purchaseCompletionByTypeProcessing(purchaseDataList, result) + }).catch((err: BusinessError) => { + Log.e(TAG, `Failed to query purchases. Code is ${err.code}, message is ${err.message}`); + result.error('' + err.code, err.message, err); + }); + } + + purchaseCompletionByTypeProcessing(purchaseDataList: string[], result: MethodResult) { + for (let i = 0; i < purchaseDataList.length; i++) { + let purchaseData = purchaseDataList[i]; + let type: number = JSON.parse(purchaseData).type + let purchaseToken = '' + let purchaseOrderId = '' + //自动订阅类型 + if (type == iap.ProductType.AUTORENEWABLE) { + let jwsSubscriptionStatus = (JSON.parse(purchaseData) as PurchaseData).jwsSubscriptionStatus; + if (!jwsSubscriptionStatus) { + Log.e(TAG, 'queryPurchases, jwsSubscriptionStatus invalid'); + continue; + } + let purchaseStr = JWTUtil.decodeJwtObj(jwsSubscriptionStatus); + let purchaseOrderPayload = JSON.parse(purchaseStr) as PurchaseSubGroupStatusPayload; + if (purchaseOrderPayload.lastSubscriptionStatus) { + purchaseToken = purchaseOrderPayload.lastSubscriptionStatus.purchaseToken + purchaseOrderId = purchaseOrderPayload.lastSubscriptionStatus.lastPurchaseOrder.purchaseOrderId + } + } else { + //非自动订阅类型 + let jwsPurchaseOrder = (JSON.parse(purchaseData) as PurchaseData).jwsPurchaseOrder; + if (!jwsPurchaseOrder) { + Log.e(TAG, 'queryPurchases, jwsPurchaseOrder invalid'); + continue; + } + let purchaseStr = JWTUtil.decodeJwtObj(jwsPurchaseOrder); + let purchaseOrderPayload = JSON.parse(purchaseStr) as PurchaseOrderPayload; + purchaseToken = purchaseOrderPayload.purchaseToken + purchaseOrderId = purchaseOrderPayload.purchaseOrderId + } + this.finishPurchase(type, purchaseToken, purchaseOrderId, result); + } + } + + iapFinishPurchase(call: MethodCall, result: MethodResult) { + let finishPurchaseMap: Map = call.args as Map + let productIdentifier: string = finishPurchaseMap.get('productIdentifier') as string + let product: iap.Product = this.productDetailsMap.get(productIdentifier) + // let productType: string = finishPurchaseMap.get('productType') as string + let productType: iap.ProductType = product.type + if (!product) { + Log.i(TAG, `Failed to get productType.`); + result.error('UNAVAILABLE', 'productType is null!', null); + return; + } else { + this.queryPurchasesAsync(productType, result); + } + } + + startObserver() { + this.observer = this.methodChannel + } + + stopObserver() { + this.observer = null + } +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/MethodNames.ets b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/MethodNames.ets new file mode 100644 index 000000000..1c6e415b1 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/MethodNames.ets @@ -0,0 +1,26 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +export class MethodNames { + static QUERY_ENVIRONMENT_STATUS: string = "iap#queryEnvironmentStatus"; + static QUERY_PRODUCTS: string = "iap#queryProducts"; + static CREATE_PURCHASE: string = "iap#createPurchase"; + static START_OBSERVING_TRANSACTION_QUEUE: string = "iap#startObservingTransactionQueue"; + static STOP_OBSERVING_TRANSACTION_QUEUE: string = "iap#stopObservingTransactionQueue"; + static RETRIEVE_RECEIPT_DATA: string = "iap#retrieveReceiptData"; + static TRANSACTIONS: string = "iap#transactions"; + static FINISH_PURCHASE: string = "iap#finishPurchase"; + static RESTORE_TRANSACTIONS: string = "iap#restoreTransactions"; +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/ProductType.ets b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/ProductType.ets new file mode 100644 index 000000000..7ca4c1205 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/ProductType.ets @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { iap } from '@kit.IAPKit' +export const ProductType: Array = [ + iap.ProductType.CONSUMABLE, + iap.ProductType.NONCONSUMABLE, + iap.ProductType.AUTORENEWABLE +] \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/PurchaseData.ets b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/PurchaseData.ets new file mode 100644 index 000000000..8440c438e --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/PurchaseData.ets @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +export interface PurchaseData { + type: number; + jwsPurchaseOrder?: string; + jwsSubscriptionStatus?: string; +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/PurchaseOrderPayload.ets b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/PurchaseOrderPayload.ets new file mode 100644 index 000000000..05fa8036f --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/PurchaseOrderPayload.ets @@ -0,0 +1,50 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { Any } from '@ohos/flutter_ohos'; + +export class PurchaseOrderPayload { + applicationId: string = ''; + countryCode: string = ''; + environment: string = ''; + payOrderId: string = ''; + price: number = 0; + productId: string = ''; + productType: number = 0; + purchaseOrderId: string = ''; + purchaseTime: number = 0; + purchaseToken: string = ''; + signedTime: number = 0; +} + +export class PurchaseSubGroupStatusPayload { + environment: string = ''; + applicationId: string = ''; + packageName: string = ''; + subGroupId: string = ''; + lastSubscriptionStatus?: PurchaseSubscriptionStatus; + historySubscriptionStatusList?: Any[] = []; +} + +export class PurchaseSubscriptionStatus { + subGroupGenerationId: string = ''; + subscriptionId: string = ''; + purchaseToken: string = ''; + status: string = ''; + expiresTime: number = 0; + lastPurchaseOrder?: Any = {}; + recentPurchaseOrderList?: Any[] = []; + renewalInfo?: Any = {}; +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/TransactionState.ts b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/TransactionState.ts new file mode 100644 index 000000000..a9a82a173 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/ets/components/TransactionState.ts @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +export enum TransactionState { + purchasing = 0, + purchased = 1, + failed = 2, + restored = 3, + deferred = 4, + unspecified = -1, +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/module.json5 b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/module.json5 new file mode 100644 index 000000000..098fbca2f --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "in_app_purchase_ohos", + "type": "har", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ] + } +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/resources/base/element/string.json b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/resources/base/element/string.json new file mode 100644 index 000000000..f51a9c846 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/resources/en_US/element/string.json b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/resources/en_US/element/string.json new file mode 100644 index 000000000..f51a9c846 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/resources/en_US/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/resources/zh_CN/element/string.json b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/resources/zh_CN/element/string.json new file mode 100644 index 000000000..f51a9c846 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/ohosTest/ets/test/Ability.test.ets b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 000000000..40eab016e --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,50 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/ohosTest/ets/test/List.test.ets b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 000000000..a88ef7e80 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/ohosTest/module.json5 b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/ohosTest/module.json5 new file mode 100644 index 000000000..7b2341f33 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/ohosTest/module.json5 @@ -0,0 +1,13 @@ +{ + "module": { + "name": "in_app_purchase_test", + "type": "feature", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/test/List.test.ets b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/test/List.test.ets new file mode 100644 index 000000000..e3f190437 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/test/List.test.ets @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/test/LocalUnit.test.ets b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/test/LocalUnit.test.ets new file mode 100644 index 000000000..13b2128b8 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/ohos/src/test/LocalUnit.test.ets @@ -0,0 +1,48 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development Co., LTD. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_ohos/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_ohos/pubspec.yaml new file mode 100644 index 000000000..f92d00d11 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_ohos/pubspec.yaml @@ -0,0 +1,35 @@ +name: in_app_purchase_ohos +description: An implementation for the Ohos platform of the Flutter `in_app_purchase` plugin. This uses the ohoa IAPKit APIs. +repository: https://gitee.com/openharmony-sig/flutter_packages/tree/master/packages/in_app_purchase/in_app_purchase_ohos +issue_tracker: https://gitee.com/openharmony-sig/flutter_packages/issues +version: 1.0.0 + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" + +flutter: + plugin: + implements: in_app_purchase + platforms: + ohos: + pluginClass: InAppPurchasePlugin + +dependencies: + collection: ^1.15.0 + flutter: + sdk: flutter + in_app_purchase_platform_interface: ^1.3.0 + json_annotation: ^4.8.0 + +dev_dependencies: + build_runner: ^2.0.0 + flutter_test: + sdk: flutter + json_serializable: ^6.3.1 + mockito: 5.4.4 + test: ^1.16.0 + +topics: + - in-app-purchase + - payment diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md index 11d14d945..9cd44dca9 100644 --- a/packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md @@ -1,8 +1,3 @@ -## 1.4.0 - -* Adds `getCountryCode` API. -* Updates minimum supported SDK version to Flutter 3.13/Dart 3.1. - ## 1.3.7 * Updates minimum required plugin_platform_interface version to 2.1.7. @@ -40,7 +35,7 @@ ## 1.3.0 -* Added new `PurchaseStatus` named `canceled` to distinguish between an error and user cancellation. +* Added new `PurchaseStatus` named `canceled` to distinguish between an error and user cancellation. ## 1.2.0 diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform.dart b/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform.dart index 7fd345380..18c6828b1 100644 --- a/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform.dart @@ -194,21 +194,4 @@ abstract class InAppPurchasePlatform extends PlatformInterface { /// [PurchaseDetails.verificationData]. Future restorePurchases({String? applicationUserName}) => throw UnimplementedError('restorePurchases() has not been implemented.'); - - /// Returns the user's country. - /// - /// Android: - /// Returns Play billing country code based on ISO-3166-1 alpha2 format. - /// - /// See: https://developer.android.com/reference/com/android/billingclient/api/BillingConfig - /// See: https://unicode.org/cldr/charts/latest/supplemental/territory_containment_un_m_49.html - /// - /// iOS: - /// Returns the country code from SKStoreFrontWrapper. - /// - /// See: https://developer.apple.com/documentation/storekit/skstorefront?language=objc - /// - /// - Future countryCode() => - throw UnimplementedError('countryCode() has not been implemented.'); } diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml index 35fc0488d..d87e073ec 100644 --- a/packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_platform_interface/pubspec.yaml @@ -4,11 +4,11 @@ repository: https://github.com/flutter/packages/tree/main/packages/in_app_purcha issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 1.4.0 +version: 1.3.7 environment: - sdk: ^3.1.0 - flutter: ">=3.13.0" + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" dependencies: flutter: diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/test/in_app_purchase_platform_test.dart b/packages/in_app_purchase/in_app_purchase_platform_interface/test/in_app_purchase_platform_test.dart index 5ae13ade1..0a79e9904 100644 --- a/packages/in_app_purchase/in_app_purchase_platform_interface/test/in_app_purchase_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_platform_interface/test/in_app_purchase_platform_test.dart @@ -119,18 +119,6 @@ void main() { throwsUnimplementedError, ); }); - - test( - 'Default implementation of countryCode should throw unimplemented error', - () { - final ExtendsInAppPurchasePlatform inAppPurchasePlatform = - ExtendsInAppPurchasePlatform(); - - expect( - () => inAppPurchasePlatform.countryCode(), - throwsUnimplementedError, - ); - }); }); group('$InAppPurchasePlatformAddition', () { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md index 114dc9e8d..3401e1415 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,41 +1,3 @@ -## 0.3.14 - -* Adds `countryCode` API. - -## 0.3.13+1 - -* Handle translation of errors nested in dictionaries. - -## 0.3.13 - -* Added new native tests for more complete test coverage. - -## 0.3.12+1 - -* Fixes type of error code returned from native code in SKReceiptManager.retrieveReceiptData. - -## 0.3.12 - -* Converts `refreshReceipt()`, `startObservingPaymentQueue()`, `stopObservingPaymentQueue()`, -`registerPaymentQueueDelegate()`, `removePaymentQueueDelegate()`, `showPriceConsentIfNeeded()` to pigeon. - -## 0.3.11 - -* Fixes SKError.userInfo not being nullable. - -## 0.3.10 - -* Converts `startProductRequest()`, `finishTransaction()`, `restoreTransactions()`, `presentCodeRedemptionSheet()` to pigeon. - -## 0.3.9 - -* Converts `storefront()`, `transactions()`, `addPayment()`, `canMakePayment` to pigeon. -* Updates minimum iOS version to 12.0 and minimum Flutter version to 3.16.6. - -## 0.3.8+1 - -* Adds privacy manifest. - ## 0.3.8 * Updates minimum supported SDK version to Flutter 3.10/Dart 3.0. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.h index ffb0f1fe9..eb97ceb44 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.h @@ -4,7 +4,6 @@ #import #import -#import "messages.g.h" NS_ASSUME_NONNULL_BEGIN @@ -57,34 +56,7 @@ NS_ASSUME_NONNULL_BEGIN withError:(NSString *_Nullable *_Nullable)error API_AVAILABLE(ios(12.2)); -+ (nullable SKPaymentTransactionMessage *)convertTransactionToPigeon: - (nullable SKPaymentTransaction *)transaction; - -+ (nullable SKStorefrontMessage *)convertStorefrontToPigeon:(nullable SKStorefront *)storefront - API_AVAILABLE(ios(13.0)); - -+ (nullable SKPaymentDiscountMessage *)convertPaymentDiscountToPigeon: - (nullable SKPaymentDiscount *)discount API_AVAILABLE(ios(12.2)); - -+ (nullable SKPaymentMessage *)convertPaymentToPigeon:(nullable SKPayment *)payment - API_AVAILABLE(ios(12.2)); - -+ (nullable SKErrorMessage *)convertSKErrorToPigeon:(nullable NSError *)error; - -+ (nullable SKProductsResponseMessage *)convertProductsResponseToPigeon: - (nullable SKProductsResponse *)payment; - -+ (nullable SKProductMessage *)convertProductToPigeon:(nullable SKProduct *)product - API_AVAILABLE(ios(12.2)); - -+ (nullable SKProductDiscountMessage *)convertProductDiscountToPigeon: - (nullable SKProductDiscount *)productDiscount API_AVAILABLE(ios(12.2)); - -+ (nullable SKPriceLocaleMessage *)convertNSLocaleToPigeon:(nullable NSLocale *)locale - API_AVAILABLE(ios(12.2)); - -+ (nullable SKProductSubscriptionPeriodMessage *)convertSKProductSubscriptionPeriodToPigeon: - (nullable SKProductSubscriptionPeriod *)period API_AVAILABLE(ios(12.2)); @end +; NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m index b9509eb4b..c656b5880 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m @@ -24,19 +24,25 @@ // https://github.com/flutter/flutter/issues/26610 [map setObject:[FIAObjectTranslator getMapFromNSLocale:product.priceLocale] ?: [NSNull null] forKey:@"priceLocale"]; - [map setObject:[FIAObjectTranslator - getMapFromSKProductSubscriptionPeriod:product.subscriptionPeriod] - ?: [NSNull null] - forKey:@"subscriptionPeriod"]; - [map setObject:[FIAObjectTranslator getMapFromSKProductDiscount:product.introductoryPrice] - ?: [NSNull null] - forKey:@"introductoryPrice"]; + if (@available(iOS 11.2, *)) { + [map setObject:[FIAObjectTranslator + getMapFromSKProductSubscriptionPeriod:product.subscriptionPeriod] + ?: [NSNull null] + forKey:@"subscriptionPeriod"]; + } + if (@available(iOS 11.2, *)) { + [map setObject:[FIAObjectTranslator getMapFromSKProductDiscount:product.introductoryPrice] + ?: [NSNull null] + forKey:@"introductoryPrice"]; + } if (@available(iOS 12.2, *)) { [map setObject:[FIAObjectTranslator getMapArrayFromSKProductDiscounts:product.discounts] forKey:@"discounts"]; } - [map setObject:product.subscriptionGroupIdentifier ?: [NSNull null] - forKey:@"subscriptionGroupIdentifier"]; + if (@available(iOS 12.0, *)) { + [map setObject:product.subscriptionGroupIdentifier ?: [NSNull null] + forKey:@"subscriptionGroupIdentifier"]; + } return map; } @@ -166,11 +172,12 @@ return nil; } - return @{ - @"code" : @(error.code), - @"domain" : error.domain ?: @"", - @"userInfo" : [FIAObjectTranslator encodeNSErrorUserInfo:error.userInfo] - }; + NSMutableDictionary *userInfo = [NSMutableDictionary new]; + for (NSErrorUserInfoKey key in error.userInfo) { + id value = error.userInfo[key]; + userInfo[key] = [FIAObjectTranslator encodeNSErrorUserInfo:value]; + } + return @{@"code" : @(error.code), @"domain" : error.domain ?: @"", @"userInfo" : userInfo}; } + (id)encodeNSErrorUserInfo:(id)value { @@ -188,12 +195,6 @@ [errors addObject:[FIAObjectTranslator encodeNSErrorUserInfo:error]]; } return errors; - } else if ([value isKindOfClass:[NSDictionary class]]) { - NSMutableDictionary *errors = [[NSMutableDictionary alloc] init]; - for (id key in value) { - errors[key] = [FIAObjectTranslator encodeNSErrorUserInfo:value[key]]; - } - return errors; } else { return [NSString stringWithFormat: @@ -293,226 +294,4 @@ return discount; } -+ (nullable SKPaymentTransactionMessage *)convertTransactionToPigeon: - (nullable SKPaymentTransaction *)transaction API_AVAILABLE(ios(12.2)) { - if (!transaction) { - return nil; - } - SKPaymentTransactionMessage *msg = [SKPaymentTransactionMessage - makeWithPayment:[self convertPaymentToPigeon:transaction.payment] - transactionState:[self convertTransactionStateToPigeon:transaction.transactionState] - originalTransaction:transaction.originalTransaction - ? [self convertTransactionToPigeon:transaction.originalTransaction] - : nil - transactionTimeStamp:[NSNumber numberWithDouble:[transaction.transactionDate - timeIntervalSince1970]] - transactionIdentifier:transaction.transactionIdentifier - error:[self convertSKErrorToPigeon:transaction.error]]; - return msg; -} - -+ (nullable SKErrorMessage *)convertSKErrorToPigeon:(nullable NSError *)error { - if (!error) { - return nil; - } - - NSMutableDictionary *userInfo = [NSMutableDictionary new]; - for (NSErrorUserInfoKey key in error.userInfo) { - id value = error.userInfo[key]; - userInfo[key] = [FIAObjectTranslator encodeNSErrorUserInfo:value]; - } - - SKErrorMessage *msg = [SKErrorMessage makeWithCode:error.code - domain:error.domain - userInfo:userInfo]; - return msg; -} - -+ (SKPaymentTransactionStateMessage)convertTransactionStateToPigeon: - (SKPaymentTransactionState)state { - switch (state) { - case SKPaymentTransactionStatePurchasing: - return SKPaymentTransactionStateMessagePurchasing; - case SKPaymentTransactionStatePurchased: - return SKPaymentTransactionStateMessagePurchased; - case SKPaymentTransactionStateFailed: - return SKPaymentTransactionStateMessageFailed; - case SKPaymentTransactionStateRestored: - return SKPaymentTransactionStateMessageRestored; - case SKPaymentTransactionStateDeferred: - return SKPaymentTransactionStateMessageDeferred; - } -} - -+ (nullable SKPaymentMessage *)convertPaymentToPigeon:(nullable SKPayment *)payment - API_AVAILABLE(ios(12.2)) { - if (!payment) { - return nil; - } - SKPaymentMessage *msg = [SKPaymentMessage - makeWithProductIdentifier:payment.productIdentifier - applicationUsername:payment.applicationUsername - requestData:[[NSString alloc] initWithData:payment.requestData - encoding:NSUTF8StringEncoding] - quantity:payment.quantity - simulatesAskToBuyInSandbox:payment.simulatesAskToBuyInSandbox - paymentDiscount:[self convertPaymentDiscountToPigeon:payment.paymentDiscount]]; - return msg; -} - -+ (nullable SKPaymentDiscountMessage *)convertPaymentDiscountToPigeon: - (nullable SKPaymentDiscount *)discount API_AVAILABLE(ios(12.2)) { - if (!discount) { - return nil; - } - SKPaymentDiscountMessage *msg = - [SKPaymentDiscountMessage makeWithIdentifier:discount.identifier - keyIdentifier:discount.keyIdentifier - nonce:[discount.nonce UUIDString] - signature:discount.signature - timestamp:[discount.timestamp intValue]]; - - return msg; -} - -+ (nullable SKStorefrontMessage *)convertStorefrontToPigeon:(nullable SKStorefront *)storefront - API_AVAILABLE(ios(13.0)) { - if (!storefront) { - return nil; - } - SKStorefrontMessage *msg = [SKStorefrontMessage makeWithCountryCode:storefront.countryCode - identifier:storefront.identifier]; - return msg; -} - -+ (nullable SKProductSubscriptionPeriodMessage *)convertSKProductSubscriptionPeriodToPigeon: - (nullable SKProductSubscriptionPeriod *)period API_AVAILABLE(ios(12.2)) { - if (!period) { - return nil; - } - - SKSubscriptionPeriodUnitMessage unit; - switch (period.unit) { - case SKProductPeriodUnitDay: - unit = SKSubscriptionPeriodUnitMessageDay; - break; - case SKProductPeriodUnitWeek: - unit = SKSubscriptionPeriodUnitMessageWeek; - break; - case SKProductPeriodUnitMonth: - unit = SKSubscriptionPeriodUnitMessageMonth; - break; - case SKProductPeriodUnitYear: - unit = SKSubscriptionPeriodUnitMessageYear; - break; - } - - SKProductSubscriptionPeriodMessage *msg = - [SKProductSubscriptionPeriodMessage makeWithNumberOfUnits:period.numberOfUnits unit:unit]; - - return msg; -} - -+ (nullable SKProductDiscountMessage *)convertProductDiscountToPigeon: - (nullable SKProductDiscount *)productDiscount API_AVAILABLE(ios(12.2)) { - if (!productDiscount) { - return nil; - } - - SKProductDiscountPaymentModeMessage paymentMode; - switch (productDiscount.paymentMode) { - case SKProductDiscountPaymentModeFreeTrial: - paymentMode = SKProductDiscountPaymentModeMessageFreeTrial; - break; - case SKProductDiscountPaymentModePayAsYouGo: - paymentMode = SKProductDiscountPaymentModeMessagePayAsYouGo; - break; - case SKProductDiscountPaymentModePayUpFront: - paymentMode = SKProductDiscountPaymentModeMessagePayUpFront; - break; - } - - SKProductDiscountTypeMessage type; - switch (productDiscount.type) { - case SKProductDiscountTypeIntroductory: - type = SKProductDiscountTypeMessageIntroductory; - break; - case SKProductDiscountTypeSubscription: - type = SKProductDiscountTypeMessageSubscription; - break; - } - - SKProductDiscountMessage *msg = [SKProductDiscountMessage - makeWithPrice:productDiscount.price.description - priceLocale:[self convertNSLocaleToPigeon:productDiscount.priceLocale] - numberOfPeriods:productDiscount.numberOfPeriods - paymentMode:paymentMode - subscriptionPeriod:[self convertSKProductSubscriptionPeriodToPigeon:productDiscount - .subscriptionPeriod] - identifier:productDiscount.identifier - type:type]; - - return msg; -} - -+ (nullable SKPriceLocaleMessage *)convertNSLocaleToPigeon:(nullable NSLocale *)locale - API_AVAILABLE(ios(12.2)) { - if (!locale) { - return nil; - } - SKPriceLocaleMessage *msg = [SKPriceLocaleMessage makeWithCurrencySymbol:locale.currencySymbol - currencyCode:locale.currencyCode - countryCode:locale.countryCode]; - - return msg; -} - -+ (nullable SKProductMessage *)convertProductToPigeon:(nullable SKProduct *)product - API_AVAILABLE(ios(12.2)) { - if (!product) { - return nil; - } - - NSArray *skProductDiscounts = product.discounts; - NSMutableArray *pigeonProductDiscounts = - [NSMutableArray arrayWithCapacity:skProductDiscounts.count]; - - for (SKProductDiscount *productDiscount in skProductDiscounts) { - [pigeonProductDiscounts addObject:[self convertProductDiscountToPigeon:productDiscount]]; - }; - - SKProductMessage *msg = [SKProductMessage - makeWithProductIdentifier:product.productIdentifier - localizedTitle:product.localizedTitle - localizedDescription:product.localizedDescription - priceLocale:[self convertNSLocaleToPigeon:product.priceLocale] - subscriptionGroupIdentifier:product.subscriptionGroupIdentifier - price:product.price.description - subscriptionPeriod: - [self convertSKProductSubscriptionPeriodToPigeon:product.subscriptionPeriod] - introductoryPrice:[self convertProductDiscountToPigeon:product.introductoryPrice] - discounts:pigeonProductDiscounts]; - - return msg; -} - -+ (nullable SKProductsResponseMessage *)convertProductsResponseToPigeon: - (nullable SKProductsResponse *)productsResponse API_AVAILABLE(ios(12.2)) { - if (!productsResponse) { - return nil; - } - NSArray *skProducts = productsResponse.products; - NSMutableArray *pigeonProducts = - [NSMutableArray arrayWithCapacity:skProducts.count]; - - for (SKProduct *product in skProducts) { - [pigeonProducts addObject:[self convertProductToPigeon:product]]; - }; - - SKProductsResponseMessage *msg = - [SKProductsResponseMessage makeWithProducts:pigeonProducts - invalidProductIdentifiers:productsResponse.invalidProductIdentifiers]; - return msg; -} - @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m index 22a3973b7..320e6072d 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m @@ -29,10 +29,9 @@ if (!receipt || receiptError) { if (flutterError) { NSDictionary *errorMap = [FIAObjectTranslator getMapFromNSError:receiptError]; - *flutterError = - [FlutterError errorWithCode:[NSString stringWithFormat:@"%@", errorMap[@"code"]] - message:errorMap[@"domain"] - details:errorMap[@"userInfo"]]; + *flutterError = [FlutterError errorWithCode:errorMap[@"code"] + message:errorMap[@"domain"] + details:errorMap[@"userInfo"]]; } return nil; } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h deleted file mode 100644 index ca090b6b9..000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin+TestOnly.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import -#import "FIAPRequestHandler.h" -#import "InAppPurchasePlugin.h" - -@interface InAppPurchasePlugin () - -// Holding strong references to FIAPRequestHandlers. Remove the handlers from the set after -// the request is finished. -@property(strong, nonatomic, readonly) NSMutableSet *requestHandlers; - -// Callback channel to dart used for when a function from the transaction observer is triggered. -@property(strong, nonatomic) FlutterMethodChannel *transactionObserverCallbackChannel; - -// Callback channel to dart used for when a function from the transaction observer is triggered. -@property(copy, nonatomic) FIAPRequestHandler * (^handlerFactory)(SKRequest *); - -// Convenience initializer with dependancy injection -- (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager - handlerFactory:(FIAPRequestHandler * (^)(SKRequest *))handlerFactory; - -// Transaction observer methods -- (void)handleTransactionsUpdated:(NSArray *)transactions; -- (void)handleTransactionsRemoved:(NSArray *)transactions; -- (void)handleTransactionRestoreFailed:(NSError *)error; -- (void)restoreCompletedTransactionsFinished; -- (BOOL)shouldAddStorePayment:(SKPayment *)payment product:(SKProduct *)product; - -@end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.h index a2b9cb0eb..eeab0a706 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.h @@ -7,12 +7,10 @@ #else #import #endif -#import "messages.g.h" - @class FIAPaymentQueueHandler; @class FIAPReceiptManager; -@interface InAppPurchasePlugin : NSObject +@interface InAppPurchasePlugin : NSObject @property(strong, nonatomic) FIAPaymentQueueHandler *paymentQueueHandler; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m index 2e2abaaed..7086f611a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m @@ -9,14 +9,20 @@ #import "FIAPReceiptManager.h" #import "FIAPRequestHandler.h" #import "FIAPaymentQueueHandler.h" -#import "InAppPurchasePlugin+TestOnly.h" @interface InAppPurchasePlugin () +// Holding strong references to FIAPRequestHandlers. Remove the handlers from the set after +// the request is finished. +@property(strong, nonatomic, readonly) NSMutableSet *requestHandlers; + // After querying the product, the available products will be saved in the map to be used // for purchase. @property(strong, nonatomic, readonly) NSMutableDictionary *productsCache; +// Callback channel to dart used for when a function from the transaction observer is triggered. +@property(strong, nonatomic, readonly) FlutterMethodChannel *transactionObserverCallbackChannel; + // Callback channel to dart used for when a function from the payment queue delegate is triggered. @property(strong, nonatomic, readonly) FlutterMethodChannel *paymentQueueDelegateCallbackChannel; @property(strong, nonatomic, readonly) NSObject *registrar; @@ -34,11 +40,8 @@ FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/in_app_purchase" binaryMessenger:[registrar messenger]]; - InAppPurchasePlugin *instance = [[InAppPurchasePlugin alloc] initWithRegistrar:registrar]; [registrar addMethodCallDelegate:instance channel:channel]; - [registrar addApplicationDelegate:instance]; - SetUpInAppPurchaseAPI(registrar.messenger, instance); } - (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager { @@ -46,16 +49,6 @@ _receiptManager = receiptManager; _requestHandlers = [NSMutableSet new]; _productsCache = [NSMutableDictionary new]; - _handlerFactory = ^FIAPRequestHandler *(SKRequest *request) { - return [[FIAPRequestHandler alloc] initWithRequest:request]; - }; - return self; -} - -- (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager - handlerFactory:(FIAPRequestHandler * (^)(SKRequest *))handlerFactory { - self = [self initWithReceiptManager:receiptManager]; - _handlerFactory = [handlerFactory copy]; return self; } @@ -91,84 +84,137 @@ return self; } -- (nullable NSNumber *)canMakePaymentsWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { - return @([SKPaymentQueue canMakePayments]); +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { + if ([@"-[SKPaymentQueue canMakePayments:]" isEqualToString:call.method]) { + [self canMakePayments:result]; + } else if ([@"-[SKPaymentQueue transactions]" isEqualToString:call.method]) { + [self getPendingTransactions:result]; + } else if ([@"-[SKPaymentQueue storefront]" isEqualToString:call.method]) { + [self getStorefront:result]; + } else if ([@"-[InAppPurchasePlugin startProductRequest:result:]" isEqualToString:call.method]) { + [self handleProductRequestMethodCall:call result:result]; + } else if ([@"-[InAppPurchasePlugin addPayment:result:]" isEqualToString:call.method]) { + [self addPayment:call result:result]; + } else if ([@"-[InAppPurchasePlugin finishTransaction:result:]" isEqualToString:call.method]) { + [self finishTransaction:call result:result]; + } else if ([@"-[InAppPurchasePlugin restoreTransactions:result:]" isEqualToString:call.method]) { + [self restoreTransactions:call result:result]; +#if TARGET_OS_IOS + } else if ([@"-[InAppPurchasePlugin presentCodeRedemptionSheet:result:]" + isEqualToString:call.method]) { + [self presentCodeRedemptionSheet:call result:result]; +#endif + } else if ([@"-[InAppPurchasePlugin retrieveReceiptData:result:]" isEqualToString:call.method]) { + [self retrieveReceiptData:call result:result]; + } else if ([@"-[InAppPurchasePlugin refreshReceipt:result:]" isEqualToString:call.method]) { + [self refreshReceipt:call result:result]; + } else if ([@"-[SKPaymentQueue startObservingTransactionQueue]" isEqualToString:call.method]) { + [self startObservingPaymentQueue:result]; + } else if ([@"-[SKPaymentQueue stopObservingTransactionQueue]" isEqualToString:call.method]) { + [self stopObservingPaymentQueue:result]; +#if TARGET_OS_IOS + } else if ([@"-[SKPaymentQueue registerDelegate]" isEqualToString:call.method]) { + [self registerPaymentQueueDelegate:result]; +#endif + } else if ([@"-[SKPaymentQueue removeDelegate]" isEqualToString:call.method]) { + [self removePaymentQueueDelegate:result]; +#if TARGET_OS_IOS + } else if ([@"-[SKPaymentQueue showPriceConsentIfNeeded]" isEqualToString:call.method]) { + [self showPriceConsentIfNeeded:result]; +#endif + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)canMakePayments:(FlutterResult)result { + result(@([SKPaymentQueue canMakePayments])); } -- (nullable NSArray *)transactionsWithError: - (FlutterError *_Nullable *_Nonnull)error { +- (void)getPendingTransactions:(FlutterResult)result { NSArray *transactions = [self.paymentQueueHandler getUnfinishedTransactions]; NSMutableArray *transactionMaps = [[NSMutableArray alloc] init]; for (SKPaymentTransaction *transaction in transactions) { - [transactionMaps addObject:[FIAObjectTranslator convertTransactionToPigeon:transaction]]; + [transactionMaps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:transaction]]; } - return transactionMaps; + result(transactionMaps); } -- (nullable SKStorefrontMessage *)storefrontWithError:(FlutterError *_Nullable *_Nonnull)error - API_AVAILABLE(ios(13.0), macos(10.15)) { - SKStorefront *storefront = self.paymentQueueHandler.storefront; - if (!storefront) { - return nil; +- (void)getStorefront:(FlutterResult)result { + if (@available(iOS 13.0, macOS 10.15, *)) { + SKStorefront *storefront = self.paymentQueueHandler.storefront; + if (!storefront) { + result(nil); + return; + } + result([FIAObjectTranslator getMapFromSKStorefront:storefront]); + return; } - return [FIAObjectTranslator convertStorefrontToPigeon:storefront]; + + NSLog(@"storefront is not avaialbe in iOS below 13.0 or macOS below 10.15."); + result(nil); + return; } -- (void)startProductRequestProductIdentifiers:(NSArray *)productIdentifiers - completion:(void (^)(SKProductsResponseMessage *_Nullable, - FlutterError *_Nullable))completion { +- (void)handleProductRequestMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { + if (![call.arguments isKindOfClass:[NSArray class]]) { + result([FlutterError errorWithCode:@"storekit_invalid_argument" + message:@"Argument type of startRequest is not array" + details:call.arguments]); + return; + } + NSArray *productIdentifiers = (NSArray *)call.arguments; SKProductsRequest *request = [self getProductRequestWithIdentifiers:[NSSet setWithArray:productIdentifiers]]; - FIAPRequestHandler *handler = self.handlerFactory(request); + FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request]; [self.requestHandlers addObject:handler]; __weak typeof(self) weakSelf = self; - [handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable response, - NSError *_Nullable startProductRequestError) { - FlutterError *error = nil; - if (startProductRequestError != nil) { - error = [FlutterError errorWithCode:@"storekit_getproductrequest_platform_error" - message:startProductRequestError.localizedDescription - details:startProductRequestError.description]; - completion(nil, error); + NSError *_Nullable error) { + if (error) { + result([FlutterError errorWithCode:@"storekit_getproductrequest_platform_error" + message:error.localizedDescription + details:error.description]); return; } if (!response) { - error = [FlutterError errorWithCode:@"storekit_platform_no_response" - message:@"Failed to get SKProductResponse in startRequest " - @"call. Error occured on iOS platform" - details:productIdentifiers]; - completion(nil, error); + result([FlutterError errorWithCode:@"storekit_platform_no_response" + message:@"Failed to get SKProductResponse in startRequest " + @"call. Error occured on iOS platform" + details:call.arguments]); return; } for (SKProduct *product in response.products) { [self.productsCache setObject:product forKey:product.productIdentifier]; } - - completion([FIAObjectTranslator convertProductsResponseToPigeon:response], error); + result([FIAObjectTranslator getMapFromSKProductsResponse:response]); [weakSelf.requestHandlers removeObject:handler]; }]; } -- (void)addPaymentPaymentMap:(nonnull NSDictionary *)paymentMap - error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { +- (void)addPayment:(FlutterMethodCall *)call result:(FlutterResult)result { + if (![call.arguments isKindOfClass:[NSDictionary class]]) { + result([FlutterError errorWithCode:@"storekit_invalid_argument" + message:@"Argument type of addPayment is not a Dictionary" + details:call.arguments]); + return; + } + NSDictionary *paymentMap = (NSDictionary *)call.arguments; NSString *productID = [paymentMap objectForKey:@"productIdentifier"]; // When a product is already fetched, we create a payment object with // the product to process the payment. SKProduct *product = [self getProduct:productID]; if (!product) { - *error = [FlutterError + result([FlutterError errorWithCode:@"storekit_invalid_payment_object" message: @"You have requested a payment for an invalid product. Either the " @"`productIdentifier` of the payment is not valid or the product has not been " @"fetched before adding the payment to the payment queue." - details:paymentMap]; + details:call.arguments]); return; } - SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; payment.applicationUsername = [paymentMap objectForKey:@"applicationUsername"]; NSNumber *quantity = [paymentMap objectForKey:@"quantity"]; @@ -181,38 +227,46 @@ if (@available(iOS 12.2, *)) { NSDictionary *paymentDiscountMap = [self getNonNullValueFromDictionary:paymentMap forKey:@"paymentDiscount"]; - NSString *errorMsg = nil; + NSString *error = nil; SKPaymentDiscount *paymentDiscount = - [FIAObjectTranslator getSKPaymentDiscountFromMap:paymentDiscountMap withError:&errorMsg]; + [FIAObjectTranslator getSKPaymentDiscountFromMap:paymentDiscountMap withError:&error]; - if (errorMsg) { - *error = [FlutterError + if (error) { + result([FlutterError errorWithCode:@"storekit_invalid_payment_discount_object" message:[NSString stringWithFormat:@"You have requested a payment and specified a " @"payment discount with invalid properties. %@", - errorMsg] - details:paymentMap]; + error] + details:call.arguments]); return; } payment.paymentDiscount = paymentDiscount; } + if (![self.paymentQueueHandler addPayment:payment]) { - *error = [FlutterError + result([FlutterError errorWithCode:@"storekit_duplicate_product_object" message:@"There is a pending transaction for the same product identifier. Please " @"either wait for it to be finished or finish it manually using " @"`completePurchase` to avoid edge cases." - details:paymentMap]; + details:call.arguments]); return; } + result(nil); } -- (void)finishTransactionFinishMap:(nonnull NSDictionary *)finishMap - error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { - NSString *transactionIdentifier = [finishMap objectForKey:@"transactionIdentifier"]; - NSString *productIdentifier = [finishMap objectForKey:@"productIdentifier"]; +- (void)finishTransaction:(FlutterMethodCall *)call result:(FlutterResult)result { + if (![call.arguments isKindOfClass:[NSDictionary class]]) { + result([FlutterError errorWithCode:@"storekit_invalid_argument" + message:@"Argument type of finishTransaction is not a Dictionary" + details:call.arguments]); + return; + } + NSDictionary *paymentMap = (NSDictionary *)call.arguments; + NSString *transactionIdentifier = [paymentMap objectForKey:@"transactionIdentifier"]; + NSString *productIdentifier = [paymentMap objectForKey:@"productIdentifier"]; NSArray *pendingTransactions = [self.paymentQueueHandler getUnfinishedTransactions]; @@ -228,84 +282,92 @@ @try { [self.paymentQueueHandler finishTransaction:transaction]; } @catch (NSException *e) { - *error = [FlutterError errorWithCode:@"storekit_finish_transaction_exception" - message:e.name - details:e.description]; + result([FlutterError errorWithCode:@"storekit_finish_transaction_exception" + message:e.name + details:e.description]); return; } } } + + result(nil); } -- (void)restoreTransactionsApplicationUserName:(nullable NSString *)applicationUserName - error:(FlutterError *_Nullable __autoreleasing *_Nonnull) - error { - [self.paymentQueueHandler restoreTransactions:applicationUserName]; +- (void)restoreTransactions:(FlutterMethodCall *)call result:(FlutterResult)result { + if (call.arguments && ![call.arguments isKindOfClass:[NSString class]]) { + result([FlutterError + errorWithCode:@"storekit_invalid_argument" + message:@"Argument is not nil and the type of finishTransaction is not a string." + details:call.arguments]); + return; + } + [self.paymentQueueHandler restoreTransactions:call.arguments]; + result(nil); } -- (void)presentCodeRedemptionSheetWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { #if TARGET_OS_IOS +- (void)presentCodeRedemptionSheet:(FlutterMethodCall *)call result:(FlutterResult)result { [self.paymentQueueHandler presentCodeRedemptionSheet]; -#endif + result(nil); } +#endif -- (nullable NSString *)retrieveReceiptDataWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { - FlutterError *flutterError; - NSString *receiptData = [self.receiptManager retrieveReceiptWithError:&flutterError]; - if (flutterError) { - *error = flutterError; - return nil; +- (void)retrieveReceiptData:(FlutterMethodCall *)call result:(FlutterResult)result { + FlutterError *error = nil; + NSString *receiptData = [self.receiptManager retrieveReceiptWithError:&error]; + if (error) { + result(error); + return; } - return receiptData; + result(receiptData); } -- (void)refreshReceiptReceiptProperties:(nullable NSDictionary *)receiptProperties - completion:(nonnull void (^)(FlutterError *_Nullable))completion { +- (void)refreshReceipt:(FlutterMethodCall *)call result:(FlutterResult)result { + NSDictionary *arguments = call.arguments; SKReceiptRefreshRequest *request; - if (receiptProperties) { - // if recieptProperties is not null, this call is for testing. + if (arguments) { + if (![arguments isKindOfClass:[NSDictionary class]]) { + result([FlutterError errorWithCode:@"storekit_invalid_argument" + message:@"Argument type of startRequest is not array" + details:call.arguments]); + return; + } NSMutableDictionary *properties = [NSMutableDictionary new]; - properties[SKReceiptPropertyIsExpired] = receiptProperties[@"isExpired"]; - properties[SKReceiptPropertyIsRevoked] = receiptProperties[@"isRevoked"]; - properties[SKReceiptPropertyIsVolumePurchase] = receiptProperties[@"isVolumePurchase"]; + properties[SKReceiptPropertyIsExpired] = arguments[@"isExpired"]; + properties[SKReceiptPropertyIsRevoked] = arguments[@"isRevoked"]; + properties[SKReceiptPropertyIsVolumePurchase] = arguments[@"isVolumePurchase"]; request = [self getRefreshReceiptRequest:properties]; } else { request = [self getRefreshReceiptRequest:nil]; } - - FIAPRequestHandler *handler = self.handlerFactory(request); + FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request]; [self.requestHandlers addObject:handler]; __weak typeof(self) weakSelf = self; [handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable response, NSError *_Nullable error) { - FlutterError *requestError; if (error) { - requestError = [FlutterError errorWithCode:@"storekit_refreshreceiptrequest_platform_error" - message:error.localizedDescription - details:error.description]; - completion(requestError); + result([FlutterError errorWithCode:@"storekit_refreshreceiptrequest_platform_error" + message:error.localizedDescription + details:error.description]); return; } - completion(nil); + result(nil); [weakSelf.requestHandlers removeObject:handler]; }]; } -- (void)startObservingPaymentQueueWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { +- (void)startObservingPaymentQueue:(FlutterResult)result { [_paymentQueueHandler startObservingPaymentQueue]; + result(nil); } -- (void)stopObservingPaymentQueueWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { +- (void)stopObservingPaymentQueue:(FlutterResult)result { [_paymentQueueHandler stopObservingPaymentQueue]; + result(nil); } -- (void)registerPaymentQueueDelegateWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { #if TARGET_OS_IOS +- (void)registerPaymentQueueDelegate:(FlutterResult)result { if (@available(iOS 13.0, *)) { _paymentQueueDelegateCallbackChannel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/in_app_purchase_payment_queue_delegate" @@ -315,25 +377,27 @@ initWithMethodChannel:_paymentQueueDelegateCallbackChannel]; _paymentQueueHandler.delegate = _paymentQueueDelegate; } -#endif + result(nil); } +#endif -- (void)removePaymentQueueDelegateWithError: - (FlutterError *_Nullable __autoreleasing *_Nonnull)error { +- (void)removePaymentQueueDelegate:(FlutterResult)result { if (@available(iOS 13.0, *)) { _paymentQueueHandler.delegate = nil; } _paymentQueueDelegate = nil; _paymentQueueDelegateCallbackChannel = nil; + result(nil); } -- (void)showPriceConsentIfNeededWithError:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { #if TARGET_OS_IOS +- (void)showPriceConsentIfNeeded:(FlutterResult)result { if (@available(iOS 13.4, *)) { [_paymentQueueHandler showPriceConsentIfNeeded]; } -#endif + result(nil); } +#endif - (id)getNonNullValueFromDictionary:(NSDictionary *)dictionary forKey:(NSString *)key { id value = dictionary[key]; @@ -401,4 +465,5 @@ - (SKReceiptRefreshRequest *)getRefreshReceiptRequest:(NSDictionary *)properties { return [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:properties]; } + @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h deleted file mode 100644 index bc315e781..000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// Autogenerated from Pigeon (v16.0.4), do not edit directly. -// See also: https://pub.dev/packages/pigeon - -#import - -@protocol FlutterBinaryMessenger; -@protocol FlutterMessageCodec; -@class FlutterError; -@class FlutterStandardTypedData; - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSUInteger, SKPaymentTransactionStateMessage) { - /// Indicates the transaction is being processed in App Store. - /// - /// You should update your UI to indicate that you are waiting for the - /// transaction to update to another state. Never complete a transaction that - /// is still in a purchasing state. - SKPaymentTransactionStateMessagePurchasing = 0, - /// The user's payment has been succesfully processed. - /// - /// You should provide the user the content that they purchased. - SKPaymentTransactionStateMessagePurchased = 1, - /// The transaction failed. - /// - /// Check the [PaymentTransactionWrapper.error] property from - /// [PaymentTransactionWrapper] for details. - SKPaymentTransactionStateMessageFailed = 2, - /// This transaction is restoring content previously purchased by the user. - /// - /// The previous transaction information can be obtained in - /// [PaymentTransactionWrapper.originalTransaction] from - /// [PaymentTransactionWrapper]. - SKPaymentTransactionStateMessageRestored = 3, - /// The transaction is in the queue but pending external action. Wait for - /// another callback to get the final state. - /// - /// You should update your UI to indicate that you are waiting for the - /// transaction to update to another state. - SKPaymentTransactionStateMessageDeferred = 4, - /// Indicates the transaction is in an unspecified state. - SKPaymentTransactionStateMessageUnspecified = 5, -}; - -/// Wrapper for SKPaymentTransactionStateMessage to allow for nullability. -@interface SKPaymentTransactionStateMessageBox : NSObject -@property(nonatomic, assign) SKPaymentTransactionStateMessage value; -- (instancetype)initWithValue:(SKPaymentTransactionStateMessage)value; -@end - -typedef NS_ENUM(NSUInteger, SKProductDiscountTypeMessage) { - /// A constant indicating the discount type is an introductory offer. - SKProductDiscountTypeMessageIntroductory = 0, - /// A constant indicating the discount type is a promotional offer. - SKProductDiscountTypeMessageSubscription = 1, -}; - -/// Wrapper for SKProductDiscountTypeMessage to allow for nullability. -@interface SKProductDiscountTypeMessageBox : NSObject -@property(nonatomic, assign) SKProductDiscountTypeMessage value; -- (instancetype)initWithValue:(SKProductDiscountTypeMessage)value; -@end - -typedef NS_ENUM(NSUInteger, SKProductDiscountPaymentModeMessage) { - /// Allows user to pay the discounted price at each payment period. - SKProductDiscountPaymentModeMessagePayAsYouGo = 0, - /// Allows user to pay the discounted price upfront and receive the product for the rest of time - /// that was paid for. - SKProductDiscountPaymentModeMessagePayUpFront = 1, - /// User pays nothing during the discounted period. - SKProductDiscountPaymentModeMessageFreeTrial = 2, - /// Unspecified mode. - SKProductDiscountPaymentModeMessageUnspecified = 3, -}; - -/// Wrapper for SKProductDiscountPaymentModeMessage to allow for nullability. -@interface SKProductDiscountPaymentModeMessageBox : NSObject -@property(nonatomic, assign) SKProductDiscountPaymentModeMessage value; -- (instancetype)initWithValue:(SKProductDiscountPaymentModeMessage)value; -@end - -typedef NS_ENUM(NSUInteger, SKSubscriptionPeriodUnitMessage) { - SKSubscriptionPeriodUnitMessageDay = 0, - SKSubscriptionPeriodUnitMessageWeek = 1, - SKSubscriptionPeriodUnitMessageMonth = 2, - SKSubscriptionPeriodUnitMessageYear = 3, -}; - -/// Wrapper for SKSubscriptionPeriodUnitMessage to allow for nullability. -@interface SKSubscriptionPeriodUnitMessageBox : NSObject -@property(nonatomic, assign) SKSubscriptionPeriodUnitMessage value; -- (instancetype)initWithValue:(SKSubscriptionPeriodUnitMessage)value; -@end - -@class SKPaymentTransactionMessage; -@class SKPaymentMessage; -@class SKErrorMessage; -@class SKPaymentDiscountMessage; -@class SKStorefrontMessage; -@class SKProductsResponseMessage; -@class SKProductMessage; -@class SKPriceLocaleMessage; -@class SKProductDiscountMessage; -@class SKProductSubscriptionPeriodMessage; - -@interface SKPaymentTransactionMessage : NSObject -/// `init` unavailable to enforce nonnull fields, see the `make` class method. -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithPayment:(SKPaymentMessage *)payment - transactionState:(SKPaymentTransactionStateMessage)transactionState - originalTransaction:(nullable SKPaymentTransactionMessage *)originalTransaction - transactionTimeStamp:(nullable NSNumber *)transactionTimeStamp - transactionIdentifier:(nullable NSString *)transactionIdentifier - error:(nullable SKErrorMessage *)error; -@property(nonatomic, strong) SKPaymentMessage *payment; -@property(nonatomic, assign) SKPaymentTransactionStateMessage transactionState; -@property(nonatomic, strong, nullable) SKPaymentTransactionMessage *originalTransaction; -@property(nonatomic, strong, nullable) NSNumber *transactionTimeStamp; -@property(nonatomic, copy, nullable) NSString *transactionIdentifier; -@property(nonatomic, strong, nullable) SKErrorMessage *error; -@end - -@interface SKPaymentMessage : NSObject -/// `init` unavailable to enforce nonnull fields, see the `make` class method. -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithProductIdentifier:(NSString *)productIdentifier - applicationUsername:(nullable NSString *)applicationUsername - requestData:(nullable NSString *)requestData - quantity:(NSInteger)quantity - simulatesAskToBuyInSandbox:(BOOL)simulatesAskToBuyInSandbox - paymentDiscount:(nullable SKPaymentDiscountMessage *)paymentDiscount; -@property(nonatomic, copy) NSString *productIdentifier; -@property(nonatomic, copy, nullable) NSString *applicationUsername; -@property(nonatomic, copy, nullable) NSString *requestData; -@property(nonatomic, assign) NSInteger quantity; -@property(nonatomic, assign) BOOL simulatesAskToBuyInSandbox; -@property(nonatomic, strong, nullable) SKPaymentDiscountMessage *paymentDiscount; -@end - -@interface SKErrorMessage : NSObject -/// `init` unavailable to enforce nonnull fields, see the `make` class method. -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithCode:(NSInteger)code - domain:(NSString *)domain - userInfo:(nullable NSDictionary *)userInfo; -@property(nonatomic, assign) NSInteger code; -@property(nonatomic, copy) NSString *domain; -@property(nonatomic, copy, nullable) NSDictionary *userInfo; -@end - -@interface SKPaymentDiscountMessage : NSObject -/// `init` unavailable to enforce nonnull fields, see the `make` class method. -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithIdentifier:(NSString *)identifier - keyIdentifier:(NSString *)keyIdentifier - nonce:(NSString *)nonce - signature:(NSString *)signature - timestamp:(NSInteger)timestamp; -@property(nonatomic, copy) NSString *identifier; -@property(nonatomic, copy) NSString *keyIdentifier; -@property(nonatomic, copy) NSString *nonce; -@property(nonatomic, copy) NSString *signature; -@property(nonatomic, assign) NSInteger timestamp; -@end - -@interface SKStorefrontMessage : NSObject -/// `init` unavailable to enforce nonnull fields, see the `make` class method. -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithCountryCode:(NSString *)countryCode identifier:(NSString *)identifier; -@property(nonatomic, copy) NSString *countryCode; -@property(nonatomic, copy) NSString *identifier; -@end - -@interface SKProductsResponseMessage : NSObject -+ (instancetype)makeWithProducts:(nullable NSArray *)products - invalidProductIdentifiers:(nullable NSArray *)invalidProductIdentifiers; -@property(nonatomic, copy, nullable) NSArray *products; -@property(nonatomic, copy, nullable) NSArray *invalidProductIdentifiers; -@end - -@interface SKProductMessage : NSObject -/// `init` unavailable to enforce nonnull fields, see the `make` class method. -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithProductIdentifier:(NSString *)productIdentifier - localizedTitle:(NSString *)localizedTitle - localizedDescription:(NSString *)localizedDescription - priceLocale:(SKPriceLocaleMessage *)priceLocale - subscriptionGroupIdentifier:(nullable NSString *)subscriptionGroupIdentifier - price:(NSString *)price - subscriptionPeriod: - (nullable SKProductSubscriptionPeriodMessage *)subscriptionPeriod - introductoryPrice:(nullable SKProductDiscountMessage *)introductoryPrice - discounts:(nullable NSArray *)discounts; -@property(nonatomic, copy) NSString *productIdentifier; -@property(nonatomic, copy) NSString *localizedTitle; -@property(nonatomic, copy) NSString *localizedDescription; -@property(nonatomic, strong) SKPriceLocaleMessage *priceLocale; -@property(nonatomic, copy, nullable) NSString *subscriptionGroupIdentifier; -@property(nonatomic, copy) NSString *price; -@property(nonatomic, strong, nullable) SKProductSubscriptionPeriodMessage *subscriptionPeriod; -@property(nonatomic, strong, nullable) SKProductDiscountMessage *introductoryPrice; -@property(nonatomic, copy, nullable) NSArray *discounts; -@end - -@interface SKPriceLocaleMessage : NSObject -/// `init` unavailable to enforce nonnull fields, see the `make` class method. -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithCurrencySymbol:(NSString *)currencySymbol - currencyCode:(NSString *)currencyCode - countryCode:(NSString *)countryCode; -/// The currency symbol for the locale, e.g. $ for US locale. -@property(nonatomic, copy) NSString *currencySymbol; -/// The currency code for the locale, e.g. USD for US locale. -@property(nonatomic, copy) NSString *currencyCode; -/// The country code for the locale, e.g. US for US locale. -@property(nonatomic, copy) NSString *countryCode; -@end - -@interface SKProductDiscountMessage : NSObject -/// `init` unavailable to enforce nonnull fields, see the `make` class method. -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithPrice:(NSString *)price - priceLocale:(SKPriceLocaleMessage *)priceLocale - numberOfPeriods:(NSInteger)numberOfPeriods - paymentMode:(SKProductDiscountPaymentModeMessage)paymentMode - subscriptionPeriod:(SKProductSubscriptionPeriodMessage *)subscriptionPeriod - identifier:(nullable NSString *)identifier - type:(SKProductDiscountTypeMessage)type; -@property(nonatomic, copy) NSString *price; -@property(nonatomic, strong) SKPriceLocaleMessage *priceLocale; -@property(nonatomic, assign) NSInteger numberOfPeriods; -@property(nonatomic, assign) SKProductDiscountPaymentModeMessage paymentMode; -@property(nonatomic, strong) SKProductSubscriptionPeriodMessage *subscriptionPeriod; -@property(nonatomic, copy, nullable) NSString *identifier; -@property(nonatomic, assign) SKProductDiscountTypeMessage type; -@end - -@interface SKProductSubscriptionPeriodMessage : NSObject -/// `init` unavailable to enforce nonnull fields, see the `make` class method. -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithNumberOfUnits:(NSInteger)numberOfUnits - unit:(SKSubscriptionPeriodUnitMessage)unit; -@property(nonatomic, assign) NSInteger numberOfUnits; -@property(nonatomic, assign) SKSubscriptionPeriodUnitMessage unit; -@end - -/// The codec used by InAppPurchaseAPI. -NSObject *InAppPurchaseAPIGetCodec(void); - -@protocol InAppPurchaseAPI -/// Returns if the current device is able to make payments -/// -/// @return `nil` only when `error != nil`. -- (nullable NSNumber *)canMakePaymentsWithError:(FlutterError *_Nullable *_Nonnull)error; -/// @return `nil` only when `error != nil`. -- (nullable NSArray *)transactionsWithError: - (FlutterError *_Nullable *_Nonnull)error; -/// @return `nil` only when `error != nil`. -- (nullable SKStorefrontMessage *)storefrontWithError:(FlutterError *_Nullable *_Nonnull)error; -- (void)addPaymentPaymentMap:(NSDictionary *)paymentMap - error:(FlutterError *_Nullable *_Nonnull)error; -- (void)startProductRequestProductIdentifiers:(NSArray *)productIdentifiers - completion:(void (^)(SKProductsResponseMessage *_Nullable, - FlutterError *_Nullable))completion; -- (void)finishTransactionFinishMap:(NSDictionary *)finishMap - error:(FlutterError *_Nullable *_Nonnull)error; -- (void)restoreTransactionsApplicationUserName:(nullable NSString *)applicationUserName - error:(FlutterError *_Nullable *_Nonnull)error; -- (void)presentCodeRedemptionSheetWithError:(FlutterError *_Nullable *_Nonnull)error; -- (nullable NSString *)retrieveReceiptDataWithError:(FlutterError *_Nullable *_Nonnull)error; -- (void)refreshReceiptReceiptProperties:(nullable NSDictionary *)receiptProperties - completion:(void (^)(FlutterError *_Nullable))completion; -- (void)startObservingPaymentQueueWithError:(FlutterError *_Nullable *_Nonnull)error; -- (void)stopObservingPaymentQueueWithError:(FlutterError *_Nullable *_Nonnull)error; -- (void)registerPaymentQueueDelegateWithError:(FlutterError *_Nullable *_Nonnull)error; -- (void)removePaymentQueueDelegateWithError:(FlutterError *_Nullable *_Nonnull)error; -- (void)showPriceConsentIfNeededWithError:(FlutterError *_Nullable *_Nonnull)error; -@end - -extern void SetUpInAppPurchaseAPI(id binaryMessenger, - NSObject *_Nullable api); - -NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m deleted file mode 100644 index 9588c883b..000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m +++ /dev/null @@ -1,897 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// Autogenerated from Pigeon (v16.0.4), do not edit directly. -// See also: https://pub.dev/packages/pigeon - -#import "messages.g.h" - -#if TARGET_OS_OSX -#import -#else -#import -#endif - -#if !__has_feature(objc_arc) -#error File requires ARC to be enabled. -#endif - -static NSArray *wrapResult(id result, FlutterError *error) { - if (error) { - return @[ - error.code ?: [NSNull null], error.message ?: [NSNull null], error.details ?: [NSNull null] - ]; - } - return @[ result ?: [NSNull null] ]; -} - -static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) { - id result = array[key]; - return (result == [NSNull null]) ? nil : result; -} - -@implementation SKPaymentTransactionStateMessageBox -- (instancetype)initWithValue:(SKPaymentTransactionStateMessage)value { - self = [super init]; - if (self) { - _value = value; - } - return self; -} -@end - -@implementation SKProductDiscountTypeMessageBox -- (instancetype)initWithValue:(SKProductDiscountTypeMessage)value { - self = [super init]; - if (self) { - _value = value; - } - return self; -} -@end - -@implementation SKProductDiscountPaymentModeMessageBox -- (instancetype)initWithValue:(SKProductDiscountPaymentModeMessage)value { - self = [super init]; - if (self) { - _value = value; - } - return self; -} -@end - -@implementation SKSubscriptionPeriodUnitMessageBox -- (instancetype)initWithValue:(SKSubscriptionPeriodUnitMessage)value { - self = [super init]; - if (self) { - _value = value; - } - return self; -} -@end - -@interface SKPaymentTransactionMessage () -+ (SKPaymentTransactionMessage *)fromList:(NSArray *)list; -+ (nullable SKPaymentTransactionMessage *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; -@end - -@interface SKPaymentMessage () -+ (SKPaymentMessage *)fromList:(NSArray *)list; -+ (nullable SKPaymentMessage *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; -@end - -@interface SKErrorMessage () -+ (SKErrorMessage *)fromList:(NSArray *)list; -+ (nullable SKErrorMessage *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; -@end - -@interface SKPaymentDiscountMessage () -+ (SKPaymentDiscountMessage *)fromList:(NSArray *)list; -+ (nullable SKPaymentDiscountMessage *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; -@end - -@interface SKStorefrontMessage () -+ (SKStorefrontMessage *)fromList:(NSArray *)list; -+ (nullable SKStorefrontMessage *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; -@end - -@interface SKProductsResponseMessage () -+ (SKProductsResponseMessage *)fromList:(NSArray *)list; -+ (nullable SKProductsResponseMessage *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; -@end - -@interface SKProductMessage () -+ (SKProductMessage *)fromList:(NSArray *)list; -+ (nullable SKProductMessage *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; -@end - -@interface SKPriceLocaleMessage () -+ (SKPriceLocaleMessage *)fromList:(NSArray *)list; -+ (nullable SKPriceLocaleMessage *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; -@end - -@interface SKProductDiscountMessage () -+ (SKProductDiscountMessage *)fromList:(NSArray *)list; -+ (nullable SKProductDiscountMessage *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; -@end - -@interface SKProductSubscriptionPeriodMessage () -+ (SKProductSubscriptionPeriodMessage *)fromList:(NSArray *)list; -+ (nullable SKProductSubscriptionPeriodMessage *)nullableFromList:(NSArray *)list; -- (NSArray *)toList; -@end - -@implementation SKPaymentTransactionMessage -+ (instancetype)makeWithPayment:(SKPaymentMessage *)payment - transactionState:(SKPaymentTransactionStateMessage)transactionState - originalTransaction:(nullable SKPaymentTransactionMessage *)originalTransaction - transactionTimeStamp:(nullable NSNumber *)transactionTimeStamp - transactionIdentifier:(nullable NSString *)transactionIdentifier - error:(nullable SKErrorMessage *)error { - SKPaymentTransactionMessage *pigeonResult = [[SKPaymentTransactionMessage alloc] init]; - pigeonResult.payment = payment; - pigeonResult.transactionState = transactionState; - pigeonResult.originalTransaction = originalTransaction; - pigeonResult.transactionTimeStamp = transactionTimeStamp; - pigeonResult.transactionIdentifier = transactionIdentifier; - pigeonResult.error = error; - return pigeonResult; -} -+ (SKPaymentTransactionMessage *)fromList:(NSArray *)list { - SKPaymentTransactionMessage *pigeonResult = [[SKPaymentTransactionMessage alloc] init]; - pigeonResult.payment = [SKPaymentMessage nullableFromList:(GetNullableObjectAtIndex(list, 0))]; - pigeonResult.transactionState = [GetNullableObjectAtIndex(list, 1) integerValue]; - pigeonResult.originalTransaction = - [SKPaymentTransactionMessage nullableFromList:(GetNullableObjectAtIndex(list, 2))]; - pigeonResult.transactionTimeStamp = GetNullableObjectAtIndex(list, 3); - pigeonResult.transactionIdentifier = GetNullableObjectAtIndex(list, 4); - pigeonResult.error = [SKErrorMessage nullableFromList:(GetNullableObjectAtIndex(list, 5))]; - return pigeonResult; -} -+ (nullable SKPaymentTransactionMessage *)nullableFromList:(NSArray *)list { - return (list) ? [SKPaymentTransactionMessage fromList:list] : nil; -} -- (NSArray *)toList { - return @[ - (self.payment ? [self.payment toList] : [NSNull null]), - @(self.transactionState), - (self.originalTransaction ? [self.originalTransaction toList] : [NSNull null]), - self.transactionTimeStamp ?: [NSNull null], - self.transactionIdentifier ?: [NSNull null], - (self.error ? [self.error toList] : [NSNull null]), - ]; -} -@end - -@implementation SKPaymentMessage -+ (instancetype)makeWithProductIdentifier:(NSString *)productIdentifier - applicationUsername:(nullable NSString *)applicationUsername - requestData:(nullable NSString *)requestData - quantity:(NSInteger)quantity - simulatesAskToBuyInSandbox:(BOOL)simulatesAskToBuyInSandbox - paymentDiscount:(nullable SKPaymentDiscountMessage *)paymentDiscount { - SKPaymentMessage *pigeonResult = [[SKPaymentMessage alloc] init]; - pigeonResult.productIdentifier = productIdentifier; - pigeonResult.applicationUsername = applicationUsername; - pigeonResult.requestData = requestData; - pigeonResult.quantity = quantity; - pigeonResult.simulatesAskToBuyInSandbox = simulatesAskToBuyInSandbox; - pigeonResult.paymentDiscount = paymentDiscount; - return pigeonResult; -} -+ (SKPaymentMessage *)fromList:(NSArray *)list { - SKPaymentMessage *pigeonResult = [[SKPaymentMessage alloc] init]; - pigeonResult.productIdentifier = GetNullableObjectAtIndex(list, 0); - pigeonResult.applicationUsername = GetNullableObjectAtIndex(list, 1); - pigeonResult.requestData = GetNullableObjectAtIndex(list, 2); - pigeonResult.quantity = [GetNullableObjectAtIndex(list, 3) integerValue]; - pigeonResult.simulatesAskToBuyInSandbox = [GetNullableObjectAtIndex(list, 4) boolValue]; - pigeonResult.paymentDiscount = - [SKPaymentDiscountMessage nullableFromList:(GetNullableObjectAtIndex(list, 5))]; - return pigeonResult; -} -+ (nullable SKPaymentMessage *)nullableFromList:(NSArray *)list { - return (list) ? [SKPaymentMessage fromList:list] : nil; -} -- (NSArray *)toList { - return @[ - self.productIdentifier ?: [NSNull null], - self.applicationUsername ?: [NSNull null], - self.requestData ?: [NSNull null], - @(self.quantity), - @(self.simulatesAskToBuyInSandbox), - (self.paymentDiscount ? [self.paymentDiscount toList] : [NSNull null]), - ]; -} -@end - -@implementation SKErrorMessage -+ (instancetype)makeWithCode:(NSInteger)code - domain:(NSString *)domain - userInfo:(nullable NSDictionary *)userInfo { - SKErrorMessage *pigeonResult = [[SKErrorMessage alloc] init]; - pigeonResult.code = code; - pigeonResult.domain = domain; - pigeonResult.userInfo = userInfo; - return pigeonResult; -} -+ (SKErrorMessage *)fromList:(NSArray *)list { - SKErrorMessage *pigeonResult = [[SKErrorMessage alloc] init]; - pigeonResult.code = [GetNullableObjectAtIndex(list, 0) integerValue]; - pigeonResult.domain = GetNullableObjectAtIndex(list, 1); - pigeonResult.userInfo = GetNullableObjectAtIndex(list, 2); - return pigeonResult; -} -+ (nullable SKErrorMessage *)nullableFromList:(NSArray *)list { - return (list) ? [SKErrorMessage fromList:list] : nil; -} -- (NSArray *)toList { - return @[ - @(self.code), - self.domain ?: [NSNull null], - self.userInfo ?: [NSNull null], - ]; -} -@end - -@implementation SKPaymentDiscountMessage -+ (instancetype)makeWithIdentifier:(NSString *)identifier - keyIdentifier:(NSString *)keyIdentifier - nonce:(NSString *)nonce - signature:(NSString *)signature - timestamp:(NSInteger)timestamp { - SKPaymentDiscountMessage *pigeonResult = [[SKPaymentDiscountMessage alloc] init]; - pigeonResult.identifier = identifier; - pigeonResult.keyIdentifier = keyIdentifier; - pigeonResult.nonce = nonce; - pigeonResult.signature = signature; - pigeonResult.timestamp = timestamp; - return pigeonResult; -} -+ (SKPaymentDiscountMessage *)fromList:(NSArray *)list { - SKPaymentDiscountMessage *pigeonResult = [[SKPaymentDiscountMessage alloc] init]; - pigeonResult.identifier = GetNullableObjectAtIndex(list, 0); - pigeonResult.keyIdentifier = GetNullableObjectAtIndex(list, 1); - pigeonResult.nonce = GetNullableObjectAtIndex(list, 2); - pigeonResult.signature = GetNullableObjectAtIndex(list, 3); - pigeonResult.timestamp = [GetNullableObjectAtIndex(list, 4) integerValue]; - return pigeonResult; -} -+ (nullable SKPaymentDiscountMessage *)nullableFromList:(NSArray *)list { - return (list) ? [SKPaymentDiscountMessage fromList:list] : nil; -} -- (NSArray *)toList { - return @[ - self.identifier ?: [NSNull null], - self.keyIdentifier ?: [NSNull null], - self.nonce ?: [NSNull null], - self.signature ?: [NSNull null], - @(self.timestamp), - ]; -} -@end - -@implementation SKStorefrontMessage -+ (instancetype)makeWithCountryCode:(NSString *)countryCode identifier:(NSString *)identifier { - SKStorefrontMessage *pigeonResult = [[SKStorefrontMessage alloc] init]; - pigeonResult.countryCode = countryCode; - pigeonResult.identifier = identifier; - return pigeonResult; -} -+ (SKStorefrontMessage *)fromList:(NSArray *)list { - SKStorefrontMessage *pigeonResult = [[SKStorefrontMessage alloc] init]; - pigeonResult.countryCode = GetNullableObjectAtIndex(list, 0); - pigeonResult.identifier = GetNullableObjectAtIndex(list, 1); - return pigeonResult; -} -+ (nullable SKStorefrontMessage *)nullableFromList:(NSArray *)list { - return (list) ? [SKStorefrontMessage fromList:list] : nil; -} -- (NSArray *)toList { - return @[ - self.countryCode ?: [NSNull null], - self.identifier ?: [NSNull null], - ]; -} -@end - -@implementation SKProductsResponseMessage -+ (instancetype)makeWithProducts:(nullable NSArray *)products - invalidProductIdentifiers:(nullable NSArray *)invalidProductIdentifiers { - SKProductsResponseMessage *pigeonResult = [[SKProductsResponseMessage alloc] init]; - pigeonResult.products = products; - pigeonResult.invalidProductIdentifiers = invalidProductIdentifiers; - return pigeonResult; -} -+ (SKProductsResponseMessage *)fromList:(NSArray *)list { - SKProductsResponseMessage *pigeonResult = [[SKProductsResponseMessage alloc] init]; - pigeonResult.products = GetNullableObjectAtIndex(list, 0); - pigeonResult.invalidProductIdentifiers = GetNullableObjectAtIndex(list, 1); - return pigeonResult; -} -+ (nullable SKProductsResponseMessage *)nullableFromList:(NSArray *)list { - return (list) ? [SKProductsResponseMessage fromList:list] : nil; -} -- (NSArray *)toList { - return @[ - self.products ?: [NSNull null], - self.invalidProductIdentifiers ?: [NSNull null], - ]; -} -@end - -@implementation SKProductMessage -+ (instancetype) - makeWithProductIdentifier:(NSString *)productIdentifier - localizedTitle:(NSString *)localizedTitle - localizedDescription:(NSString *)localizedDescription - priceLocale:(SKPriceLocaleMessage *)priceLocale - subscriptionGroupIdentifier:(nullable NSString *)subscriptionGroupIdentifier - price:(NSString *)price - subscriptionPeriod:(nullable SKProductSubscriptionPeriodMessage *)subscriptionPeriod - introductoryPrice:(nullable SKProductDiscountMessage *)introductoryPrice - discounts:(nullable NSArray *)discounts { - SKProductMessage *pigeonResult = [[SKProductMessage alloc] init]; - pigeonResult.productIdentifier = productIdentifier; - pigeonResult.localizedTitle = localizedTitle; - pigeonResult.localizedDescription = localizedDescription; - pigeonResult.priceLocale = priceLocale; - pigeonResult.subscriptionGroupIdentifier = subscriptionGroupIdentifier; - pigeonResult.price = price; - pigeonResult.subscriptionPeriod = subscriptionPeriod; - pigeonResult.introductoryPrice = introductoryPrice; - pigeonResult.discounts = discounts; - return pigeonResult; -} -+ (SKProductMessage *)fromList:(NSArray *)list { - SKProductMessage *pigeonResult = [[SKProductMessage alloc] init]; - pigeonResult.productIdentifier = GetNullableObjectAtIndex(list, 0); - pigeonResult.localizedTitle = GetNullableObjectAtIndex(list, 1); - pigeonResult.localizedDescription = GetNullableObjectAtIndex(list, 2); - pigeonResult.priceLocale = - [SKPriceLocaleMessage nullableFromList:(GetNullableObjectAtIndex(list, 3))]; - pigeonResult.subscriptionGroupIdentifier = GetNullableObjectAtIndex(list, 4); - pigeonResult.price = GetNullableObjectAtIndex(list, 5); - pigeonResult.subscriptionPeriod = - [SKProductSubscriptionPeriodMessage nullableFromList:(GetNullableObjectAtIndex(list, 6))]; - pigeonResult.introductoryPrice = - [SKProductDiscountMessage nullableFromList:(GetNullableObjectAtIndex(list, 7))]; - pigeonResult.discounts = GetNullableObjectAtIndex(list, 8); - return pigeonResult; -} -+ (nullable SKProductMessage *)nullableFromList:(NSArray *)list { - return (list) ? [SKProductMessage fromList:list] : nil; -} -- (NSArray *)toList { - return @[ - self.productIdentifier ?: [NSNull null], - self.localizedTitle ?: [NSNull null], - self.localizedDescription ?: [NSNull null], - (self.priceLocale ? [self.priceLocale toList] : [NSNull null]), - self.subscriptionGroupIdentifier ?: [NSNull null], - self.price ?: [NSNull null], - (self.subscriptionPeriod ? [self.subscriptionPeriod toList] : [NSNull null]), - (self.introductoryPrice ? [self.introductoryPrice toList] : [NSNull null]), - self.discounts ?: [NSNull null], - ]; -} -@end - -@implementation SKPriceLocaleMessage -+ (instancetype)makeWithCurrencySymbol:(NSString *)currencySymbol - currencyCode:(NSString *)currencyCode - countryCode:(NSString *)countryCode { - SKPriceLocaleMessage *pigeonResult = [[SKPriceLocaleMessage alloc] init]; - pigeonResult.currencySymbol = currencySymbol; - pigeonResult.currencyCode = currencyCode; - pigeonResult.countryCode = countryCode; - return pigeonResult; -} -+ (SKPriceLocaleMessage *)fromList:(NSArray *)list { - SKPriceLocaleMessage *pigeonResult = [[SKPriceLocaleMessage alloc] init]; - pigeonResult.currencySymbol = GetNullableObjectAtIndex(list, 0); - pigeonResult.currencyCode = GetNullableObjectAtIndex(list, 1); - pigeonResult.countryCode = GetNullableObjectAtIndex(list, 2); - return pigeonResult; -} -+ (nullable SKPriceLocaleMessage *)nullableFromList:(NSArray *)list { - return (list) ? [SKPriceLocaleMessage fromList:list] : nil; -} -- (NSArray *)toList { - return @[ - self.currencySymbol ?: [NSNull null], - self.currencyCode ?: [NSNull null], - self.countryCode ?: [NSNull null], - ]; -} -@end - -@implementation SKProductDiscountMessage -+ (instancetype)makeWithPrice:(NSString *)price - priceLocale:(SKPriceLocaleMessage *)priceLocale - numberOfPeriods:(NSInteger)numberOfPeriods - paymentMode:(SKProductDiscountPaymentModeMessage)paymentMode - subscriptionPeriod:(SKProductSubscriptionPeriodMessage *)subscriptionPeriod - identifier:(nullable NSString *)identifier - type:(SKProductDiscountTypeMessage)type { - SKProductDiscountMessage *pigeonResult = [[SKProductDiscountMessage alloc] init]; - pigeonResult.price = price; - pigeonResult.priceLocale = priceLocale; - pigeonResult.numberOfPeriods = numberOfPeriods; - pigeonResult.paymentMode = paymentMode; - pigeonResult.subscriptionPeriod = subscriptionPeriod; - pigeonResult.identifier = identifier; - pigeonResult.type = type; - return pigeonResult; -} -+ (SKProductDiscountMessage *)fromList:(NSArray *)list { - SKProductDiscountMessage *pigeonResult = [[SKProductDiscountMessage alloc] init]; - pigeonResult.price = GetNullableObjectAtIndex(list, 0); - pigeonResult.priceLocale = - [SKPriceLocaleMessage nullableFromList:(GetNullableObjectAtIndex(list, 1))]; - pigeonResult.numberOfPeriods = [GetNullableObjectAtIndex(list, 2) integerValue]; - pigeonResult.paymentMode = [GetNullableObjectAtIndex(list, 3) integerValue]; - pigeonResult.subscriptionPeriod = - [SKProductSubscriptionPeriodMessage nullableFromList:(GetNullableObjectAtIndex(list, 4))]; - pigeonResult.identifier = GetNullableObjectAtIndex(list, 5); - pigeonResult.type = [GetNullableObjectAtIndex(list, 6) integerValue]; - return pigeonResult; -} -+ (nullable SKProductDiscountMessage *)nullableFromList:(NSArray *)list { - return (list) ? [SKProductDiscountMessage fromList:list] : nil; -} -- (NSArray *)toList { - return @[ - self.price ?: [NSNull null], - (self.priceLocale ? [self.priceLocale toList] : [NSNull null]), - @(self.numberOfPeriods), - @(self.paymentMode), - (self.subscriptionPeriod ? [self.subscriptionPeriod toList] : [NSNull null]), - self.identifier ?: [NSNull null], - @(self.type), - ]; -} -@end - -@implementation SKProductSubscriptionPeriodMessage -+ (instancetype)makeWithNumberOfUnits:(NSInteger)numberOfUnits - unit:(SKSubscriptionPeriodUnitMessage)unit { - SKProductSubscriptionPeriodMessage *pigeonResult = - [[SKProductSubscriptionPeriodMessage alloc] init]; - pigeonResult.numberOfUnits = numberOfUnits; - pigeonResult.unit = unit; - return pigeonResult; -} -+ (SKProductSubscriptionPeriodMessage *)fromList:(NSArray *)list { - SKProductSubscriptionPeriodMessage *pigeonResult = - [[SKProductSubscriptionPeriodMessage alloc] init]; - pigeonResult.numberOfUnits = [GetNullableObjectAtIndex(list, 0) integerValue]; - pigeonResult.unit = [GetNullableObjectAtIndex(list, 1) integerValue]; - return pigeonResult; -} -+ (nullable SKProductSubscriptionPeriodMessage *)nullableFromList:(NSArray *)list { - return (list) ? [SKProductSubscriptionPeriodMessage fromList:list] : nil; -} -- (NSArray *)toList { - return @[ - @(self.numberOfUnits), - @(self.unit), - ]; -} -@end - -@interface InAppPurchaseAPICodecReader : FlutterStandardReader -@end -@implementation InAppPurchaseAPICodecReader -- (nullable id)readValueOfType:(UInt8)type { - switch (type) { - case 128: - return [SKErrorMessage fromList:[self readValue]]; - case 129: - return [SKPaymentDiscountMessage fromList:[self readValue]]; - case 130: - return [SKPaymentMessage fromList:[self readValue]]; - case 131: - return [SKPaymentTransactionMessage fromList:[self readValue]]; - case 132: - return [SKPriceLocaleMessage fromList:[self readValue]]; - case 133: - return [SKProductDiscountMessage fromList:[self readValue]]; - case 134: - return [SKProductMessage fromList:[self readValue]]; - case 135: - return [SKProductSubscriptionPeriodMessage fromList:[self readValue]]; - case 136: - return [SKProductsResponseMessage fromList:[self readValue]]; - case 137: - return [SKStorefrontMessage fromList:[self readValue]]; - default: - return [super readValueOfType:type]; - } -} -@end - -@interface InAppPurchaseAPICodecWriter : FlutterStandardWriter -@end -@implementation InAppPurchaseAPICodecWriter -- (void)writeValue:(id)value { - if ([value isKindOfClass:[SKErrorMessage class]]) { - [self writeByte:128]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[SKPaymentDiscountMessage class]]) { - [self writeByte:129]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[SKPaymentMessage class]]) { - [self writeByte:130]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[SKPaymentTransactionMessage class]]) { - [self writeByte:131]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[SKPriceLocaleMessage class]]) { - [self writeByte:132]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[SKProductDiscountMessage class]]) { - [self writeByte:133]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[SKProductMessage class]]) { - [self writeByte:134]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[SKProductSubscriptionPeriodMessage class]]) { - [self writeByte:135]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[SKProductsResponseMessage class]]) { - [self writeByte:136]; - [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[SKStorefrontMessage class]]) { - [self writeByte:137]; - [self writeValue:[value toList]]; - } else { - [super writeValue:value]; - } -} -@end - -@interface InAppPurchaseAPICodecReaderWriter : FlutterStandardReaderWriter -@end -@implementation InAppPurchaseAPICodecReaderWriter -- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { - return [[InAppPurchaseAPICodecWriter alloc] initWithData:data]; -} -- (FlutterStandardReader *)readerWithData:(NSData *)data { - return [[InAppPurchaseAPICodecReader alloc] initWithData:data]; -} -@end - -NSObject *InAppPurchaseAPIGetCodec(void) { - static FlutterStandardMessageCodec *sSharedObject = nil; - static dispatch_once_t sPred = 0; - dispatch_once(&sPred, ^{ - InAppPurchaseAPICodecReaderWriter *readerWriter = - [[InAppPurchaseAPICodecReaderWriter alloc] init]; - sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; - }); - return sSharedObject; -} - -void SetUpInAppPurchaseAPI(id binaryMessenger, - NSObject *api) { - /// Returns if the current device is able to make payments - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName: - @"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.canMakePayments" - binaryMessenger:binaryMessenger - codec:InAppPurchaseAPIGetCodec()]; - if (api) { - NSCAssert( - [api respondsToSelector:@selector(canMakePaymentsWithError:)], - @"InAppPurchaseAPI api (%@) doesn't respond to @selector(canMakePaymentsWithError:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - NSNumber *output = [api canMakePaymentsWithError:&error]; - callback(wrapResult(output, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.transactions" - binaryMessenger:binaryMessenger - codec:InAppPurchaseAPIGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(transactionsWithError:)], - @"InAppPurchaseAPI api (%@) doesn't respond to @selector(transactionsWithError:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - NSArray *output = [api transactionsWithError:&error]; - callback(wrapResult(output, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.storefront" - binaryMessenger:binaryMessenger - codec:InAppPurchaseAPIGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(storefrontWithError:)], - @"InAppPurchaseAPI api (%@) doesn't respond to @selector(storefrontWithError:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - SKStorefrontMessage *output = [api storefrontWithError:&error]; - callback(wrapResult(output, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.addPayment" - binaryMessenger:binaryMessenger - codec:InAppPurchaseAPIGetCodec()]; - if (api) { - NSCAssert( - [api respondsToSelector:@selector(addPaymentPaymentMap:error:)], - @"InAppPurchaseAPI api (%@) doesn't respond to @selector(addPaymentPaymentMap:error:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSDictionary *arg_paymentMap = GetNullableObjectAtIndex(args, 0); - FlutterError *error; - [api addPaymentPaymentMap:arg_paymentMap error:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName: - @"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.startProductRequest" - binaryMessenger:binaryMessenger - codec:InAppPurchaseAPIGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(startProductRequestProductIdentifiers: - completion:)], - @"InAppPurchaseAPI api (%@) doesn't respond to " - @"@selector(startProductRequestProductIdentifiers:completion:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSArray *arg_productIdentifiers = GetNullableObjectAtIndex(args, 0); - [api startProductRequestProductIdentifiers:arg_productIdentifiers - completion:^(SKProductsResponseMessage *_Nullable output, - FlutterError *_Nullable error) { - callback(wrapResult(output, error)); - }]; - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName: - @"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.finishTransaction" - binaryMessenger:binaryMessenger - codec:InAppPurchaseAPIGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(finishTransactionFinishMap:error:)], - @"InAppPurchaseAPI api (%@) doesn't respond to " - @"@selector(finishTransactionFinishMap:error:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSDictionary *arg_finishMap = GetNullableObjectAtIndex(args, 0); - FlutterError *error; - [api finishTransactionFinishMap:arg_finishMap error:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName: - @"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.restoreTransactions" - binaryMessenger:binaryMessenger - codec:InAppPurchaseAPIGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(restoreTransactionsApplicationUserName:error:)], - @"InAppPurchaseAPI api (%@) doesn't respond to " - @"@selector(restoreTransactionsApplicationUserName:error:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSString *arg_applicationUserName = GetNullableObjectAtIndex(args, 0); - FlutterError *error; - [api restoreTransactionsApplicationUserName:arg_applicationUserName error:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI." - @"presentCodeRedemptionSheet" - binaryMessenger:binaryMessenger - codec:InAppPurchaseAPIGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(presentCodeRedemptionSheetWithError:)], - @"InAppPurchaseAPI api (%@) doesn't respond to " - @"@selector(presentCodeRedemptionSheetWithError:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - [api presentCodeRedemptionSheetWithError:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName: - @"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.retrieveReceiptData" - binaryMessenger:binaryMessenger - codec:InAppPurchaseAPIGetCodec()]; - if (api) { - NSCAssert( - [api respondsToSelector:@selector(retrieveReceiptDataWithError:)], - @"InAppPurchaseAPI api (%@) doesn't respond to @selector(retrieveReceiptDataWithError:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - NSString *output = [api retrieveReceiptDataWithError:&error]; - callback(wrapResult(output, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName: - @"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.refreshReceipt" - binaryMessenger:binaryMessenger - codec:InAppPurchaseAPIGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(refreshReceiptReceiptProperties:completion:)], - @"InAppPurchaseAPI api (%@) doesn't respond to " - @"@selector(refreshReceiptReceiptProperties:completion:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSDictionary *arg_receiptProperties = GetNullableObjectAtIndex(args, 0); - [api refreshReceiptReceiptProperties:arg_receiptProperties - completion:^(FlutterError *_Nullable error) { - callback(wrapResult(nil, error)); - }]; - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI." - @"startObservingPaymentQueue" - binaryMessenger:binaryMessenger - codec:InAppPurchaseAPIGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(startObservingPaymentQueueWithError:)], - @"InAppPurchaseAPI api (%@) doesn't respond to " - @"@selector(startObservingPaymentQueueWithError:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - [api startObservingPaymentQueueWithError:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI." - @"stopObservingPaymentQueue" - binaryMessenger:binaryMessenger - codec:InAppPurchaseAPIGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(stopObservingPaymentQueueWithError:)], - @"InAppPurchaseAPI api (%@) doesn't respond to " - @"@selector(stopObservingPaymentQueueWithError:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - [api stopObservingPaymentQueueWithError:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI." - @"registerPaymentQueueDelegate" - binaryMessenger:binaryMessenger - codec:InAppPurchaseAPIGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(registerPaymentQueueDelegateWithError:)], - @"InAppPurchaseAPI api (%@) doesn't respond to " - @"@selector(registerPaymentQueueDelegateWithError:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - [api registerPaymentQueueDelegateWithError:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI." - @"removePaymentQueueDelegate" - binaryMessenger:binaryMessenger - codec:InAppPurchaseAPIGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(removePaymentQueueDelegateWithError:)], - @"InAppPurchaseAPI api (%@) doesn't respond to " - @"@selector(removePaymentQueueDelegateWithError:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - [api removePaymentQueueDelegateWithError:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI." - @"showPriceConsentIfNeeded" - binaryMessenger:binaryMessenger - codec:InAppPurchaseAPIGetCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(showPriceConsentIfNeededWithError:)], - @"InAppPurchaseAPI api (%@) doesn't respond to " - @"@selector(showPriceConsentIfNeededWithError:)", - api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - [api showPriceConsentIfNeededWithError:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } -} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Resources/PrivacyInfo.xcprivacy b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Resources/PrivacyInfo.xcprivacy deleted file mode 100644 index a34b7e2e6..000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Resources/PrivacyInfo.xcprivacy +++ /dev/null @@ -1,14 +0,0 @@ - - - - - NSPrivacyTrackingDomains - - NSPrivacyAccessedAPITypes - - NSPrivacyCollectedDataTypes - - NSPrivacyTracking - - - diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec index 428c61254..66e2f1bab 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/in_app_purchase_storekit.podspec @@ -20,8 +20,7 @@ Downloaded by pub (not CocoaPods). s.public_header_files = 'Classes/**/*.h' s.ios.dependency 'Flutter' s.osx.dependency 'FlutterMacOS' - s.ios.deployment_target = '12.0' + s.ios.deployment_target = '11.0' s.osx.deployment_target = '10.15' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } - s.resource_bundles = {'in_app_purchase_storekit_privacy' => ['Resources/PrivacyInfo.xcprivacy']} end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Flutter/AppFrameworkInfo.plist b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Flutter/AppFrameworkInfo.plist index 7c5696400..9625e105d 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 12.0 + 11.0 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile index 035842459..4f563887c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '12.0' +# platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj index 06e0b3ac9..d5047192e 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj @@ -221,7 +221,6 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 67CBAA37FA50343E43E988F6 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -258,7 +257,7 @@ isa = PBXProject; attributes = { DefaultBuildSystemTypeForWorkspace = Original; - LastUpgradeCheck = 1510; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "The Flutter Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -333,24 +332,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 67CBAA37FA50343E43E988F6 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/in_app_purchase_storekit/in_app_purchase_storekit_privacy.bundle", - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/in_app_purchase_storekit_privacy.bundle", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 95C7A5986B77A8DF76F6DF3A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -515,7 +496,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -565,7 +546,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -586,10 +567,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -613,10 +591,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -641,11 +616,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = RunnerTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; @@ -668,11 +639,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = RunnerTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 477131feb..fc4170f31 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> + parallelizable = "YES"> =3.16.6" + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" dependencies: flutter: diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m index 905903df0..f8aa0e34a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m @@ -5,7 +5,6 @@ #import #import #import "FIAPaymentQueueHandler.h" -#import "InAppPurchasePlugin+TestOnly.h" #import "Stubs.h" @import in_app_purchase_storekit; @@ -27,315 +26,156 @@ - (void)tearDown { } -- (void)testCanMakePayments { - FlutterError *error; - NSNumber *result = [self.plugin canMakePaymentsWithError:&error]; - XCTAssertTrue([result boolValue]); - XCTAssertNil(error); -} - -- (void)testPaymentQueueStorefront { - if (@available(iOS 13, macOS 10.15, *)) { - SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class); - NSDictionary *storefrontMap = @{ - @"countryCode" : @"USA", - @"identifier" : @"unique_identifier", - }; - - OCMStub(mockQueue.storefront).andReturn([[SKStorefrontStub alloc] initWithMap:storefrontMap]); - - self.plugin.paymentQueueHandler = - [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue - transactionsUpdated:nil - transactionRemoved:nil - restoreTransactionFailed:nil - restoreCompletedTransactionsFinished:nil - shouldAddStorePayment:nil - updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; - - FlutterError *error; - SKStorefrontMessage *result = [self.plugin storefrontWithError:&error]; - - XCTAssertEqualObjects(result.countryCode, storefrontMap[@"countryCode"]); - XCTAssertEqualObjects(result.identifier, storefrontMap[@"identifier"]); - XCTAssertNil(error); - } else { - NSLog(@"Skip testPaymentQueueStorefront for iOS lower than 13.0 or macOS lower than 10.15."); - } -} - -- (void)testPaymentQueueStorefrontReturnsNil { - if (@available(iOS 13, macOS 10.15, *)) { - SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class); - - OCMStub(mockQueue.storefront).andReturn(nil); - - self.plugin.paymentQueueHandler = - [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue - transactionsUpdated:nil - transactionRemoved:nil - restoreTransactionFailed:nil - restoreCompletedTransactionsFinished:nil - shouldAddStorePayment:nil - updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; - - FlutterError *error; - SKStorefrontMessage *resultMap = [self.plugin storefrontWithError:&error]; - - XCTAssertNil(resultMap); - XCTAssertNil(error); - } else { - NSLog(@"Skip testPaymentQueueStorefront for iOS lower than 13.0 or macOS lower than 10.15."); - } -} - -- (void)testGetProductResponse { - NSArray *argument = @[ @"123" ]; +- (void)testInvalidMethodCall { XCTestExpectation *expectation = - [self expectationWithDescription:@"completion handler successfully called"]; - [self.plugin - startProductRequestProductIdentifiers:argument - completion:^(SKProductsResponseMessage *_Nullable response, - FlutterError *_Nullable startProductRequestError) { - XCTAssert( - [response isKindOfClass:[SKProductsResponseMessage class]]); - XCTAssertEqual(response.products.count, 1); - XCTAssertEqual(response.invalidProductIdentifiers.count, 0); - XCTAssertEqual(response.products[0].productIdentifier, @"123"); - [expectation fulfill]; - }]; + [self expectationWithDescription:@"expect result to be not implemented"]; + FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"invalid" arguments:NULL]; + __block id result; + [self.plugin handleMethodCall:call + result:^(id r) { + [expectation fulfill]; + result = r; + }]; [self waitForExpectations:@[ expectation ] timeout:5]; + XCTAssertEqual(result, FlutterMethodNotImplemented); } -- (void)testFinishTransactionSucceeds { - NSDictionary *args = @{ - @"transactionIdentifier" : @"567", - @"productIdentifier" : @"unique_identifier", - }; - - NSDictionary *transactionMap = @{ - @"transactionIdentifier" : @"567", - @"transactionState" : @(SKPaymentTransactionStatePurchasing), - @"payment" : [NSNull null], - @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" - code:123 - userInfo:@{}]], - @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), - }; - - SKPaymentTransactionStub *paymentTransaction = - [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; - NSArray *array = @[ paymentTransaction ]; - - FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - OCMStub([mockHandler getUnfinishedTransactions]).andReturn(array); - - self.plugin.paymentQueueHandler = mockHandler; - - FlutterError *error; - [self.plugin finishTransactionFinishMap:args error:&error]; - - XCTAssertNil(error); -} - -- (void)testFinishTransactionSucceedsWithNilTransaction { - NSDictionary *args = @{ - @"transactionIdentifier" : [NSNull null], - @"productIdentifier" : @"unique_identifier", - }; - - NSDictionary *paymentMap = @{ - @"productIdentifier" : @"123", - @"requestData" : @"abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh", - @"quantity" : @(2), - @"applicationUsername" : @"app user name", - @"simulatesAskToBuyInSandbox" : @(NO) - }; - - NSDictionary *transactionMap = @{ - @"transactionState" : @(SKPaymentTransactionStatePurchasing), - @"payment" : paymentMap, - @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" - code:123 - userInfo:@{}]], - @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), - }; - - SKPaymentTransactionStub *paymentTransaction = - [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; - - FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - OCMStub([mockHandler getUnfinishedTransactions]).andReturn(@[ paymentTransaction ]); - - self.plugin.paymentQueueHandler = mockHandler; - - FlutterError *error; - [self.plugin finishTransactionFinishMap:args error:&error]; - - XCTAssertNil(error); +- (void)testCanMakePayments { + XCTestExpectation *expectation = [self expectationWithDescription:@"expect result to be YES"]; + FlutterMethodCall *call = + [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue canMakePayments:]" + arguments:NULL]; + __block id result; + [self.plugin handleMethodCall:call + result:^(id r) { + [expectation fulfill]; + result = r; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; + XCTAssertEqual(result, @YES); } -- (void)testGetProductResponseWithRequestError { - NSArray *argument = @[ @"123" ]; +- (void)testGetProductResponse { XCTestExpectation *expectation = - [self expectationWithDescription:@"completion handler successfully called"]; - - id mockHandler = OCMClassMock([FIAPRequestHandler class]); - InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] - initWithReceiptManager:nil - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return mockHandler; - }]; - - NSError *error = [NSError errorWithDomain:@"errorDomain" - code:0 - userInfo:@{NSLocalizedDescriptionKey : @"description"}]; - - OCMStub([mockHandler - startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], error, - nil])]); - - [plugin - startProductRequestProductIdentifiers:argument - completion:^(SKProductsResponseMessage *_Nullable response, - FlutterError *_Nullable startProductRequestError) { - [expectation fulfill]; - XCTAssertNotNil(error); - XCTAssertNotNil(startProductRequestError); - XCTAssertEqualObjects( - startProductRequestError.code, - @"storekit_getproductrequest_platform_error"); - }]; + [self expectationWithDescription:@"expect response contains 1 item"]; + FlutterMethodCall *call = [FlutterMethodCall + methodCallWithMethodName:@"-[InAppPurchasePlugin startProductRequest:result:]" + arguments:@[ @"123" ]]; + __block id result; + [self.plugin handleMethodCall:call + result:^(id r) { + [expectation fulfill]; + result = r; + }]; [self waitForExpectations:@[ expectation ] timeout:5]; + XCTAssert([result isKindOfClass:[NSDictionary class]]); + NSArray *resultArray = [result objectForKey:@"products"]; + XCTAssertEqual(resultArray.count, 1); + XCTAssertTrue([resultArray.firstObject[@"productIdentifier"] isEqualToString:@"123"]); } -- (void)testGetProductResponseWithNoResponse { - NSArray *argument = @[ @"123" ]; +- (void)testAddPaymentShouldReturnFlutterErrorWhenArgumentsAreInvalid { XCTestExpectation *expectation = - [self expectationWithDescription:@"completion handler successfully called"]; - - id mockHandler = OCMClassMock([FIAPRequestHandler class]); - - InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] - initWithReceiptManager:nil - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return mockHandler; - }]; - - NSError *error = [NSError errorWithDomain:@"errorDomain" - code:0 - userInfo:@{NSLocalizedDescriptionKey : @"description"}]; - - OCMStub([mockHandler - startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], - [NSNull null], nil])]); - - [plugin - startProductRequestProductIdentifiers:argument - completion:^(SKProductsResponseMessage *_Nullable response, - FlutterError *_Nullable startProductRequestError) { - [expectation fulfill]; - XCTAssertNotNil(error); - XCTAssertNotNil(startProductRequestError); - XCTAssertEqualObjects(startProductRequestError.code, - @"storekit_platform_no_response"); - }]; + [self expectationWithDescription: + @"Result should contain a FlutterError when invalid parameters are passed in."]; + NSString *argument = @"Invalid argument"; + FlutterMethodCall *call = + [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin addPayment:result:]" + arguments:argument]; + [self.plugin handleMethodCall:call + result:^(id _Nullable result) { + FlutterError *error = result; + XCTAssertEqualObjects(@"storekit_invalid_argument", error.code); + XCTAssertEqualObjects(@"Argument type of addPayment is not a Dictionary", + error.message); + XCTAssertEqualObjects(argument, error.details); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; } - (void)testAddPaymentShouldReturnFlutterErrorWhenPaymentFails { - NSDictionary *argument = @{ + NSDictionary *arguments = @{ @"productIdentifier" : @"123", @"quantity" : @(1), @"simulatesAskToBuyInSandbox" : @YES, }; + XCTestExpectation *expectation = + [self expectationWithDescription:@"Result should return failed state."]; + FlutterMethodCall *call = + [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin addPayment:result:]" + arguments:arguments]; FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(NO); self.plugin.paymentQueueHandler = mockHandler; - FlutterError *error; - - [self.plugin addPaymentPaymentMap:argument error:&error]; + [self.plugin handleMethodCall:call + result:^(id _Nullable result) { + FlutterError *error = result; + XCTAssertEqualObjects(@"storekit_duplicate_product_object", error.code); + XCTAssertEqualObjects( + @"There is a pending transaction for the same product identifier. " + @"Please either wait for it to be finished or finish it manually " + @"using `completePurchase` to avoid edge cases.", + error.message); + XCTAssertEqualObjects(arguments, error.details); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; OCMVerify(times(1), [mockHandler addPayment:[OCMArg any]]); - XCTAssertEqualObjects(@"storekit_duplicate_product_object", error.code); - XCTAssertEqualObjects(@"There is a pending transaction for the same product identifier. " - @"Please either wait for it to be finished or finish it manually " - @"using `completePurchase` to avoid edge cases.", - error.message); - XCTAssertEqualObjects(argument, error.details); -} - -- (void)testAddPaymentShouldReturnFlutterErrorWhenInvalidProduct { - NSDictionary *argument = @{ - // stubbed function will return nil for an empty productIdentifier - @"productIdentifier" : @"", - @"quantity" : @(1), - @"simulatesAskToBuyInSandbox" : @YES, - }; - - FlutterError *error; - - [self.plugin addPaymentPaymentMap:argument error:&error]; - - XCTAssertEqualObjects(@"storekit_invalid_payment_object", error.code); - XCTAssertEqualObjects( - @"You have requested a payment for an invalid product. Either the " - @"`productIdentifier` of the payment is not valid or the product has not been " - @"fetched before adding the payment to the payment queue.", - error.message); - XCTAssertEqualObjects(argument, error.details); } - (void)testAddPaymentSuccessWithoutPaymentDiscount { - NSDictionary *argument = @{ - @"productIdentifier" : @"123", - @"quantity" : @(1), - @"simulatesAskToBuyInSandbox" : @YES, - }; - + XCTestExpectation *expectation = + [self expectationWithDescription:@"Result should return success state"]; + FlutterMethodCall *call = + [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin addPayment:result:]" + arguments:@{ + @"productIdentifier" : @"123", + @"quantity" : @(1), + @"simulatesAskToBuyInSandbox" : @YES, + }]; FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(YES); self.plugin.paymentQueueHandler = mockHandler; - FlutterError *error; - - [self.plugin addPaymentPaymentMap:argument error:&error]; - - XCTAssertNil(error); - OCMVerify(times(1), [mockHandler addPayment:[OCMArg checkWithBlock:^BOOL(id obj) { - SKPayment *payment = obj; - XCTAssert(payment != nil); - XCTAssertEqual(payment.productIdentifier, @"123"); - XCTAssert(payment.quantity == 1); - return YES; - }]]); + [self.plugin handleMethodCall:call + result:^(id _Nullable result) { + XCTAssertNil(result); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; } - (void)testAddPaymentSuccessWithPaymentDiscount { - NSDictionary *argument = @{ - @"productIdentifier" : @"123", - @"quantity" : @(1), - @"simulatesAskToBuyInSandbox" : @YES, - @"paymentDiscount" : @{ - @"identifier" : @"test_identifier", - @"keyIdentifier" : @"test_key_identifier", - @"nonce" : @"4a11a9cc-3bc3-11ec-8d3d-0242ac130003", - @"signature" : @"test_signature", - @"timestamp" : @(1635847102), - } - }; + XCTestExpectation *expectation = + [self expectationWithDescription:@"Result should return success state"]; + FlutterMethodCall *call = + [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin addPayment:result:]" + arguments:@{ + @"productIdentifier" : @"123", + @"quantity" : @(1), + @"simulatesAskToBuyInSandbox" : @YES, + @"paymentDiscount" : @{ + @"identifier" : @"test_identifier", + @"keyIdentifier" : @"test_key_identifier", + @"nonce" : @"4a11a9cc-3bc3-11ec-8d3d-0242ac130003", + @"signature" : @"test_signature", + @"timestamp" : @(1635847102), + } + }]; FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(YES); self.plugin.paymentQueueHandler = mockHandler; - - FlutterError *error; - - [self.plugin addPaymentPaymentMap:argument error:&error]; - XCTAssertNil(error); + [self.plugin handleMethodCall:call + result:^(id _Nullable result) { + XCTAssertNil(result); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; OCMVerify( times(1), [mockHandler @@ -360,7 +200,9 @@ - (void)testAddPaymentFailureWithInvalidPaymentDiscount { // Support for payment discount is only available on iOS 12.2 and higher. if (@available(iOS 12.2, *)) { - NSDictionary *argument = @{ + XCTestExpectation *expectation = + [self expectationWithDescription:@"Result should return success state"]; + NSDictionary *arguments = @{ @"productIdentifier" : @"123", @"quantity" : @(1), @"simulatesAskToBuyInSandbox" : @YES, @@ -371,42 +213,55 @@ @"timestamp" : @(1635847102), } }; + FlutterMethodCall *call = + [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin addPayment:result:]" + arguments:arguments]; FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); id translator = OCMClassMock(FIAObjectTranslator.class); - NSString *errorMsg = @"Some error occurred"; + NSString *error = @"Some error occurred"; OCMStub(ClassMethod([translator getSKPaymentDiscountFromMap:[OCMArg any] - withError:(NSString __autoreleasing **)[OCMArg setTo:errorMsg]])) + withError:(NSString __autoreleasing **)[OCMArg setTo:error]])) .andReturn(nil); self.plugin.paymentQueueHandler = mockHandler; - FlutterError *error; - - [self.plugin addPaymentPaymentMap:argument error:&error]; - - XCTAssertEqualObjects(@"storekit_invalid_payment_discount_object", error.code); - XCTAssertEqualObjects(@"You have requested a payment and specified a " - @"payment discount with invalid properties. Some error occurred", - error.message); - XCTAssertEqualObjects(argument, error.details); + [self.plugin + handleMethodCall:call + result:^(id _Nullable result) { + FlutterError *error = result; + XCTAssertEqualObjects(@"storekit_invalid_payment_discount_object", error.code); + XCTAssertEqualObjects( + @"You have requested a payment and specified a " + @"payment discount with invalid properties. Some error occurred", + error.message); + XCTAssertEqualObjects(arguments, error.details); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; OCMVerify(never(), [mockHandler addPayment:[OCMArg any]]); } } - (void)testAddPaymentWithNullSandboxArgument { - NSDictionary *argument = @{ - @"productIdentifier" : @"123", - @"quantity" : @(1), - @"simulatesAskToBuyInSandbox" : [NSNull null], - }; - + XCTestExpectation *expectation = + [self expectationWithDescription:@"result should return success state"]; + FlutterMethodCall *call = + [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin addPayment:result:]" + arguments:@{ + @"productIdentifier" : @"123", + @"quantity" : @(1), + @"simulatesAskToBuyInSandbox" : [NSNull null], + }]; FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(YES); self.plugin.paymentQueueHandler = mockHandler; - FlutterError *error; - - [self.plugin addPaymentPaymentMap:argument error:&error]; + [self.plugin handleMethodCall:call + result:^(id _Nullable result) { + XCTAssertNil(result); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; OCMVerify(times(1), [mockHandler addPayment:[OCMArg checkWithBlock:^BOOL(id obj) { SKPayment *payment = obj; return !payment.simulatesAskToBuyInSandbox; @@ -416,10 +271,11 @@ - (void)testRestoreTransactions { XCTestExpectation *expectation = [self expectationWithDescription:@"result successfully restore transactions"]; - + FlutterMethodCall *call = [FlutterMethodCall + methodCallWithMethodName:@"-[InAppPurchasePlugin restoreTransactions:result:]" + arguments:nil]; SKPaymentQueueStub *queue = [SKPaymentQueueStub new]; queue.testState = SKPaymentTransactionStatePurchased; - __block BOOL callbackInvoked = NO; self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { @@ -434,17 +290,25 @@ updatedDownloads:nil transactionCache:OCMClassMock(FIATransactionCache.class)]; [queue addTransactionObserver:self.plugin.paymentQueueHandler]; - - FlutterError *error; - [self.plugin restoreTransactionsApplicationUserName:nil error:&error]; - + [self.plugin handleMethodCall:call + result:^(id r){ + }]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertTrue(callbackInvoked); } - (void)testRetrieveReceiptDataSuccess { - FlutterError *error; - NSString *result = [self.plugin retrieveReceiptDataWithError:&error]; + XCTestExpectation *expectation = [self expectationWithDescription:@"receipt data retrieved"]; + FlutterMethodCall *call = [FlutterMethodCall + methodCallWithMethodName:@"-[InAppPurchasePlugin retrieveReceiptData:result:]" + arguments:nil]; + __block NSDictionary *result; + [self.plugin handleMethodCall:call + result:^(id r) { + result = r; + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertNotNil(result); XCTAssert([result isKindOfClass:[NSString class]]); } @@ -452,100 +316,76 @@ - (void)testRetrieveReceiptDataNil { NSBundle *mockBundle = OCMPartialMock([NSBundle mainBundle]); OCMStub(mockBundle.appStoreReceiptURL).andReturn(nil); - FlutterError *error; - NSString *result = [self.plugin retrieveReceiptDataWithError:&error]; + XCTestExpectation *expectation = [self expectationWithDescription:@"nil receipt data retrieved"]; + FlutterMethodCall *call = [FlutterMethodCall + methodCallWithMethodName:@"-[InAppPurchasePlugin retrieveReceiptData:result:]" + arguments:nil]; + __block NSDictionary *result; + [self.plugin handleMethodCall:call + result:^(id r) { + result = r; + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertNil(result); } - (void)testRetrieveReceiptDataError { + XCTestExpectation *expectation = [self expectationWithDescription:@"receipt data retrieved"]; + FlutterMethodCall *call = [FlutterMethodCall + methodCallWithMethodName:@"-[InAppPurchasePlugin retrieveReceiptData:result:]" + arguments:nil]; + __block NSDictionary *result; self.receiptManagerStub.returnError = YES; - - FlutterError *error; - NSString *result = [self.plugin retrieveReceiptDataWithError:&error]; - - XCTAssertNil(result); - XCTAssertNotNil(error); - XCTAssert([error.code isKindOfClass:[NSString class]]); - NSDictionary *details = error.details; + [self.plugin handleMethodCall:call + result:^(id r) { + result = r; + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; + XCTAssertNotNil(result); + XCTAssert([result isKindOfClass:[FlutterError class]]); + NSDictionary *details = ((FlutterError *)result).details; XCTAssertNotNil(details[@"error"]); NSNumber *errorCode = (NSNumber *)details[@"error"][@"code"]; XCTAssertEqual(errorCode, [NSNumber numberWithInteger:99]); } - (void)testRefreshReceiptRequest { - XCTestExpectation *expectation = - [self expectationWithDescription:@"completion handler successfully called"]; - [self.plugin refreshReceiptReceiptProperties:nil - completion:^(FlutterError *_Nullable error) { - [expectation fulfill]; - }]; - [self waitForExpectations:@[ expectation ] timeout:5]; -} - -- (void)testRefreshReceiptRequestWithParams { - NSDictionary *properties = @{ - @"isExpired" : @NO, - @"isRevoked" : @NO, - @"isVolumePurchase" : @NO, - }; - - XCTestExpectation *expectation = - [self expectationWithDescription:@"completion handler successfully called"]; - [self.plugin refreshReceiptReceiptProperties:properties - completion:^(FlutterError *_Nullable error) { - [expectation fulfill]; - }]; + XCTestExpectation *expectation = [self expectationWithDescription:@"expect success"]; + FlutterMethodCall *call = + [FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin refreshReceipt:result:]" + arguments:nil]; + __block BOOL result = NO; + [self.plugin handleMethodCall:call + result:^(id r) { + result = YES; + [expectation fulfill]; + }]; [self waitForExpectations:@[ expectation ] timeout:5]; + XCTAssertTrue(result); } -- (void)testRefreshReceiptRequestWithError { - NSDictionary *properties = @{ - @"isExpired" : @NO, - @"isRevoked" : @NO, - @"isVolumePurchase" : @NO, - }; +- (void)testPresentCodeRedemptionSheet { XCTestExpectation *expectation = - [self expectationWithDescription:@"completion handler successfully called"]; - - id mockHandler = OCMClassMock([FIAPRequestHandler class]); - InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] - initWithReceiptManager:nil - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return mockHandler; - }]; - - NSError *recieptError = [NSError errorWithDomain:@"errorDomain" - code:0 - userInfo:@{NSLocalizedDescriptionKey : @"description"}]; - - OCMStub([mockHandler - startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], - recieptError, nil])]); - - [plugin refreshReceiptReceiptProperties:properties - completion:^(FlutterError *_Nullable error) { - XCTAssertNotNil(error); - XCTAssertEqualObjects( - error.code, @"storekit_refreshreceiptrequest_platform_error"); - [expectation fulfill]; - }]; + [self expectationWithDescription:@"expect successfully present Code Redemption Sheet"]; + FlutterMethodCall *call = [FlutterMethodCall + methodCallWithMethodName:@"-[InAppPurchasePlugin presentCodeRedemptionSheet:result:]" + arguments:nil]; + __block BOOL callbackInvoked = NO; + [self.plugin handleMethodCall:call + result:^(id r) { + callbackInvoked = YES; + [expectation fulfill]; + }]; [self waitForExpectations:@[ expectation ] timeout:5]; + XCTAssertTrue(callbackInvoked); } -/// presentCodeRedemptionSheetWithError:error is only available on iOS -#if TARGET_OS_IOS -- (void)testPresentCodeRedemptionSheet { - FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); - self.plugin.paymentQueueHandler = mockHandler; - - FlutterError *error; - [self.plugin presentCodeRedemptionSheetWithError:&error]; - - OCMVerify(times(1), [mockHandler presentCodeRedemptionSheet]); -} -#endif - - (void)testGetPendingTransactions { + XCTestExpectation *expectation = [self expectationWithDescription:@"expect success"]; + FlutterMethodCall *call = + [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue transactions]" arguments:nil]; SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class); NSDictionary *transactionMap = @{ @"transactionIdentifier" : [NSNull null], @@ -560,6 +400,7 @@ OCMStub(mockQueue.transactions).andReturn(@[ [[SKPaymentTransactionStub alloc] initWithMap:transactionMap] ]); + __block NSArray *resultArray; self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue transactionsUpdated:nil @@ -569,41 +410,123 @@ shouldAddStorePayment:nil updatedDownloads:nil transactionCache:OCMClassMock(FIATransactionCache.class)]; - FlutterError *error; - SKPaymentTransactionStub *original = - [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; + [self.plugin handleMethodCall:call + result:^(id r) { + resultArray = r; + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; + XCTAssertEqualObjects(resultArray, @[ transactionMap ]); +} - SKPaymentTransactionMessage *originalPigeon = - [FIAObjectTranslator convertTransactionToPigeon:original]; - SKPaymentTransactionMessage *result = [self.plugin transactionsWithError:&error][0]; +- (void)testPaymentQueueStorefront { + if (@available(iOS 13, macOS 10.15, *)) { + // storefront is not nil + XCTestExpectation *expectation = [self expectationWithDescription:@"expect success"]; + FlutterMethodCall *call = + [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue storefront]" arguments:nil]; + SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class); + NSDictionary *storefrontMap = @{ + @"countryCode" : @"USA", + @"identifier" : @"unique_identifier", + }; + OCMStub(mockQueue.storefront).andReturn([[SKStorefrontStub alloc] initWithMap:storefrontMap]); - XCTAssertEqualObjects([self paymentTransactionToList:result], - [self paymentTransactionToList:originalPigeon]); + __block NSDictionary *resultMap; + self.plugin.paymentQueueHandler = + [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:OCMClassMock(FIATransactionCache.class)]; + [self.plugin handleMethodCall:call + result:^(id r) { + resultMap = r; + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; + XCTAssertEqualObjects(resultMap, storefrontMap); + } else { + NSLog(@"Skip testPaymentQueueStorefront for iOS lower than 13.0 or macOS lower than 10.15."); + } +} + +- (void)testPaymentQueueStorefrontReturnsNil { + if (@available(iOS 13, macOS 10.15, *)) { + XCTestExpectation *expectation = [self expectationWithDescription:@"expect success"]; + FlutterMethodCall *call = + [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue storefront]" arguments:nil]; + SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class); + OCMStub(mockQueue.storefront).andReturn(nil); + + __block NSDictionary *resultMap; + self.plugin.paymentQueueHandler = + [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:OCMClassMock(FIATransactionCache.class)]; + [self.plugin handleMethodCall:call + result:^(id r) { + resultMap = r; + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:5]; + XCTAssertNil(resultMap); + } else { + NSLog(@"Skip testPaymentQueueStorefront for iOS lower than 13.0 or macOS lower than 10.15."); + } } - (void)testStartObservingPaymentQueue { + XCTestExpectation *expectation = + [self expectationWithDescription:@"Should return success result"]; + FlutterMethodCall *startCall = [FlutterMethodCall + methodCallWithMethodName:@"-[SKPaymentQueue startObservingTransactionQueue]" + arguments:nil]; FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); self.plugin.paymentQueueHandler = mockHandler; + [self.plugin handleMethodCall:startCall + result:^(id _Nullable result) { + XCTAssertNil(result); + [expectation fulfill]; + }]; - FlutterError *error; - [self.plugin startObservingPaymentQueueWithError:&error]; - + [self waitForExpectations:@[ expectation ] timeout:5]; OCMVerify(times(1), [mockHandler startObservingPaymentQueue]); } - (void)testStopObservingPaymentQueue { + XCTestExpectation *expectation = + [self expectationWithDescription:@"Should return success result"]; + FlutterMethodCall *stopCall = + [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue stopObservingTransactionQueue]" + arguments:nil]; FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); self.plugin.paymentQueueHandler = mockHandler; + [self.plugin handleMethodCall:stopCall + result:^(id _Nullable result) { + XCTAssertNil(result); + [expectation fulfill]; + }]; - FlutterError *error; - [self.plugin stopObservingPaymentQueueWithError:&error]; - + [self waitForExpectations:@[ expectation ] timeout:5]; OCMVerify(times(1), [mockHandler stopObservingPaymentQueue]); } #if TARGET_OS_IOS - (void)testRegisterPaymentQueueDelegate { if (@available(iOS 13, *)) { + FlutterMethodCall *call = + [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue registerDelegate]" + arguments:nil]; + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new] transactionsUpdated:nil @@ -617,8 +540,9 @@ // Verify the delegate is nil before we register one. XCTAssertNil(self.plugin.paymentQueueHandler.delegate); - FlutterError *error; - [self.plugin registerPaymentQueueDelegateWithError:&error]; + [self.plugin handleMethodCall:call + result:^(id r){ + }]; // Verify the delegate is not nil after we registered one. XCTAssertNotNil(self.plugin.paymentQueueHandler.delegate); @@ -628,6 +552,10 @@ - (void)testRemovePaymentQueueDelegate { if (@available(iOS 13, *)) { + FlutterMethodCall *call = + [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue removeDelegate]" + arguments:nil]; + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new] transactionsUpdated:nil @@ -642,131 +570,27 @@ // Verify the delegate is not nil before removing it. XCTAssertNotNil(self.plugin.paymentQueueHandler.delegate); - FlutterError *error; - [self.plugin removePaymentQueueDelegateWithError:&error]; + [self.plugin handleMethodCall:call + result:^(id r){ + }]; // Verify the delegate is nill after removing it. XCTAssertNil(self.plugin.paymentQueueHandler.delegate); } } -- (void)testHandleTransactionsUpdated { - NSDictionary *transactionMap = @{ - @"transactionIdentifier" : @"567", - @"transactionState" : @(SKPaymentTransactionStatePurchasing), - @"payment" : [NSNull null], - @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" - code:123 - userInfo:@{}]], - @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), - }; - - InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; - FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); - plugin.transactionObserverCallbackChannel = mockChannel; - OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); - - SKPaymentTransactionStub *paymentTransaction = - [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; - NSArray *array = [NSArray arrayWithObjects:paymentTransaction, nil]; - NSMutableArray *maps = [NSMutableArray new]; - [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransaction]]; - - [plugin handleTransactionsUpdated:array]; - OCMVerify(times(1), [mockChannel invokeMethod:@"updatedTransactions" arguments:[OCMArg any]]); -} - -- (void)testHandleTransactionsRemoved { - NSDictionary *transactionMap = @{ - @"transactionIdentifier" : @"567", - @"transactionState" : @(SKPaymentTransactionStatePurchasing), - @"payment" : [NSNull null], - @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub" - code:123 - userInfo:@{}]], - @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), - }; - - InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; - FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); - plugin.transactionObserverCallbackChannel = mockChannel; - OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); - - SKPaymentTransactionStub *paymentTransaction = - [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; - NSArray *array = [NSArray arrayWithObjects:paymentTransaction, nil]; - NSMutableArray *maps = [NSMutableArray new]; - [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransaction]]; - - [plugin handleTransactionsRemoved:array]; - OCMVerify(times(1), [mockChannel invokeMethod:@"removedTransactions" arguments:maps]); -} - -- (void)testHandleTransactionRestoreFailed { - InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; - FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); - plugin.transactionObserverCallbackChannel = mockChannel; - OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); - - NSError *error; - [plugin handleTransactionRestoreFailed:error]; - OCMVerify(times(1), [mockChannel invokeMethod:@"restoreCompletedTransactionsFailed" - arguments:[FIAObjectTranslator getMapFromNSError:error]]); -} - -- (void)testRestoreCompletedTransactionsFinished { - InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; - FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); - plugin.transactionObserverCallbackChannel = mockChannel; - OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); - - [plugin restoreCompletedTransactionsFinished]; - OCMVerify(times(1), [mockChannel invokeMethod:@"paymentQueueRestoreCompletedTransactionsFinished" - arguments:nil]); -} - -- (void)testShouldAddStorePayment { - NSDictionary *paymentMap = @{ - @"productIdentifier" : @"123", - @"requestData" : @"abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh", - @"quantity" : @(2), - @"applicationUsername" : @"app user name", - @"simulatesAskToBuyInSandbox" : @(NO) - }; - - NSDictionary *productMap = @{ - @"price" : @"1", - @"priceLocale" : [FIAObjectTranslator getMapFromNSLocale:NSLocale.systemLocale], - @"productIdentifier" : @"123", - @"localizedTitle" : @"title", - @"localizedDescription" : @"des", - }; - - SKMutablePayment *payment = [FIAObjectTranslator getSKMutablePaymentFromMap:paymentMap]; - SKProductStub *product = [[SKProductStub alloc] initWithMap:productMap]; - - InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:nil]; - FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); - plugin.transactionObserverCallbackChannel = mockChannel; - OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); - - NSDictionary *args = @{ - @"payment" : [FIAObjectTranslator getMapFromSKPayment:payment], - @"product" : [FIAObjectTranslator getMapFromSKProduct:product] - }; - - BOOL result = [plugin shouldAddStorePayment:payment product:product]; - XCTAssertEqual(result, NO); - OCMVerify(times(1), [mockChannel invokeMethod:@"shouldAddStorePayment" arguments:args]); -} - #if TARGET_OS_IOS - (void)testShowPriceConsentIfNeeded { + FlutterMethodCall *call = + [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue showPriceConsentIfNeeded]" + arguments:nil]; + FIAPaymentQueueHandler *mockQueueHandler = OCMClassMock(FIAPaymentQueueHandler.class); self.plugin.paymentQueueHandler = mockQueueHandler; - FlutterError *error; - [self.plugin showPriceConsentIfNeededWithError:&error]; + [self.plugin handleMethodCall:call + result:^(id r){ + }]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpartial-availability" @@ -779,48 +603,4 @@ } #endif -// The following methods are deserializer copied from Pigeon's output. - -- (NSArray *)paymentTransactionToList:(SKPaymentTransactionMessage *)paymentTransaction { - return @[ - (paymentTransaction.payment ? [self paymentToList:paymentTransaction.payment] : [NSNull null]), - @(paymentTransaction.transactionState), - (paymentTransaction.originalTransaction - ? [self paymentTransactionToList:paymentTransaction.originalTransaction] - : [NSNull null]), - paymentTransaction.transactionTimeStamp ?: [NSNull null], - paymentTransaction.transactionIdentifier ?: [NSNull null], - (paymentTransaction.error ? [self errorToList:paymentTransaction.error] : [NSNull null]), - ]; -} - -- (NSArray *)paymentToList:(SKPaymentMessage *)payment { - return @[ - payment.productIdentifier ?: [NSNull null], - payment.applicationUsername ?: [NSNull null], - payment.requestData ?: [NSNull null], - @(payment.quantity), - @(payment.simulatesAskToBuyInSandbox), - (payment.paymentDiscount ? [self paymentDiscountToList:payment.paymentDiscount] - : [NSNull null]), - ]; -} - -- (NSArray *)paymentDiscountToList:(SKPaymentDiscountMessage *)discount { - return @[ - discount.identifier ?: [NSNull null], - discount.keyIdentifier ?: [NSNull null], - discount.nonce ?: [NSNull null], - discount.signature ?: [NSNull null], - @(discount.timestamp), - ]; -} - -- (NSArray *)errorToList:(SKErrorMessage *)error { - return @[ - @(error.code), - error.domain ?: [NSNull null], - error.userInfo ?: [NSNull null], - ]; -} @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h index 2ef8e2318..d4e8df3eb 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h @@ -23,7 +23,6 @@ API_AVAILABLE(ios(11.2), macos(10.13.2)) @end @interface SKProductRequestStub : SKProductsRequest -@property(assign, nonatomic) BOOL returnError; - (instancetype)initWithProductIdentifiers:(NSSet *)productIdentifiers; - (instancetype)initWithFailureError:(NSError *)error; @end @@ -35,9 +34,6 @@ API_AVAILABLE(ios(11.2), macos(10.13.2)) @interface InAppPurchasePluginStub : InAppPurchasePlugin @end -@interface SKRequestStub : SKRequest -@end - @interface SKPaymentQueueStub : SKPaymentQueue @property(assign, nonatomic) SKPaymentTransactionState testState; @property(strong, nonatomic, nullable) id observer; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m index b4dba710f..f5e44d78b 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m @@ -55,14 +55,16 @@ NSLocale *locale = NSLocale.systemLocale; [self setValue:locale ?: [NSNull null] forKey:@"priceLocale"]; [self setValue:map[@"downloadContentLengths"] ?: @(0) forKey:@"downloadContentLengths"]; - SKProductSubscriptionPeriodStub *period = - [[SKProductSubscriptionPeriodStub alloc] initWithMap:map[@"subscriptionPeriod"]]; - [self setValue:period ?: [NSNull null] forKey:@"subscriptionPeriod"]; - SKProductDiscountStub *discount = - [[SKProductDiscountStub alloc] initWithMap:map[@"introductoryPrice"]]; - [self setValue:discount ?: [NSNull null] forKey:@"introductoryPrice"]; - [self setValue:map[@"subscriptionGroupIdentifier"] ?: [NSNull null] - forKey:@"subscriptionGroupIdentifier"]; + if (@available(iOS 11.2, *)) { + SKProductSubscriptionPeriodStub *period = + [[SKProductSubscriptionPeriodStub alloc] initWithMap:map[@"subscriptionPeriod"]]; + [self setValue:period ?: [NSNull null] forKey:@"subscriptionPeriod"]; + SKProductDiscountStub *discount = + [[SKProductDiscountStub alloc] initWithMap:map[@"introductoryPrice"]]; + [self setValue:discount ?: [NSNull null] forKey:@"introductoryPrice"]; + [self setValue:map[@"subscriptionGroupIdentifier"] ?: [NSNull null] + forKey:@"subscriptionGroupIdentifier"]; + } if (@available(iOS 12.2, *)) { NSMutableArray *discounts = [[NSMutableArray alloc] init]; for (NSDictionary *discountMap in map[@"discounts"]) { @@ -111,13 +113,8 @@ for (NSString *identifier in self.identifers) { [productArray addObject:@{@"productIdentifier" : identifier}]; } - SKProductsResponseStub *response; - if (self.returnError) { - response = nil; - } else { - response = [[SKProductsResponseStub alloc] initWithMap:@{@"products" : productArray}]; - } - + SKProductsResponseStub *response = + [[SKProductsResponseStub alloc] initWithMap:@{@"products" : productArray}]; if (self.error) { [self.delegate request:self didFailWithError:self.error]; } else { @@ -145,6 +142,7 @@ @end @interface InAppPurchasePluginStub () + @end @implementation InAppPurchasePluginStub @@ -154,9 +152,6 @@ } - (SKProduct *)getProduct:(NSString *)productID { - if ([productID isEqualToString:@""]) { - return nil; - } return [[SKProductStub alloc] initWithProductID:productID]; } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m index 7ffe4c6ac..6f77fa72a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m @@ -21,9 +21,11 @@ @property(strong, nonatomic) NSDictionary *localeMap; @property(strong, nonatomic) NSDictionary *storefrontMap; @property(strong, nonatomic) NSDictionary *storefrontAndPaymentTransactionMap; + @end @implementation TranslatorTest + - (void)setUp { self.periodMap = @{@"numberOfUnits" : @(0), @"unit" : @(0)}; @@ -55,12 +57,17 @@ @"localizedTitle" : @"title", @"localizedDescription" : @"des", }]; - self.productMap[@"subscriptionPeriod"] = self.periodMap; - self.productMap[@"introductoryPrice"] = self.discountMap; + if (@available(iOS 11.2, *)) { + self.productMap[@"subscriptionPeriod"] = self.periodMap; + self.productMap[@"introductoryPrice"] = self.discountMap; + } if (@available(iOS 12.2, *)) { self.productMap[@"discounts"] = @[ self.discountMap ]; } - self.productMap[@"subscriptionGroupIdentifier"] = @"com.group"; + + if (@available(iOS 12.0, *)) { + self.productMap[@"subscriptionGroupIdentifier"] = @"com.group"; + } self.productResponseMap = @{@"products" : @[ self.productMap ], @"invalidProductIdentifiers" : @[]}; @@ -117,16 +124,20 @@ } - (void)testSKProductSubscriptionPeriodStubToMap { - SKProductSubscriptionPeriodStub *period = - [[SKProductSubscriptionPeriodStub alloc] initWithMap:self.periodMap]; - NSDictionary *map = [FIAObjectTranslator getMapFromSKProductSubscriptionPeriod:period]; - XCTAssertEqualObjects(map, self.periodMap); + if (@available(iOS 11.2, *)) { + SKProductSubscriptionPeriodStub *period = + [[SKProductSubscriptionPeriodStub alloc] initWithMap:self.periodMap]; + NSDictionary *map = [FIAObjectTranslator getMapFromSKProductSubscriptionPeriod:period]; + XCTAssertEqualObjects(map, self.periodMap); + } } - (void)testSKProductDiscountStubToMap { - SKProductDiscountStub *discount = [[SKProductDiscountStub alloc] initWithMap:self.discountMap]; - NSDictionary *map = [FIAObjectTranslator getMapFromSKProductDiscount:discount]; - XCTAssertEqualObjects(map, self.discountMap); + if (@available(iOS 11.2, *)) { + SKProductDiscountStub *discount = [[SKProductDiscountStub alloc] initWithMap:self.discountMap]; + NSDictionary *map = [FIAObjectTranslator getMapFromSKProductDiscount:discount]; + XCTAssertEqualObjects(map, self.discountMap); + } } - (void)testProductToMap { @@ -191,26 +202,6 @@ XCTAssertEqualObjects(expectedMap, map); } -- (void)testErrorWithNestedUnderlyingError { - NSError *underlyingError = [NSError errorWithDomain:SKErrorDomain code:2 userInfo:nil]; - NSError *mainError = - [NSError errorWithDomain:SKErrorDomain - code:3 - userInfo:@{@"nesting" : @{@"underlyingError" : underlyingError}}]; - NSDictionary *expectedMap = @{ - @"domain" : SKErrorDomain, - @"code" : @3, - @"userInfo" : @{ - @"nesting" : @{ - @"underlyingError" : @{@"domain" : SKErrorDomain, @"code" : @2, @"userInfo" : @{}}, - - } - } - }; - NSDictionary *map = [FIAObjectTranslator getMapFromNSError:mainError]; - XCTAssertEqualObjects(expectedMap, map); -} - - (void)testErrorWithUnsupportedUserInfo { NSError *error = [NSError errorWithDomain:SKErrorDomain code:3 @@ -420,99 +411,4 @@ } } -- (void)testSKPaymentDiscountConvertToPigeon { - if (@available(iOS 12.2, *)) { - NSString *error = nil; - SKPaymentDiscount *paymentDiscount = - [FIAObjectTranslator getSKPaymentDiscountFromMap:self.paymentDiscountMap withError:&error]; - SKPaymentDiscountMessage *paymentDiscountPigeon = - [FIAObjectTranslator convertPaymentDiscountToPigeon:paymentDiscount]; - - XCTAssertNotNil(paymentDiscountPigeon); - XCTAssertEqual(paymentDiscount.identifier, paymentDiscountPigeon.identifier); - XCTAssertEqual(paymentDiscount.keyIdentifier, paymentDiscount.keyIdentifier); - XCTAssertEqualObjects(paymentDiscount.nonce, - [[NSUUID alloc] initWithUUIDString:paymentDiscountPigeon.nonce]); - XCTAssertEqual(paymentDiscount.signature, paymentDiscountPigeon.signature); - XCTAssertEqual([paymentDiscount.timestamp intValue], paymentDiscountPigeon.timestamp); - } -} - -- (void)testSKErrorConvertToPigeon { - NSError *error = [NSError errorWithDomain:SKErrorDomain code:3 userInfo:@{@"key" : @42}]; - SKErrorMessage *msg = [SKErrorMessage makeWithCode:3 - domain:SKErrorDomain - userInfo:@{@"key" : @42}]; - - SKErrorMessage *skerror = [FIAObjectTranslator convertSKErrorToPigeon:error]; - XCTAssertEqual(skerror.domain, msg.domain); - XCTAssertEqual(skerror.code, msg.code); - XCTAssertEqualObjects(skerror.userInfo, msg.userInfo); -} - -- (void)testSKPaymentConvertToPigeon { - if (@available(iOS 12.2, *)) { - SKMutablePayment *payment = [FIAObjectTranslator getSKMutablePaymentFromMap:self.paymentMap]; - SKPaymentMessage *msg = [FIAObjectTranslator convertPaymentToPigeon:payment]; - - XCTAssertEqual(payment.productIdentifier, msg.productIdentifier); - XCTAssertEqualObjects(payment.requestData, - [msg.requestData dataUsingEncoding:NSUTF8StringEncoding]); - XCTAssertEqual(payment.quantity, msg.quantity); - XCTAssertEqual(payment.applicationUsername, msg.applicationUsername); - XCTAssertEqual(payment.simulatesAskToBuyInSandbox, msg.simulatesAskToBuyInSandbox); - } -} - -- (void)testSKPaymentTransactionConvertToPigeon { - SKPaymentTransactionStub *paymentTransaction = - [[SKPaymentTransactionStub alloc] initWithMap:self.transactionMap]; - - SKPaymentTransactionMessage *msg = - [FIAObjectTranslator convertTransactionToPigeon:paymentTransaction]; - - XCTAssertEqual(msg.payment, NULL); - XCTAssertEqual(msg.transactionState, SKPaymentTransactionStateMessagePurchasing); - XCTAssertEqual(paymentTransaction.transactionDate, - [NSDate dateWithTimeIntervalSince1970:[msg.transactionTimeStamp doubleValue]]); - XCTAssertEqual(paymentTransaction.transactionIdentifier, msg.transactionIdentifier); -} - -- (void)testSKProductResponseCovertToPigeon { - SKProductsResponseStub *response = - [[SKProductsResponseStub alloc] initWithMap:self.productResponseMap]; - SKProductsResponseMessage *responseMsg = - [FIAObjectTranslator convertProductsResponseToPigeon:response]; - - XCTAssertEqual(responseMsg.products.count, 1); - XCTAssertEqual(responseMsg.invalidProductIdentifiers.count, 0); - - SKProductMessage *productMsg = responseMsg.products[0]; - - // These values are being set in productResponseMap in setUp() - XCTAssertEqualObjects(productMsg.price, @"1"); - XCTAssertEqualObjects(productMsg.productIdentifier, @"123"); - XCTAssertEqualObjects(productMsg.localizedTitle, @"title"); - XCTAssertEqualObjects(productMsg.localizedDescription, @"des"); - XCTAssertEqualObjects(productMsg.subscriptionGroupIdentifier, @"com.group"); - - SKPriceLocaleMessage *localeMsg = productMsg.priceLocale; - SKProductSubscriptionPeriodMessage *subPeriod = productMsg.subscriptionPeriod; - SKProductDiscountMessage *introDiscount = productMsg.introductoryPrice; - NSArray *discounts = productMsg.discounts; - - XCTAssertEqualObjects(localeMsg.countryCode, nil); - XCTAssertEqualObjects(localeMsg.currencyCode, nil); - XCTAssertEqualObjects(localeMsg.currencySymbol, @"\u00a4"); - - XCTAssertEqual(subPeriod.unit, SKSubscriptionPeriodUnitMessageDay); - XCTAssertEqual(subPeriod.numberOfUnits, 0); - - XCTAssertEqualObjects(introDiscount.price, @"1"); - XCTAssertEqual(introDiscount.numberOfPeriods, 1); - XCTAssertEqual(introDiscount.paymentMode, SKProductDiscountPaymentModeMessagePayUpFront); - - XCTAssertEqual(discounts.count, 1); -} - @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart index acdc4a297..9dbdc4785 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart @@ -153,14 +153,6 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { ); return productDetailsResponse; } - - /// Returns the country code from SKStoreFrontWrapper. - /// - /// Uses the ISO 3166-1 Alpha-3 country code representation. - /// See: https://developer.apple.com/documentation/storekit/skstorefront?language=objc - Future getCountryCode() async { - return (await _skPaymentQueueWrapper.storefront())?.countryCode; - } } enum _TransactionRestoreState { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart deleted file mode 100644 index 7ce35bded..000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart +++ /dev/null @@ -1,963 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// Autogenerated from Pigeon (v16.0.4), do not edit directly. -// See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers - -import 'dart:async'; -import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; - -import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; -import 'package:flutter/services.dart'; - -PlatformException _createConnectionError(String channelName) { - return PlatformException( - code: 'channel-error', - message: 'Unable to establish connection on channel: "$channelName".', - ); -} - -List wrapResponse( - {Object? result, PlatformException? error, bool empty = false}) { - if (empty) { - return []; - } - if (error == null) { - return [result]; - } - return [error.code, error.message, error.details]; -} - -enum SKPaymentTransactionStateMessage { - /// Indicates the transaction is being processed in App Store. - /// - /// You should update your UI to indicate that you are waiting for the - /// transaction to update to another state. Never complete a transaction that - /// is still in a purchasing state. - purchasing, - - /// The user's payment has been succesfully processed. - /// - /// You should provide the user the content that they purchased. - purchased, - - /// The transaction failed. - /// - /// Check the [PaymentTransactionWrapper.error] property from - /// [PaymentTransactionWrapper] for details. - failed, - - /// This transaction is restoring content previously purchased by the user. - /// - /// The previous transaction information can be obtained in - /// [PaymentTransactionWrapper.originalTransaction] from - /// [PaymentTransactionWrapper]. - restored, - - /// The transaction is in the queue but pending external action. Wait for - /// another callback to get the final state. - /// - /// You should update your UI to indicate that you are waiting for the - /// transaction to update to another state. - deferred, - - /// Indicates the transaction is in an unspecified state. - unspecified, -} - -enum SKProductDiscountTypeMessage { - /// A constant indicating the discount type is an introductory offer. - introductory, - - /// A constant indicating the discount type is a promotional offer. - subscription, -} - -enum SKProductDiscountPaymentModeMessage { - /// Allows user to pay the discounted price at each payment period. - payAsYouGo, - - /// Allows user to pay the discounted price upfront and receive the product for the rest of time that was paid for. - payUpFront, - - /// User pays nothing during the discounted period. - freeTrial, - - /// Unspecified mode. - unspecified, -} - -enum SKSubscriptionPeriodUnitMessage { - day, - week, - month, - year, -} - -class SKPaymentTransactionMessage { - SKPaymentTransactionMessage({ - required this.payment, - required this.transactionState, - this.originalTransaction, - this.transactionTimeStamp, - this.transactionIdentifier, - this.error, - }); - - SKPaymentMessage payment; - - SKPaymentTransactionStateMessage transactionState; - - SKPaymentTransactionMessage? originalTransaction; - - double? transactionTimeStamp; - - String? transactionIdentifier; - - SKErrorMessage? error; - - Object encode() { - return [ - payment.encode(), - transactionState.index, - originalTransaction?.encode(), - transactionTimeStamp, - transactionIdentifier, - error?.encode(), - ]; - } - - static SKPaymentTransactionMessage decode(Object result) { - result as List; - return SKPaymentTransactionMessage( - payment: SKPaymentMessage.decode(result[0]! as List), - transactionState: - SKPaymentTransactionStateMessage.values[result[1]! as int], - originalTransaction: result[2] != null - ? SKPaymentTransactionMessage.decode(result[2]! as List) - : null, - transactionTimeStamp: result[3] as double?, - transactionIdentifier: result[4] as String?, - error: result[5] != null - ? SKErrorMessage.decode(result[5]! as List) - : null, - ); - } -} - -class SKPaymentMessage { - SKPaymentMessage({ - required this.productIdentifier, - this.applicationUsername, - this.requestData, - this.quantity = 1, - this.simulatesAskToBuyInSandbox = false, - this.paymentDiscount, - }); - - String productIdentifier; - - String? applicationUsername; - - String? requestData; - - int quantity; - - bool simulatesAskToBuyInSandbox; - - SKPaymentDiscountMessage? paymentDiscount; - - Object encode() { - return [ - productIdentifier, - applicationUsername, - requestData, - quantity, - simulatesAskToBuyInSandbox, - paymentDiscount?.encode(), - ]; - } - - static SKPaymentMessage decode(Object result) { - result as List; - return SKPaymentMessage( - productIdentifier: result[0]! as String, - applicationUsername: result[1] as String?, - requestData: result[2] as String?, - quantity: result[3]! as int, - simulatesAskToBuyInSandbox: result[4]! as bool, - paymentDiscount: result[5] != null - ? SKPaymentDiscountMessage.decode(result[5]! as List) - : null, - ); - } -} - -class SKErrorMessage { - SKErrorMessage({ - required this.code, - required this.domain, - this.userInfo, - }); - - int code; - - String domain; - - Map? userInfo; - - Object encode() { - return [ - code, - domain, - userInfo, - ]; - } - - static SKErrorMessage decode(Object result) { - result as List; - return SKErrorMessage( - code: result[0]! as int, - domain: result[1]! as String, - userInfo: (result[2] as Map?)?.cast(), - ); - } -} - -class SKPaymentDiscountMessage { - SKPaymentDiscountMessage({ - required this.identifier, - required this.keyIdentifier, - required this.nonce, - required this.signature, - required this.timestamp, - }); - - String identifier; - - String keyIdentifier; - - String nonce; - - String signature; - - int timestamp; - - Object encode() { - return [ - identifier, - keyIdentifier, - nonce, - signature, - timestamp, - ]; - } - - static SKPaymentDiscountMessage decode(Object result) { - result as List; - return SKPaymentDiscountMessage( - identifier: result[0]! as String, - keyIdentifier: result[1]! as String, - nonce: result[2]! as String, - signature: result[3]! as String, - timestamp: result[4]! as int, - ); - } -} - -class SKStorefrontMessage { - SKStorefrontMessage({ - required this.countryCode, - required this.identifier, - }); - - String countryCode; - - String identifier; - - Object encode() { - return [ - countryCode, - identifier, - ]; - } - - static SKStorefrontMessage decode(Object result) { - result as List; - return SKStorefrontMessage( - countryCode: result[0]! as String, - identifier: result[1]! as String, - ); - } -} - -class SKProductsResponseMessage { - SKProductsResponseMessage({ - this.products, - this.invalidProductIdentifiers, - }); - - List? products; - - List? invalidProductIdentifiers; - - Object encode() { - return [ - products, - invalidProductIdentifiers, - ]; - } - - static SKProductsResponseMessage decode(Object result) { - result as List; - return SKProductsResponseMessage( - products: (result[0] as List?)?.cast(), - invalidProductIdentifiers: (result[1] as List?)?.cast(), - ); - } -} - -class SKProductMessage { - SKProductMessage({ - required this.productIdentifier, - required this.localizedTitle, - required this.localizedDescription, - required this.priceLocale, - this.subscriptionGroupIdentifier, - required this.price, - this.subscriptionPeriod, - this.introductoryPrice, - this.discounts, - }); - - String productIdentifier; - - String localizedTitle; - - String localizedDescription; - - SKPriceLocaleMessage priceLocale; - - String? subscriptionGroupIdentifier; - - String price; - - SKProductSubscriptionPeriodMessage? subscriptionPeriod; - - SKProductDiscountMessage? introductoryPrice; - - List? discounts; - - Object encode() { - return [ - productIdentifier, - localizedTitle, - localizedDescription, - priceLocale.encode(), - subscriptionGroupIdentifier, - price, - subscriptionPeriod?.encode(), - introductoryPrice?.encode(), - discounts, - ]; - } - - static SKProductMessage decode(Object result) { - result as List; - return SKProductMessage( - productIdentifier: result[0]! as String, - localizedTitle: result[1]! as String, - localizedDescription: result[2]! as String, - priceLocale: SKPriceLocaleMessage.decode(result[3]! as List), - subscriptionGroupIdentifier: result[4] as String?, - price: result[5]! as String, - subscriptionPeriod: result[6] != null - ? SKProductSubscriptionPeriodMessage.decode( - result[6]! as List) - : null, - introductoryPrice: result[7] != null - ? SKProductDiscountMessage.decode(result[7]! as List) - : null, - discounts: - (result[8] as List?)?.cast(), - ); - } -} - -class SKPriceLocaleMessage { - SKPriceLocaleMessage({ - required this.currencySymbol, - required this.currencyCode, - required this.countryCode, - }); - - ///The currency symbol for the locale, e.g. $ for US locale. - String currencySymbol; - - ///The currency code for the locale, e.g. USD for US locale. - String currencyCode; - - ///The country code for the locale, e.g. US for US locale. - String countryCode; - - Object encode() { - return [ - currencySymbol, - currencyCode, - countryCode, - ]; - } - - static SKPriceLocaleMessage decode(Object result) { - result as List; - return SKPriceLocaleMessage( - currencySymbol: result[0]! as String, - currencyCode: result[1]! as String, - countryCode: result[2]! as String, - ); - } -} - -class SKProductDiscountMessage { - SKProductDiscountMessage({ - required this.price, - required this.priceLocale, - required this.numberOfPeriods, - required this.paymentMode, - required this.subscriptionPeriod, - this.identifier, - required this.type, - }); - - String price; - - SKPriceLocaleMessage priceLocale; - - int numberOfPeriods; - - SKProductDiscountPaymentModeMessage paymentMode; - - SKProductSubscriptionPeriodMessage subscriptionPeriod; - - String? identifier; - - SKProductDiscountTypeMessage type; - - Object encode() { - return [ - price, - priceLocale.encode(), - numberOfPeriods, - paymentMode.index, - subscriptionPeriod.encode(), - identifier, - type.index, - ]; - } - - static SKProductDiscountMessage decode(Object result) { - result as List; - return SKProductDiscountMessage( - price: result[0]! as String, - priceLocale: SKPriceLocaleMessage.decode(result[1]! as List), - numberOfPeriods: result[2]! as int, - paymentMode: - SKProductDiscountPaymentModeMessage.values[result[3]! as int], - subscriptionPeriod: SKProductSubscriptionPeriodMessage.decode( - result[4]! as List), - identifier: result[5] as String?, - type: SKProductDiscountTypeMessage.values[result[6]! as int], - ); - } -} - -class SKProductSubscriptionPeriodMessage { - SKProductSubscriptionPeriodMessage({ - required this.numberOfUnits, - required this.unit, - }); - - int numberOfUnits; - - SKSubscriptionPeriodUnitMessage unit; - - Object encode() { - return [ - numberOfUnits, - unit.index, - ]; - } - - static SKProductSubscriptionPeriodMessage decode(Object result) { - result as List; - return SKProductSubscriptionPeriodMessage( - numberOfUnits: result[0]! as int, - unit: SKSubscriptionPeriodUnitMessage.values[result[1]! as int], - ); - } -} - -class _InAppPurchaseAPICodec extends StandardMessageCodec { - const _InAppPurchaseAPICodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is SKErrorMessage) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is SKPaymentDiscountMessage) { - buffer.putUint8(129); - writeValue(buffer, value.encode()); - } else if (value is SKPaymentMessage) { - buffer.putUint8(130); - writeValue(buffer, value.encode()); - } else if (value is SKPaymentTransactionMessage) { - buffer.putUint8(131); - writeValue(buffer, value.encode()); - } else if (value is SKPriceLocaleMessage) { - buffer.putUint8(132); - writeValue(buffer, value.encode()); - } else if (value is SKProductDiscountMessage) { - buffer.putUint8(133); - writeValue(buffer, value.encode()); - } else if (value is SKProductMessage) { - buffer.putUint8(134); - writeValue(buffer, value.encode()); - } else if (value is SKProductSubscriptionPeriodMessage) { - buffer.putUint8(135); - writeValue(buffer, value.encode()); - } else if (value is SKProductsResponseMessage) { - buffer.putUint8(136); - writeValue(buffer, value.encode()); - } else if (value is SKStorefrontMessage) { - buffer.putUint8(137); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 128: - return SKErrorMessage.decode(readValue(buffer)!); - case 129: - return SKPaymentDiscountMessage.decode(readValue(buffer)!); - case 130: - return SKPaymentMessage.decode(readValue(buffer)!); - case 131: - return SKPaymentTransactionMessage.decode(readValue(buffer)!); - case 132: - return SKPriceLocaleMessage.decode(readValue(buffer)!); - case 133: - return SKProductDiscountMessage.decode(readValue(buffer)!); - case 134: - return SKProductMessage.decode(readValue(buffer)!); - case 135: - return SKProductSubscriptionPeriodMessage.decode(readValue(buffer)!); - case 136: - return SKProductsResponseMessage.decode(readValue(buffer)!); - case 137: - return SKStorefrontMessage.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - -class InAppPurchaseAPI { - /// Constructor for [InAppPurchaseAPI]. The [binaryMessenger] named argument is - /// available for dependency injection. If it is left null, the default - /// BinaryMessenger will be used which routes to the host platform. - InAppPurchaseAPI({BinaryMessenger? binaryMessenger}) - : __pigeon_binaryMessenger = binaryMessenger; - final BinaryMessenger? __pigeon_binaryMessenger; - - static const MessageCodec pigeonChannelCodec = - _InAppPurchaseAPICodec(); - - /// Returns if the current device is able to make payments - Future canMakePayments() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.canMakePayments'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] as bool?)!; - } - } - - Future> transactions() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.transactions'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] as List?)! - .cast(); - } - } - - Future storefront() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.storefront'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] as SKStorefrontMessage?)!; - } - } - - Future addPayment(Map paymentMap) async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.addPayment'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send([paymentMap]) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else { - return; - } - } - - Future startProductRequest( - List productIdentifiers) async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.startProductRequest'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = await __pigeon_channel - .send([productIdentifiers]) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else if (__pigeon_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (__pigeon_replyList[0] as SKProductsResponseMessage?)!; - } - } - - Future finishTransaction(Map finishMap) async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.finishTransaction'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send([finishMap]) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else { - return; - } - } - - Future restoreTransactions(String? applicationUserName) async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.restoreTransactions'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = await __pigeon_channel - .send([applicationUserName]) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else { - return; - } - } - - Future presentCodeRedemptionSheet() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.presentCodeRedemptionSheet'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else { - return; - } - } - - Future retrieveReceiptData() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.retrieveReceiptData'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else { - return (__pigeon_replyList[0] as String?); - } - } - - Future refreshReceipt( - {Map? receiptProperties}) async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.refreshReceipt'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = await __pigeon_channel - .send([receiptProperties]) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else { - return; - } - } - - Future startObservingPaymentQueue() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.startObservingPaymentQueue'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else { - return; - } - } - - Future stopObservingPaymentQueue() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.stopObservingPaymentQueue'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else { - return; - } - } - - Future registerPaymentQueueDelegate() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.registerPaymentQueueDelegate'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else { - return; - } - } - - Future removePaymentQueueDelegate() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.removePaymentQueueDelegate'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else { - return; - } - } - - Future showPriceConsentIfNeeded() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.showPriceConsentIfNeeded'; - final BasicMessageChannel __pigeon_channel = - BasicMessageChannel( - __pigeon_channelName, - pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, - ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { - throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], - ); - } else { - return; - } - } -} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart index 8f1af1c64..eace938c5 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart @@ -12,18 +12,9 @@ import 'package:json_annotation/json_annotation.dart'; import '../../store_kit_wrappers.dart'; import '../channel.dart'; import '../in_app_purchase_storekit_platform.dart'; -import '../messages.g.dart'; part 'sk_payment_queue_wrapper.g.dart'; -InAppPurchaseAPI _hostApi = InAppPurchaseAPI(); - -/// Set up pigeon API. -@visibleForTesting -void setInAppPurchaseHostApi(InAppPurchaseAPI api) { - _hostApi = api; -} - /// A wrapper around /// [`SKPaymentQueue`](https://developer.apple.com/documentation/storekit/skpaymentqueue?language=objc). /// @@ -33,7 +24,7 @@ void setInAppPurchaseHostApi(InAppPurchaseAPI api) { /// /// Full information on using `SKPaymentQueue` and processing purchases is /// available at the [In-App Purchase Programming -/// Guide](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Introduction.html#//apple_ref/doc/uid/TP40008267) +/// Guide](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Introduction.html#//apple_ref/doc/uid/TP40008267). class SKPaymentQueueWrapper { /// Returns the default payment queue. /// @@ -54,21 +45,25 @@ class SKPaymentQueueWrapper { /// /// Returns `null` if the user's device is below iOS 13.0 or macOS 10.15. Future storefront() async { - return SKStorefrontWrapper.convertFromPigeon(await _hostApi.storefront()); + final Map? storefrontMap = await channel + .invokeMapMethod('-[SKPaymentQueue storefront]'); + if (storefrontMap == null) { + return null; + } + return SKStorefrontWrapper.fromJson(storefrontMap); } /// Calls [`-[SKPaymentQueue transactions]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506026-transactions?language=objc). Future> transactions() async { - final List pigeonMsgs = - await _hostApi.transactions(); - return pigeonMsgs - .map((SKPaymentTransactionMessage? msg) => - SKPaymentTransactionWrapper.convertFromPigeon(msg!)) - .toList(); + return _getTransactionList((await channel + .invokeListMethod('-[SKPaymentQueue transactions]'))!); } /// Calls [`-[SKPaymentQueue canMakePayments:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506139-canmakepayments?language=objc). - static Future canMakePayments() async => _hostApi.canMakePayments(); + static Future canMakePayments() async => + (await channel + .invokeMethod('-[SKPaymentQueue canMakePayments:]')) ?? + false; /// Sets an observer to listen to all incoming transaction events. /// @@ -86,16 +81,16 @@ class SKPaymentQueueWrapper { /// /// Call this method when the first listener is subscribed to the /// [InAppPurchaseStoreKitPlatform.purchaseStream]. - Future startObservingTransactionQueue() => - _hostApi.startObservingPaymentQueue(); + Future startObservingTransactionQueue() => channel + .invokeMethod('-[SKPaymentQueue startObservingTransactionQueue]'); /// Instructs the iOS implementation to remove the transaction observer and /// stop listening to it. /// /// Call this when there are no longer any listeners subscribed to the /// [InAppPurchaseStoreKitPlatform.purchaseStream]. - Future stopObservingTransactionQueue() => - _hostApi.stopObservingPaymentQueue(); + Future stopObservingTransactionQueue() => channel + .invokeMethod('-[SKPaymentQueue stopObservingTransactionQueue]'); /// Sets an implementation of the [SKPaymentQueueDelegateWrapper]. /// @@ -109,10 +104,10 @@ class SKPaymentQueueWrapper { /// default behaviour will apply (see [documentation](https://developer.apple.com/documentation/storekit/skpaymentqueue/3182429-delegate?language=objc)). Future setDelegate(SKPaymentQueueDelegateWrapper? delegate) async { if (delegate == null) { - await _hostApi.removePaymentQueueDelegate(); + await channel.invokeMethod('-[SKPaymentQueue removeDelegate]'); paymentQueueDelegateChannel.setMethodCallHandler(null); } else { - await _hostApi.registerPaymentQueueDelegate(); + await channel.invokeMethod('-[SKPaymentQueue registerDelegate]'); paymentQueueDelegateChannel .setMethodCallHandler(handlePaymentQueueDelegateCallbacks); } @@ -143,8 +138,11 @@ class SKPaymentQueueWrapper { Future addPayment(SKPaymentWrapper payment) async { assert(_observer != null, '[in_app_purchase]: Trying to add a payment without an observer. One must be set using `SkPaymentQueueWrapper.setTransactionObserver` before the app launches.'); - - await _hostApi.addPayment(payment.toMap()); + final Map requestMap = payment.toMap(); + await channel.invokeMethod( + '-[InAppPurchasePlugin addPayment:result:]', + requestMap, + ); } /// Finishes a transaction and removes it from the queue. @@ -161,7 +159,10 @@ class SKPaymentQueueWrapper { Future finishTransaction( SKPaymentTransactionWrapper transaction) async { final Map requestMap = transaction.toFinishMap(); - await _hostApi.finishTransaction(requestMap); + await channel.invokeMethod( + '-[InAppPurchasePlugin finishTransaction:result:]', + requestMap, + ); } /// Restore previously purchased transactions. @@ -185,7 +186,9 @@ class SKPaymentQueueWrapper { /// or [`-[SKPayment restoreCompletedTransactionsWithApplicationUsername:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1505992-restorecompletedtransactionswith?language=objc) /// depending on whether the `applicationUserName` is set. Future restoreTransactions({String? applicationUserName}) async { - await _hostApi.restoreTransactions(applicationUserName); + await channel.invokeMethod( + '-[InAppPurchasePlugin restoreTransactions:result:]', + applicationUserName); } /// Present Code Redemption Sheet @@ -195,7 +198,8 @@ class SKPaymentQueueWrapper { /// This method triggers [`-[SKPayment /// presentCodeRedemptionSheet]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3566726-presentcoderedemptionsheet?language=objc) Future presentCodeRedemptionSheet() async { - await _hostApi.presentCodeRedemptionSheet(); + await channel.invokeMethod( + '-[InAppPurchasePlugin presentCodeRedemptionSheet:result:]'); } /// Shows the price consent sheet if the user has not yet responded to a @@ -207,7 +211,8 @@ class SKPaymentQueueWrapper { /// /// See documentation of StoreKit's [`-[SKPaymentQueue showPriceConsentIfNeeded]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3521327-showpriceconsentifneeded?language=objc). Future showPriceConsentIfNeeded() async { - await _hostApi.showPriceConsentIfNeeded(); + await channel + .invokeMethod('-[SKPaymentQueue showPriceConsentIfNeeded]'); } /// Triage a method channel call from the platform and triggers the correct observer method. @@ -353,7 +358,7 @@ class SKError { /// /// Any key of the map must be a valid [NSErrorUserInfoKey](https://developer.apple.com/documentation/foundation/nserroruserinfokey?language=objc). @JsonKey(defaultValue: {}) - final Map? userInfo; + final Map userInfo; @override bool operator ==(Object other) { @@ -376,14 +381,6 @@ class SKError { domain, userInfo, ); - - /// Converts [SKErrorMessage] into the dart equivalent - static SKError convertFromPigeon(SKErrorMessage msg) { - return SKError( - code: msg.code, - domain: msg.domain, - userInfo: msg.userInfo ?? {}); - } } /// Dart wrapper around StoreKit's @@ -501,18 +498,6 @@ class SKPaymentWrapper { @override String toString() => _$SKPaymentWrapperToJson(this).toString(); - - /// Converts [SKPaymentMessage] into the dart equivalent - static SKPaymentWrapper convertFromPigeon(SKPaymentMessage msg) { - return SKPaymentWrapper( - productIdentifier: msg.productIdentifier, - applicationUsername: msg.applicationUsername, - quantity: msg.quantity, - simulatesAskToBuyInSandbox: msg.simulatesAskToBuyInSandbox, - requestData: msg.requestData, - paymentDiscount: - SKPaymentDiscountWrapper.convertFromPigeon(msg.paymentDiscount)); - } } /// Dart wrapper around StoreKit's @@ -611,18 +596,4 @@ class SKPaymentDiscountWrapper { @override int get hashCode => Object.hash(identifier, keyIdentifier, nonce, signature, timestamp); - - /// Converts [SKPaymentDiscountMessage] into the dart equivalent - static SKPaymentDiscountWrapper? convertFromPigeon( - SKPaymentDiscountMessage? msg) { - if (msg == null) { - return null; - } - return SKPaymentDiscountWrapper( - identifier: msg.identifier, - keyIdentifier: msg.keyIdentifier, - nonce: msg.nonce, - signature: msg.signature, - timestamp: msg.timestamp); - } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.dart index 7f16e1492..3894721a1 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_transaction_wrappers.dart @@ -5,7 +5,6 @@ import 'package:flutter/foundation.dart'; import 'package:json_annotation/json_annotation.dart'; -import '../messages.g.dart'; import 'enum_converters.dart'; import 'sk_payment_queue_wrapper.dart'; import 'sk_product_wrapper.dart'; @@ -91,26 +90,7 @@ enum SKPaymentTransactionStateWrapper { /// Indicates the transaction is in an unspecified state. @JsonValue(-1) - unspecified; - - /// Converts [SKPaymentTransactionStateMessages] into the dart equivalent - static SKPaymentTransactionStateWrapper convertFromPigeon( - SKPaymentTransactionStateMessage msg) { - switch (msg) { - case SKPaymentTransactionStateMessage.purchased: - return SKPaymentTransactionStateWrapper.purchased; - case SKPaymentTransactionStateMessage.purchasing: - return SKPaymentTransactionStateWrapper.purchasing; - case SKPaymentTransactionStateMessage.failed: - return SKPaymentTransactionStateWrapper.failed; - case SKPaymentTransactionStateMessage.restored: - return SKPaymentTransactionStateWrapper.restored; - case SKPaymentTransactionStateMessage.deferred: - return SKPaymentTransactionStateWrapper.deferred; - case SKPaymentTransactionStateMessage.unspecified: - return SKPaymentTransactionStateWrapper.unspecified; - } - } + unspecified, } /// Created when a payment is added to the [SKPaymentQueueWrapper]. @@ -219,20 +199,4 @@ class SKPaymentTransactionWrapper { 'transactionIdentifier': transactionIdentifier, 'productIdentifier': payment.productIdentifier, }; - - /// Converts [SKPaymentTransactionMessages] into the dart equivalent - static SKPaymentTransactionWrapper convertFromPigeon( - SKPaymentTransactionMessage msg) { - return SKPaymentTransactionWrapper( - payment: SKPaymentWrapper.convertFromPigeon(msg.payment), - transactionState: SKPaymentTransactionStateWrapper.convertFromPigeon( - msg.transactionState), - originalTransaction: msg.originalTransaction == null - ? null - : convertFromPigeon(msg.originalTransaction!), - transactionTimeStamp: msg.transactionTimeStamp, - transactionIdentifier: msg.transactionIdentifier, - error: - msg.error == null ? null : SKError.convertFromPigeon(msg.error!)); - } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_product_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_product_wrapper.dart index dfd8c3083..5eace6fda 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_product_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_product_wrapper.dart @@ -5,8 +5,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:json_annotation/json_annotation.dart'; - -import '../messages.g.dart'; import 'enum_converters.dart'; // WARNING: Changes to `@JsonSerializable` classes need to be reflected in the @@ -66,31 +64,6 @@ class SkProductResponseWrapper { @override int get hashCode => Object.hash(products, invalidProductIdentifiers); - - /// Convert from [SkProductResponseMessage] to [SkProductResponseWrapper] - static SkProductResponseWrapper convertFromPigeon( - SKProductsResponseMessage msg) { - return SkProductResponseWrapper( - products: msg.products! - .map((SKProductMessage? e) => SKProductWrapper.convertFromPigeon(e!)) - .toList(), - invalidProductIdentifiers: msg.invalidProductIdentifiers != null - ? msg.invalidProductIdentifiers!.cast() - : [], - ); - } - - /// Convert from [SkProductResponseWrapper] to [SkProductResponseWrapper] - @visibleForTesting - static SKProductsResponseMessage convertToPigeon( - SkProductResponseWrapper wrapper) { - return SKProductsResponseMessage( - products: wrapper.products - .map((SKProductWrapper? e) => SKProductWrapper.convertToPigeon(e!)) - .toList(), - invalidProductIdentifiers: - wrapper.invalidProductIdentifiers.cast()); - } } /// Dart wrapper around StoreKit's [SKProductPeriodUnit](https://developer.apple.com/documentation/storekit/skproductperiodunit?language=objc). @@ -115,38 +88,7 @@ enum SKSubscriptionPeriodUnit { /// An interval lasting one year. @JsonValue(3) - year; - - /// Convert from [SKSubscriptionPeriodUnitMessage] to [SKSubscriptionPeriodUnit] - static SKSubscriptionPeriodUnit convertFromPigeon( - SKSubscriptionPeriodUnitMessage msg) { - switch (msg) { - case SKSubscriptionPeriodUnitMessage.day: - return SKSubscriptionPeriodUnit.day; - case SKSubscriptionPeriodUnitMessage.week: - return SKSubscriptionPeriodUnit.week; - case SKSubscriptionPeriodUnitMessage.month: - return SKSubscriptionPeriodUnit.month; - case SKSubscriptionPeriodUnitMessage.year: - return SKSubscriptionPeriodUnit.year; - } - } - - /// Convert from [SKSubscriptionPeriodUnit] to [SKSubscriptionPeriodUnitMessage] - @visibleForTesting - static SKSubscriptionPeriodUnitMessage convertToPigeon( - SKSubscriptionPeriodUnit msg) { - switch (msg) { - case SKSubscriptionPeriodUnit.day: - return SKSubscriptionPeriodUnitMessage.day; - case SKSubscriptionPeriodUnit.week: - return SKSubscriptionPeriodUnitMessage.week; - case SKSubscriptionPeriodUnit.month: - return SKSubscriptionPeriodUnitMessage.month; - case SKSubscriptionPeriodUnit.year: - return SKSubscriptionPeriodUnitMessage.year; - } - } + year, } /// Dart wrapper around StoreKit's [SKProductSubscriptionPeriod](https://developer.apple.com/documentation/storekit/skproductsubscriptionperiod?language=objc). @@ -200,23 +142,6 @@ class SKProductSubscriptionPeriodWrapper { @override int get hashCode => Object.hash(numberOfUnits, unit); - - /// Convert from [SKProductSubscriptionPeriodMessage] to [SKProductSubscriptionPeriodWrapper] - static SKProductSubscriptionPeriodWrapper convertFromPigeon( - SKProductSubscriptionPeriodMessage msg) { - return SKProductSubscriptionPeriodWrapper( - numberOfUnits: msg.numberOfUnits, - unit: SKSubscriptionPeriodUnit.convertFromPigeon(msg.unit)); - } - - /// Convert from [SKProductSubscriptionPeriodWrapper] to [SKProductSubscriptionPeriodMessage] - @visibleForTesting - static SKProductSubscriptionPeriodMessage convertToPigeon( - SKProductSubscriptionPeriodWrapper wrapper) { - return SKProductSubscriptionPeriodMessage( - numberOfUnits: wrapper.numberOfUnits, - unit: SKSubscriptionPeriodUnit.convertToPigeon(wrapper.unit)); - } } /// Dart wrapper around StoreKit's [SKProductDiscountPaymentMode](https://developer.apple.com/documentation/storekit/skproductdiscountpaymentmode?language=objc). @@ -239,38 +164,7 @@ enum SKProductDiscountPaymentMode { /// Unspecified mode. @JsonValue(-1) - unspecified; - - /// Convert from [SKProductDiscountPaymentModeMessage] to [SKProductDiscountPaymentModeWrapper] - static SKProductDiscountPaymentMode convertFromPigeon( - SKProductDiscountPaymentModeMessage msg) { - switch (msg) { - case SKProductDiscountPaymentModeMessage.payAsYouGo: - return SKProductDiscountPaymentMode.payAsYouGo; - case SKProductDiscountPaymentModeMessage.payUpFront: - return SKProductDiscountPaymentMode.payUpFront; - case SKProductDiscountPaymentModeMessage.freeTrial: - return SKProductDiscountPaymentMode.freeTrail; - case SKProductDiscountPaymentModeMessage.unspecified: - return SKProductDiscountPaymentMode.unspecified; - } - } - - /// Convert from [SKProductDiscountPaymentModeMessage] to [SKProductDiscountPaymentMode] - @visibleForTesting - static SKProductDiscountPaymentModeMessage convertToPigeon( - SKProductDiscountPaymentMode wrapper) { - switch (wrapper) { - case SKProductDiscountPaymentMode.payAsYouGo: - return SKProductDiscountPaymentModeMessage.payAsYouGo; - case SKProductDiscountPaymentMode.payUpFront: - return SKProductDiscountPaymentModeMessage.payUpFront; - case SKProductDiscountPaymentMode.freeTrail: - return SKProductDiscountPaymentModeMessage.freeTrial; - case SKProductDiscountPaymentMode.unspecified: - return SKProductDiscountPaymentModeMessage.unspecified; - } - } + unspecified, } /// Dart wrapper around StoreKit's [SKProductDiscountType] @@ -288,30 +182,7 @@ enum SKProductDiscountType { /// A constant indicating the discount type is a promotional offer. @JsonValue(1) - subscription; - - /// Convert from [SKProductDiscountTypeMessage] to [SKProductDiscountType] - static SKProductDiscountType convertFromPigeon( - SKProductDiscountTypeMessage msg) { - switch (msg) { - case SKProductDiscountTypeMessage.introductory: - return SKProductDiscountType.introductory; - case SKProductDiscountTypeMessage.subscription: - return SKProductDiscountType.subscription; - } - } - - /// Convert from [SKProductDiscountType] to [SKProductDiscountTypeMessage] - @visibleForTesting - static SKProductDiscountTypeMessage convertToPigeon( - SKProductDiscountType wrapper) { - switch (wrapper) { - case SKProductDiscountType.introductory: - return SKProductDiscountTypeMessage.introductory; - case SKProductDiscountType.subscription: - return SKProductDiscountTypeMessage.subscription; - } - } + subscription, } /// Dart wrapper around StoreKit's [SKProductDiscount](https://developer.apple.com/documentation/storekit/skproductdiscount?language=objc). @@ -394,38 +265,6 @@ class SKProductDiscountWrapper { @override int get hashCode => Object.hash(price, priceLocale, numberOfPeriods, paymentMode, subscriptionPeriod, identifier, type); - - /// Convert from [SKProductDiscountMessage] to [SKProductDiscountWrapper] - static SKProductDiscountWrapper convertFromPigeon( - SKProductDiscountMessage msg) { - return SKProductDiscountWrapper( - price: msg.price, - priceLocale: SKPriceLocaleWrapper.convertFromPigeon(msg.priceLocale), - numberOfPeriods: msg.numberOfPeriods, - paymentMode: - SKProductDiscountPaymentMode.convertFromPigeon(msg.paymentMode), - subscriptionPeriod: - SKProductSubscriptionPeriodWrapper.convertFromPigeon( - msg.subscriptionPeriod), - identifier: msg.identifier, - type: SKProductDiscountType.convertFromPigeon(msg.type)); - } - - /// Convert from [SKProductDiscountWrapper] to [SKProductDiscountMessage] - @visibleForTesting - static SKProductDiscountMessage convertToPigeon( - SKProductDiscountWrapper wrapper) { - return SKProductDiscountMessage( - price: wrapper.price, - priceLocale: SKPriceLocaleWrapper.convertToPigeon(wrapper.priceLocale), - numberOfPeriods: wrapper.numberOfPeriods, - paymentMode: - SKProductDiscountPaymentMode.convertToPigeon(wrapper.paymentMode), - subscriptionPeriod: SKProductSubscriptionPeriodWrapper.convertToPigeon( - wrapper.subscriptionPeriod), - identifier: wrapper.identifier, - type: SKProductDiscountType.convertToPigeon(wrapper.type)); - } } /// Dart wrapper around StoreKit's [SKProduct](https://developer.apple.com/documentation/storekit/skproduct?language=objc). @@ -544,53 +383,6 @@ class SKProductWrapper { subscriptionPeriod, introductoryPrice, discounts); - - /// Convert from [SKProductMessage] to [SKProductWrapper] - static SKProductWrapper convertFromPigeon(SKProductMessage msg) { - return SKProductWrapper( - productIdentifier: msg.productIdentifier, - localizedTitle: msg.localizedTitle, - localizedDescription: msg.localizedDescription, - priceLocale: SKPriceLocaleWrapper.convertFromPigeon(msg.priceLocale), - price: msg.price, - subscriptionGroupIdentifier: msg.subscriptionGroupIdentifier, - subscriptionPeriod: msg.subscriptionPeriod != null - ? SKProductSubscriptionPeriodWrapper.convertFromPigeon( - msg.subscriptionPeriod!) - : null, - introductoryPrice: msg.introductoryPrice != null - ? SKProductDiscountWrapper.convertFromPigeon(msg.introductoryPrice!) - : null, - discounts: msg.discounts != null - ? msg.discounts! - .map((SKProductDiscountMessage? e) => - SKProductDiscountWrapper.convertFromPigeon(e!)) - .toList() - : []); - } - - /// Convert from [SKProductWrapper] to [SKProductMessage] - static SKProductMessage convertToPigeon(SKProductWrapper wrapper) { - return SKProductMessage( - productIdentifier: wrapper.productIdentifier, - localizedTitle: wrapper.localizedTitle, - localizedDescription: wrapper.localizedDescription, - priceLocale: SKPriceLocaleWrapper.convertToPigeon(wrapper.priceLocale), - price: wrapper.price, - subscriptionGroupIdentifier: wrapper.subscriptionGroupIdentifier, - subscriptionPeriod: wrapper.subscriptionPeriod != null - ? SKProductSubscriptionPeriodWrapper.convertToPigeon( - wrapper.subscriptionPeriod!) - : null, - introductoryPrice: wrapper.introductoryPrice != null - ? SKProductDiscountWrapper.convertToPigeon( - wrapper.introductoryPrice!) - : null, - discounts: wrapper.discounts - .map((SKProductDiscountWrapper? e) => - SKProductDiscountWrapper.convertToPigeon(e!)) - .toList()); - } } /// Object that indicates the locale of the price @@ -650,21 +442,4 @@ class SKPriceLocaleWrapper { @override int get hashCode => Object.hash(currencySymbol, currencyCode); - - /// Convert from [SKPriceLocaleMessage] to [SKPriceLocaleWrapper] - static SKPriceLocaleWrapper convertFromPigeon(SKPriceLocaleMessage msg) { - return SKPriceLocaleWrapper( - currencySymbol: msg.currencySymbol, - currencyCode: msg.currencyCode, - countryCode: msg.countryCode); - } - - /// Convert from [SKPriceLocaleWrapper] to [SKPriceLocaleMessage] - @visibleForTesting - static SKPriceLocaleMessage convertToPigeon(SKPriceLocaleWrapper msg) { - return SKPriceLocaleMessage( - currencySymbol: msg.currencySymbol, - currencyCode: msg.currencyCode, - countryCode: msg.countryCode); - } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_receipt_manager.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_receipt_manager.dart index f61105aa2..b31a3d59c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_receipt_manager.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_receipt_manager.dart @@ -4,9 +4,7 @@ import 'dart:async'; -import '../messages.g.dart'; - -InAppPurchaseAPI _hostApi = InAppPurchaseAPI(); +import '../channel.dart'; // ignore: avoid_classes_with_only_static_members /// This class contains static methods to manage StoreKit receipts. @@ -19,6 +17,8 @@ class SKReceiptManager { /// For more details on how to validate the receipt data, you can refer to Apple's document about [`About Receipt Validation`](https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Introduction.html#//apple_ref/doc/uid/TP40010573-CH105-SW1). /// If the receipt is invalid or missing, you can use [SKRequestMaker.startRefreshReceiptRequest] to request a new receipt. static Future retrieveReceiptData() async { - return (await _hostApi.retrieveReceiptData()) ?? ''; + return (await channel.invokeMethod( + '-[InAppPurchasePlugin retrieveReceiptData:result:]')) ?? + ''; } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart index 5a16f261c..d59f66fce 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart @@ -6,11 +6,9 @@ import 'dart:async'; import 'package:flutter/services.dart'; -import '../messages.g.dart'; +import '../channel.dart'; import 'sk_product_wrapper.dart'; -InAppPurchaseAPI _hostApi = InAppPurchaseAPI(); - /// A request maker that handles all the requests made by SKRequest subclasses. /// /// There are multiple [SKRequest](https://developer.apple.com/documentation/storekit/skrequest?language=objc) subclasses handling different requests in the `StoreKit` with multiple delegate methods, @@ -28,18 +26,18 @@ class SKRequestMaker { /// A [PlatformException] is thrown if the platform code making the request fails. Future startProductRequest( List productIdentifiers) async { - final SKProductsResponseMessage productResponsePigeon = - await _hostApi.startProductRequest(productIdentifiers); - - // should products be null or [] ? - if (productResponsePigeon.products == null) { + final Map? productResponseMap = + await channel.invokeMapMethod( + '-[InAppPurchasePlugin startProductRequest:result:]', + productIdentifiers, + ); + if (productResponseMap == null) { throw PlatformException( code: 'storekit_no_response', message: 'StoreKit: Failed to get response from platform.', ); } - - return SkProductResponseWrapper.convertFromPigeon(productResponsePigeon); + return SkProductResponseWrapper.fromJson(productResponseMap); } /// Uses [SKReceiptRefreshRequest](https://developer.apple.com/documentation/storekit/skreceiptrefreshrequest?language=objc) to request a new receipt. @@ -53,6 +51,9 @@ class SKRequestMaker { /// * isVolumePurchase: whether the receipt is a Volume Purchase Plan receipt. Future startRefreshReceiptRequest( {Map? receiptProperties}) { - return _hostApi.refreshReceipt(receiptProperties: receiptProperties); + return channel.invokeMethod( + '-[InAppPurchasePlugin refreshReceipt:result:]', + receiptProperties, + ); } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_storefront_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_storefront_wrapper.dart index 2ba25e086..ff9e9b7db 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_storefront_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_storefront_wrapper.dart @@ -5,8 +5,6 @@ import 'package:flutter/foundation.dart'; import 'package:json_annotation/json_annotation.dart'; -import '../messages.g.dart'; - part 'sk_storefront_wrapper.g.dart'; /// Contains the location and unique identifier of an Apple App Store storefront. @@ -67,10 +65,4 @@ class SKStorefrontWrapper { /// Converts the instance to a key value map which can be used to serialize /// to JSON format. Map toMap() => _$SKStorefrontWrapperToJson(this); - - /// Converts the pigeon equivalent to an instance of SKStorefrontWrapper - static SKStorefrontWrapper convertFromPigeon(SKStorefrontMessage msg) { - return SKStorefrontWrapper( - countryCode: msg.countryCode, identifier: msg.identifier); - } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/copyright.txt b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/copyright.txt deleted file mode 100644 index fb682b1ab..000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/copyright.txt +++ /dev/null @@ -1,3 +0,0 @@ -Copyright 2013 The Flutter Authors. All rights reserved. -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart deleted file mode 100644 index fe1042c80..000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:pigeon/pigeon.dart'; - -@ConfigurePigeon(PigeonOptions( - dartOut: 'lib/src/messages.g.dart', - dartTestOut: 'test/test_api.g.dart', - objcHeaderOut: 'darwin/Classes/messages.g.h', - objcSourceOut: 'darwin/Classes/messages.g.m', - copyrightHeader: 'pigeons/copyright.txt', -)) -class SKPaymentTransactionMessage { - SKPaymentTransactionMessage({ - required this.payment, - required this.transactionState, - this.originalTransaction, - this.transactionTimeStamp, - this.transactionIdentifier, - this.error, - }); - - final SKPaymentMessage payment; - final SKPaymentTransactionStateMessage transactionState; - final SKPaymentTransactionMessage? originalTransaction; - final double? transactionTimeStamp; - final String? transactionIdentifier; - final SKErrorMessage? error; -} - -enum SKPaymentTransactionStateMessage { - /// Indicates the transaction is being processed in App Store. - /// - /// You should update your UI to indicate that you are waiting for the - /// transaction to update to another state. Never complete a transaction that - /// is still in a purchasing state. - purchasing, - - /// The user's payment has been succesfully processed. - /// - /// You should provide the user the content that they purchased. - purchased, - - /// The transaction failed. - /// - /// Check the [PaymentTransactionWrapper.error] property from - /// [PaymentTransactionWrapper] for details. - failed, - - /// This transaction is restoring content previously purchased by the user. - /// - /// The previous transaction information can be obtained in - /// [PaymentTransactionWrapper.originalTransaction] from - /// [PaymentTransactionWrapper]. - restored, - - /// The transaction is in the queue but pending external action. Wait for - /// another callback to get the final state. - /// - /// You should update your UI to indicate that you are waiting for the - /// transaction to update to another state. - deferred, - - /// Indicates the transaction is in an unspecified state. - unspecified, -} - -class SKPaymentMessage { - /// Creates a new [SKPaymentWrapper] with the provided information. - const SKPaymentMessage({ - required this.productIdentifier, - this.applicationUsername, - this.requestData, - this.quantity = 1, - this.simulatesAskToBuyInSandbox = false, - this.paymentDiscount, - }); - - final String productIdentifier; - final String? applicationUsername; - final String? requestData; - final int quantity; - final bool simulatesAskToBuyInSandbox; - final SKPaymentDiscountMessage? paymentDiscount; -} - -class SKErrorMessage { - const SKErrorMessage( - {required this.code, required this.domain, required this.userInfo}); - - final int code; - final String domain; - final Map? userInfo; -} - -class SKPaymentDiscountMessage { - const SKPaymentDiscountMessage({ - required this.identifier, - required this.keyIdentifier, - required this.nonce, - required this.signature, - required this.timestamp, - }); - - final String identifier; - final String keyIdentifier; - final String nonce; - final String signature; - final int timestamp; -} - -class SKStorefrontMessage { - const SKStorefrontMessage({ - required this.countryCode, - required this.identifier, - }); - - final String countryCode; - final String identifier; -} - -class SKProductsResponseMessage { - const SKProductsResponseMessage( - {required this.products, required this.invalidProductIdentifiers}); - final List? products; - final List? invalidProductIdentifiers; -} - -class SKProductMessage { - const SKProductMessage( - {required this.productIdentifier, - required this.localizedTitle, - required this.localizedDescription, - required this.priceLocale, - required this.price, - this.subscriptionGroupIdentifier, - this.subscriptionPeriod, - this.introductoryPrice, - this.discounts}); - - final String productIdentifier; - final String localizedTitle; - final String localizedDescription; - final SKPriceLocaleMessage priceLocale; - final String? subscriptionGroupIdentifier; - final String price; - final SKProductSubscriptionPeriodMessage? subscriptionPeriod; - final SKProductDiscountMessage? introductoryPrice; - final List? discounts; -} - -class SKPriceLocaleMessage { - SKPriceLocaleMessage({ - required this.currencySymbol, - required this.currencyCode, - required this.countryCode, - }); - - ///The currency symbol for the locale, e.g. $ for US locale. - final String currencySymbol; - - ///The currency code for the locale, e.g. USD for US locale. - final String currencyCode; - - ///The country code for the locale, e.g. US for US locale. - final String countryCode; -} - -class SKProductDiscountMessage { - const SKProductDiscountMessage( - {required this.price, - required this.priceLocale, - required this.numberOfPeriods, - required this.paymentMode, - required this.subscriptionPeriod, - required this.identifier, - required this.type}); - - final String price; - final SKPriceLocaleMessage priceLocale; - final int numberOfPeriods; - final SKProductDiscountPaymentModeMessage paymentMode; - final SKProductSubscriptionPeriodMessage subscriptionPeriod; - final String? identifier; - final SKProductDiscountTypeMessage type; -} - -enum SKProductDiscountTypeMessage { - /// A constant indicating the discount type is an introductory offer. - introductory, - - /// A constant indicating the discount type is a promotional offer. - subscription, -} - -enum SKProductDiscountPaymentModeMessage { - /// Allows user to pay the discounted price at each payment period. - payAsYouGo, - - /// Allows user to pay the discounted price upfront and receive the product for the rest of time that was paid for. - payUpFront, - - /// User pays nothing during the discounted period. - freeTrial, - - /// Unspecified mode. - unspecified, -} - -class SKProductSubscriptionPeriodMessage { - SKProductSubscriptionPeriodMessage( - {required this.numberOfUnits, required this.unit}); - - final int numberOfUnits; - final SKSubscriptionPeriodUnitMessage unit; -} - -enum SKSubscriptionPeriodUnitMessage { - day, - week, - month, - year, -} - -@HostApi(dartHostTestHandler: 'TestInAppPurchaseApi') -abstract class InAppPurchaseAPI { - /// Returns if the current device is able to make payments - bool canMakePayments(); - - List transactions(); - - SKStorefrontMessage storefront(); - - void addPayment(Map paymentMap); - - @async - SKProductsResponseMessage startProductRequest( - List productIdentifiers); - - void finishTransaction(Map finishMap); - - void restoreTransactions(String? applicationUserName); - - void presentCodeRedemptionSheet(); - - String? retrieveReceiptData(); - - @async - void refreshReceipt({Map? receiptProperties}); - - void startObservingPaymentQueue(); - - void stopObservingPaymentQueue(); - - void registerPaymentQueueDelegate(); - - void removePaymentQueueDelegate(); - - void showPriceConsentIfNeeded(); -} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml index d20cdec6f..cc9cb27d4 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml @@ -2,11 +2,11 @@ name: in_app_purchase_storekit description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework. repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_storekit issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.3.14 +version: 0.3.8 environment: - sdk: ^3.2.3 - flutter: ">=3.16.6" + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" flutter: plugin: @@ -23,7 +23,7 @@ dependencies: collection: ^1.15.0 flutter: sdk: flutter - in_app_purchase_platform_interface: ^1.4.0 + in_app_purchase_platform_interface: ^1.3.0 json_annotation: ^4.3.0 dev_dependencies: @@ -31,7 +31,6 @@ dev_dependencies: flutter_test: sdk: flutter json_serializable: ^6.0.0 - pigeon: ^16.0.4 test: ^1.16.0 topics: diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart index f5a755d83..c73ba81e3 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart @@ -2,21 +2,28 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:io'; + import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; -import 'package:in_app_purchase_storekit/src/messages.g.dart'; +import 'package:in_app_purchase_storekit/src/channel.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; import '../store_kit_wrappers/sk_test_stub_objects.dart'; -import '../test_api.g.dart'; -class FakeStoreKitPlatform implements TestInAppPurchaseApi { +class FakeStoreKitPlatform { + FakeStoreKitPlatform() { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, onMethodCall); + } + // pre-configured store information String? receiptData; late Set validProductIDs; late Map validProducts; - late List transactionList; + late List transactions; late List finishedTransactions; late bool testRestoredTransactionsNull; late bool testTransactionFail; @@ -26,12 +33,9 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { SKError? testRestoredError; bool queueIsActive = false; Map discountReceived = {}; - bool isPaymentQueueDelegateRegistered = false; - String _countryCode = 'USA'; - String _countryIdentifier = 'LL'; void reset() { - transactionList = []; + transactions = []; receiptData = 'dummy base64data'; validProductIDs = {'123', '456'}; validProducts = {}; @@ -54,9 +58,6 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { testRestoredError = null; queueIsActive = false; discountReceived = {}; - isPaymentQueueDelegateRegistered = false; - _countryCode = 'USA'; - _countryIdentifier = 'LL'; } SKPaymentTransactionWrapper createPendingTransaction(String id, @@ -120,156 +121,121 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { transactionIdentifier: transactionId); } - @override - bool canMakePayments() { - return true; - } - - @override - void addPayment(Map paymentMap) { - final String id = paymentMap['productIdentifier']! as String; - final int quantity = paymentMap['quantity']! as int; - - // Keep the received paymentDiscount parameter when testing payment with discount. - if (paymentMap['applicationUsername']! == 'userWithDiscount') { - final Map? discountArgument = - paymentMap['paymentDiscount'] as Map?; - if (discountArgument != null) { - discountReceived = discountArgument.cast(); - } else { - discountReceived = {}; - } - } - - final SKPaymentTransactionWrapper transaction = - createPendingTransaction(id, quantity: quantity); - transactionList.add(transaction); - InAppPurchaseStoreKitPlatform.observer.updatedTransactions( - transactions: [transaction]); - if (testTransactionFail) { - final SKPaymentTransactionWrapper transactionFailed = - createFailedTransaction(id, quantity: quantity); - InAppPurchaseStoreKitPlatform.observer.updatedTransactions( - transactions: [transactionFailed]); - } else if (testTransactionCancel > 0) { - final SKPaymentTransactionWrapper transactionCanceled = - createCanceledTransaction(id, testTransactionCancel, - quantity: quantity); - InAppPurchaseStoreKitPlatform.observer.updatedTransactions( - transactions: [transactionCanceled]); - } else { - final SKPaymentTransactionWrapper transactionFinished = - createPurchasedTransaction( - id, transaction.transactionIdentifier ?? '', - quantity: quantity); - InAppPurchaseStoreKitPlatform.observer.updatedTransactions( - transactions: [transactionFinished]); - } - } - - void setStoreFrontInfo( - {required String countryCode, required String identifier}) { - _countryCode = countryCode; - _countryIdentifier = identifier; - } - - @override - SKStorefrontMessage storefront() { - return SKStorefrontMessage( - countryCode: _countryCode, identifier: _countryIdentifier); - } - - @override - List transactions() { - throw UnimplementedError(); - } - - @override - void finishTransaction(Map finishMap) { - finishedTransactions.add(createPurchasedTransaction( - finishMap['productIdentifier']!, finishMap['transactionIdentifier']!, - quantity: transactionList.first.payment.quantity)); - } - - @override - void presentCodeRedemptionSheet() {} - - @override - void restoreTransactions(String? applicationUserName) { - if (restoreException != null) { - throw restoreException!; - } - if (testRestoredError != null) { - InAppPurchaseStoreKitPlatform.observer - .restoreCompletedTransactionsFailed(error: testRestoredError!); - return; + Future onMethodCall(MethodCall call) { + switch (call.method) { + case '-[SKPaymentQueue canMakePayments:]': + return Future.value(true); + case '-[InAppPurchasePlugin startProductRequest:result:]': + if (queryProductException != null) { + throw queryProductException!; + } + final List productIDS = + List.castFrom(call.arguments as List); + final List invalidFound = []; + final List products = []; + for (final String productID in productIDS) { + if (!validProductIDs.contains(productID)) { + invalidFound.add(productID); + } else { + products.add(validProducts[productID]!); + } + } + final SkProductResponseWrapper response = SkProductResponseWrapper( + products: products, invalidProductIdentifiers: invalidFound); + return Future>.value( + buildProductResponseMap(response)); + case '-[InAppPurchasePlugin restoreTransactions:result:]': + if (restoreException != null) { + throw restoreException!; + } + if (testRestoredError != null) { + InAppPurchaseStoreKitPlatform.observer + .restoreCompletedTransactionsFailed(error: testRestoredError!); + return Future.sync(() {}); + } + if (!testRestoredTransactionsNull) { + InAppPurchaseStoreKitPlatform.observer + .updatedTransactions(transactions: transactions); + } + InAppPurchaseStoreKitPlatform.observer + .paymentQueueRestoreCompletedTransactionsFinished(); + + return Future.sync(() {}); + case '-[InAppPurchasePlugin retrieveReceiptData:result:]': + if (receiptData != null) { + return Future.value(receiptData!); + } else { + throw PlatformException(code: 'no_receipt_data'); + } + case '-[InAppPurchasePlugin refreshReceipt:result:]': + receiptData = 'refreshed receipt data'; + return Future.sync(() {}); + case '-[InAppPurchasePlugin addPayment:result:]': + final Map arguments = _getArgumentDictionary(call); + final String id = arguments['productIdentifier']! as String; + final int quantity = arguments['quantity']! as int; + + // Keep the received paymentDiscount parameter when testing payment with discount. + if (arguments['applicationUsername']! == 'userWithDiscount') { + final Map? discountArgument = + arguments['paymentDiscount'] as Map?; + if (discountArgument != null) { + discountReceived = discountArgument.cast(); + } else { + discountReceived = {}; + } + } + + final SKPaymentTransactionWrapper transaction = + createPendingTransaction(id, quantity: quantity); + transactions.add(transaction); + InAppPurchaseStoreKitPlatform.observer.updatedTransactions( + transactions: [transaction]); + sleep(const Duration(milliseconds: 30)); + if (testTransactionFail) { + final SKPaymentTransactionWrapper transactionFailed = + createFailedTransaction(id, quantity: quantity); + InAppPurchaseStoreKitPlatform.observer.updatedTransactions( + transactions: [transactionFailed]); + } else if (testTransactionCancel > 0) { + final SKPaymentTransactionWrapper transactionCanceled = + createCanceledTransaction(id, testTransactionCancel, + quantity: quantity); + InAppPurchaseStoreKitPlatform.observer.updatedTransactions( + transactions: [transactionCanceled]); + } else { + final SKPaymentTransactionWrapper transactionFinished = + createPurchasedTransaction( + id, transaction.transactionIdentifier ?? '', + quantity: quantity); + InAppPurchaseStoreKitPlatform.observer.updatedTransactions( + transactions: [transactionFinished]); + } + case '-[InAppPurchasePlugin finishTransaction:result:]': + final Map arguments = _getArgumentDictionary(call); + finishedTransactions.add(createPurchasedTransaction( + arguments['productIdentifier']! as String, + arguments['transactionIdentifier']! as String, + quantity: transactions.first.payment.quantity)); + case '-[SKPaymentQueue startObservingTransactionQueue]': + queueIsActive = true; + case '-[SKPaymentQueue stopObservingTransactionQueue]': + queueIsActive = false; } - if (!testRestoredTransactionsNull) { - InAppPurchaseStoreKitPlatform.observer - .updatedTransactions(transactions: transactionList); - } - InAppPurchaseStoreKitPlatform.observer - .paymentQueueRestoreCompletedTransactionsFinished(); - } - - @override - Future startProductRequest( - List productIdentifiers) { - if (queryProductException != null) { - throw queryProductException!; - } - final List productIDS = productIdentifiers; - final List invalidFound = []; - final List products = []; - for (final String? productID in productIDS) { - if (!validProductIDs.contains(productID)) { - invalidFound.add(productID!); - } else { - products.add(validProducts[productID]!); - } - } - final SkProductResponseWrapper response = SkProductResponseWrapper( - products: products, invalidProductIdentifiers: invalidFound); - - return Future.value( - SkProductResponseWrapper.convertToPigeon(response)); - } - - @override - Future refreshReceipt({Map? receiptProperties}) { - receiptData = 'refreshed receipt data'; return Future.sync(() {}); } - @override - void registerPaymentQueueDelegate() { - isPaymentQueueDelegateRegistered = true; - } - - @override - void removePaymentQueueDelegate() { - isPaymentQueueDelegateRegistered = false; - } - - @override - String retrieveReceiptData() { - if (receiptData != null) { - return receiptData!; - } else { - throw PlatformException(code: 'no_receipt_data'); - } - } - - @override - void showPriceConsentIfNeeded() {} - - @override - void startObservingPaymentQueue() { - queueIsActive = true; - } - - @override - void stopObservingPaymentQueue() { - queueIsActive = false; + /// Returns the arguments of [call] as typed string-keyed Map. + /// + /// This does not do any type validation, so is only safe to call if the + /// arguments are known to be a map. + Map _getArgumentDictionary(MethodCall call) { + return (call.arguments as Map).cast(); } } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_addtion_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_addtion_test.dart index 40068db75..2890e7542 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_addtion_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_addtion_test.dart @@ -2,12 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; import 'fakes/fake_storekit_platform.dart'; -import 'test_api.g.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -15,7 +15,10 @@ void main() { final FakeStoreKitPlatform fakeStoreKitPlatform = FakeStoreKitPlatform(); setUpAll(() { - TestInAppPurchaseApi.setup(fakeStoreKitPlatform); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + SystemChannels.platform, fakeStoreKitPlatform.onMethodCall); }); group('present code redemption sheet', () { @@ -38,3 +41,9 @@ void main() { }); }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart index 97d13509f..fbb37974a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_platform_test.dart @@ -13,7 +13,6 @@ import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; import 'fakes/fake_storekit_platform.dart'; import 'store_kit_wrappers/sk_test_stub_objects.dart'; -import 'test_api.g.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -22,7 +21,10 @@ void main() { late InAppPurchaseStoreKitPlatform iapStoreKitPlatform; setUpAll(() { - TestInAppPurchaseApi.setup(fakeStoreKitPlatform); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + SystemChannels.platform, fakeStoreKitPlatform.onMethodCall); }); setUp(() { @@ -78,9 +80,9 @@ void main() { group('restore purchases', () { test('should emit restored transactions on purchase stream', () async { - fakeStoreKitPlatform.transactionList.insert( + fakeStoreKitPlatform.transactions.insert( 0, fakeStoreKitPlatform.createRestoredTransaction('foo', 'RT1')); - fakeStoreKitPlatform.transactionList.insert( + fakeStoreKitPlatform.transactions.insert( 1, fakeStoreKitPlatform.createRestoredTransaction('foo', 'RT2')); final Completer> completer = Completer>(); @@ -99,9 +101,9 @@ void main() { final List details = await completer.future; expect(details.length, 2); - for (int i = 0; i < fakeStoreKitPlatform.transactionList.length; i++) { + for (int i = 0; i < fakeStoreKitPlatform.transactions.length; i++) { final SKPaymentTransactionWrapper expected = - fakeStoreKitPlatform.transactionList[i]; + fakeStoreKitPlatform.transactions[i]; final PurchaseDetails actual = details[i]; expect(actual.purchaseID, expected.transactionIdentifier); @@ -136,11 +138,11 @@ void main() { }); test('should not block transaction updates', () async { - fakeStoreKitPlatform.transactionList.insert( + fakeStoreKitPlatform.transactions.insert( 0, fakeStoreKitPlatform.createRestoredTransaction('foo', 'RT1')); - fakeStoreKitPlatform.transactionList.insert( + fakeStoreKitPlatform.transactions.insert( 1, fakeStoreKitPlatform.createPurchasedTransaction('foo', 'bar')); - fakeStoreKitPlatform.transactionList.insert( + fakeStoreKitPlatform.transactions.insert( 2, fakeStoreKitPlatform.createRestoredTransaction('foo', 'RT2')); final Completer> completer = Completer>(); @@ -157,9 +159,9 @@ void main() { await iapStoreKitPlatform.restorePurchases(); final List details = await completer.future; expect(details.length, 3); - for (int i = 0; i < fakeStoreKitPlatform.transactionList.length; i++) { + for (int i = 0; i < fakeStoreKitPlatform.transactions.length; i++) { final SKPaymentTransactionWrapper expected = - fakeStoreKitPlatform.transactionList[i]; + fakeStoreKitPlatform.transactions[i]; final PurchaseDetails actual = details[i]; expect(actual.purchaseID, expected.transactionIdentifier); @@ -180,7 +182,7 @@ void main() { test( 'should emit empty transaction if transactions array does not contain a transaction with PurchaseStatus.restored status.', () async { - fakeStoreKitPlatform.transactionList.insert( + fakeStoreKitPlatform.transactions.insert( 0, fakeStoreKitPlatform.createPurchasedTransaction('foo', 'bar')); final Completer>> completer = Completer>>(); @@ -202,9 +204,9 @@ void main() { final List> details = await completer.future; expect(details.length, 2); expect(details[0], >[]); - for (int i = 0; i < fakeStoreKitPlatform.transactionList.length; i++) { + for (int i = 0; i < fakeStoreKitPlatform.transactions.length; i++) { final SKPaymentTransactionWrapper expected = - fakeStoreKitPlatform.transactionList[i]; + fakeStoreKitPlatform.transactions[i]; final PurchaseDetails actual = details[1][i]; expect(actual.purchaseID, expected.transactionIdentifier); @@ -224,9 +226,9 @@ void main() { test('receipt error should populate null to verificationData.data', () async { - fakeStoreKitPlatform.transactionList.insert( + fakeStoreKitPlatform.transactions.insert( 0, fakeStoreKitPlatform.createRestoredTransaction('foo', 'RT1')); - fakeStoreKitPlatform.transactionList.insert( + fakeStoreKitPlatform.transactions.insert( 1, fakeStoreKitPlatform.createRestoredTransaction('foo', 'RT2')); fakeStoreKitPlatform.receiptData = null; final Completer> completer = @@ -570,14 +572,10 @@ void main() { expect(fakeStoreKitPlatform.queueIsActive, false); }); }); - - group('billing configuration', () { - test('country_code', () async { - const String expectedCountryCode = 'CA'; - fakeStoreKitPlatform.setStoreFrontInfo( - countryCode: expectedCountryCode, identifier: 'ABC'); - final String? countryCode = await iapStoreKitPlatform.getCountryCode(); - expect(countryCode, expectedCountryCode); - }); - }); } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/pigeon_converter_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/pigeon_converter_test.dart deleted file mode 100644 index 3bdbd0af6..000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/pigeon_converter_test.dart +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_test/flutter_test.dart'; -import 'package:in_app_purchase_storekit/src/messages.g.dart'; -import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; - -void main() { - final SKPriceLocaleWrapper locale = SKPriceLocaleWrapper( - currencySymbol: r'$', currencyCode: 'USD', countryCode: 'USA'); - - final SKProductSubscriptionPeriodWrapper subPeriod = - SKProductSubscriptionPeriodWrapper( - numberOfUnits: 1, unit: SKSubscriptionPeriodUnit.month); - - final SKProductDiscountWrapper discount = SKProductDiscountWrapper( - price: '0.99', - priceLocale: locale, - numberOfPeriods: 1, - paymentMode: SKProductDiscountPaymentMode.payUpFront, - subscriptionPeriod: subPeriod, - identifier: 'discount', - type: SKProductDiscountType.subscription); - - final SKProductWrapper product = SKProductWrapper( - productIdentifier: 'fake_product', - localizedTitle: 'title', - localizedDescription: 'description', - priceLocale: locale, - price: '3.99', - subscriptionGroupIdentifier: 'sub_group', - discounts: [discount]); - - final SkProductResponseWrapper productResponse = SkProductResponseWrapper( - products: [product], - invalidProductIdentifiers: const ['invalid_identifier']); - - test('test SKPriceLocale pigeon converters', () { - final SKPriceLocaleMessage msg = - SKPriceLocaleWrapper.convertToPigeon(locale); - expect(msg.currencySymbol, r'$'); - expect(msg.currencyCode, 'USD'); - expect(msg.countryCode, 'USA'); - - final SKPriceLocaleWrapper convertedWrapper = - SKPriceLocaleWrapper.convertFromPigeon(msg); - expect(convertedWrapper, locale); - }); - - test('test SKProductSubscription pigeon converters', () { - final SKProductSubscriptionPeriodMessage msg = - SKProductSubscriptionPeriodWrapper.convertToPigeon(subPeriod); - expect(msg.unit, SKSubscriptionPeriodUnitMessage.month); - expect(msg.numberOfUnits, 1); - final SKProductSubscriptionPeriodWrapper convertedWrapper = - SKProductSubscriptionPeriodWrapper.convertFromPigeon(msg); - expect(convertedWrapper, subPeriod); - }); - - test('test SKProductDiscount pigeon converters', () { - final SKProductDiscountMessage msg = - SKProductDiscountWrapper.convertToPigeon(discount); - expect(msg.price, '0.99'); - expect(msg.numberOfPeriods, 1); - expect(msg.paymentMode, SKProductDiscountPaymentModeMessage.payUpFront); - expect(msg.identifier, 'discount'); - expect(msg.type, SKProductDiscountTypeMessage.subscription); - - final SKProductDiscountWrapper convertedWrapper = - SKProductDiscountWrapper.convertFromPigeon(msg); - expect(convertedWrapper, discount); - }); - - test('test SKProduct pigeon converters', () { - final SKProductMessage msg = SKProductWrapper.convertToPigeon(product); - expect(msg.productIdentifier, 'fake_product'); - expect(msg.localizedTitle, 'title'); - expect(msg.localizedDescription, 'description'); - expect(msg.price, '3.99'); - expect(msg.discounts?.length, 1); - - final SKProductWrapper convertedWrapper = - SKProductWrapper.convertFromPigeon(msg); - expect(convertedWrapper, product); - }); - - test('test SKProductResponse pigeon converters', () { - final SKProductsResponseMessage msg = - SkProductResponseWrapper.convertToPigeon(productResponse); - expect(msg.products?.length, 1); - expect(msg.invalidProductIdentifiers, ['invalid_identifier']); - - final SkProductResponseWrapper convertedWrapper = - SkProductResponseWrapper.convertFromPigeon(msg); - expect(convertedWrapper, productResponse); - }); - - test('test SKerror pigeon converter', () { - final SKErrorMessage msg = SKErrorMessage(code: 99, domain: 'domain'); - final SKError wrapper = SKError.convertFromPigeon(msg); - - expect(wrapper.code, 99); - expect(wrapper.domain, 'domain'); - expect(wrapper.userInfo, {}); - }); -} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart index 82775f6b2..f04f4b3eb 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart @@ -4,9 +4,8 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:in_app_purchase_storekit/src/messages.g.dart'; +import 'package:in_app_purchase_storekit/src/channel.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; -import '../test_api.g.dart'; import 'sk_test_stub_objects.dart'; void main() { @@ -15,7 +14,10 @@ void main() { final FakeStoreKitPlatform fakeStoreKitPlatform = FakeStoreKitPlatform(); setUpAll(() { - TestInAppPurchaseApi.setup(fakeStoreKitPlatform); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + SystemChannels.platform, fakeStoreKitPlatform.onMethodCall); }); setUp(() {}); @@ -72,10 +74,10 @@ void main() { }); test('refreshed receipt', () async { - final int receiptCountBefore = fakeStoreKitPlatform.refreshReceiptCount; + final int receiptCountBefore = fakeStoreKitPlatform.refreshReceipt; await SKRequestMaker().startRefreshReceiptRequest( receiptProperties: {'isExpired': true}); - expect(fakeStoreKitPlatform.refreshReceiptCount, receiptCountBefore + 1); + expect(fakeStoreKitPlatform.refreshReceipt, receiptCountBefore + 1); expect(fakeStoreKitPlatform.refreshReceiptParam, {'isExpired': true}); }); @@ -100,6 +102,12 @@ void main() { expect(await SKPaymentQueueWrapper.canMakePayments(), true); }); + test('canMakePayment returns false if method channel returns null', + () async { + fakeStoreKitPlatform.testReturnNull = true; + expect(await SKPaymentQueueWrapper.canMakePayments(), false); + }); + test('storefront returns valid SKStoreFrontWrapper object', () async { final SKPaymentQueueWrapper queue = SKPaymentQueueWrapper(); expect( @@ -110,6 +118,12 @@ void main() { })); }); + test('storefront returns null', () async { + fakeStoreKitPlatform.testReturnNull = true; + final SKPaymentQueueWrapper queue = SKPaymentQueueWrapper(); + expect(await queue.storefront(), isNull); + }); + test('transactions should return a valid list of transactions', () async { expect(await SKPaymentQueueWrapper().transactions(), isNotEmpty); }); @@ -171,9 +185,9 @@ void main() { }); test('showPriceConsentIfNeeded should call methodChannel', () async { - expect(fakeStoreKitPlatform.showPriceConsent, false); + expect(fakeStoreKitPlatform.showPriceConsentIfNeeded, false); await SKPaymentQueueWrapper().showPriceConsentIfNeeded(); - expect(fakeStoreKitPlatform.showPriceConsent, true); + expect(fakeStoreKitPlatform.showPriceConsentIfNeeded, true); }); }); @@ -187,7 +201,12 @@ void main() { }); } -class FakeStoreKitPlatform implements TestInAppPurchaseApi { +class FakeStoreKitPlatform { + FakeStoreKitPlatform() { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, onMethodCall); + } // get product request List startProductRequestParam = []; bool getProductRequestFailTest = false; @@ -197,7 +216,7 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { bool getReceiptFailTest = false; // refresh receipt request - int refreshReceiptCount = 0; + int refreshReceipt = 0; late Map refreshReceiptParam; // payment queue @@ -209,7 +228,7 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { bool presentCodeRedemption = false; // show price consent sheet - bool showPriceConsent = false; + bool showPriceConsentIfNeeded = false; // indicate if the payment queue delegate is registered bool isPaymentQueueDelegateRegistered = false; @@ -217,92 +236,75 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { // Listen to purchase updates bool? queueIsActive; - @override - void addPayment(Map paymentMap) { - payments - .add(SKPaymentWrapper.fromJson(Map.from(paymentMap))); - } - - @override - bool canMakePayments() { - return true; - } - - @override - SKStorefrontMessage storefront() { - return SKStorefrontMessage( - countryCode: 'USA', identifier: 'unique_identifier'); - } - - @override - List transactions() => - [dummyTransactionMessage]; - - @override - void finishTransaction(Map finishMap) { - transactionsFinished.add(Map.from(finishMap)); - } - - @override - void presentCodeRedemptionSheet() { - presentCodeRedemption = true; - } - - @override - void restoreTransactions(String? applicationUserName) { - applicationNameHasTransactionRestored = applicationUserName!; - } - - @override - Future startProductRequest( - List productIdentifiers) { - startProductRequestParam = productIdentifiers; - if (getProductRequestFailTest) { - return Future.value( - SKProductsResponseMessage()); - } - return Future.value(dummyProductResponseMessage); - } - - @override - void registerPaymentQueueDelegate() { - isPaymentQueueDelegateRegistered = true; - } - - @override - void removePaymentQueueDelegate() { - isPaymentQueueDelegateRegistered = false; - } - - @override - void startObservingPaymentQueue() { - queueIsActive = true; - } - - @override - void stopObservingPaymentQueue() { - queueIsActive = false; - } - - @override - String retrieveReceiptData() { - if (getReceiptFailTest) { - throw Exception('some arbitrary error'); + Future onMethodCall(MethodCall call) { + switch (call.method) { + // request makers + case '-[InAppPurchasePlugin startProductRequest:result:]': + startProductRequestParam = call.arguments as List; + if (getProductRequestFailTest) { + return Future.value(); + } + return Future>.value( + buildProductResponseMap(dummyProductResponseWrapper)); + case '-[InAppPurchasePlugin refreshReceipt:result:]': + refreshReceipt++; + refreshReceiptParam = Map.castFrom( + call.arguments as Map); + return Future.sync(() {}); + // receipt manager + case '-[InAppPurchasePlugin retrieveReceiptData:result:]': + if (getReceiptFailTest) { + throw Exception('some arbitrary error'); + } + return Future.value('receipt data'); + // payment queue + case '-[SKPaymentQueue canMakePayments:]': + if (testReturnNull) { + return Future.value(); + } + return Future.value(true); + case '-[SKPaymentQueue transactions]': + return Future>.value( + [buildTransactionMap(dummyTransaction)]); + case '-[SKPaymentQueue storefront]': + if (testReturnNull) { + return Future.value(); + } + return Future>.value(const { + 'countryCode': 'USA', + 'identifier': 'unique_identifier', + }); + case '-[InAppPurchasePlugin addPayment:result:]': + payments.add(SKPaymentWrapper.fromJson(Map.from( + call.arguments as Map))); + return Future.sync(() {}); + case '-[InAppPurchasePlugin finishTransaction:result:]': + transactionsFinished.add( + Map.from(call.arguments as Map)); + return Future.sync(() {}); + case '-[InAppPurchasePlugin restoreTransactions:result:]': + applicationNameHasTransactionRestored = call.arguments as String; + return Future.sync(() {}); + case '-[InAppPurchasePlugin presentCodeRedemptionSheet:result:]': + presentCodeRedemption = true; + return Future.sync(() {}); + case '-[SKPaymentQueue startObservingTransactionQueue]': + queueIsActive = true; + return Future.sync(() {}); + case '-[SKPaymentQueue stopObservingTransactionQueue]': + queueIsActive = false; + return Future.sync(() {}); + case '-[SKPaymentQueue registerDelegate]': + isPaymentQueueDelegateRegistered = true; + return Future.sync(() {}); + case '-[SKPaymentQueue removeDelegate]': + isPaymentQueueDelegateRegistered = false; + return Future.sync(() {}); + case '-[SKPaymentQueue showPriceConsentIfNeeded]': + showPriceConsentIfNeeded = true; + return Future.sync(() {}); } - return 'receipt data'; - } - - @override - Future refreshReceipt({Map? receiptProperties}) { - refreshReceiptCount++; - refreshReceiptParam = - Map.castFrom(receiptProperties!); - return Future.sync(() {}); - } - - @override - void showPriceConsentIfNeeded() { - showPriceConsent = true; + return Future.error('method not mocked'); } } @@ -329,3 +331,9 @@ class TestPaymentTransactionObserver extends SKTransactionObserverWrapper { return true; } } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_payment_queue_delegate_api_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_payment_queue_delegate_api_test.dart index 03c9fda38..3d55fe27d 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_payment_queue_delegate_api_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_payment_queue_delegate_api_test.dart @@ -4,18 +4,19 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:in_app_purchase_storekit/src/channel.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; -import '../fakes/fake_storekit_platform.dart'; -import '../test_api.g.dart'; - void main() { TestWidgetsFlutterBinding.ensureInitialized(); final FakeStoreKitPlatform fakeStoreKitPlatform = FakeStoreKitPlatform(); setUpAll(() { - TestInAppPurchaseApi.setup(fakeStoreKitPlatform); + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler( + SystemChannels.platform, fakeStoreKitPlatform.onMethodCall); }); test( @@ -146,3 +147,32 @@ class TestPaymentQueueDelegate extends SKPaymentQueueDelegateWrapper { return false; } } + +class FakeStoreKitPlatform { + FakeStoreKitPlatform() { + _ambiguate(TestDefaultBinaryMessengerBinding.instance)! + .defaultBinaryMessenger + .setMockMethodCallHandler(channel, onMethodCall); + } + + // indicate if the payment queue delegate is registered + bool isPaymentQueueDelegateRegistered = false; + + Future onMethodCall(MethodCall call) { + switch (call.method) { + case '-[SKPaymentQueue registerDelegate]': + isPaymentQueueDelegateRegistered = true; + return Future.sync(() {}); + case '-[SKPaymentQueue removeDelegate]': + isPaymentQueueDelegateRegistered = false; + return Future.sync(() {}); + } + return Future.error('method not mocked'); + } +} + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart index 59022aa2f..6601a21c4 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:in_app_purchase_storekit/src/messages.g.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; const SKPaymentWrapper dummyPayment = SKPaymentWrapper( @@ -12,13 +11,6 @@ const SKPaymentWrapper dummyPayment = SKPaymentWrapper( quantity: 2, simulatesAskToBuyInSandbox: true); -SKPaymentMessage dummyPaymentMessage = SKPaymentMessage( - productIdentifier: 'prod-id', - applicationUsername: 'app-user-name', - requestData: 'fake-data-utf8', - quantity: 2, - simulatesAskToBuyInSandbox: true); - final SKPaymentWrapper dummyPaymentWithDiscount = SKPaymentWrapper( productIdentifier: 'prod-id', applicationUsername: 'app-user-name', @@ -51,23 +43,12 @@ final SKPaymentTransactionWrapper dummyTransaction = error: dummyError, ); -final SKPaymentTransactionMessage dummyTransactionMessage = - SKPaymentTransactionMessage( - payment: dummyPaymentMessage, - transactionState: SKPaymentTransactionStateMessage.purchased); - final SKPriceLocaleWrapper dollarLocale = SKPriceLocaleWrapper( currencySymbol: r'$', currencyCode: 'USD', countryCode: 'US', ); -final SKPriceLocaleMessage dollarLocaleMessage = SKPriceLocaleMessage( - currencySymbol: r'$', - currencyCode: 'USD', - countryCode: 'US', -); - final SKPriceLocaleWrapper noSymbolLocale = SKPriceLocaleWrapper( currencySymbol: '', currencyCode: 'EUR', @@ -80,12 +61,6 @@ final SKProductSubscriptionPeriodWrapper dummySubscription = unit: SKSubscriptionPeriodUnit.month, ); -final SKProductSubscriptionPeriodMessage dummySubscriptionMessage = - SKProductSubscriptionPeriodMessage( - numberOfUnits: 1, - unit: SKSubscriptionPeriodUnitMessage.month, -); - final SKProductDiscountWrapper dummyDiscount = SKProductDiscountWrapper( price: '1.0', priceLocale: dollarLocale, @@ -96,16 +71,6 @@ final SKProductDiscountWrapper dummyDiscount = SKProductDiscountWrapper( type: SKProductDiscountType.subscription, ); -final SKProductDiscountMessage dummyDiscountMessage = SKProductDiscountMessage( - price: '1.0', - priceLocale: dollarLocaleMessage, - numberOfPeriods: 1, - paymentMode: SKProductDiscountPaymentModeMessage.payUpFront, - subscriptionPeriod: dummySubscriptionMessage, - identifier: 'id', - type: SKProductDiscountTypeMessage.subscription, -); - final SKProductDiscountWrapper dummyDiscountMissingIdentifierAndType = SKProductDiscountWrapper( price: '1.0', @@ -129,30 +94,12 @@ final SKProductWrapper dummyProductWrapper = SKProductWrapper( discounts: [dummyDiscount], ); -final SKProductMessage dummyProductMessage = SKProductMessage( - productIdentifier: 'id', - localizedTitle: 'title', - localizedDescription: 'description', - priceLocale: dollarLocaleMessage, - subscriptionGroupIdentifier: 'com.group', - price: '1.0', - subscriptionPeriod: dummySubscriptionMessage, - introductoryPrice: dummyDiscountMessage, - discounts: [dummyDiscountMessage], -); - final SkProductResponseWrapper dummyProductResponseWrapper = SkProductResponseWrapper( products: [dummyProductWrapper], invalidProductIdentifiers: const ['123'], ); -final SKProductsResponseMessage dummyProductResponseMessage = - SKProductsResponseMessage( - products: [dummyProductMessage], - invalidProductIdentifiers: const ['123'], -); - Map buildLocaleMap(SKPriceLocaleWrapper local) { return { 'currencySymbol': local.currencySymbol, @@ -249,22 +196,6 @@ Map buildTransactionMap( return map; } -Map buildTransactionMessage( - SKPaymentTransactionWrapper transaction) { - final Map map = { - 'transactionState': SKPaymentTransactionStateWrapper.values - .indexOf(SKPaymentTransactionStateWrapper.purchased), - 'payment': transaction.payment.toMap(), - 'originalTransaction': transaction.originalTransaction == null - ? null - : buildTransactionMap(transaction.originalTransaction!), - 'transactionTimeStamp': transaction.transactionTimeStamp, - 'transactionIdentifier': transaction.transactionIdentifier, - 'error': buildErrorMap(transaction.error!), - }; - return map; -} - final SKPaymentDiscountWrapper dummyPaymentDiscountWrapper = SKPaymentDiscountWrapper.fromJson(const { 'identifier': 'dummy-discount-identifier', diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart deleted file mode 100644 index 65cd6e0bc..000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart +++ /dev/null @@ -1,532 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// Autogenerated from Pigeon (v16.0.4), do not edit directly. -// See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers -// ignore_for_file: avoid_relative_lib_imports -import 'dart:async'; -import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; -import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:in_app_purchase_storekit/src/messages.g.dart'; - -class _TestInAppPurchaseApiCodec extends StandardMessageCodec { - const _TestInAppPurchaseApiCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is SKErrorMessage) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is SKPaymentDiscountMessage) { - buffer.putUint8(129); - writeValue(buffer, value.encode()); - } else if (value is SKPaymentMessage) { - buffer.putUint8(130); - writeValue(buffer, value.encode()); - } else if (value is SKPaymentTransactionMessage) { - buffer.putUint8(131); - writeValue(buffer, value.encode()); - } else if (value is SKPriceLocaleMessage) { - buffer.putUint8(132); - writeValue(buffer, value.encode()); - } else if (value is SKProductDiscountMessage) { - buffer.putUint8(133); - writeValue(buffer, value.encode()); - } else if (value is SKProductMessage) { - buffer.putUint8(134); - writeValue(buffer, value.encode()); - } else if (value is SKProductSubscriptionPeriodMessage) { - buffer.putUint8(135); - writeValue(buffer, value.encode()); - } else if (value is SKProductsResponseMessage) { - buffer.putUint8(136); - writeValue(buffer, value.encode()); - } else if (value is SKStorefrontMessage) { - buffer.putUint8(137); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 128: - return SKErrorMessage.decode(readValue(buffer)!); - case 129: - return SKPaymentDiscountMessage.decode(readValue(buffer)!); - case 130: - return SKPaymentMessage.decode(readValue(buffer)!); - case 131: - return SKPaymentTransactionMessage.decode(readValue(buffer)!); - case 132: - return SKPriceLocaleMessage.decode(readValue(buffer)!); - case 133: - return SKProductDiscountMessage.decode(readValue(buffer)!); - case 134: - return SKProductMessage.decode(readValue(buffer)!); - case 135: - return SKProductSubscriptionPeriodMessage.decode(readValue(buffer)!); - case 136: - return SKProductsResponseMessage.decode(readValue(buffer)!); - case 137: - return SKStorefrontMessage.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - -abstract class TestInAppPurchaseApi { - static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => - TestDefaultBinaryMessengerBinding.instance; - static const MessageCodec pigeonChannelCodec = - _TestInAppPurchaseApiCodec(); - - /// Returns if the current device is able to make payments - bool canMakePayments(); - - List transactions(); - - SKStorefrontMessage storefront(); - - void addPayment(Map paymentMap); - - Future startProductRequest( - List productIdentifiers); - - void finishTransaction(Map finishMap); - - void restoreTransactions(String? applicationUserName); - - void presentCodeRedemptionSheet(); - - String? retrieveReceiptData(); - - Future refreshReceipt({Map? receiptProperties}); - - void startObservingPaymentQueue(); - - void stopObservingPaymentQueue(); - - void registerPaymentQueueDelegate(); - - void removePaymentQueueDelegate(); - - void showPriceConsentIfNeeded(); - - static void setup(TestInAppPurchaseApi? api, - {BinaryMessenger? binaryMessenger}) { - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.canMakePayments', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, - (Object? message) async { - try { - final bool output = api.canMakePayments(); - return [output]; - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.transactions', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, - (Object? message) async { - try { - final List output = - api.transactions(); - return [output]; - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.storefront', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, - (Object? message) async { - try { - final SKStorefrontMessage output = api.storefront(); - return [output]; - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.addPayment', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.addPayment was null.'); - final List args = (message as List?)!; - final Map? arg_paymentMap = - (args[0] as Map?)?.cast(); - assert(arg_paymentMap != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.addPayment was null, expected non-null Map.'); - try { - api.addPayment(arg_paymentMap!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.startProductRequest', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.startProductRequest was null.'); - final List args = (message as List?)!; - final List? arg_productIdentifiers = - (args[0] as List?)?.cast(); - assert(arg_productIdentifiers != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.startProductRequest was null, expected non-null List.'); - try { - final SKProductsResponseMessage output = - await api.startProductRequest(arg_productIdentifiers!); - return [output]; - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.finishTransaction', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.finishTransaction was null.'); - final List args = (message as List?)!; - final Map? arg_finishMap = - (args[0] as Map?)?.cast(); - assert(arg_finishMap != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.finishTransaction was null, expected non-null Map.'); - try { - api.finishTransaction(arg_finishMap!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.restoreTransactions', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.restoreTransactions was null.'); - final List args = (message as List?)!; - final String? arg_applicationUserName = (args[0] as String?); - try { - api.restoreTransactions(arg_applicationUserName); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.presentCodeRedemptionSheet', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, - (Object? message) async { - try { - api.presentCodeRedemptionSheet(); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.retrieveReceiptData', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, - (Object? message) async { - try { - final String? output = api.retrieveReceiptData(); - return [output]; - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.refreshReceipt', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.refreshReceipt was null.'); - final List args = (message as List?)!; - final Map? arg_receiptProperties = - (args[0] as Map?)?.cast(); - try { - await api.refreshReceipt(receiptProperties: arg_receiptProperties); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.startObservingPaymentQueue', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, - (Object? message) async { - try { - api.startObservingPaymentQueue(); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.stopObservingPaymentQueue', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, - (Object? message) async { - try { - api.stopObservingPaymentQueue(); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.registerPaymentQueueDelegate', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, - (Object? message) async { - try { - api.registerPaymentQueueDelegate(); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.removePaymentQueueDelegate', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, - (Object? message) async { - try { - api.removePaymentQueueDelegate(); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.showPriceConsentIfNeeded', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, - (Object? message) async { - try { - api.showPriceConsentIfNeeded(); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - } -} -- Gitee