├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── pull_request_template.md ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── pay ├── .pubignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── analysis_options.yaml │ ├── android │ │ ├── .gitignore │ │ ├── app │ │ │ ├── build.gradle │ │ │ └── src │ │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── res │ │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ └── values │ │ │ │ │ └── styles.xml │ │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ └── gradle-wrapper.properties │ │ └── settings.gradle │ ├── assets │ │ ├── default_apple_pay_config.json │ │ ├── default_google_pay_config.json │ │ └── images │ │ │ └── ts_10_11019a.jpg │ ├── integration_test │ │ └── payment_flow_test.dart │ ├── ios │ │ ├── .gitignore │ │ ├── Flutter │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ └── Release.xcconfig │ │ ├── Podfile │ │ ├── Podfile.lock │ │ ├── Runner.xcodeproj │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata │ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ └── xcshareddata │ │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ ├── Runner.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── Runner │ │ │ ├── AppDelegate.h │ │ │ ├── AppDelegate.m │ │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ │ └── LaunchImage.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ └── README.md │ │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ ├── de.lproj │ │ │ ├── LaunchScreen.strings │ │ │ └── Main.strings │ │ │ ├── es.lproj │ │ │ ├── LaunchScreen.strings │ │ │ └── Main.strings │ │ │ └── main.m │ ├── lib │ │ ├── advanced.dart │ │ ├── main.dart │ │ └── payment_configurations.dart │ └── pubspec.yaml ├── lib │ ├── pay.dart │ └── src │ │ ├── pay.dart │ │ └── widgets │ │ ├── apple_pay_button.dart │ │ ├── google_pay_button.dart │ │ └── pay_button.dart ├── pubspec.yaml └── test │ ├── assets │ └── google_pay_default_payment_profile.json │ └── pay_test.dart ├── pay_android ├── .pubignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── settings.gradle │ └── src │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ │ └── io │ │ │ └── flutter │ │ │ └── plugins │ │ │ └── pay_android │ │ │ ├── GooglePayHandler.kt │ │ │ ├── PayMethodCallHandler.kt │ │ │ ├── PayPlugin.kt │ │ │ ├── util │ │ │ └── PaymentsUtil.kt │ │ │ └── view │ │ │ ├── PayButtonView.kt │ │ │ └── PayButtonViewFactory.kt │ │ └── test │ │ └── kotlin │ │ └── io │ │ └── flutter │ │ └── plugins │ │ └── pay_android │ │ └── GooglePayHandlerTest.kt ├── lib │ ├── pay_android.dart │ └── src │ │ └── widgets │ │ └── google_pay_button.dart ├── pubspec.yaml └── test │ └── pay_button_test.dart ├── pay_ios ├── .pubignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── ios │ ├── .gitignore │ ├── Assets │ │ └── .gitkeep │ ├── Classes │ │ ├── ApplePayButtonView.swift │ │ ├── PayPlugin.swift │ │ ├── PaymentExtensions.swift │ │ └── PaymentHandler.swift │ └── pay_ios.podspec ├── lib │ ├── pay_ios.dart │ └── src │ │ └── widgets │ │ └── apple_pay_button.dart ├── pubspec.yaml └── test │ └── pay_button_test.dart └── pay_platform_interface ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── lib ├── core │ ├── payment_configuration.dart │ └── payment_item.dart ├── pay_channel.dart ├── pay_platform_interface.dart └── util │ └── configurations.dart ├── pubspec.yaml └── test ├── assets ├── google_pay_default_payment_profile.json ├── google_pay_prod_payment_profile.json └── google_pay_test_payment_profile.json ├── pay_channel_test.dart └── payment_configuration_test.dart /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **`flutter doctor`** output 27 | Include a copy of the output after running `flutter doctor` if you suspect your issue is related with structural properties in Flutter or other libraries. 28 | 29 | **Additional context** 30 | Add any other context about the problem here. 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | > :warning: 2 | > Hello, and thank you for your contributions!
3 | > Please review the following checklist before creating a new pull request: 4 | > 5 | > - Review and contribute to existing pull requests and open issues to avoid redundant efforts. 6 | > - Read [the contribution guidelines](CONTRIBUTING.md). 7 | > - Assign reviewers. 8 | > - If applicable, include a reference to related issues. 9 | > - If this is a new feature, create an issue and coordinate with other contributors to make sure the feature can be made available in all platforms supported. 10 | > 11 | > :warning: Delete this block before creating the pull request. 12 | 13 | **Describe the changes proposed**
14 | A clear and concise description of the changes included in this pull request. 15 | 16 | **Screenshots / Videos**
17 | If useful, include any screeshots or screencasts to better explain the change. 18 | 19 | **Additional context**
20 | Add any other context about the change here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/guides/libraries/private-files 2 | 3 | # Files and directories created by pub 4 | .dart_tool/ 5 | .packages 6 | build/ 7 | 8 | # If you're building an application, you may want to check-in your pubspec.lock 9 | pubspec.lock 10 | 11 | # iOS 12 | Podfile.lock 13 | Pods/ 14 | .symlinks/ 15 | 16 | # Directory created by dartdoc 17 | # If you don't generate documentation locally you can remove this line. 18 | doc/api/ 19 | 20 | # Generated files on example apps 21 | flutter_export_environment.sh 22 | .flutter-plugins* 23 | local.properties 24 | keystore.properties 25 | **/Flutter/Generated.xcconfig 26 | **/Flutter/App.framework/ 27 | **/Flutter/ephemeral/ 28 | **/Flutter/Flutter.podspec 29 | **/Flutter/Flutter.framework/ 30 | **/Flutter/flutter_assets/ 31 | 32 | ServiceDefinitions.json 33 | xcuserdata/ 34 | **/DerivedData/ 35 | 36 | generated_plugin_registrant.* 37 | GeneratedPluginRegistrant.* 38 | 39 | # Gradle 40 | **/gradle-wrapper.jar 41 | .gradle/ 42 | gradlew 43 | gradlew.bat 44 | 45 | .project 46 | .classpath 47 | .settings 48 | 49 | # Avoid committing generated Javascript files: 50 | *.dart.js 51 | *.info.json # Produced by the --dump-info flag. 52 | *.js # When generated by dart2js. Don't specify *.js if your 53 | # project includes source files written in JavaScript. 54 | *.js_ 55 | *.js.deps 56 | *.js.map 57 | .idea 58 | .flutter-plugins 59 | .flutter-plugins-dependencies 60 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | _See also: [Flutter's code of conduct](https://flutter.io/design-principles/#code-of-conduct)_ 3 | 4 | We'd love to accept your patches and contributions to this project. There are just a few small guidelines you need to 5 | follow. 6 | 7 | Please use the 8 | [Flutter style guide](https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo) and 9 | [design principles](https://flutter.io/design-principles/) before 10 | working on anything non-trivial. These guidelines are intended to 11 | keep the code consistent and avoid common pitfalls. 12 | 13 | ## Contributor License Agreement 14 | 15 | Contributions to this project must be accompanied by a Contributor License Agreement (CLA). You (or your employer) 16 | retain the copyright to your contribution; this simply gives us permission to use and redistribute your contributions as 17 | part of the project. Head over to to see your current agreements on file or to sign 18 | a new one. 19 | 20 | You generally only need to submit a CLA once, so if you've already submitted one (even if it was for a different 21 | project), you probably don't need to do it again. 22 | 23 | ## 1. Things you will need 24 | 25 | - Linux, Mac OS X, or Windows. 26 | - [git](https://git-scm.com) (used for source version control). 27 | - An SSH client (used to authenticate with GitHub). 28 | - An IDE such as [Android Studio](https://developer.android.com/studio) or [Visual Studio Code](https://code.visualstudio.com/). 29 | - [`tuneup`](https://pub.dev/packages/tuneup) locally activated. 30 | 31 | ## 2. Forking & cloning the repository 32 | 33 | - Fork the [repository](repo-url) into your own GitHub account. 34 | - If you haven't configured your machine with an SSH key that's known to github, then 35 | follow [GitHub's directions](https://help.github.com/articles/generating-ssh-keys/) 36 | to generate an SSH key. 37 | - Clone your new forked repository: `git clone git@github.com:/plugins.git` 38 | - Add the original repository to the list of remotes: `git remote add upstream git@github.com:flutter/plugins.git` 39 | 40 | ## 3. Running examples 41 | 42 | To run an example, run the `flutter run` command from the `example` directory of each plugins' main 43 | directory. For example, for the `pay` example: 44 | 45 | ```bash 46 | cd pay/example 47 | flutter run 48 | ``` 49 | 50 | ## 4. Running tests 51 | 52 | This plugin comprises of a number of end-to-end (e2e) and unit tests. 53 | 54 | ### Unit tests 55 | 56 | Unit tests are responsible for ensuring expected behavior whilst developing the plugins Dart code. Unit tests do not 57 | interact with 3rd party services, and mock where possible. To run unit tests for a specific plugin, run the 58 | `flutter test` command from the plugins root directory. For example: 59 | 60 | ```bash 61 | cd pay 62 | flutter test 63 | ``` 64 | 65 | ### End-to-end (e2e) tests 66 | 67 | E2e tests are those which directly communicate with Flutter, whose results cannot be mocked. **These tests run directly from 68 | an example application.** 69 | 70 | To run e2e tests, run the `flutter drive` command from the plugins' main `example` directory, targeting the 71 | entry e2e test file. 72 | 73 | ```bash 74 | cd pay/example 75 | flutter drive \ 76 | --driver=test_driver/integration_test.dart \ 77 | --target=integration_test/payment_flow_test.dart 78 | ``` 79 | 80 | ## 5. Start contributing 81 | To start working on a patch: 82 | 83 | 1. `git fetch upstream` 84 | 2. `git checkout upstream/main -b ` 85 | 3. Hack away! 86 | 87 | Once you have made your changes, ensure that it passes the internal analyzer & formatting checks. The following 88 | commands can be run locally to highlight any issues before committing your code: 89 | 90 | Assuming all is successful, commit and push your code: 91 | 92 | 1. `git commit -a -m ""` 93 | 2. `git push origin ` 94 | 95 | ### Commit Messages 96 | 97 | We follow the [Conventional Commits specification][conventional-commits] to help keep the commit history readable and to 98 | automate release process with updated changelog details. 99 | 100 | The commit messages should follow this format: 101 | 102 | ```text 103 | [optional scope]: 104 | 105 | [optional body] 106 | 107 | [optional footer(s)] 108 | ``` 109 | 110 | For example: 111 | 112 | `fix(pay_android): fixed a bug!` 113 | 114 | Refer to the [specification][conventional-commits] for more information. 115 | 116 | ## 6. Create a pull request 117 | Go to the [repository](repo-url) and click the "Compare & pull request" button 118 | 119 | Plugins tests are run automatically on contributions using GitHub Actions. Depending on your code contributions, various tests will be run against your updated code automatically. 120 | 121 | Once you've gotten an LGTM from a project maintainer and once your PR has received the green light from all our automated testing, wait for one the package maintainers to merge the pull request. 122 | 123 | ## 7. The review process 124 | 125 | Newly opened PRs first go through initial triage which results in one of: 126 | 127 | - **Merging the PR** - if the PR can be quickly reviewed and looks good. 128 | - **Closing the PR** - if the PR maintainer decides that the PR should not be merged. 129 | - **Moving the PR to the backlog** - if the review requires non trivial effort and the issue isn't a priority; in this case the maintainer will: 130 | - Make sure that the PR has an associated issue labeled with "plugin". 131 | - Add the "backlog" label to the issue. 132 | - Leave a comment on the PR explaining that the review is not trivial and that the issue will be looked at according to priority order. 133 | - **Starting a non trivial review** - if the review requires non trivial effort and the issue is a priority; in this case the maintainer will: 134 | - Add the "in review" label to the issue. 135 | - Self assign the PR. 136 | 137 | ## 8. The release process 138 | 139 | Changelogs and version updates are automatically updated by a project maintainer. The new version is automatically 140 | generated via the commit types and changelogs via the commit messages. 141 | 142 | ## Community Guidelines 143 | 144 | This project follows [Google's Open Source Community Guidelines](https://opensource.google/conduct/). 145 | 146 | [conventional-commits]: https://www.conventionalcommits.org/en/v1.0.0/ 147 | [repo-url]: https://github.com/flutter/plugins -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A plugin to add payments to your Flutter application. 2 | 3 | | pay | pay_android | pay_ios | pay_platform_interface | 4 | |:---:|:---:|:---:|:---:| 5 | | [![pub package](https://img.shields.io/pub/v/pay.svg)](https://pub.dartlang.org/packages/pay) | [![pub package](https://img.shields.io/pub/v/pay_android.svg)](https://pub.dartlang.org/packages/pay_android) | [![pub package](https://img.shields.io/pub/v/pay_ios.svg)](https://pub.dartlang.org/packages/pay_ios) | [![pub package](https://img.shields.io/pub/v/pay_platform_interface.svg)](https://pub.dartlang.org/packages/pay_platform_interface) | 6 | 7 | ## Platform Support 8 | | Android | iOS | 9 | |:---:|:---:| 10 | | Google Pay | Apple Pay | 11 | 12 | ## Getting started 13 | Before you start, create an account with the payment providers you are planning to support and follow the setup instructions: 14 | 15 | #### Apple Pay: 16 | 1. Take a look at the [integration requirements](https://developer.apple.com/documentation/passkit/apple_pay/setting_up_apple_pay_requirements). 17 | 2. Create a [merchant identifier](https://developer.apple.com/help/account/configure-app-capabilities/configure-apple-pay#create-a-merchant-identifier) for your business. 18 | 3. Create a [payment processing certificate](https://developer.apple.com/help/account/configure-app-capabilities/configure-apple-pay#create-a-payment-processing-certificate) to encrypt payment information. 19 | 20 | #### Google Pay: 21 | 1. Take a look at the [integration requirements](https://developers.google.com/pay/api/android/overview). 22 | 2. Sign up to the [business console](https://pay.google.com/business/console) and create an account. 23 | 24 | ## Installation 25 | This adds the `pay` package to the [list of dependencies in your pubspec.yaml file](https://flutter.io/platform-plugins/) with the following command: 26 | 27 | ```shell 28 | flutter pub add pay 29 | ``` 30 | 31 | ## Usage 32 | 33 | Define the configuration for your payment provider(s). Take a look at the parameters available in the documentation for [Apple Pay](https://developer.apple.com/documentation/passkit/pkpaymentrequest) and [Google Pay](https://developers.google.com/pay/api/android/reference/request-objects), and explore the [sample configurations in this package](https://github.com/google-pay/flutter-plugin/tree/main/pay/example/lib/payment_configurations.dart). 34 | 35 | ### Example 36 | This snippet assumes the existence of a payment configuration for Apple Pay ([`defaultApplePayConfig`](https://github.com/google-pay/flutter-plugin/tree/main/pay/example/lib/payment_configurations.dart#L29)) and another one for Google Pay ([`defaultGooglePayConfig`](https://github.com/google-pay/flutter-plugin/tree/main/pay/example/lib/payment_configurations.dart#L69)): 37 | 38 | ```dart 39 | import 'package:pay/pay.dart'; 40 | import 'payment_configurations.dart' as payment_configurations; 41 | 42 | const _paymentItems = [ 43 | PaymentItem( 44 | label: 'Total', 45 | amount: '99.99', 46 | status: PaymentItemStatus.final_price, 47 | ) 48 | ]; 49 | 50 | ApplePayButton( 51 | paymentConfiguration: payment_configurations.defaultApplePayConfig, 52 | paymentItems: _paymentItems, 53 | style: ApplePayButtonStyle.black, 54 | type: ApplePayButtonType.buy, 55 | margin: const EdgeInsets.only(top: 15.0), 56 | onPaymentResult: onApplePayResult, 57 | loadingIndicator: const Center( 58 | child: CircularProgressIndicator(), 59 | ), 60 | ), 61 | 62 | GooglePayButton( 63 | paymentConfiguration: payment_configurations.defaultGooglePayConfig, 64 | paymentItems: _paymentItems, 65 | type: GooglePayButtonType.buy, 66 | margin: const EdgeInsets.only(top: 15.0), 67 | onPaymentResult: onGooglePayResult, 68 | loadingIndicator: const Center( 69 | child: CircularProgressIndicator(), 70 | ), 71 | ), 72 | 73 | void onApplePayResult(paymentResult) { 74 | // Send the resulting Apple Pay token to your server / PSP 75 | } 76 | 77 | void onGooglePayResult(paymentResult) { 78 | // Send the resulting Google Pay token to your server / PSP 79 | } 80 | ``` 81 | 82 | To learn more about the `pay` plugin and alternative integration paths, check out [the readme in the `pay` folder](./pay/README.md). 83 | 84 | ## Other packages in this plugin 85 | 86 | The packages in this repository follow the [federated plugin](https://docs.flutter.dev/packages-and-plugins/developing-packages#federated-plugins) architecture. Each package has a specific responsibility and can be used independently to fulfil less conventional integration needs: 87 | 88 | | Package | Description | When to use | 89 | |:---:|:---:|:---:| 90 | | [pay](./pay/) | An app-facing package with support for all the platforms supported by this plugin. | This package offers an agnostic integration for the platforms supported in this plugin and features the easiest path to add payments to your Flutter application. | 91 | | [pay_android](./pay_android/) | The endorsed implementation of this plugin for Android. | This package contains the necessary business logic to support the Android platform. You can integrate this package separately or use it to build your own app-facing package. | 92 | | [pay_ios](./pay_ios/) | The endorsed implementation of this plugin for iOS. | This package contains the necessary business logic to support the iOS platform. You can integrate this package separately or use it to build your own app-facing package. | 93 | | [pay_platform_interface](./pay_platform_interface/) | A common API contract for platform-specific implementations. | Folow the contract in this package to add new platforms to the plugin or create your own Android or iOS implementations. Take a look at the [guide about plugin development](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#federated-plugins) to learn more. | 94 | 95 | ## Additional resources 96 | Check out the following resources to manage your payment accounts and learn more about the APIs for the supported providers: 97 | 98 | | | Google Pay | Apple Pay | 99 | |:---|:---|:---| 100 | | Platforms | Android | iOS | 101 | | Documentation | [Overview](https://developers.google.com/pay/api/android/overview) | [Overview](https://developer.apple.com/apple-pay/implementation/) 102 | | Console | [Google Pay Business Console](https://pay.google.com/business/console/) | [Developer portal](https://developer.apple.com/account/) | 103 | | Reference | [API reference](https://developers.google.com/pay/api/android/reference/client) | [Apple Pay API](https://developer.apple.com/documentation/passkit/apple_pay/) 104 | | Style guidelines | [Brand guidelines](https://developers.google.com/pay/api/android/guides/brand-guidelines) | [Buttons and Marks](https://developer.apple.com/design/human-interface-guidelines/apple-pay/overview/buttons-and-marks/) 105 | 106 |
107 | Note: This is not an officially supported Google product. 108 | 109 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | analyzer: 4 | language: 5 | strict-casts: true 6 | strict-raw-types: true 7 | errors: 8 | deprecated_member_use_from_same_package: ignore 9 | 10 | linter: 11 | rules: 12 | - annotate_overrides 13 | - avoid_annotating_with_dynamic 14 | - avoid_empty_else 15 | - avoid_function_literals_in_foreach_calls 16 | - avoid_init_to_null 17 | - avoid_null_checks_in_equality_operators 18 | - avoid_relative_lib_imports 19 | - avoid_renaming_method_parameters 20 | - avoid_return_types_on_setters 21 | - avoid_returning_null_for_void 22 | - avoid_shadowing_type_parameters 23 | - avoid_single_cascade_in_expression_statements 24 | - avoid_types_as_parameter_names 25 | - await_only_futures 26 | - camel_case_extensions 27 | - camel_case_types 28 | - constant_identifier_names 29 | - control_flow_in_finally 30 | - curly_braces_in_flow_control_structures 31 | - depend_on_referenced_packages 32 | - empty_catches 33 | - empty_constructor_bodies 34 | - empty_statements 35 | - exhaustive_cases 36 | - file_names 37 | - library_names 38 | - library_prefixes 39 | - library_private_types_in_public_api 40 | - no_duplicate_case_values 41 | - no_leading_underscores_for_library_prefixes 42 | - no_leading_underscores_for_local_identifiers 43 | - non_constant_identifier_names 44 | - null_check_on_nullable_type_parameter 45 | - null_closures 46 | - overridden_fields 47 | - package_names 48 | - package_prefixed_library_names 49 | - prefer_adjacent_string_concatenation 50 | - prefer_collection_literals 51 | - prefer_conditional_assignment 52 | - prefer_contains 53 | - prefer_final_fields 54 | - prefer_for_elements_to_map_fromIterable 55 | - prefer_function_declarations_over_variables 56 | - prefer_if_null_operators 57 | - prefer_initializing_formals 58 | - prefer_inlined_adds 59 | - prefer_interpolation_to_compose_strings 60 | - prefer_is_empty 61 | - prefer_is_not_empty 62 | - prefer_is_not_operator 63 | - prefer_iterable_whereType 64 | - prefer_null_aware_operators 65 | - prefer_spread_collections 66 | - prefer_typing_uninitialized_variables 67 | - prefer_void_to_null 68 | - provide_deprecation_message 69 | - recursive_getters 70 | - slash_for_doc_comments 71 | - type_init_formals 72 | - unnecessary_brace_in_string_interps 73 | - unnecessary_const 74 | - unnecessary_constructor_name 75 | - unnecessary_getters_setters 76 | - unnecessary_late 77 | - unnecessary_new 78 | - unnecessary_null_aware_assignments 79 | - unnecessary_null_in_if_null_operators 80 | - unnecessary_nullable_for_final_variable_declarations 81 | - unnecessary_overrides 82 | - unnecessary_string_escapes 83 | - unnecessary_string_interpolations 84 | - unnecessary_this 85 | - unrelated_type_equality_checks 86 | - use_function_type_syntax_for_parameters 87 | - use_rethrow_when_possible 88 | - valid_regexps 89 | - void_checks -------------------------------------------------------------------------------- /pay/.pubignore: -------------------------------------------------------------------------------- 1 | # Dirs created by pub and Android 2 | .dart_tool/ 3 | build/ 4 | 5 | # Test files 6 | test/ 7 | integration_test/ -------------------------------------------------------------------------------- /pay/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: ../analysis_options.yaml -------------------------------------------------------------------------------- /pay/example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Exceptions to above rules. 44 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 45 | -------------------------------------------------------------------------------- /pay/example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: fba99f6cf9a14512e461e3122c8ddfaa25394e89 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /pay/example/README.md: -------------------------------------------------------------------------------- 1 | # pay_example 2 | 3 | Demonstrates how to use the `pay` plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /pay/example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: ../../analysis_options.yaml -------------------------------------------------------------------------------- /pay/example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /pay/example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | 24 | } 25 | 26 | android { 27 | namespace 'io.flutter.plugins.pay_example' 28 | compileSdk flutter.compileSdkVersion 29 | 30 | defaultConfig { 31 | applicationId 'io.flutter.plugins.pay_example' 32 | minSdkVersion 21 33 | targetSdkVersion flutter.targetSdkVersion 34 | versionCode flutterVersionCode.toInteger() 35 | versionName flutterVersionName 36 | } 37 | 38 | compileOptions { 39 | sourceCompatibility JavaVersion.VERSION_1_8 40 | targetCompatibility JavaVersion.VERSION_1_8 41 | } 42 | 43 | kotlinOptions { 44 | jvmTarget = '1.8' 45 | } 46 | 47 | sourceSets { 48 | main.java.srcDirs += 'src/main/kotlin' 49 | } 50 | 51 | lint { 52 | disable 'InvalidPackage' 53 | } 54 | } 55 | 56 | flutter { 57 | source '../..' 58 | } 59 | -------------------------------------------------------------------------------- /pay/example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /pay/example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /pay/example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /pay/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /pay/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /pay/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /pay/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /pay/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /pay/example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /pay/example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /pay/example/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = '../build' 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(':app') 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /pay/example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /pay/example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip -------------------------------------------------------------------------------- /pay/example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "8.3.2" apply false 22 | id "org.jetbrains.kotlin.android" version "2.0.20" apply false 23 | } 24 | 25 | rootProject.name = "Google Pay Flutter sample application" 26 | include ":app" -------------------------------------------------------------------------------- /pay/example/assets/default_apple_pay_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "apple_pay", 3 | "data": { 4 | "merchantIdentifier": "merchant.com.sams.fish", 5 | "displayName": "Sam's Fish", 6 | "merchantCapabilities": ["3DS", "debit", "credit"], 7 | "supportedNetworks": ["amex", "visa", "discover", "masterCard"], 8 | "countryCode": "US", 9 | "currencyCode": "USD", 10 | "requiredBillingContactFields": ["emailAddress", "name", "phoneNumber", "postalAddress"], 11 | "requiredShippingContactFields": [], 12 | "shippingMethods": [ 13 | { 14 | "amount": "0.00", 15 | "detail": "Available within an hour", 16 | "identifier": "in_store_pickup", 17 | "label": "In-Store Pickup" 18 | }, 19 | { 20 | "amount": "4.99", 21 | "detail": "5-8 Business Days", 22 | "identifier": "flat_rate_shipping_id_2", 23 | "label": "UPS Ground" 24 | }, 25 | { 26 | "amount": "29.99", 27 | "detail": "1-3 Business Days", 28 | "identifier": "flat_rate_shipping_id_1", 29 | "label": "FedEx Priority Mail" 30 | } 31 | ] 32 | } 33 | } -------------------------------------------------------------------------------- /pay/example/assets/default_google_pay_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "google_pay", 3 | "data": { 4 | "environment": "TEST", 5 | "apiVersion": 2, 6 | "apiVersionMinor": 0, 7 | "allowedPaymentMethods": [ 8 | { 9 | "type": "CARD", 10 | "tokenizationSpecification": { 11 | "type": "PAYMENT_GATEWAY", 12 | "parameters": { 13 | "gateway": "example", 14 | "gatewayMerchantId": "exampleGatewayMerchantId" 15 | } 16 | }, 17 | "parameters": { 18 | "allowedCardNetworks": ["VISA", "MASTERCARD"], 19 | "allowedAuthMethods": ["PAN_ONLY", "CRYPTOGRAM_3DS"], 20 | "billingAddressRequired": true, 21 | "billingAddressParameters": { 22 | "format": "FULL", 23 | "phoneNumberRequired": true 24 | } 25 | } 26 | } 27 | ], 28 | "merchantInfo": { 29 | "merchantName": "Example Merchant Name" 30 | }, 31 | "transactionInfo": { 32 | "countryCode": "US", 33 | "currencyCode": "USD" 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /pay/example/assets/images/ts_10_11019a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/assets/images/ts_10_11019a.jpg -------------------------------------------------------------------------------- /pay/example/integration_test/payment_flow_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:flutter_test/flutter_test.dart'; 16 | import 'package:integration_test/integration_test.dart'; 17 | import 'package:pay/pay.dart'; 18 | 19 | void main() { 20 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 21 | 22 | testWidgets("correctly creates an instance of the client", (_) async { 23 | Pay client = Pay({}); 24 | await expectLater(client, isNotNull); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /pay/example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /pay/example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /pay/example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /pay/example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /pay/example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /pay/example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - integration_test (0.0.1): 4 | - Flutter 5 | - pay_ios (0.0.1): 6 | - Flutter 7 | 8 | DEPENDENCIES: 9 | - Flutter (from `Flutter`) 10 | - integration_test (from `.symlinks/plugins/integration_test/ios`) 11 | - pay_ios (from `.symlinks/plugins/pay_ios/ios`) 12 | 13 | EXTERNAL SOURCES: 14 | Flutter: 15 | :path: Flutter 16 | integration_test: 17 | :path: ".symlinks/plugins/integration_test/ios" 18 | pay_ios: 19 | :path: ".symlinks/plugins/pay_ios/ios" 20 | 21 | SPEC CHECKSUMS: 22 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 23 | integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4 24 | pay_ios: 8c7beb9c61d885f3f51b61f75f8793023fc8843a 25 | 26 | PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 27 | 28 | COCOAPODS: 1.11.3 29 | -------------------------------------------------------------------------------- /pay/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /pay/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pay/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pay/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /pay/example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /pay/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pay/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pay/example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | #import 19 | 20 | @interface AppDelegate : FlutterAppDelegate 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /pay/example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import "AppDelegate.h" 18 | #import "GeneratedPluginRegistrant.h" 19 | 20 | @implementation AppDelegate 21 | 22 | - (BOOL)application:(UIApplication *)application 23 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 24 | [GeneratedPluginRegistrant registerWithRegistry:self]; 25 | // Override point for customization after application launch. 26 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /pay/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /pay/example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /pay/example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /pay/example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | pay_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /pay/example/ios/Runner/de.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pay/example/ios/Runner/de.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pay/example/ios/Runner/es.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pay/example/ios/Runner/es.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pay/example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | #import 19 | #import "AppDelegate.h" 20 | 21 | int main(int argc, char* argv[]) { 22 | @autoreleasepool { 23 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pay/example/lib/advanced.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async'; 16 | 17 | import 'package:flutter/foundation.dart'; 18 | import 'package:flutter/material.dart'; 19 | import 'package:flutter/services.dart'; 20 | import 'package:flutter_localizations/flutter_localizations.dart'; 21 | import 'package:pay/pay.dart'; 22 | 23 | import 'payment_configurations.dart' as payment_configurations; 24 | 25 | void main() { 26 | runApp(const PayAdvancedMaterialApp()); 27 | } 28 | 29 | const googlePayEventChannelName = 'plugins.flutter.io/pay/payment_result'; 30 | const _paymentItems = [ 31 | PaymentItem( 32 | label: 'Total', 33 | amount: '99.99', 34 | status: PaymentItemStatus.final_price, 35 | ) 36 | ]; 37 | 38 | class PayAdvancedMaterialApp extends StatelessWidget { 39 | const PayAdvancedMaterialApp({super.key}); 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | return MaterialApp( 44 | title: 'Pay for Flutter Advanced Integration Demo', 45 | localizationsDelegates: const [ 46 | ...GlobalMaterialLocalizations.delegates, 47 | GlobalWidgetsLocalizations.delegate, 48 | ], 49 | supportedLocales: const [ 50 | Locale('en', ''), 51 | Locale('es', ''), 52 | Locale('de', ''), 53 | ], 54 | home: PayAdvancedSampleApp(), 55 | ); 56 | } 57 | } 58 | 59 | class PayAdvancedSampleApp extends StatefulWidget { 60 | final Pay payClient; 61 | 62 | PayAdvancedSampleApp({super.key}) 63 | : payClient = Pay({ 64 | PayProvider.google_pay: payment_configurations.defaultGooglePayConfig, 65 | PayProvider.apple_pay: payment_configurations.defaultApplePayConfig, 66 | }); 67 | 68 | @override 69 | State createState() => _PayAdvancedSampleAppState(); 70 | } 71 | 72 | class _PayAdvancedSampleAppState extends State { 73 | static const eventChannel = EventChannel(googlePayEventChannelName); 74 | StreamSubscription? _googlePayResultSubscription; 75 | 76 | late final Future _canPayGoogleFuture; 77 | late final Future _canPayAppleFuture; 78 | 79 | // A method to listen to events coming from the event channel on Android 80 | void _startListeningForPaymentResults() { 81 | _googlePayResultSubscription = eventChannel 82 | .receiveBroadcastStream() 83 | .cast() 84 | .listen(debugPrint, onError: (error) => debugPrint(error.toString())); 85 | } 86 | 87 | final bool _collectPaymentResultSynchronously = 88 | defaultTargetPlatform == TargetPlatform.iOS; 89 | 90 | @override 91 | void initState() { 92 | super.initState(); 93 | if (!_collectPaymentResultSynchronously) { 94 | _startListeningForPaymentResults(); 95 | } 96 | 97 | // Initialize userCanPay futures 98 | _canPayGoogleFuture = widget.payClient.userCanPay(PayProvider.google_pay); 99 | _canPayAppleFuture = widget.payClient.userCanPay(PayProvider.apple_pay); 100 | } 101 | 102 | void _onGooglePayPressed() => 103 | _showPaymentSelectorForProvider(PayProvider.google_pay); 104 | 105 | void _onApplePayPressed() => 106 | _showPaymentSelectorForProvider(PayProvider.apple_pay); 107 | 108 | void _showPaymentSelectorForProvider(PayProvider provider) async { 109 | try { 110 | final result = 111 | await widget.payClient.showPaymentSelector(provider, _paymentItems); 112 | if (_collectPaymentResultSynchronously) debugPrint(result.toString()); 113 | } catch (error) { 114 | debugPrint(error.toString()); 115 | } 116 | } 117 | 118 | @override 119 | void dispose() { 120 | _googlePayResultSubscription?.cancel(); 121 | _googlePayResultSubscription = null; 122 | super.dispose(); 123 | } 124 | 125 | @override 126 | Widget build(BuildContext context) { 127 | return Scaffold( 128 | appBar: AppBar( 129 | title: const Text('T-shirt Shop'), 130 | ), 131 | backgroundColor: Colors.white, 132 | body: ListView( 133 | padding: const EdgeInsets.symmetric(horizontal: 20), 134 | children: [ 135 | Container( 136 | margin: const EdgeInsets.symmetric(vertical: 20), 137 | child: const Image( 138 | image: AssetImage('assets/images/ts_10_11019a.jpg'), 139 | height: 350, 140 | ), 141 | ), 142 | const Text( 143 | 'Amanda\'s Polo Shirt', 144 | style: TextStyle( 145 | fontSize: 20, 146 | color: Color(0xff333333), 147 | fontWeight: FontWeight.bold, 148 | ), 149 | ), 150 | const SizedBox(height: 5), 151 | const Text( 152 | '\$50.20', 153 | style: TextStyle( 154 | color: Color(0xff777777), 155 | fontSize: 15, 156 | ), 157 | ), 158 | const SizedBox(height: 15), 159 | const Text( 160 | 'Description', 161 | style: TextStyle( 162 | fontSize: 15, 163 | color: Color(0xff333333), 164 | fontWeight: FontWeight.bold, 165 | ), 166 | ), 167 | const SizedBox(height: 5), 168 | const Text( 169 | 'A versatile full-zip that you can wear all day long and even...', 170 | style: TextStyle( 171 | color: Color(0xff777777), 172 | fontSize: 15, 173 | ), 174 | ), 175 | const SizedBox(height: 15), 176 | 177 | // Google Pay button 178 | FutureBuilder( 179 | future: _canPayGoogleFuture, 180 | builder: (context, snapshot) { 181 | if (snapshot.connectionState == ConnectionState.done) { 182 | if (snapshot.data == true) { 183 | return RawGooglePayButton( 184 | paymentConfiguration: 185 | payment_configurations.defaultGooglePayConfig, 186 | type: GooglePayButtonType.buy, 187 | onPressed: _onGooglePayPressed); 188 | } else { 189 | // userCanPay returned false 190 | // Consider showing an alternative payment method 191 | } 192 | } else { 193 | // The operation hasn't finished loading 194 | // Consider showing a loading indicator 195 | } 196 | // This example shows an empty box if userCanPay returns false 197 | return const SizedBox.shrink(); 198 | }, 199 | ), 200 | 201 | // Apple Pay button 202 | FutureBuilder( 203 | future: _canPayAppleFuture, 204 | builder: (context, snapshot) { 205 | if (snapshot.connectionState == ConnectionState.done) { 206 | if (snapshot.data == true) { 207 | return RawApplePayButton( 208 | type: ApplePayButtonType.buy, 209 | onPressed: _onApplePayPressed); 210 | } else { 211 | // userCanPay returned false 212 | // Consider showing an alternative payment method 213 | } 214 | } else { 215 | // The operation hasn't finished loading 216 | // Consider showing a loading indicator 217 | } 218 | // This example shows an empty box if userCanPay returns false 219 | return const SizedBox.shrink(); 220 | }, 221 | ), 222 | ], 223 | ), 224 | ); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /pay/example/lib/main.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:flutter/material.dart'; 16 | import 'package:flutter_localizations/flutter_localizations.dart'; 17 | import 'package:pay/pay.dart'; 18 | 19 | import 'payment_configurations.dart' as payment_configurations; 20 | 21 | void main() { 22 | runApp(const PayMaterialApp()); 23 | } 24 | 25 | const _paymentItems = [ 26 | PaymentItem( 27 | label: 'Total', 28 | amount: '99.99', 29 | status: PaymentItemStatus.final_price, 30 | ) 31 | ]; 32 | 33 | class PayMaterialApp extends StatelessWidget { 34 | const PayMaterialApp({super.key}); 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return const MaterialApp( 39 | title: 'Pay for Flutter Demo', 40 | localizationsDelegates: [ 41 | ...GlobalMaterialLocalizations.delegates, 42 | GlobalWidgetsLocalizations.delegate, 43 | ], 44 | supportedLocales: [ 45 | Locale('en', ''), 46 | Locale('es', ''), 47 | Locale('de', ''), 48 | ], 49 | home: PaySampleApp(), 50 | ); 51 | } 52 | } 53 | 54 | class PaySampleApp extends StatefulWidget { 55 | const PaySampleApp({super.key}); 56 | 57 | @override 58 | State createState() => _PaySampleAppState(); 59 | } 60 | 61 | class _PaySampleAppState extends State { 62 | late final Future _googlePayConfigFuture; 63 | 64 | @override 65 | void initState() { 66 | super.initState(); 67 | _googlePayConfigFuture = 68 | PaymentConfiguration.fromAsset('default_google_pay_config.json'); 69 | } 70 | 71 | void onGooglePayResult(paymentResult) { 72 | debugPrint(paymentResult.toString()); 73 | } 74 | 75 | void onApplePayResult(paymentResult) { 76 | debugPrint(paymentResult.toString()); 77 | } 78 | 79 | @override 80 | Widget build(BuildContext context) { 81 | return Scaffold( 82 | appBar: AppBar( 83 | title: const Text('T-shirt Shop'), 84 | ), 85 | backgroundColor: Colors.white, 86 | body: ListView( 87 | padding: const EdgeInsets.symmetric(horizontal: 20), 88 | children: [ 89 | Container( 90 | margin: const EdgeInsets.symmetric(vertical: 20), 91 | child: const Image( 92 | image: AssetImage('assets/images/ts_10_11019a.jpg'), 93 | height: 350, 94 | ), 95 | ), 96 | const Text( 97 | 'Amanda\'s Polo Shirt', 98 | style: TextStyle( 99 | fontSize: 20, 100 | color: Color(0xff333333), 101 | fontWeight: FontWeight.bold, 102 | ), 103 | ), 104 | const SizedBox(height: 5), 105 | const Text( 106 | '\$50.20', 107 | style: TextStyle( 108 | color: Color(0xff777777), 109 | fontSize: 15, 110 | ), 111 | ), 112 | const SizedBox(height: 15), 113 | const Text( 114 | 'Description', 115 | style: TextStyle( 116 | fontSize: 15, 117 | color: Color(0xff333333), 118 | fontWeight: FontWeight.bold, 119 | ), 120 | ), 121 | const SizedBox(height: 5), 122 | const Text( 123 | 'A versatile full-zip that you can wear all day long and even...', 124 | style: TextStyle( 125 | color: Color(0xff777777), 126 | fontSize: 15, 127 | ), 128 | ), 129 | // Example pay button configured using an asset 130 | FutureBuilder( 131 | future: _googlePayConfigFuture, 132 | builder: (context, snapshot) => snapshot.hasData 133 | ? GooglePayButton( 134 | paymentConfiguration: snapshot.data!, 135 | paymentItems: _paymentItems, 136 | type: GooglePayButtonType.buy, 137 | margin: const EdgeInsets.only(top: 15.0), 138 | onPaymentResult: onGooglePayResult, 139 | loadingIndicator: const Center( 140 | child: CircularProgressIndicator(), 141 | ), 142 | ) 143 | : const SizedBox.shrink()), 144 | // Example pay button configured using a string 145 | ApplePayButton( 146 | paymentConfiguration: payment_configurations.defaultApplePayConfig, 147 | paymentItems: _paymentItems, 148 | style: ApplePayButtonStyle.black, 149 | type: ApplePayButtonType.buy, 150 | margin: const EdgeInsets.only(top: 15.0), 151 | onPaymentResult: onApplePayResult, 152 | loadingIndicator: const Center( 153 | child: CircularProgressIndicator(), 154 | ), 155 | ), 156 | const SizedBox(height: 15) 157 | ], 158 | ), 159 | ); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /pay/example/lib/payment_configurations.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// This file contain sample payment configurations that can be used with the 16 | /// payment providers in this library. 17 | /// 18 | /// Although payment configurations can be hardcoded in your application source 19 | /// (as displayed in this example), we recommend you to keep this information in 20 | /// a remote location that can be accessed from your application (e.g.: a 21 | /// backend server). That way, you benefit from being able to use multiple 22 | /// payment configurations that can be modified without the need to update your 23 | /// application. 24 | library; 25 | 26 | import 'package:pay/pay.dart'; 27 | 28 | /// Sample [PaymentConfiguration] for Apple Pay 29 | final defaultApplePayConfig = 30 | PaymentConfiguration.fromJsonString(defaultApplePay); 31 | 32 | /// Sample configuration for Apple Pay. Contains the same content as the file 33 | /// under `assets/default_payment_profile_apple_pay.json`. 34 | const String defaultApplePay = '''{ 35 | "provider": "apple_pay", 36 | "data": { 37 | "merchantIdentifier": "merchant.com.sams.fish", 38 | "displayName": "Sam's Fish", 39 | "merchantCapabilities": ["3DS", "debit", "credit"], 40 | "supportedNetworks": ["amex", "visa", "discover", "masterCard"], 41 | "countryCode": "US", 42 | "currencyCode": "USD", 43 | "requiredBillingContactFields": ["emailAddress", "name", "phoneNumber", "postalAddress"], 44 | "requiredShippingContactFields": [], 45 | "shippingMethods": [ 46 | { 47 | "amount": "0.00", 48 | "detail": "Available within an hour", 49 | "identifier": "in_store_pickup", 50 | "label": "In-Store Pickup" 51 | }, 52 | { 53 | "amount": "4.99", 54 | "detail": "5-8 Business Days", 55 | "identifier": "flat_rate_shipping_id_2", 56 | "label": "UPS Ground" 57 | }, 58 | { 59 | "amount": "29.99", 60 | "detail": "1-3 Business Days", 61 | "identifier": "flat_rate_shipping_id_1", 62 | "label": "FedEx Priority Mail" 63 | } 64 | ] 65 | } 66 | }'''; 67 | 68 | /// Sample [PaymentConfiguration] for Google Pay 69 | final defaultGooglePayConfig = 70 | PaymentConfiguration.fromJsonString(defaultGooglePay); 71 | 72 | /// Sample configuration for Google Pay. Contains the same content as the file 73 | /// under `assets/default_payment_profile_google_pay.json`. 74 | const String defaultGooglePay = '''{ 75 | "provider": "google_pay", 76 | "data": { 77 | "environment": "TEST", 78 | "apiVersion": 2, 79 | "apiVersionMinor": 0, 80 | "allowedPaymentMethods": [ 81 | { 82 | "type": "CARD", 83 | "tokenizationSpecification": { 84 | "type": "PAYMENT_GATEWAY", 85 | "parameters": { 86 | "gateway": "example", 87 | "gatewayMerchantId": "exampleGatewayMerchantId" 88 | } 89 | }, 90 | "parameters": { 91 | "allowedCardNetworks": ["VISA", "MASTERCARD"], 92 | "allowedAuthMethods": ["PAN_ONLY", "CRYPTOGRAM_3DS"], 93 | "billingAddressRequired": true, 94 | "billingAddressParameters": { 95 | "format": "FULL", 96 | "phoneNumberRequired": true 97 | } 98 | } 99 | } 100 | ], 101 | "merchantInfo": { 102 | "merchantName": "Example Merchant Name" 103 | }, 104 | "transactionInfo": { 105 | "countryCode": "US", 106 | "currencyCode": "USD" 107 | } 108 | } 109 | }'''; 110 | 111 | const String basicGooglePayIsReadyToPay = '''{ 112 | "provider": "google_pay", 113 | "data": { 114 | "apiVersion": 2, 115 | "apiVersionMinor": 0, 116 | "allowedPaymentMethods": [ 117 | { 118 | "type": "CARD", 119 | "parameters": { 120 | "allowedAuthMethods": ["PAN_ONLY", "CRYPTOGRAM_3DS"], 121 | "allowedCardNetworks": ["AMEX", "DISCOVER", "INTERAC", "JCB", "MASTERCARD", "VISA"] 122 | } 123 | } 124 | ] 125 | } 126 | }'''; 127 | 128 | const String basicGooglePayLoadPaymentData = '''{ 129 | "provider": "google_pay", 130 | "data": { 131 | "apiVersion": 2, 132 | "apiVersionMinor": 0, 133 | "merchantInfo": { 134 | "merchantName": "Example Merchant" 135 | }, 136 | "allowedPaymentMethods": [ 137 | { 138 | "type": "CARD", 139 | "parameters": { 140 | "allowedAuthMethods": ["PAN_ONLY", "CRYPTOGRAM_3DS"], 141 | "allowedCardNetworks": ["AMEX", "DISCOVER", "INTERAC", "JCB", "MASTERCARD", "VISA"] 142 | }, 143 | "tokenizationSpecification": { 144 | "type": "PAYMENT_GATEWAY", 145 | "parameters": { 146 | "gateway": "example", 147 | "gatewayMerchantId": "exampleGatewayMerchantId" 148 | } 149 | } 150 | } 151 | ], 152 | "transactionInfo": { 153 | "totalPriceStatus": "FINAL", 154 | "totalPrice": "12.34", 155 | "currencyCode": "USD" 156 | } 157 | } 158 | }'''; 159 | 160 | const String invalidGooglePayIsReadyToPay = '''{ 161 | "provider": "google_pay", 162 | "data": { 163 | "apiVersion": 2, 164 | "apiVersionMinor": 0, 165 | "allowedPaymentMethods": [ 166 | { 167 | "type": "CARD", 168 | "parameters": {} 169 | } 170 | ] 171 | } 172 | }'''; 173 | 174 | const String invalidGooglePayLoadPaymentData = '''{ 175 | "provider": "google_pay", 176 | "data": { 177 | "apiVersion": 2, 178 | "apiVersionMinor": 0, 179 | "merchantInfo": { 180 | "merchantName": "Example Merchant" 181 | }, 182 | "allowedPaymentMethods": [ 183 | { 184 | "type": "CARD", 185 | "parameters": { 186 | "allowedAuthMethods": ["PAN_ONLY", "CRYPTOGRAM_3DS"], 187 | "allowedCardNetworks": ["AMEX", "DISCOVER", "INTERAC", "JCB", "MASTERCARD", "VISA"] 188 | }, 189 | "tokenizationSpecification": { 190 | "type": "PAYMENT_GATEWAY", 191 | "parameters": { 192 | "gateway": "example", 193 | "gatewayMerchantId": "exampleGatewayMerchantId" 194 | } 195 | } 196 | } 197 | ], 198 | "transactionInfo": { 199 | "totalPriceStatus": "FINAL", 200 | "totalPrice": "12.34", 201 | "currencyCode": "USD" 202 | } 203 | } 204 | }'''; 205 | -------------------------------------------------------------------------------- /pay/example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: pay_example 16 | description: Shows how to use the pay plugin. 17 | publish_to: none 18 | 19 | environment: 20 | sdk: ">=3.0.0 <4.0.0" 21 | flutter: ">=3.10.0" 22 | 23 | flutter: 24 | uses-material-design: true 25 | assets: 26 | - assets/ 27 | - assets/images/ 28 | 29 | dependencies: 30 | flutter: 31 | sdk: flutter 32 | flutter_localizations: 33 | sdk: flutter 34 | pay: 35 | path: ../ 36 | 37 | # The following adds the Cupertino Icons font to your application. 38 | # Use with the CupertinoIcons class for iOS style icons. 39 | cupertino_icons: ^1.0.3 40 | 41 | dependency_overrides: 42 | pay_android: 43 | path: ../../pay_android 44 | pay_ios: 45 | path: ../../pay_ios 46 | pay_platform_interface: 47 | path: ../../pay_platform_interface 48 | 49 | dev_dependencies: 50 | flutter_test: 51 | sdk: flutter 52 | integration_test: 53 | sdk: flutter 54 | flutter_lints: ^5.0.0 55 | -------------------------------------------------------------------------------- /pay/lib/pay.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | library; 16 | 17 | import 'dart:convert'; 18 | import 'dart:async'; 19 | 20 | import 'package:flutter/foundation.dart'; 21 | import 'package:flutter/material.dart'; 22 | import 'package:flutter/services.dart'; 23 | import 'package:pay_ios/pay_ios.dart'; 24 | import 'package:pay_android/pay_android.dart'; 25 | import 'package:pay_platform_interface/core/payment_configuration.dart'; 26 | import 'package:pay_platform_interface/core/payment_item.dart'; 27 | import 'package:pay_platform_interface/pay_channel.dart'; 28 | import 'package:pay_platform_interface/pay_platform_interface.dart'; 29 | 30 | export 'package:pay_platform_interface/core/payment_configuration.dart' 31 | show PayProvider, PaymentConfiguration; 32 | 33 | export 'package:pay_platform_interface/core/payment_item.dart' 34 | show PaymentItem, PaymentItemType, PaymentItemStatus; 35 | 36 | export 'package:pay_android/pay_android.dart' 37 | show RawGooglePayButton, GooglePayButtonTheme, GooglePayButtonType; 38 | 39 | export 'package:pay_ios/pay_ios.dart' 40 | show RawApplePayButton, ApplePayButtonStyle, ApplePayButtonType; 41 | 42 | part 'src/pay.dart'; 43 | part 'src/widgets/pay_button.dart'; 44 | part 'src/widgets/apple_pay_button.dart'; 45 | part 'src/widgets/google_pay_button.dart'; 46 | -------------------------------------------------------------------------------- /pay/lib/src/pay.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | part of '../pay.dart'; 16 | 17 | /// List of supported payment providers and platform pairs. 18 | const supportedProviders = { 19 | TargetPlatform.android: [PayProvider.google_pay], 20 | TargetPlatform.iOS: [PayProvider.apple_pay], 21 | }; 22 | 23 | /// High level layer to easily manage cross-platform integrations. 24 | /// 25 | /// This class simplifies using the plugin and abstracts platform-specific 26 | /// directives. 27 | /// To use it, instantiate it with a list of configurations for the payment 28 | /// providers supported: 29 | /// ```dart 30 | /// final payConfiguration = PaymentConfiguration.fromJsonString(); 31 | /// final payClient = Pay({: payConfiguration}); 32 | /// await payClient.showPaymentSelector(, paymentItems); 33 | /// ``` 34 | class Pay { 35 | /// The implementation of the platform interface to talk to the native ends. 36 | final PayPlatform _payPlatform; 37 | 38 | /// Map of configurations for the payment providers targeted. 39 | final Map _configurations; 40 | 41 | /// Creates an instance with a dictionary of [_configurations] and 42 | /// instantiates the [_payPlatform] to communicate with the native platforms. 43 | Pay(this._configurations) : _payPlatform = PayMethodChannel(); 44 | 45 | /// Determines whether a user can pay with the selected [provider]. 46 | /// 47 | /// This method wraps the [userCanPay] method in the platform interface. It 48 | /// makes sure that the [provider] exists and is available in the platform 49 | /// running the logic. 50 | Future userCanPay(PayProvider provider) async { 51 | await throwIfProviderIsNotDefined(provider); 52 | if (supportedProviders[defaultTargetPlatform]!.contains(provider)) { 53 | return _payPlatform.userCanPay(_configurations[provider]!); 54 | } 55 | 56 | return Future.value(false); 57 | } 58 | 59 | /// Shows the payment selector to initiate a payment process. 60 | /// 61 | /// This method wraps the [showPaymentSelector] method in the platform 62 | /// interface, and opens the payment selector for the [provider] of choice, 63 | /// with the [paymentItems] in the price summary. 64 | Future> showPaymentSelector( 65 | PayProvider provider, 66 | List paymentItems, 67 | ) async { 68 | await throwIfProviderIsNotDefined(provider); 69 | return _payPlatform.showPaymentSelector( 70 | _configurations[provider]!, paymentItems); 71 | } 72 | 73 | /// Verifies that the selected provider has been previously configured or 74 | /// throws otherwise. 75 | Future throwIfProviderIsNotDefined(PayProvider provider) async { 76 | if (!_configurations.containsKey(provider)) { 77 | throw ProviderNotConfiguredException( 78 | 'No configuration has been provided for the provider ($provider)'); 79 | } 80 | } 81 | } 82 | 83 | /// Thrown to indicate that the configuration for a request provider has not 84 | /// been provided. 85 | class ProviderNotConfiguredException implements Exception { 86 | ProviderNotConfiguredException(this.message); 87 | 88 | /// A human-readable error message, possibly null. 89 | final String? message; 90 | 91 | @override 92 | String toString() => 'ProviderNotConfiguredException: $message'; 93 | } 94 | -------------------------------------------------------------------------------- /pay/lib/src/widgets/apple_pay_button.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | part of '../../pay.dart'; 16 | 17 | /// A widget to show the Apple Pay button according to the rules and constraints 18 | /// specified in [PayButton]. 19 | /// 20 | /// Example usage: 21 | /// ```dart 22 | /// ApplePayButton( 23 | /// paymentConfiguration: _paymentConfiguration, 24 | /// paymentItems: _paymentItems, 25 | /// style: ApplePayButtonStyle.black, 26 | /// type: ApplePayButtonType.buy, 27 | /// margin: const EdgeInsets.only(top: 15.0), 28 | /// onPaymentResult: onApplePayResult, 29 | /// loadingIndicator: const Center( 30 | /// child: CircularProgressIndicator(), 31 | /// ), 32 | /// ) 33 | /// ``` 34 | class ApplePayButton extends PayButton { 35 | late final Widget _applePayButton; 36 | 37 | ApplePayButton({ 38 | super.key, 39 | super.buttonProvider = PayProvider.apple_pay, 40 | required super.paymentConfiguration, 41 | super.onPaymentResult, 42 | required List paymentItems, 43 | double? cornerRadius, 44 | ApplePayButtonStyle style = ApplePayButtonStyle.black, 45 | ApplePayButtonType type = ApplePayButtonType.plain, 46 | super.width = RawApplePayButton.minimumButtonWidth, 47 | super.height = RawApplePayButton.minimumButtonHeight, 48 | super.margin = EdgeInsets.zero, 49 | VoidCallback? onPressed, 50 | super.onError, 51 | super.childOnError, 52 | super.loadingIndicator, 53 | }) : assert(width >= RawApplePayButton.minimumButtonWidth), 54 | assert(height >= RawApplePayButton.minimumButtonHeight) { 55 | _applePayButton = RawApplePayButton( 56 | style: style, 57 | type: type, 58 | cornerRadius: cornerRadius, 59 | onPressed: _defaultOnPressed(onPressed, paymentItems)); 60 | } 61 | 62 | @override 63 | final List _supportedPlatforms = [TargetPlatform.iOS]; 64 | 65 | @override 66 | late final Widget _payButton = _applePayButton; 67 | 68 | @override 69 | final bool _collectPaymentResultSynchronously = true; 70 | } 71 | -------------------------------------------------------------------------------- /pay/lib/src/widgets/google_pay_button.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | part of '../../pay.dart'; 16 | 17 | /// A widget to show the Google Pay button according to the rules and 18 | /// constraints specified in [PayButton]. 19 | /// 20 | /// Example usage: 21 | /// ```dart 22 | /// GooglePayButton( 23 | /// paymentConfiguration: _paymentConfiguration, 24 | /// paymentItems: _paymentItems, 25 | /// theme: GooglePayButtonTheme.dark, 26 | /// type: GooglePayButtonType.pay, 27 | /// margin: const EdgeInsets.only(top: 15.0), 28 | /// onPaymentResult: onGooglePayResult, 29 | /// loadingIndicator: const Center( 30 | /// child: CircularProgressIndicator(), 31 | /// ), 32 | /// ) 33 | /// ``` 34 | class GooglePayButton extends PayButton { 35 | late final Widget _googlePayButton; 36 | 37 | GooglePayButton({ 38 | super.key, 39 | super.buttonProvider = PayProvider.google_pay, 40 | required final PaymentConfiguration paymentConfiguration, 41 | super.onPaymentResult, 42 | required List paymentItems, 43 | int cornerRadius = RawGooglePayButton.defaultButtonHeight ~/ 2, 44 | GooglePayButtonTheme theme = GooglePayButtonTheme.dark, 45 | GooglePayButtonType type = GooglePayButtonType.buy, 46 | super.width = RawGooglePayButton.minimumButtonWidth, 47 | super.height = RawGooglePayButton.defaultButtonHeight, 48 | super.margin = EdgeInsets.zero, 49 | VoidCallback? onPressed, 50 | super.onError, 51 | super.childOnError, 52 | super.loadingIndicator, 53 | }) : assert(width >= RawGooglePayButton.minimumButtonWidth), 54 | assert(height >= RawGooglePayButton.defaultButtonHeight), 55 | super(paymentConfiguration: paymentConfiguration) { 56 | _googlePayButton = RawGooglePayButton( 57 | paymentConfiguration: paymentConfiguration, 58 | cornerRadius: cornerRadius, 59 | theme: theme, 60 | type: type, 61 | onPressed: _defaultOnPressed(onPressed, paymentItems)); 62 | } 63 | 64 | @override 65 | final List _supportedPlatforms = [TargetPlatform.android]; 66 | 67 | @override 68 | late final Widget _payButton = _googlePayButton; 69 | 70 | @override 71 | final bool _collectPaymentResultSynchronously = false; 72 | 73 | @override 74 | State createState() => _GooglePayButtonState(); 75 | } 76 | 77 | class _GooglePayButtonState extends _PayButtonState { 78 | static const eventChannel = 79 | EventChannel('plugins.flutter.io/pay/payment_result'); 80 | StreamSubscription>? _paymentResultSubscription; 81 | 82 | @override 83 | void _preparePaymentResultStream() { 84 | _paymentResultSubscription = eventChannel 85 | .receiveBroadcastStream() 86 | .cast() 87 | .map(jsonDecode) 88 | .cast>() 89 | .listen(widget._deliverPaymentResult, onError: widget._deliverError); 90 | } 91 | 92 | @override 93 | void dispose() { 94 | _paymentResultSubscription?.cancel(); 95 | _paymentResultSubscription = null; 96 | super.dispose(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /pay/lib/src/widgets/pay_button.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | part of '../../pay.dart'; 16 | 17 | /// A widget that handles the API logic to facilitate the integration. 18 | /// 19 | /// This widget provides an alternative UI-based integration path that wraps 20 | /// the API calls of the payment libraries and includes them as part of the 21 | /// lifecycle of the widget. As a result of that: 22 | /// 23 | /// 1. The widget only shows if the [Pay.userCanPay] method returns `true`, or 24 | /// displays the [childOnError] widget and calls the [onError] function 25 | /// otherwise. 26 | /// 2. Tapping the button automatically triggers the [Pay.showPaymentSelector] 27 | /// method which starts the payment process. 28 | abstract class PayButton extends StatefulWidget { 29 | /// A resident client to issue requests against the APIs. 30 | final Pay _payClient; 31 | 32 | /// Specifies the payment provider supported by the button 33 | final PayProvider buttonProvider; 34 | 35 | /// A function called when the payment process yields a result. 36 | final void Function(Map result)? onPaymentResult; 37 | 38 | final double width; 39 | final double height; 40 | final EdgeInsets margin; 41 | 42 | /// A function called when there's an error in the payment process. 43 | final void Function(Object? error)? onError; 44 | 45 | /// A replacement widget shown instead of the button when the payment process 46 | /// errors. This can be used to show a different checkout button or an error 47 | /// message. 48 | final Widget? childOnError; 49 | 50 | /// An optional widget to show while the payment provider checks whether 51 | /// a user can pay with it and the button loads. 52 | final Widget? loadingIndicator; 53 | 54 | /// Initializes the button and the payment client that handles the requests. 55 | PayButton({ 56 | super.key, 57 | required this.buttonProvider, 58 | required final PaymentConfiguration paymentConfiguration, 59 | this.onPaymentResult, 60 | this.width = 0, 61 | this.height = 0, 62 | this.margin = const EdgeInsets.all(0), 63 | this.onError, 64 | this.childOnError, 65 | this.loadingIndicator, 66 | }) : _payClient = Pay({buttonProvider: paymentConfiguration}); 67 | 68 | /// Determines the list of supported platforms for the button. 69 | List get _supportedPlatforms; 70 | 71 | /// Accessor for the widget to show as the payment button. 72 | /// 73 | /// This method returns a [Widget] that is conditionally shown based on the 74 | /// result of the `isReadyToPay` request. 75 | Widget get _payButton; 76 | 77 | /// Defines the strategy to return payment data information to the caller. 78 | /// 79 | /// This field is defined by implementations of this class to determine if the 80 | /// payment result is returned right after calling [Pay.showPaymentSelector] 81 | /// or rather received through asynchronous means (e.g.: an event stream). 82 | bool get _collectPaymentResultSynchronously; 83 | 84 | /// Determines whether the current platform is supported by the button. 85 | bool get _isPlatformSupported => 86 | _supportedPlatforms.contains(defaultTargetPlatform); 87 | 88 | @override 89 | State createState() => _PayButtonState(); 90 | 91 | /// Callback function to respond to tap events. 92 | /// 93 | /// This is the default function for tap events. Calls the [onPressed] 94 | /// function if set, and initiates the payment process with the [paymentItems] 95 | /// specified. 96 | VoidCallback _defaultOnPressed( 97 | VoidCallback? onPressed, List paymentItems) { 98 | return () async { 99 | onPressed?.call(); 100 | try { 101 | final result = 102 | await _payClient.showPaymentSelector(buttonProvider, paymentItems); 103 | if (_collectPaymentResultSynchronously) _deliverPaymentResult(result); 104 | } catch (error) { 105 | _deliverError(error); 106 | } 107 | }; 108 | } 109 | 110 | void _deliverPaymentResult(Map result) { 111 | onPaymentResult?.call(result); 112 | } 113 | 114 | void _deliverError(error) { 115 | onError?.call(error); 116 | } 117 | } 118 | 119 | /// Button state that adds the widgets to the tree and holds the result of the 120 | /// `userCanPay` request. 121 | /// 122 | /// This state executes the logic that shows the [loadingIndicator] while the 123 | /// button loads. If the payment provider is available for a given user, the 124 | /// [_payButton] is added to the tree. Otherwise, if set, the replacement widget 125 | /// in [childOnError] is shown. 126 | class _PayButtonState extends State { 127 | late final Future _userCanPayFuture; 128 | 129 | /// A method to initialize payment result streams 130 | /// 131 | /// Some platforms supported by this plugin, use [EventChannel]s to relay 132 | /// payment results back to Flutter. This method lets platform-specific 133 | /// implementations, set up necessary business logic to subscribe to event 134 | /// channel streams and deliver results to implementers of the API. 135 | void _preparePaymentResultStream() {} 136 | 137 | Future _userCanPay() async { 138 | try { 139 | return await widget._payClient.userCanPay(widget.buttonProvider); 140 | } catch (error) { 141 | widget.onError?.call(error); 142 | rethrow; 143 | } 144 | } 145 | 146 | @override 147 | void initState() { 148 | super.initState(); 149 | if (!widget._isPlatformSupported) return; 150 | 151 | _userCanPayFuture = _userCanPay(); 152 | 153 | if (!widget._collectPaymentResultSynchronously) { 154 | _preparePaymentResultStream(); 155 | } 156 | } 157 | 158 | @override 159 | Widget build(BuildContext context) { 160 | if (!widget._isPlatformSupported) { 161 | return const SizedBox.shrink(); 162 | } 163 | 164 | // Future builder running the `userCanPayFuture` and decides what to show 165 | // based on the result. 166 | return FutureBuilder( 167 | future: _userCanPayFuture, 168 | builder: (context, snapshot) { 169 | if (snapshot.connectionState == ConnectionState.done) { 170 | if (snapshot.data == true) { 171 | return Container( 172 | margin: widget.margin, 173 | width: widget.width, 174 | height: widget.height, 175 | child: widget._payButton, 176 | ); 177 | } else { 178 | return ButtonPlaceholder( 179 | margin: widget.margin, 180 | child: widget.childOnError, 181 | ); 182 | } 183 | } 184 | 185 | return ButtonPlaceholder( 186 | margin: widget.margin, 187 | child: widget.loadingIndicator, 188 | ); 189 | }, 190 | ); 191 | } 192 | } 193 | 194 | /// Shows the appropriate widget based on the API requests above, respecting the 195 | /// [margin] if the [child] is set. 196 | class ButtonPlaceholder extends StatelessWidget { 197 | final Widget? child; 198 | final EdgeInsets margin; 199 | 200 | const ButtonPlaceholder({ 201 | super.key, 202 | this.child, 203 | required this.margin, 204 | }); 205 | 206 | @override 207 | Widget build(BuildContext context) { 208 | return child == null 209 | ? const SizedBox.shrink() 210 | : Container(margin: margin, child: child); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /pay/pubspec.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: pay 16 | description: A plug-in to add support for payments on Flutter applications. 17 | version: 3.2.1 18 | repository: https://github.com/google-pay/flutter-plugin/tree/main/pay 19 | 20 | environment: 21 | sdk: ">=3.0.0 <4.0.0" 22 | flutter: ">=3.10.0" 23 | 24 | flutter: 25 | plugin: 26 | platforms: 27 | android: 28 | default_package: pay_android 29 | ios: 30 | default_package: pay_ios 31 | 32 | dependencies: 33 | flutter: 34 | sdk: flutter 35 | flutter_localizations: 36 | sdk: flutter 37 | pay_android: ^3.1.1 38 | pay_ios: ^1.1.0 39 | pay_platform_interface: ^2.0.0 40 | meta: ^1.10.0 41 | 42 | dev_dependencies: 43 | flutter_test: 44 | sdk: flutter 45 | mockito: ^5.0.8 46 | flutter_lints: ^5.0.0 47 | -------------------------------------------------------------------------------- /pay/test/assets/google_pay_default_payment_profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "google_pay", 3 | "data": { 4 | "environment": "TEST", 5 | "apiVersion": 2, 6 | "apiVersionMinor": 0 7 | } 8 | } -------------------------------------------------------------------------------- /pay/test/pay_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | @TestOn('vm') 16 | library; 17 | 18 | import 'dart:io'; 19 | import 'dart:convert'; 20 | 21 | import 'package:flutter_test/flutter_test.dart'; 22 | import 'package:pay/pay.dart'; 23 | 24 | String _fixtureAsset(String name) { 25 | var currentPath = Directory.current.path; 26 | var dir = currentPath.endsWith('/test') 27 | ? Directory.current.parent.path 28 | : currentPath; 29 | 30 | return File('$dir/test/assets/$name').readAsStringSync(); 31 | } 32 | 33 | Future> _testProfileLoader( 34 | String paymentConfigurationAsset) async => 35 | jsonDecode(_fixtureAsset(paymentConfigurationAsset)) 36 | as Map; 37 | 38 | void main() { 39 | TestWidgetsFlutterBinding.ensureInitialized(); 40 | setUp(() async {}); 41 | 42 | test('A Pay client can load configuration from an asset', () async { 43 | final configuration = await PaymentConfiguration.fromAsset( 44 | 'google_pay_default_payment_profile.json', 45 | profileLoader: _testProfileLoader); 46 | 47 | final client = Pay({ 48 | PayProvider.google_pay: configuration, 49 | }); 50 | expect(client, isNotNull); 51 | }); 52 | 53 | test('A Pay client can load configuration from multiple providers', () async { 54 | final multiplePaymentConfig = { 55 | PayProvider.apple_pay: PaymentConfiguration.fromJsonString( 56 | '{"provider": "apple_pay", "data": {}}'), 57 | PayProvider.google_pay: PaymentConfiguration.fromJsonString( 58 | '{"provider": "google_pay", "data": {}}'), 59 | }; 60 | 61 | final client = Pay(multiplePaymentConfig); 62 | expect(client, isNotNull); 63 | }); 64 | 65 | test('Throw exception when a missing configuration is used', () async { 66 | final client = Pay({}); 67 | expect(client.userCanPay(PayProvider.google_pay), 68 | throwsA(isA())); 69 | }); 70 | 71 | tearDown(() async {}); 72 | } 73 | -------------------------------------------------------------------------------- /pay_android/.pubignore: -------------------------------------------------------------------------------- 1 | # Dirs created by pub and Android 2 | .dart_tool/ 3 | build/ 4 | 5 | libs/ 6 | test/ -------------------------------------------------------------------------------- /pay_android/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 3.1.1 (2024-03-28) 4 | 5 | * Remove inexisting version of the `androidx.test:rules` dependency. 6 | 7 | ## 3.1.0 (2024-03-14) 8 | 9 | ### Features 10 | 11 | * Introduce more detailed error messages in the result of the payment operation object. You can use this information to troubleshoot and debug your integrations at build time, and react to unexpected errors at runtime. 12 | 13 | ### Fixes 14 | * ([#279](https://github.com/google-pay/flutter-plugin/issues/279)) Conform a schema for every call to prevent unexpected properties in requests. 15 | 16 | ## 3.0.0 (2024-12-23) 17 | ### ⚠ BREAKING CHANGE 18 | Introduce an event channel to communicate the Flutter and native ends using a continuous stream of information. Take a look at the [README to see an example](../pay/README.md#handling-a-payment-result-response-android-only). 19 | 20 | ### Features 21 | 22 | * Add a separate event channel (named `plugins.flutter.io/pay/payment_result`) to handle payment result information. 23 | 24 | ### Fixes 25 | * ([#277](https://github.com/google-pay/flutter-plugin/issues/277), [#274](https://github.com/google-pay/flutter-plugin/issues/274), [#261](https://github.com/google-pay/flutter-plugin/issues/261), [#206](https://github.com/google-pay/flutter-plugin/issues/206)) Avoid lifecycle conflicts when the activity managing the payment operation is re-created before the payment result is returned. 26 | 27 | ## 3.0.0-beta.1 (2024-10-10) 28 | ### ⚠ BREAKING CHANGE 29 | Introduce an event channel to communicate the Flutter and native ends using a continuous stream of information. This addresses various challenges derived from activity lifecycle management events on Android 30 | 31 | ### Features 32 | 33 | * Add a separate event channel (named `plugins.flutter.io/pay/payment_result`) to handle payment result information. An `EventChannel` object is required to consume this information. 34 | 35 | ### Fixes 36 | * ([#277](https://github.com/google-pay/flutter-plugin/issues/277), [#274](https://github.com/google-pay/flutter-plugin/issues/274), [#261](https://github.com/google-pay/flutter-plugin/issues/261), [#206](https://github.com/google-pay/flutter-plugin/issues/206)) Avoid lifecycle conflicts when the activity managing the payment operation is re-created before the payment result is returned. 37 | 38 | ## 2.0.0 (2024-02-27) 39 | ### ⚠ BREAKING CHANGE 40 | Update the Google Pay button to support the last 4 digits of a suitable card for this payment, and extend its configuration capabilities. 41 | 42 | ### Features 43 | 44 | * Introduce the new dynamic button for Google Pay (#110). This view is part of the Google Pay Android SDK, and handles graphics and translations. The component also introduces updates to the Google Pay button. This includes: 45 | * ⚠ Adding the `paymentConfiguration` mandatory parameter to configure the dynamic component of the button. 46 | * ⚠ Removing the `add` property in `GooglePayButtonType`. If you are using this package to integrate the Google Wallet API, stay tuned for updates on [a new plugin for Google Wallet](https://github.com/google-wallet) coming soon. 47 | * ⚠ Renaming the `GooglePayButtonStyle` to `GooglePayButtonTheme` to increase consistency with the Android API. 48 | * Adding the `cornerRadius` parameter to let you adjust the corner roundness of the button (#187). 49 | * Update the minimum supported SDK version to Flutter 3.10/Dart 3.0 (#233). 50 | * Use `flutter_lints` for static checks (#182, #210). 51 | 52 | ## 2.0.0-beta01 (2024-02-01) 53 | ### ⚠ BREAKING CHANGE 54 | Update the Google Pay button to support the last 4 digits of a suitable card for this payment, and extend its configuration capabilities. 55 | 56 | ### Features 57 | 58 | * Introduce the new dynamic button for Google Pay (#110). This view is part of the Google Pay Android SDK, and handles graphics and translations. The component also introduces updates to the Google Pay button. This includes: 59 | * ⚠ Adding the `paymentConfiguration` mandatory parameter to configure the dynamic component of the button. 60 | * ⚠ Removing the `add` property in `GooglePayButtonType`. If you are using this package to integrate the Google Wallet API, stay tuned for updates on [a new plugin for Google Wallet](https://github.com/google-wallet) coming soon. 61 | * ⚠ Renaming the `GooglePayButtonStyle` to `GooglePayButtonTheme` to increase consistency with the Android API. 62 | * Adding the `cornerRadius` parameter to let you adjust the corner roundness of the button (#187). 63 | * Update the minimum supported SDK version to Flutter 3.10/Dart 3.0 (#233). 64 | * Use `flutter_lints` for static checks (#182, #210). 65 | 66 | ## 1.0.11 (2023-07-31) 67 | 68 | * Update dependencies' versions 69 | 70 | ## 1.0.10 (2023-02-02) 71 | 72 | * Bump `flutter_svg` to version 2.0.5. 73 | 74 | ## 1.0.9 (2023-01-24) 75 | 76 | * Support for the latest platform interface. 77 | 78 | ## 1.0.8 (2022-09-14) 79 | 80 | * Update the Google Pay button to adhere to the new specification and [brand guidelines](https://developers.google.com/pay/api/android/guides/brand-guidelines). 81 | 82 | ## 1.0.7 (2022-05-24) 83 | 84 | * Add support for Flutter 3.0 85 | 86 | ## 1.0.6 (2022-01-04) 87 | 88 | * Update `flutter_svg` to `1.0.0`. 89 | * Use the latest `pay_platform_interface`. 90 | 91 | ## 1.0.5 (2021-10-04) 92 | 93 | ### Features 94 | * Capture the dismissal of the payment selector and expose it to the Flutter end through the `onError` callback ([#90](https://github.com/google-pay/flutter-plugin/issues/90), [#61](https://github.com/google-pay/flutter-plugin/issues/61)). 95 | 96 | ## 1.0.4 (2021-05-31) 97 | Enrich `dartdoc` comments to facilitate the adoption of the package. 98 | 99 | ## 1.0.2 (2021-05-25) 100 | 101 | * Update dependencies. 102 | 103 | ## 1.0.1 (2021-05-18) 104 | 105 | ### Fixes 106 | 107 | * Use absolute routes for intra-repo links. 108 | 109 | ## 1.0.0 (2021-05-18) 110 | Initial release of the Android bit for the [pay](https://pub.dev/packages/pay) plugin. 111 | 112 | ### Features 113 | 114 | * Includes a button widget with the flavors and styles available for Google Pay. 115 | -------------------------------------------------------------------------------- /pay_android/README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/pay_android.svg)](https://pub.dartlang.org/packages/pay_android) 2 | 3 | This is an implementation of the [`pay_platform_interface`](https://github.com/google-pay/flutter-plugin/tree/main/pay_platform_interface) package for Android. 4 | 5 | ## Usage 6 | 7 | ### With the `pay` plugin 8 | 9 | This package is the endorsed implementation of the [`pay` plugin](https://pub.dev/packages/pay), so it gets automatically added to your [dependencies](https://flutter.dev/platform-plugins/) when you add the `pay` package to your `pubspec.yaml`. 10 | 11 | ### Using this package directly 12 | 13 | If you prefer to integrate or extend this package separately, add it as a dependency in your `pubspec.yaml` file with the following command: 14 | 15 | ```shell 16 | flutter pub add pay_android 17 | ``` 18 | 19 | Now, you can use the buttons available for the supported payment providers and the methods exposed in [the interface that this package uses](https://github.com/google-pay/flutter-plugin/tree/main/pay_platform_interface) to communicate with the native end. 20 | 21 | ```dart 22 | final PaymentConfiguration configuration = 23 | PaymentConfiguration.fromJsonString(''); 24 | 25 | RawGooglePayButton( 26 | paymentConfiguration: configuration, 27 | type: GooglePayButtonType.buy), 28 | ``` 29 | 30 |
31 | Note: This is not an officially supported Google product. -------------------------------------------------------------------------------- /pay_android/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: ../analysis_options.yaml -------------------------------------------------------------------------------- /pay_android/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /pay_android/android/build.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Google LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | group 'io.flutter.plugins.pay_android' 18 | version '1.0-SNAPSHOT' 19 | 20 | buildscript { 21 | ext.kotlin_version = '1.8.21' 22 | repositories { 23 | google() 24 | mavenCentral() 25 | } 26 | 27 | dependencies { 28 | classpath 'com.android.tools.build:gradle:7.4.2' 29 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 30 | } 31 | } 32 | 33 | rootProject.allprojects { 34 | repositories { 35 | google() 36 | mavenCentral() 37 | } 38 | } 39 | 40 | apply plugin: 'com.android.library' 41 | apply plugin: 'kotlin-android' 42 | 43 | android { 44 | // Conditional for compatibility with AGP <4.2. 45 | if (project.android.hasProperty("namespace")) { 46 | namespace 'io.flutter.plugins.pay_android' 47 | } 48 | compileSdk 34 49 | 50 | kotlinOptions { 51 | jvmTarget = '1.8' 52 | } 53 | 54 | sourceSets { 55 | main.java.srcDirs += 'src/main/kotlin' 56 | test.java.srcDirs += 'src/test/kotlin' 57 | androidTest.java.srcDirs += 'src/androidTest/kotlin' 58 | } 59 | 60 | defaultConfig { 61 | minSdkVersion 21 62 | } 63 | 64 | lintOptions { 65 | disable 'InvalidPackage' 66 | } 67 | } 68 | 69 | dependencies { 70 | implementation 'com.google.android.gms:play-services-wallet:19.4.0' 71 | 72 | testImplementation 'androidx.test.ext:truth:1.6.0' 73 | testImplementation 'androidx.test.ext:junit:1.2.1' 74 | testImplementation 'junit:junit:4.13.2' 75 | testImplementation 'org.mockito:mockito-inline:5.1.0' 76 | testImplementation 'org.robolectric:robolectric:4.11.1' 77 | 78 | androidTestImplementation 'androidx.test:core:1.6.1' 79 | androidTestImplementation 'androidx.test:runner:1.6.2' 80 | androidTestImplementation 'androidx.test:rules:1.6.1' 81 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' 82 | } 83 | -------------------------------------------------------------------------------- /pay_android/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /pay_android/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay_android/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /pay_android/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /pay_android/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'Google Pay Flutter plug-in' 2 | -------------------------------------------------------------------------------- /pay_android/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /pay_android/android/src/main/kotlin/io/flutter/plugins/pay_android/PayMethodCallHandler.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.flutter.plugins.pay_android 18 | 19 | import android.app.Activity 20 | import io.flutter.embedding.engine.plugins.FlutterPlugin 21 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding 22 | import io.flutter.plugin.common.BinaryMessenger 23 | import io.flutter.plugin.common.EventChannel 24 | import io.flutter.plugin.common.EventChannel.EventSink 25 | import io.flutter.plugin.common.EventChannel.StreamHandler 26 | import io.flutter.plugin.common.MethodCall 27 | import io.flutter.plugin.common.MethodChannel 28 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 29 | import io.flutter.plugin.common.MethodChannel.Result 30 | 31 | private const val METHOD_CHANNEL_NAME = "plugins.flutter.io/pay" 32 | private const val PAYMENT_EVENT_CHANNEL_NAME = "plugins.flutter.io/pay/payment_result" 33 | 34 | private const val METHOD_USER_CAN_PAY = "userCanPay" 35 | private const val METHOD_SHOW_PAYMENT_SELECTOR = "showPaymentSelector" 36 | 37 | /** 38 | * A simple class that configures and register a method handler for the `pay` plugin. 39 | */ 40 | class PayMethodCallHandler private constructor( 41 | messenger: BinaryMessenger, 42 | activity: Activity, 43 | private val activityBinding: ActivityPluginBinding?, 44 | ) : MethodCallHandler, StreamHandler { 45 | 46 | private val methodChannel: MethodChannel = MethodChannel(messenger, METHOD_CHANNEL_NAME) 47 | private var eventChannelIsActive = false 48 | private val paymentResultEventChannel: EventChannel = 49 | EventChannel(messenger, PAYMENT_EVENT_CHANNEL_NAME) 50 | 51 | private val googlePayHandler: GooglePayHandler = GooglePayHandler(activity) 52 | 53 | init { 54 | methodChannel.setMethodCallHandler(this) 55 | paymentResultEventChannel.setStreamHandler(this) 56 | } 57 | 58 | constructor( 59 | flutterBinding: FlutterPlugin.FlutterPluginBinding, 60 | activityBinding: ActivityPluginBinding, 61 | ) : this(flutterBinding.binaryMessenger, activityBinding.activity, activityBinding) { 62 | activityBinding.addActivityResultListener(googlePayHandler) 63 | } 64 | 65 | /** 66 | * Clears the handler in the method channel when not needed anymore. 67 | */ 68 | fun stopListening() { 69 | methodChannel.setMethodCallHandler(null) 70 | paymentResultEventChannel.setStreamHandler(null) 71 | activityBinding?.removeActivityResultListener(googlePayHandler) 72 | } 73 | 74 | // MethodCallHandler interface 75 | @Suppress("UNCHECKED_CAST") 76 | override fun onMethodCall(call: MethodCall, result: Result) { 77 | when (call.method) { 78 | METHOD_USER_CAN_PAY -> googlePayHandler.isReadyToPay(result, call.arguments()!!) 79 | METHOD_SHOW_PAYMENT_SELECTOR -> { 80 | if (eventChannelIsActive) { 81 | val arguments = call.arguments>()!! 82 | googlePayHandler.loadPaymentData( 83 | arguments.getValue("payment_profile") as String, 84 | arguments.getValue("payment_items") as List> 85 | ) 86 | result.success("{}") 87 | } else { 88 | result.error( 89 | "illegalEventChannelState", 90 | "Your event channel stream needs to be initialized and listening before calling the `showPaymentSelector` method. See the integration tutorial to learn more (https://pub.dev/packages/pay#advanced-usage)", 91 | null 92 | ) 93 | } 94 | } 95 | 96 | else -> result.notImplemented() 97 | } 98 | } 99 | 100 | // StreamHandler interface 101 | override fun onListen(arguments: Any?, events: EventSink?) { 102 | googlePayHandler.setPaymentResultEventSink(events) 103 | eventChannelIsActive = true 104 | } 105 | 106 | override fun onCancel(arguments: Any?) { 107 | googlePayHandler.setPaymentResultEventSink(null) 108 | eventChannelIsActive = false 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /pay_android/android/src/main/kotlin/io/flutter/plugins/pay_android/PayPlugin.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.flutter.plugins.pay_android 18 | 19 | import io.flutter.embedding.engine.plugins.FlutterPlugin 20 | import io.flutter.embedding.engine.plugins.activity.ActivityAware 21 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding 22 | import io.flutter.plugins.pay_android.view.PayButtonViewFactory 23 | 24 | /** 25 | * Entry point handler for the plugin. 26 | */ 27 | class PayPlugin : FlutterPlugin, ActivityAware { 28 | 29 | private val googlePayButtonViewType = "plugins.flutter.io/pay/google_pay_button" 30 | 31 | private lateinit var flutterPluginBinding: FlutterPlugin.FlutterPluginBinding 32 | private lateinit var methodCallHandler: PayMethodCallHandler 33 | 34 | override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 35 | this.flutterPluginBinding = flutterPluginBinding 36 | flutterPluginBinding.platformViewRegistry.registerViewFactory( 37 | googlePayButtonViewType, PayButtonViewFactory(flutterPluginBinding.binaryMessenger) 38 | ) 39 | } 40 | 41 | override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) = Unit 42 | 43 | override fun onAttachedToActivity(activityPluginBinding: ActivityPluginBinding) { 44 | methodCallHandler = PayMethodCallHandler(flutterPluginBinding, activityPluginBinding) 45 | } 46 | 47 | override fun onDetachedFromActivity() = methodCallHandler.stopListening() 48 | 49 | override fun onReattachedToActivityForConfigChanges( 50 | activityPluginBinding: ActivityPluginBinding, 51 | ) = onAttachedToActivity(activityPluginBinding) 52 | 53 | override fun onDetachedFromActivityForConfigChanges() = onDetachedFromActivity() 54 | } 55 | -------------------------------------------------------------------------------- /pay_android/android/src/main/kotlin/io/flutter/plugins/pay_android/util/PaymentsUtil.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.flutter.plugins.pay_android.util 18 | 19 | import com.google.android.gms.common.api.ApiException 20 | import com.google.android.gms.wallet.WalletConstants 21 | import java.util.Locale 22 | 23 | /** 24 | * A set of utilities with static methods for dealing with the Payments API. 25 | */ 26 | object PaymentsUtil { 27 | 28 | /** 29 | * Extracts the status code of the exception if included. 30 | * 31 | * An [ApiException] is returned by the Google Pay library when there is an error in the process 32 | * of initiating a payment process. This type has a status code that concretely defines the issue. 33 | * @param exception the exception to extract the status code from. 34 | * @return the integer that specifies the concrete issue. 35 | */ 36 | fun statusCodeForException(exception: Exception): Int = when (exception) { 37 | is ApiException -> exception.statusCode 38 | else -> -1 39 | } 40 | 41 | /** 42 | * Creates an environment constant from a string that the Google Pay library understands. 43 | * 44 | * Google Pay can be used in `TEST` or `PRODUCTION` modes. In `TEST` mode, the payment result 45 | * returns a dummy token to help validate the flow before going to production. When `PRODUCTION` 46 | * is used, a real encrypted payload is offered instead. 47 | * 48 | * @param environmentString the environment in [String] format. 49 | * @return a constant from [WalletConstants] with the environment. 50 | * @throws IllegalArgumentException if the value provided is unrecognized. 51 | */ 52 | fun environmentForString(environmentString: String?): Int = 53 | when (environmentString?.lowercase(Locale.ROOT)) { 54 | "test" -> WalletConstants.ENVIRONMENT_TEST 55 | "production" -> WalletConstants.ENVIRONMENT_PRODUCTION 56 | else -> throw IllegalArgumentException( 57 | "Environment must be one of TEST or PRODUCTION") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pay_android/android/src/main/kotlin/io/flutter/plugins/pay_android/view/PayButtonView.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.flutter.plugins.pay_android.view 18 | 19 | import android.content.Context 20 | import com.google.android.gms.wallet.button.ButtonOptions 21 | import com.google.android.gms.wallet.button.ButtonConstants.ButtonTheme 22 | import com.google.android.gms.wallet.button.ButtonConstants.ButtonType 23 | import com.google.android.gms.wallet.button.PayButton 24 | import io.flutter.plugin.common.BinaryMessenger 25 | import io.flutter.plugin.common.MethodChannel 26 | import io.flutter.plugin.platform.PlatformView 27 | import org.json.JSONArray 28 | import org.json.JSONObject 29 | 30 | private const val VIEW_TYPE = "plugins.flutter.io/pay/google_pay_button" 31 | 32 | private const val METHOD_ON_PRESSED = "onPressed" 33 | 34 | internal class PayButtonView(private val context: Context, binaryMessenger: BinaryMessenger, viewId: Int, creationParams: Map) : PlatformView { 35 | 36 | private val payButton: PayButton 37 | private val methodChannel: MethodChannel 38 | 39 | init { 40 | // Instantiate method channel 41 | methodChannel = MethodChannel(binaryMessenger, "$VIEW_TYPE/$viewId"); 42 | 43 | // Build pay button 44 | payButton = PayButton(context) 45 | buildPayButton(creationParams) 46 | } 47 | 48 | private fun buildPayButton(buttonParams: Map) { 49 | val buttonTheme = ButtonThemeFactory.fromString(buttonParams["theme"] as String) 50 | val buttonType = ButtonTypeFactory.fromString(buttonParams["type"] as String) 51 | val cornerRadiusDp = buttonParams["cornerRadius"] as Int 52 | val cornerRadius = (cornerRadiusDp * context.resources.displayMetrics.density).toInt() 53 | 54 | // Parse payment configuration 55 | val payConfiguration = JSONObject(buttonParams["paymentConfiguration"] as String) 56 | val allowedPaymentMethods: JSONArray = payConfiguration["allowedPaymentMethods"] as JSONArray 57 | 58 | payButton.initialize(ButtonOptions.newBuilder() 59 | .setButtonTheme(buttonTheme) 60 | .setButtonType(buttonType) 61 | .setCornerRadius(cornerRadius) 62 | .setAllowedPaymentMethods(allowedPaymentMethods.toString()) 63 | .build()) 64 | 65 | payButton.setOnClickListener { 66 | methodChannel.invokeMethod(METHOD_ON_PRESSED, null) 67 | } 68 | } 69 | 70 | override fun getView() = payButton 71 | 72 | override fun dispose() = Unit 73 | } 74 | 75 | class ButtonThemeFactory { 76 | companion object { 77 | fun fromString(buttonThemeString: String) = when(buttonThemeString) { 78 | "dark" -> ButtonTheme.DARK 79 | "light" -> ButtonTheme.LIGHT 80 | else -> ButtonTheme.DARK 81 | } 82 | } 83 | } 84 | 85 | class ButtonTypeFactory { 86 | companion object { 87 | fun fromString(buttonTypeString: String) = when(buttonTypeString) { 88 | "pay" -> ButtonType.PAY 89 | "buy" -> ButtonType.BUY 90 | "book" -> ButtonType.BOOK 91 | "donate" -> ButtonType.DONATE 92 | "checkout" -> ButtonType.CHECKOUT 93 | "order" -> ButtonType.ORDER 94 | "plain" -> ButtonType.PLAIN 95 | "subscribe" -> ButtonType.SUBSCRIBE 96 | else -> ButtonType.BUY 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /pay_android/android/src/main/kotlin/io/flutter/plugins/pay_android/view/PayButtonViewFactory.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.flutter.plugins.pay_android.view 18 | 19 | import android.content.Context 20 | import io.flutter.plugin.common.BinaryMessenger 21 | import io.flutter.plugin.common.StandardMessageCodec 22 | import io.flutter.plugin.platform.PlatformView 23 | import io.flutter.plugin.platform.PlatformViewFactory 24 | 25 | class PayButtonViewFactory(private val binaryMessenger: BinaryMessenger) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { 26 | override fun create(context: Context, viewId: Int, args: Any?): PlatformView { 27 | return PayButtonView(context, binaryMessenger, viewId, args as Map) 28 | } 29 | } -------------------------------------------------------------------------------- /pay_android/android/src/test/kotlin/io/flutter/plugins/pay_android/GooglePayHandlerTest.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.flutter.plugins.pay_android 18 | 19 | import android.app.Activity 20 | import android.content.pm.PackageInfo 21 | import android.content.pm.PackageManager 22 | import androidx.test.ext.junit.runners.AndroidJUnit4 23 | import com.google.android.gms.tasks.Task 24 | import com.google.android.gms.wallet.AutoResolvableResult 25 | import com.google.android.gms.wallet.AutoResolveHelper 26 | import com.google.android.gms.wallet.PaymentsClient 27 | import com.google.android.gms.wallet.Wallet 28 | import com.google.common.truth.Truth.assertThat 29 | import io.flutter.plugin.common.MethodChannel 30 | import org.junit.After 31 | import org.junit.Before 32 | import org.junit.Test 33 | import org.junit.runner.RunWith 34 | import org.mockito.MockedStatic 35 | import org.mockito.Mockito.any 36 | import org.mockito.Mockito.anyInt 37 | import org.mockito.Mockito.mock 38 | import org.mockito.Mockito.mockStatic 39 | import org.mockito.Mockito.`when` 40 | 41 | 42 | @RunWith(AndroidJUnit4::class) 43 | class GooglePayHandlerTest { 44 | 45 | abstract class AutoResolvableTask : Task() 46 | 47 | private val basicPaymentProfile = "{environment: 'TEST', transactionInfo: {}}" 48 | private val finalPaymentProfile = 49 | "{environment: 'TEST', transactionInfo: {totalPriceStatus: 'FINAL'}}" 50 | 51 | private val versionName = "0.0.0" 52 | private val paymentItems = listOf( 53 | mapOf( 54 | "type" to "total", "status" to "final_price", "amount" to "0" 55 | ) 56 | ) 57 | 58 | private lateinit var googlePayHandler: GooglePayHandler 59 | private lateinit var basicLoadPaymentDataCall: () -> Unit 60 | 61 | private lateinit var mockedActivity: Activity 62 | private lateinit var mockedPackageManager: PackageManager 63 | private lateinit var mockedPackageInfo: PackageInfo 64 | private lateinit var mockedResult: MethodChannel.Result 65 | private lateinit var mockedResolveHelper: MockedStatic 66 | private lateinit var mockedWallet: MockedStatic 67 | 68 | @Before 69 | fun setUp() { 70 | initializeMocks() 71 | 72 | googlePayHandler = GooglePayHandler(mockedActivity) 73 | basicLoadPaymentDataCall = { 74 | googlePayHandler.loadPaymentData(basicPaymentProfile, paymentItems) 75 | } 76 | } 77 | 78 | private fun initializeMocks() { 79 | mockedPackageInfo = PackageInfo() 80 | mockedPackageInfo.versionName = versionName 81 | 82 | mockedActivity = mock(Activity::class.java) 83 | mockedPackageManager = mock(PackageManager::class.java) 84 | `when`(mockedActivity.packageManager).thenReturn(mockedPackageManager) 85 | `when`(mockedPackageManager.getPackageInfo(mockedActivity.packageName, 0)).thenReturn( 86 | mockedPackageInfo 87 | ) 88 | 89 | mockedResult = mock(MethodChannel.Result::class.java) 90 | 91 | mockedResolveHelper = mockStatic(AutoResolveHelper::class.java) 92 | mockedResolveHelper.`when` { 93 | AutoResolveHelper.resolveTask( 94 | any(AutoResolvableTask::class.java), any(Activity::class.java), anyInt() 95 | ) 96 | }.then { } 97 | 98 | mockedWallet = mockStatic(Wallet::class.java) 99 | mockedWallet.`when` { 100 | Wallet.getPaymentsClient( 101 | any(Activity::class.java), any(Wallet.WalletOptions::class.java) 102 | ) 103 | }.thenReturn(mock(PaymentsClient::class.java)) 104 | } 105 | 106 | @Test 107 | fun loadPaymentDataRequestContainsTheRightPrice() { 108 | val paymentProfile = GooglePayHandler.buildPaymentProfile( 109 | finalPaymentProfile, 110 | onlyIncludeFields = listOf("environment", "transactionInfo"), 111 | paymentItems 112 | ) 113 | 114 | val transactionInfo = paymentProfile.getJSONObject("transactionInfo") 115 | assertThat(transactionInfo.optString("totalPriceStatus")).isEqualTo("FINAL") 116 | assertThat(transactionInfo.optString("totalPrice")).isEqualTo(paymentItems.first()["amount"]) 117 | } 118 | 119 | @Test 120 | fun showingThePaymentSelectorDoesNotReturnError() { 121 | assertThat(basicLoadPaymentDataCall()).isEqualTo(Unit) 122 | } 123 | 124 | @After 125 | fun cleanUp() { 126 | mockedResolveHelper.close() 127 | mockedWallet.close() 128 | } 129 | } -------------------------------------------------------------------------------- /pay_android/lib/pay_android.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:flutter/foundation.dart'; 16 | import 'package:flutter/gestures.dart'; 17 | import 'package:flutter/material.dart'; 18 | import 'package:flutter/rendering.dart'; 19 | import 'package:flutter/services.dart'; 20 | import 'package:pay_platform_interface/core/payment_configuration.dart'; 21 | 22 | export 'package:pay_platform_interface/core/payment_configuration.dart' 23 | show PaymentConfiguration; 24 | 25 | part 'src/widgets/google_pay_button.dart'; 26 | -------------------------------------------------------------------------------- /pay_android/lib/src/widgets/google_pay_button.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | part of '../../pay_android.dart'; 16 | 17 | /// The types of button supported on Google Pay. 18 | enum GooglePayButtonType { 19 | book, 20 | buy, 21 | checkout, 22 | donate, 23 | order, 24 | pay, 25 | plain, 26 | subscribe 27 | } 28 | 29 | /// The button themes supported on Google Pay. 30 | enum GooglePayButtonTheme { 31 | dark, 32 | light, 33 | } 34 | 35 | /// A button widget that follows the Google Pay button themes and design 36 | /// guidelines. 37 | /// 38 | /// This widget is a representation of the Google Pay button in Flutter. The 39 | /// button is drawn on the Flutter end using official assets, featuring all 40 | /// the labels, and themes available, and can be used independently as a 41 | /// standalone component. 42 | /// 43 | /// To use this button independently, simply add it to your layout: 44 | /// ```dart 45 | /// RawGooglePayButton( 46 | /// type: GooglePayButtonType.pay, 47 | /// onPressed: () => print('Button pressed')); 48 | /// ``` 49 | class RawGooglePayButton extends StatelessWidget { 50 | /// The payment configuration for the button to show the last 4 digits of a 51 | /// pre-selected card 52 | final PaymentConfiguration _paymentConfiguration; 53 | 54 | /// The default width for the Google Pay Button. 55 | static const double minimumButtonWidth = 168; 56 | 57 | /// The default height for the Google Pay Button. 58 | static const double defaultButtonHeight = 48; 59 | 60 | /// The constraints used to limit the size of the button. 61 | final BoxConstraints constraints; 62 | 63 | // Identifier to register the view on the platform end. 64 | static const String viewType = 'plugins.flutter.io/pay/google_pay_button'; 65 | 66 | /// Called when the button is pressed. 67 | final VoidCallback? onPressed; 68 | 69 | /// The amount of roundness applied to the corners of the button background. 70 | final int cornerRadius; 71 | 72 | /// The theme of the Google Pay button, to be adjusted based on the color 73 | /// scheme of the application. 74 | final GooglePayButtonTheme theme; 75 | 76 | /// The type of button depending on the activity initiated with the payment 77 | /// transaction. 78 | final GooglePayButtonType type; 79 | 80 | final Set> gestureRecognizers; 81 | 82 | /// Creates a Google Pay button widget with the parameters specified. 83 | RawGooglePayButton({ 84 | super.key, 85 | required final PaymentConfiguration paymentConfiguration, 86 | this.onPressed, 87 | this.cornerRadius = defaultButtonHeight ~/ 2, 88 | this.theme = GooglePayButtonTheme.dark, 89 | this.type = GooglePayButtonType.buy, 90 | this.gestureRecognizers = const >{}, 91 | }) : _paymentConfiguration = paymentConfiguration, 92 | constraints = const BoxConstraints.tightFor( 93 | width: minimumButtonWidth, 94 | height: defaultButtonHeight, 95 | ) { 96 | assert(constraints.debugAssertIsValid()); 97 | } 98 | 99 | @override 100 | Widget build(BuildContext context) { 101 | return ConstrainedBox( 102 | constraints: constraints, 103 | child: _platformButton, 104 | ); 105 | } 106 | 107 | /// Wrapper method to deliver the button 108 | Widget get _platformButton { 109 | switch (defaultTargetPlatform) { 110 | case TargetPlatform.android: 111 | return PlatformViewLink( 112 | viewType: viewType, 113 | surfaceFactory: (context, controller) { 114 | return AndroidViewSurface( 115 | controller: controller as AndroidViewController, 116 | gestureRecognizers: gestureRecognizers, 117 | hitTestBehavior: PlatformViewHitTestBehavior.opaque, 118 | ); 119 | }, 120 | onCreatePlatformView: (params) => 121 | PlatformViewsService.initAndroidView( 122 | id: params.id, 123 | viewType: viewType, 124 | layoutDirection: TextDirection.ltr, 125 | creationParams: { 126 | 'theme': theme.enumString, 127 | 'type': type.enumString, 128 | 'cornerRadius': cornerRadius, 129 | 'paymentConfiguration': 130 | _paymentConfiguration.rawConfigurationData(), 131 | }, 132 | creationParamsCodec: const StandardMessageCodec(), 133 | onFocus: () { 134 | params.onFocusChanged(true); 135 | }, 136 | ) 137 | ..addOnPlatformViewCreatedListener(_onPlatformViewCreated) 138 | ..addOnPlatformViewCreatedListener( 139 | params.onPlatformViewCreated) 140 | ..create()); 141 | default: 142 | throw UnsupportedError( 143 | 'This platform $defaultTargetPlatform does not support Google Pay'); 144 | } 145 | } 146 | 147 | _onPlatformViewCreated(int viewId) { 148 | MethodChannel methodChannel = MethodChannel('$viewType/$viewId'); 149 | methodChannel.setMethodCallHandler((call) async { 150 | if (call.method == 'onPressed') onPressed?.call(); 151 | }); 152 | } 153 | 154 | static bool get supported => defaultTargetPlatform == TargetPlatform.android; 155 | } 156 | 157 | /// A set of utility methods associated to the [GooglePayButtonType] enumeration. 158 | extension on GooglePayButtonType { 159 | /// Creates a string representation of the [GooglePayButtonType] enumeration. 160 | String get enumString => { 161 | GooglePayButtonType.plain: 'plain', 162 | GooglePayButtonType.buy: 'buy', 163 | GooglePayButtonType.donate: 'donate', 164 | GooglePayButtonType.checkout: 'checkout', 165 | GooglePayButtonType.book: 'book', 166 | GooglePayButtonType.subscribe: 'subscribe', 167 | GooglePayButtonType.pay: 'pay', 168 | GooglePayButtonType.order: 'order', 169 | }[this]!; 170 | } 171 | 172 | /// A set of utility methods associated to the [GooglePayButtonTheme] enumeration. 173 | extension on GooglePayButtonTheme { 174 | /// Creates a string representation of the [GooglePayButtonTheme] enumeration. 175 | String get enumString => { 176 | GooglePayButtonTheme.dark: 'dark', 177 | GooglePayButtonTheme.light: 'light', 178 | }[this]!; 179 | } 180 | -------------------------------------------------------------------------------- /pay_android/pubspec.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: pay_android 16 | description: A plug-in to add support for payments on the Android side of Flutter applications. 17 | version: 3.1.1 18 | homepage: https://github.com/google-pay/flutter-plugin 19 | 20 | environment: 21 | sdk: ">=3.0.0 <4.0.0" 22 | flutter: ">=3.10.0" 23 | 24 | flutter: 25 | plugin: 26 | platforms: 27 | android: 28 | package: io.flutter.plugins.pay_android 29 | pluginClass: PayPlugin 30 | 31 | dependencies: 32 | flutter: 33 | sdk: flutter 34 | flutter_localizations: 35 | sdk: flutter 36 | pay_platform_interface: ^2.0.0 37 | 38 | 39 | dev_dependencies: 40 | flutter_test: 41 | sdk: flutter 42 | mockito: ^5.0.8 43 | flutter_lints: ^5.0.0 44 | -------------------------------------------------------------------------------- /pay_android/test/pay_button_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:flutter/material.dart'; 16 | import 'package:flutter_test/flutter_test.dart'; 17 | import 'package:pay_android/pay_android.dart'; 18 | import 'package:pay_platform_interface/core/payment_configuration.dart'; 19 | 20 | const PayProvider _providerGooglePay = PayProvider.google_pay; 21 | final String _payConfigString = 22 | '{"provider": "${_providerGooglePay.toSimpleString()}",' 23 | '"data": { "allowedPaymentMethods": []}}'; 24 | 25 | void main() { 26 | setUp(() async {}); 27 | 28 | group('Button theme:', () { 29 | testWidgets('defaults to type buy and dark', (WidgetTester tester) async { 30 | await tester.pumpWidget(Directionality( 31 | textDirection: TextDirection.ltr, 32 | child: RawGooglePayButton( 33 | paymentConfiguration: 34 | PaymentConfiguration.fromJsonString(_payConfigString), 35 | onPressed: () {}), 36 | )); 37 | 38 | expect( 39 | find.byWidgetPredicate((widget) => 40 | widget is RawGooglePayButton && 41 | widget.theme == GooglePayButtonTheme.dark && 42 | widget.type == GooglePayButtonType.buy), 43 | findsOneWidget); 44 | }); 45 | 46 | testWidgets('height defaults to 48 as specified in the brand guidelines', 47 | (WidgetTester tester) async { 48 | await tester.pumpWidget( 49 | Directionality( 50 | textDirection: TextDirection.ltr, 51 | child: Center( 52 | child: RawGooglePayButton( 53 | paymentConfiguration: 54 | PaymentConfiguration.fromJsonString(_payConfigString), 55 | onPressed: () {}), 56 | ), 57 | ), 58 | ); 59 | 60 | final buttonSize = tester.getSize(find.byType(RawGooglePayButton)); 61 | expect(buttonSize.height, RawGooglePayButton.defaultButtonHeight); 62 | }); 63 | }); 64 | } 65 | -------------------------------------------------------------------------------- /pay_ios/.pubignore: -------------------------------------------------------------------------------- 1 | # Dirs created by pub 2 | .dart_tool/ 3 | build/ 4 | 5 | test/ -------------------------------------------------------------------------------- /pay_ios/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.1.0 (2025-01-10) 4 | Introduce new payment methods available in [`PKPaymentNetwork`](https://developer.apple.com/documentation/passkit/pkpaymentnetwork). New values include: 5 | 6 | * .bankAxept 7 | * .bancontact 8 | * .dankort 9 | * .meeza 10 | * .mir 11 | * .nanaco 12 | * .NAPAS 13 | * .pagoBancomat 14 | * .postFinance 15 | * .tmoney 16 | * .waon 17 | 18 | ## 1.0.12 (2024-10-10) 19 | Homogeneize the name of the method channel used to communicate with the platforms available. 20 | 21 | ## 1.0.11 (2024-01-21) 22 | Introduce new properties and fixes for the Apple Pay button. 23 | 24 | ### Features 25 | 26 | * Surface the `cornerRadius` property to the `ApplePayButton` widget to allow changing the corner roundness of the Apple Pay button ([#127](https://github.com/google-pay/flutter-plugin/issues/127)). 27 | 28 | ### Fixes 29 | 30 | * Make the `ApplePayButton` widget reactive to changes in the items used to calculate the price ([#235](https://github.com/google-pay/flutter-plugin/issues/235)). 31 | 32 | ## 1.0.10 (2024-01-19) 33 | Bump versions of dependencies and update static analysis tooling. 34 | 35 | ### Features 36 | 37 | * Use `flutter_lints` for static checks ([#182](https://github.com/google-pay/flutter-plugin/issues/182), [#210](https://github.com/google-pay/flutter-plugin/issues/210)). 38 | * Update minimum supported SDK version to Flutter 3.10/Dart 3.0. 39 | 40 | ## 1.0.9 (2023-07-31) 41 | 42 | * Fix typo in public property. 43 | 44 | ## 1.0.8 (2023-01-24) 45 | 46 | * Support for the latest platform interface. 47 | 48 | ## 1.0.7 (2021-06-01) 49 | 50 | * Include the [`transactionIdentifier`](https://developer.apple.com/documentation/passkit/pkpaymenttoken/1617003-transactionidentifier) property in the Apple Pay result. 51 | 52 | ## 1.0.6 (2022-01-31) 53 | 54 | ### ⚠ BREAKING CHANGES 55 | 56 | * The payment result object contains the following changes: 57 | * Added a `postalAddress` property. 58 | * Changed the resulting `name` in `PKContact` from `String` to `Dictionary`. 59 | * Trimmed `null` properties before returning the JSON object. 60 | 61 | ### Fixes 62 | * Univocally use the dot character (.) to separate the whole and the decimal part in a decimal number ([#84](https://github.com/google-pay/flutter-plugin/issues/84)). 63 | * Include billing and shipping addresses on the response ([#72](https://github.com/google-pay/flutter-plugin/issues/72)). 64 | 65 | ## 1.0.5 (2021-10-04) 66 | 67 | ### Features 68 | * Make the package available for iOS versions lower than 12.0 ([#36](https://github.com/google-pay/flutter-plugin/issues/36)). 69 | * Capture the dismissal of the payment selector and expose it to the Flutter end through the `onError` callback ([#90](https://github.com/google-pay/flutter-plugin/issues/90), [#61](https://github.com/google-pay/flutter-plugin/issues/61)). 70 | 71 | ### Fixes 72 | * Fix not being able to capture a payment result on the second and further payment attempts ([#80](https://github.com/google-pay/flutter-plugin/issues/80)). 73 | 74 | ## 1.0.4 (2021-05-27) 75 | Enrich `dartdoc` comments to facilitate the adoption of the package. 76 | 77 | ### Fixes 78 | 79 | * Fix iOS not returning a result after consecutive payment attempts. 80 | 81 | ## 1.0.3 (2021-05-26) 82 | 83 | ### Fixes 84 | 85 | * Fix incorrect handling of billing and shipping addresses. 86 | 87 | ## 1.0.2 (2021-05-25) 88 | 89 | * Update dependencies. 90 | 91 | ## 1.0.1 (2021-05-18) 92 | 93 | ### Fixes 94 | 95 | * Fixed typo in the documentation. 96 | 97 | ## 1.0.0 (2021-05-18) 98 | Initial release of the iOS bit for the [pay](https://pub.dev/packages/pay) plugin. 99 | 100 | ### Features 101 | 102 | * Includes a button widget with the flavors and styles available for Apple Pay. -------------------------------------------------------------------------------- /pay_ios/README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/pay_ios.svg)](https://pub.dartlang.org/packages/pay_ios) 2 | 3 | This is an implementation of the [`pay_platform_interface`](https://github.com/google-pay/flutter-plugin/tree/main/pay_platform_interface) package for iOS. 4 | 5 | ## Usage 6 | 7 | ### With the `pay` plugin 8 | 9 | This package is the endorsed implementation of the [`pay` plugin](https://pub.dev/packages/pay), so it gets automatically added to your [dependencies](https://flutter.dev/platform-plugins/) by adding the `pay` package to your `pubspec.yaml`. 10 | 11 | ### Using this package directly 12 | 13 | If you prefer to integrate or extend this package separately, add it as a dependency in your `pubspec.yaml` file as follows: 14 | 15 | ```yaml 16 | dependencies: 17 | pay_ios: ^1.1.0 18 | ``` 19 | 20 | Now, you can use the buttons available for the supported payment providers and the methods exposed in [the interface that this package uses](https://github.com/google-pay/flutter-plugin/tree/main/pay_platform_interface) to communicate with the native end. 21 | 22 | ```dart 23 | RawApplePayButton( 24 | style: ApplePayButtonStyle.black, 25 | type: ApplePayButtonType.buy); 26 | ``` 27 | 28 |
29 | Note: This is not an officially supported Google product. -------------------------------------------------------------------------------- /pay_ios/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: ../analysis_options.yaml -------------------------------------------------------------------------------- /pay_ios/ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /pay_ios/ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google-pay/flutter-plugin/4fc0c8b76448a76b26b9fe2a3514270e977b91a4/pay_ios/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /pay_ios/ios/Classes/PayPlugin.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import Flutter 18 | import PassKit 19 | import UIKit 20 | 21 | /// A class that receives and handles calls from Flutter to complete the payment. 22 | public class PayPlugin: NSObject, FlutterPlugin { 23 | private static let methodChannelName = "plugins.flutter.io/pay" 24 | 25 | private let methodUserCanPay = "userCanPay" 26 | private let methodShowPaymentSelector = "showPaymentSelector" 27 | 28 | private let paymentHandler = PaymentHandler() 29 | 30 | public static func register(with registrar: FlutterPluginRegistrar) { 31 | let messenger = registrar.messenger() 32 | let channel = FlutterMethodChannel(name: methodChannelName, binaryMessenger: messenger) 33 | registrar.addMethodCallDelegate(PayPlugin(), channel: channel) 34 | 35 | // Register the PlatformView to show the Apple Pay button. 36 | let buttonFactory = ApplePayButtonViewFactory(messenger: messenger) 37 | registrar.register(buttonFactory, withId: ApplePayButtonView.buttonMethodChannelName) 38 | } 39 | 40 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 41 | switch call.method { 42 | case methodUserCanPay: 43 | result(paymentHandler.canMakePayments(call.arguments as! String)) 44 | 45 | case methodShowPaymentSelector: 46 | let arguments = call.arguments as! [String: Any] 47 | paymentHandler.startPayment( 48 | result: result, 49 | paymentConfiguration: arguments["payment_profile"] as! String, 50 | paymentItems: arguments["payment_items"] as! [[String: Any?]]) 51 | 52 | default: 53 | result(FlutterMethodNotImplemented) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /pay_ios/ios/Classes/PaymentExtensions.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import PassKit 18 | 19 | /// A set of utility methods associated to `PKPayment`. 20 | extension PKPayment { 21 | 22 | /// Creates a `Dictionary` representation of the `PKPayment` object. 23 | public func toDictionary() -> [String: Any] { 24 | return [ 25 | "token": String(decoding: token.paymentData, as: UTF8.self) as Any?, 26 | "transactionIdentifier": token.transactionIdentifier, 27 | "paymentMethod": token.paymentMethod.toDictionary(), 28 | "billingContact": billingContact?.toDictionary(), 29 | "shippingContact": shippingContact?.toDictionary(), 30 | "shippingMethod": shippingMethod?.toDictionary(), 31 | ].compactMapValues { $0 } 32 | } 33 | } 34 | 35 | /// A set of utility methods associated to `PKPaymentMethod`. 36 | extension PKPaymentMethod { 37 | 38 | /// Creates a `Dictionary` representation of the `PKPaymentMethod` object. 39 | public func toDictionary() -> [String: Any] { 40 | return [ 41 | "displayName": displayName, 42 | "network": network?.rawValue, 43 | "type": type.rawValue 44 | ].compactMapValues { $0 } 45 | } 46 | } 47 | 48 | /// A set of utility methods associated to `PKContact`. 49 | extension PKContact { 50 | 51 | /// Creates a `Dictionary` representation of the `PKContact` object. 52 | public func toDictionary() -> [String: Any] { 53 | return [ 54 | "name": name?.toDictionary() as Any?, 55 | "emailAddress": emailAddress, 56 | "phoneNumber": phoneNumber?.stringValue, 57 | "postalAddress": postalAddress?.toDictionary() 58 | ].compactMapValues { $0 } 59 | } 60 | } 61 | 62 | /// A set of utility methods associated to `PersonNameComponents`. 63 | extension PersonNameComponents { 64 | 65 | /// Creates a `Dictionary` representation of the `PersonNameComponents` object. 66 | public func toDictionary() -> [String: Any]? { 67 | let dict = [ 68 | "namePrefix": namePrefix, 69 | "givenName": givenName, 70 | "middleName": middleName, 71 | "familyName": familyName, 72 | "nameSuffix": nameSuffix, 73 | "nickname": nickname, 74 | "phoneticRepresentation": phoneticRepresentation?.toDictionary(), 75 | ].compactMapValues { $0 } 76 | 77 | return !dict.isEmpty ? dict : nil 78 | } 79 | } 80 | 81 | /// A set of utility methods associated to `CNPostalAddress`. 82 | extension CNPostalAddress { 83 | 84 | /// Creates a `Dictionary` representation of the `CNPostalAddress` object. 85 | public func toDictionary() -> [String: Any] { 86 | return [ 87 | "street": street, 88 | "subLocality": subLocality, 89 | "city": city, 90 | "subAdministrativeArea": subAdministrativeArea, 91 | "state": state, 92 | "postalCode": postalCode, 93 | "country": country, 94 | "isoCountryCode": isoCountryCode 95 | ].compactMapValues { $0 } 96 | } 97 | } 98 | 99 | /// A set of utility methods associated to `PKShippingMethod`. 100 | extension PKShippingMethod { 101 | 102 | /// Creates a `Dictionary` representation of the `PKShippingMethod` object. 103 | public func toDictionary() -> [String: Any?] { 104 | return [ 105 | "id": identifier, 106 | "details": detail 107 | ].compactMapValues { $0 } 108 | } 109 | } 110 | 111 | /// A set of utility methods associated to `PKPaymentNetwork`. 112 | extension PKPaymentNetwork { 113 | 114 | /// Creates a `PKPaymentNetwork` object from a network in string format. 115 | public static func fromString(_ paymentNetworkString: String) -> PKPaymentNetwork? { 116 | switch paymentNetworkString { 117 | case "amex": 118 | return .amex 119 | case "bankAxept": 120 | guard #available(iOS 17.5, *) else { return nil } 121 | return .bankAxept 122 | case "bancontact": 123 | guard #available(iOS 16.0, *) else { return nil } 124 | return .bancontact 125 | case "barcode": 126 | guard #available(iOS 14.0, *) else { return nil } 127 | return .barcode 128 | case "cartesBancaires": 129 | guard #available(iOS 12.0, *) else { return nil } 130 | return .cartesBancaires 131 | case "chinaUnionPay": 132 | return .chinaUnionPay 133 | case "dankort": 134 | guard #available(iOS 15.1, *) else { return nil } 135 | return .dankort 136 | case "discover": 137 | return .discover 138 | case "eftpos": 139 | guard #available(iOS 12.0, *) else { return nil } 140 | return .eftpos 141 | case "electron": 142 | guard #available(iOS 12.0, *) else { return nil } 143 | return .electron 144 | case "elo": 145 | guard #available(iOS 12.1.1, *) else { return nil } 146 | return .elo 147 | case "girocard": 148 | guard #available(iOS 14.0, *) else { return nil } 149 | return .girocard 150 | case "idCredit": 151 | return .idCredit 152 | case "interac": 153 | return .interac 154 | case "JCB": 155 | return .JCB 156 | case "mada": 157 | guard #available(iOS 12.1.1, *) else { return nil } 158 | return .mada 159 | case "maestro": 160 | guard #available(iOS 12.0, *) else { return nil } 161 | return .maestro 162 | case "masterCard": 163 | return .masterCard 164 | case "meeza": 165 | guard #available(iOS 17.4, *) else { return nil } 166 | return .meeza 167 | case "mir": 168 | guard #available(iOS 14.5, *) else { return nil } 169 | return .mir 170 | case "nanaco": 171 | guard #available(iOS 15.0, *) else { return nil } 172 | return .nanaco 173 | case "NAPAS": 174 | guard #available(iOS 17.5, *) else { return nil } 175 | return .NAPAS 176 | case "pagoBancomat": 177 | guard #available(iOS 17.0, *) else { return nil } 178 | return .pagoBancomat 179 | case "postFinance": 180 | guard #available(iOS 16.4, *) else { return nil } 181 | return .postFinance 182 | case "privateLabel": 183 | return .privateLabel 184 | case "quicPay": 185 | return .quicPay 186 | case "suica": 187 | return .suica 188 | case "tmoney": 189 | guard #available(iOS 17.0, *) else { return nil } 190 | return .tmoney 191 | case "visa": 192 | return .visa 193 | case "vPay": 194 | guard #available(iOS 12.0, *) else { return nil } 195 | return .vPay 196 | case "waon": 197 | guard #available(iOS 15.0, *) else { return nil } 198 | return .waon 199 | default: 200 | return nil 201 | } 202 | } 203 | } 204 | 205 | /// A set of utility methods associated to `PKPaymentSummaryItemType`. 206 | extension PKPaymentSummaryItemType { 207 | 208 | /// Creates a `PKPaymentSummaryItemType` object from an item type in string format. 209 | public static func fromString(_ summaryItemType: String) -> PKPaymentSummaryItemType { 210 | switch summaryItemType { 211 | case "pending": 212 | return .pending 213 | default: 214 | return .final // final_price 215 | } 216 | } 217 | } 218 | 219 | /// A set of utility methods associated to `PKMerchantCapability`. 220 | extension PKMerchantCapability { 221 | 222 | /// Creates a `PKMerchantCapability` object from a capability in string format. 223 | public static func fromString(_ merchantCapability: String) -> PKMerchantCapability? { 224 | switch merchantCapability { 225 | case "3DS": 226 | return .capability3DS 227 | case "EMV": 228 | return .capabilityEMV 229 | case "credit": 230 | return .capabilityCredit 231 | case "debit": 232 | return .capabilityDebit 233 | default: 234 | return nil 235 | } 236 | } 237 | } 238 | 239 | /// A set of utility methods associated to `PKContactField`. 240 | extension PKContactField { 241 | 242 | /// Creates a `PKContactField` object from a contact field item in string format. 243 | public static func fromString(_ contactFieldItem: String) -> PKContactField? { 244 | switch contactFieldItem { 245 | case "emailAddress": 246 | return .emailAddress 247 | case "name": 248 | return .name 249 | case "phoneNumber": 250 | return .phoneNumber 251 | case "phoneticName": 252 | return .phoneticName 253 | case "postalAddress": 254 | return .postalAddress 255 | default: 256 | return nil 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /pay_ios/ios/pay_ios.podspec: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | Pod::Spec.new do |s| 5 | s.name = 'pay_ios' 6 | s.version = '0.0.1' 7 | s.summary = 'A new flutter plugin project.' 8 | s.description = <<-DESC 9 | No-op implementation of the pay plugin on iOS 10 | DESC 11 | s.homepage = 'https://github.com/google-pay/flutter-plugin' 12 | s.license = { :file => '../LICENSE' } 13 | s.author = { 'Google Pay Developer Relations' => 'payments-devrel-flutter@googlegroups.com 14 | ' } 15 | s.source = { :http => 'https://github.com/google-pay/flutter-plugin' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'Flutter' 18 | s.platform = :ios, '8.0' 19 | s.frameworks = 'PassKit' 20 | # Flutter.framework does not contain a i386 slice. 21 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 22 | s.swift_version = '5.0' 23 | end -------------------------------------------------------------------------------- /pay_ios/lib/pay_ios.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:flutter/foundation.dart'; 16 | import 'package:flutter/material.dart'; 17 | import 'package:flutter/services.dart'; 18 | 19 | part 'src/widgets/apple_pay_button.dart'; 20 | -------------------------------------------------------------------------------- /pay_ios/lib/src/widgets/apple_pay_button.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | part of '../../pay_ios.dart'; 16 | 17 | /// The types of button supported on Apple Pay. 18 | /// 19 | /// See the [PKPaymentButtonType](https://developer.apple.com/documentation/passkit/pkpaymentbuttontype) 20 | /// class in the Apple Pay documentation to learn more. 21 | enum ApplePayButtonType { 22 | plain, 23 | buy, 24 | setUp, 25 | inStore, 26 | donate, 27 | checkout, 28 | book, 29 | subscribe, 30 | reload, 31 | addMoney, 32 | topUp, 33 | order, 34 | rent, 35 | support, 36 | contribute, 37 | tip 38 | } 39 | 40 | /// The button styles supported on Apple Pay. 41 | /// 42 | /// See the [PKPaymentButtonStyle](https://developer.apple.com/documentation/passkit/pkpaymentbuttonstyle) 43 | /// class in the Apple Pay documentation to learn more. 44 | enum ApplePayButtonStyle { 45 | white, 46 | whiteOutline, 47 | black, 48 | automatic, 49 | } 50 | 51 | /// A set of utility methods associated to the [ApplePayButtonType] enumeration. 52 | extension on ApplePayButtonType { 53 | /// The minimum width for this button type according to 54 | /// [Apple Pay's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/apple-pay/overview/buttons-and-marks/) 55 | /// for the button. 56 | double get minimumAssetWidth => this == ApplePayButtonType.plain ? 100 : 140; 57 | } 58 | 59 | /// A button widget that follows the Apple Pay button styles and design 60 | /// guidelines. 61 | /// 62 | /// This widget is a representation of the Apple Pay button in Flutter. The 63 | /// button is drawn natively through a [PlatformView] and sent back to the UI 64 | /// element tree in Flutter. The button features all the labels, and styles 65 | /// available, and can be used independently as a standalone component. 66 | /// 67 | /// To use this button independently, simply add it to your layout: 68 | /// ```dart 69 | /// RawApplePayButton( 70 | /// style: ApplePayButtonStyle.black, 71 | /// type: ApplePayButtonType.buy, 72 | /// onPressed: () => print('Button pressed')); 73 | /// ``` 74 | class RawApplePayButton extends StatelessWidget { 75 | /// The default width for the Apple Pay Button. 76 | static const double minimumButtonWidth = 100; 77 | 78 | /// The default height for the Apple Pay Button. 79 | static const double minimumButtonHeight = 30; 80 | 81 | /// The constraints used to limit the size of the button. 82 | final BoxConstraints constraints; 83 | 84 | /// Called when the button is pressed. 85 | final VoidCallback? onPressed; 86 | 87 | /// The style of the Apple Pay button, to be adjusted based on the color 88 | /// scheme of the application. 89 | final ApplePayButtonStyle style; 90 | 91 | /// The type of button depending on the activity initiated with the payment 92 | /// transaction. 93 | final ApplePayButtonType type; 94 | 95 | /// The amount of roundness applied to the corners of the button. 96 | final double? cornerRadius; 97 | 98 | /// Creates an Apple Pay button widget with the parameters specified. 99 | RawApplePayButton({ 100 | super.key, 101 | this.onPressed, 102 | this.style = ApplePayButtonStyle.black, 103 | this.type = ApplePayButtonType.plain, 104 | this.cornerRadius, 105 | }) : constraints = BoxConstraints.tightFor( 106 | width: type.minimumAssetWidth, 107 | height: minimumButtonHeight, 108 | ) { 109 | assert(constraints.debugAssertIsValid()); 110 | } 111 | 112 | @override 113 | Widget build(BuildContext context) { 114 | return ConstrainedBox( 115 | constraints: constraints, 116 | child: _platformButton, 117 | ); 118 | } 119 | 120 | /// Wrapper method to deliver the button only to applitcations running on iOS. 121 | Widget get _platformButton { 122 | switch (defaultTargetPlatform) { 123 | case TargetPlatform.iOS: 124 | return _UiKitApplePayButton( 125 | onPressed: onPressed, 126 | style: style, 127 | type: type, 128 | cornerRadius: cornerRadius, 129 | ); 130 | default: 131 | throw UnsupportedError( 132 | 'This platform $defaultTargetPlatform does not support Apple Pay'); 133 | } 134 | } 135 | 136 | static bool get supported => defaultTargetPlatform == TargetPlatform.iOS; 137 | } 138 | 139 | /// A widget to draw the Apple Pay button through a [PlatforView]. 140 | class _UiKitApplePayButton extends StatefulWidget { 141 | static const buttonId = 'plugins.flutter.io/pay/apple_pay_button'; 142 | 143 | final VoidCallback? onPressed; 144 | final ApplePayButtonStyle style; 145 | final ApplePayButtonType type; 146 | final double? cornerRadius; 147 | 148 | const _UiKitApplePayButton({ 149 | this.onPressed, 150 | this.style = ApplePayButtonStyle.black, 151 | this.type = ApplePayButtonType.plain, 152 | this.cornerRadius, 153 | }); 154 | 155 | @override 156 | State<_UiKitApplePayButton> createState() => _UiKitApplePayButtonState(); 157 | } 158 | 159 | class _UiKitApplePayButtonState extends State<_UiKitApplePayButton> { 160 | @override 161 | Widget build(BuildContext context) { 162 | return UiKitView( 163 | viewType: _UiKitApplePayButton.buttonId, 164 | creationParamsCodec: const StandardMessageCodec(), 165 | creationParams: { 166 | 'style': widget.style.enumString, 167 | 'type': widget.type.enumString, 168 | 'cornerRadius': widget.cornerRadius 169 | }..removeWhere((_, value) => value == null), 170 | onPlatformViewCreated: (viewId) { 171 | MethodChannel methodChannel = 172 | MethodChannel('${_UiKitApplePayButton.buttonId}/$viewId'); 173 | methodChannel.setMethodCallHandler((call) async { 174 | if (call.method == 'onPressed') widget.onPressed?.call(); 175 | }); 176 | }, 177 | ); 178 | } 179 | } 180 | 181 | /// A set of utility methods associated to the [ApplePayButtonType] enumeration. 182 | extension on ApplePayButtonType { 183 | /// Creates a string representation of the [ApplePayButtonType] enumeration. 184 | String get enumString => { 185 | ApplePayButtonType.plain: 'plain', 186 | ApplePayButtonType.buy: 'buy', 187 | ApplePayButtonType.setUp: 'setUp', 188 | ApplePayButtonType.inStore: 'inStore', 189 | ApplePayButtonType.donate: 'donate', 190 | ApplePayButtonType.checkout: 'checkout', 191 | ApplePayButtonType.book: 'book', 192 | ApplePayButtonType.subscribe: 'subscribe', 193 | ApplePayButtonType.reload: 'reload', 194 | ApplePayButtonType.addMoney: 'addMoney', 195 | ApplePayButtonType.topUp: 'topUp', 196 | ApplePayButtonType.order: 'order', 197 | ApplePayButtonType.rent: 'rent', 198 | ApplePayButtonType.support: 'support', 199 | ApplePayButtonType.contribute: 'contribute', 200 | ApplePayButtonType.tip: 'tip', 201 | }[this]!; 202 | } 203 | 204 | /// A set of utility methods associated to the [ApplePayButtonStyle] enumeration. 205 | extension on ApplePayButtonStyle { 206 | /// Creates a string representation of the [ApplePayButtonStyle] enumeration. 207 | String get enumString => { 208 | ApplePayButtonStyle.white: 'white', 209 | ApplePayButtonStyle.whiteOutline: 'whiteOutline', 210 | ApplePayButtonStyle.black: 'black', 211 | ApplePayButtonStyle.automatic: 'automatic', 212 | }[this]!; 213 | } 214 | -------------------------------------------------------------------------------- /pay_ios/pubspec.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: pay_ios 16 | description: A plug-in to add support for payments on the iOS side on Flutter applications. 17 | version: 1.1.0 18 | homepage: https://github.com/google-pay/flutter-plugin 19 | 20 | environment: 21 | sdk: ">=3.0.0 <4.0.0" 22 | flutter: ">=3.10.0" 23 | 24 | flutter: 25 | plugin: 26 | platforms: 27 | ios: 28 | pluginClass: PayPlugin 29 | 30 | dependencies: 31 | flutter: 32 | sdk: flutter 33 | pay_platform_interface: ^2.0.0 34 | 35 | 36 | dev_dependencies: 37 | flutter_test: 38 | sdk: flutter 39 | mockito: ^5.0.8 40 | flutter_lints: ^5.0.0 -------------------------------------------------------------------------------- /pay_ios/test/pay_button_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:flutter/foundation.dart'; 16 | import 'package:flutter/material.dart'; 17 | import 'package:flutter_test/flutter_test.dart'; 18 | import 'package:pay_ios/pay_ios.dart'; 19 | 20 | void main() { 21 | setUp(() async { 22 | debugDefaultTargetPlatformOverride = TargetPlatform.iOS; 23 | }); 24 | 25 | group('Button style:', () { 26 | testWidgets('defaults to type plain and black', 27 | (WidgetTester tester) async { 28 | await tester.pumpWidget(Directionality( 29 | textDirection: TextDirection.ltr, 30 | child: RawApplePayButton(onPressed: () {}), 31 | )); 32 | 33 | expect( 34 | find.byWidgetPredicate((widget) => 35 | widget is UiKitView && 36 | widget.creationParams['style'] == 'black' && 37 | widget.creationParams['type'] == 'plain'), 38 | findsOneWidget); 39 | 40 | expect(find.byType(UiKitView), findsOneWidget); 41 | debugDefaultTargetPlatformOverride = null; 42 | }); 43 | 44 | testWidgets('height defaults to 30 as specified in the brand guidelines', 45 | (WidgetTester tester) async { 46 | await tester.pumpWidget( 47 | Directionality( 48 | textDirection: TextDirection.ltr, 49 | child: Center( 50 | child: RawApplePayButton(onPressed: () {}), 51 | ), 52 | ), 53 | ); 54 | 55 | final buttonSize = tester.getSize(find.byType(UiKitView)); 56 | expect( 57 | buttonSize.height, 58 | RawApplePayButton.minimumButtonHeight, 59 | ); 60 | debugDefaultTargetPlatformOverride = null; 61 | }); 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /pay_platform_interface/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.0.0 (2024-10-10) 4 | Homogeneize the name of the method channel used to communicate with the platforms available. 5 | 6 | ## 1.0.4 (2024-01-17) 7 | Bump versions of dependencies and update static analysis tooling. 8 | 9 | ### Features 10 | 11 | * Use `flutter_lints` for static checks (#182, #210). 12 | * Update minimum supported SDK version to Flutter 3.10/Dart 3.0. 13 | 14 | ## 1.0.3 (2023-01-24) 15 | Populate the configuration in the initializer list to simplify the business logic perception. 16 | 17 | ## 1.0.2 (2021-05-25) 18 | Enrich `dartdoc` comments to facilitate the adoption of the package. 19 | 20 | ## 1.0.1 (2021-05-18) 21 | 22 | ### Fixes 23 | 24 | * Use absolute routes for intra-repo links. 25 | 26 | ## 1.0.0 (2021-05-18) 27 | Initial release of the platform interface for the [pay](https://pub.dev/packages/pay) plugin. 28 | 29 | ### Features 30 | 31 | * A common contract to build on top of to support multiple platform implementations. 32 | * A default implementation of the contract using the `MethodChannel` class. 33 | * Basic data structures to work with multi-provider payment configurations. 34 | 35 | ## 1.0.0-beta.3 (2021-05-17) 36 | Configuration updates. 37 | 38 | ### Fixes 39 | 40 | * Fix homepage URL errors. 41 | 42 | ## 1.0.0-beta.2 (2021-05-10) 43 | Configuration updates. 44 | 45 | ### Fixes 46 | 47 | * Update versioning and package configuration. 48 | 49 | ## 1.0.0-beta.1 (2021-05-10) 50 | Initial release of the platform interface for this plugin. 51 | 52 | ### Features 53 | 54 | * A common contract to build on top of to support multiple platform implementations. 55 | * A default implementation of the contract using the `MethodChannel` class. 56 | * Basic data structures to work with multi-provider payment configurations. 57 | -------------------------------------------------------------------------------- /pay_platform_interface/README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/pay_platform_interface.svg)](https://pub.dartlang.org/packages/pay_platform_interface) 2 | 3 | A common platform interface for the [`pay`](https://github.com/google-pay/flutter-plugin/tree/main/pay) plugin. 4 | 5 | The contract in this package allows platform-specific implementations of the `pay` plugin, to ensure a common interface across plugins. 6 | 7 | Take a look at the [guide about plugin development](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#federated-plugins) if you'd like to learn more about the process of creating federated plugins. 8 | 9 | ## Usage 10 | To implement a new platform-specific implementation, add `pay_platform_interface` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/): 11 | 12 | ```yaml 13 | dependencies: 14 | pay_platform_interface: ^2.0.0 15 | ``` 16 | 17 | Start by extending [`PayPlatform`](https://github.com/google-pay/flutter-plugin/tree/main/pay_platform_interface/lib/pay_platform_interface.dart) with an implementation that performs the platform-specific behavior. 18 | 19 | The methods in the interface are: 20 | * `Future userCanPay(PaymentConfiguration paymentConfiguration)` 21 |
This method helps users of the plugin learn about whether a user can pay with a selected provider. The logic in this call is in charge of communicating directly with the payment provider specified in the payment configuration to return a result. 22 | * `Future> showPaymentSelector(PaymentConfiguration paymentConfiguration, List paymentItems)` 23 |
This method takes provider-specific payment configuration and a list of payment items (eg.: articles, taxes, subtotal, etc) and starts the payment process by showing the payment selector to users. 24 | 25 | ## Payment configuration 26 | The configuration to setup a payment provider is based on a open-ended schema (with provider-specific classes coming soon) with two required properties: 27 | * `provider`: with the target payment provider (eg.: `PayProvider.apple_pay`, `PayProvider.google_pay`). 28 | * `data`: a schemaless object with specific fields for the target payment provider. Take a look at the [test assets folder](https://github.com/google-pay/flutter-plugin/tree/main/pay_platform_interface/test/assets) to see sample configurations. 29 | 30 | ## Note on breaking changes 31 | 32 | Strongly prefer non-breaking changes (such as adding a method to the interface) over breaking changes for this package. 33 | 34 | Take a look at [this discussion](https://flutter.dev/go/platform-interface-breaking-changes) on why a less-clean interface is preferable to a breaking change. 35 | 36 |
37 | Note: This is not an officially supported Google product. -------------------------------------------------------------------------------- /pay_platform_interface/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: ../analysis_options.yaml -------------------------------------------------------------------------------- /pay_platform_interface/lib/core/payment_configuration.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:convert'; 16 | 17 | import 'package:flutter/services.dart'; 18 | 19 | import '../util/configurations.dart'; 20 | 21 | /// The payment providers supported by this package. 22 | enum PayProvider { apple_pay, google_pay } 23 | 24 | /// A type used to define the signature of methods and objects used to load 25 | /// payment configurations from various sources. 26 | typedef ConfigLoader = Future> Function(String value); 27 | 28 | /// An object that holds information about a payment transaction. 29 | /// 30 | /// This object helps load and store configuration information needed to 31 | /// issue a payment transaction for a given provider. It offers multiple 32 | /// options to create an instance, based on the source of the configuration. 33 | /// 34 | /// For example, if the payment configuration is in string format as a result 35 | /// of loading it from a remote server: 36 | /// 37 | /// ```dart 38 | /// PaymentConfiguration.fromJsonString( 39 | /// '{"provider": "apple_pay", "data": {}}'); 40 | /// ``` 41 | /// 42 | /// Or, if the configuration is loaded from the `assets` folder instead: 43 | /// 44 | /// ```dart 45 | /// PaymentConfiguration.fromAsset('configuration_asset.json'); 46 | /// ``` 47 | class PaymentConfiguration { 48 | /// The payment provider for this configuration. 49 | final PayProvider provider; 50 | 51 | /// The configuration parameters for a given payment provider. 52 | final Future> _parameters; 53 | 54 | /// The raw configuration provided 55 | final String _rawConfigurationData; 56 | 57 | /// Creates a [PaymentConfiguration] object with the properties in the map 58 | /// and ensures the necessary fields are present and valid. 59 | PaymentConfiguration._(Map configuration) 60 | : assert(configuration.containsKey('provider')), 61 | assert(configuration.containsKey('data')), 62 | assert( 63 | PayProviders.isValidProvider(configuration['provider'] as String)), 64 | provider = 65 | PayProviders.fromString(configuration['provider'] as String)!, 66 | _rawConfigurationData = jsonEncode(configuration['data']), 67 | _parameters = Configurations.extractParameters(configuration); 68 | 69 | /// Creates a [PaymentConfiguration] object from the 70 | /// [paymentConfigurationString] in JSON format. 71 | PaymentConfiguration.fromJsonString(String paymentConfigurationString) 72 | : this._(jsonDecode(paymentConfigurationString) as Map); 73 | 74 | /// Creates a [PaymentConfiguration] object wrapped in a [Future] from a 75 | /// configuration loaded from an external source. 76 | /// 77 | /// The configuration is referenced in the [paymentConfigurationAsset] and 78 | /// retrieved with the [profileLoader] specified. If empty, a default loader 79 | /// is used to get the configuration from the `assets` folder in the package. 80 | /// 81 | /// Here's an example of a configuration loaded with a bespoke loader: 82 | /// 83 | /// ```dart 84 | /// Future> _fileSystemLoader(String filePath) async => 85 | /// jsonDecode(File(filePath).readAsStringSync()); 86 | /// final config = await PaymentConfiguration.fromAsset('path-to-file.json', 87 | /// profileLoader: _fileSystemLoader); 88 | /// ``` 89 | static Future fromAsset( 90 | String paymentConfigurationAsset, 91 | {ConfigLoader profileLoader = _defaultProfileLoader}) async { 92 | final configuration = await profileLoader(paymentConfigurationAsset); 93 | return PaymentConfiguration._(configuration); 94 | } 95 | 96 | /// Configuration loader used as a default option in the [fromAsset] method. 97 | /// 98 | /// This loader retrieves the configuration with the 99 | /// [paymentConfigurationAsset] key from the `assets` folder in the 100 | /// package and decodes the JSON content before returning it back to the 101 | /// caller. 102 | static Future> _defaultProfileLoader( 103 | String paymentConfigurationAsset) async => 104 | await rootBundle.loadStructuredData('assets/$paymentConfigurationAsset', 105 | (s) async => jsonDecode(s) as Map); 106 | 107 | /// Returns the core configuration map in this object. 108 | Future> parameterMap() async { 109 | return _parameters; 110 | } 111 | 112 | /// Returns the raw data in the configuration 113 | String rawConfigurationData() { 114 | return _rawConfigurationData; 115 | } 116 | } 117 | 118 | extension PayProviders on PayProvider { 119 | static PayProvider? fromString(String providerString) => { 120 | 'apple_pay': PayProvider.apple_pay, 121 | 'google_pay': PayProvider.google_pay, 122 | }[providerString]; 123 | 124 | static bool isValidProvider(String providerString) => 125 | fromString(providerString) != null; 126 | 127 | String? toSimpleString() => { 128 | PayProvider.apple_pay: 'apple_pay', 129 | PayProvider.google_pay: 'google_pay', 130 | }[this]; 131 | } 132 | -------------------------------------------------------------------------------- /pay_platform_interface/lib/core/payment_item.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /// The payment price statuses supported in the payment selector. 16 | /// 17 | /// * [final_price] determines that the price has been calculated and 18 | /// won't change. 19 | /// * [pending] expects changes to the final price in response to user selection 20 | /// or other circumstances that are not known when first requesting the payment. 21 | /// * [unknown] for any other scenario. 22 | enum PaymentItemStatus { unknown, pending, final_price } 23 | 24 | /// A set of utility methods associated to the [PaymentItemStatus] enumeration. 25 | extension on PaymentItemStatus { 26 | /// Creates a string representation of the [PaymentItemStatus] enumeration. 27 | String toSimpleString() => { 28 | PaymentItemStatus.unknown: 'unknown', 29 | PaymentItemStatus.pending: 'pending', 30 | PaymentItemStatus.final_price: 'final_price', 31 | }[this]!; 32 | } 33 | 34 | /// The payment types supported when showing the payment selector. 35 | /// 36 | /// * [item] defines an element as a regular entry in the payment summary. 37 | /// * [total] describes the summary item with the total amount to be paid. 38 | enum PaymentItemType { item, total } 39 | 40 | /// A set of utility methods associated to the [PaymentItemType] enumeration. 41 | extension on PaymentItemType { 42 | /// Creates a string representation of the [PaymentItemType] enumeration. 43 | String toSimpleString() => { 44 | PaymentItemType.item: 'item', 45 | PaymentItemType.total: 'total', 46 | }[this]!; 47 | } 48 | 49 | /// A simple object that holds information about individual entries in the 50 | /// payment summary shown before completing a payment. 51 | /// 52 | /// Payment items are typically shown as a collection of entries with basic 53 | /// information about the items being purchased. 54 | /// 55 | /// Here is an example of an individual payment item: 56 | /// ```dart 57 | /// PaymentItem( 58 | /// label: 'Your new shoes', 59 | /// amount: '99.99', 60 | /// status: PaymentItemStatus.final_price, 61 | /// type: PaymentItemType.item, 62 | /// ) 63 | /// ``` 64 | /// 65 | /// And a summary entry with the total price. 66 | /// ```dart 67 | /// PaymentItem( 68 | /// label: 'Total', 69 | /// amount: '102.99', 70 | /// status: PaymentItemStatus.final_price, 71 | /// type: PaymentItemType.total, 72 | /// ) 73 | /// ``` 74 | class PaymentItem { 75 | /// A text with basic information about the item. 76 | final String label; 77 | 78 | /// The price of the item in string format. 79 | final String amount; 80 | 81 | /// The type of the item, either [item] or [total]. 82 | final PaymentItemType type; 83 | 84 | /// The status of the price, either [unknown], [pending] or [final_price]. 85 | final PaymentItemStatus status; 86 | 87 | /// Creates a new payment item with the specified parameters, defaulting to 88 | /// a [total] [type], and an [unknown] [status]. 89 | const PaymentItem({ 90 | required this.amount, 91 | required this.label, 92 | this.type = PaymentItemType.total, 93 | this.status = PaymentItemStatus.unknown, 94 | }); 95 | 96 | /// Creates a map representation of the object. 97 | Map toMap() => { 98 | 'label': label, 99 | 'amount': amount, 100 | 'type': type.toSimpleString(), 101 | 'status': status.toSimpleString(), 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /pay_platform_interface/lib/pay_channel.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'dart:async'; 16 | import 'dart:convert'; 17 | 18 | import 'package:flutter/services.dart'; 19 | 20 | import 'core/payment_configuration.dart'; 21 | import 'core/payment_item.dart'; 22 | import 'pay_platform_interface.dart'; 23 | 24 | /// An implementation of the contract in this plugin that uses a [MethodChannel] 25 | /// to communicate with the native end. 26 | /// 27 | /// Example of a simple method channel: 28 | /// 29 | /// ```dart 30 | /// PayPlatform platform = PayMethodChannel(); 31 | /// final canPay = await platform.userCanPay(configuration); 32 | /// final paymentData = await platform.showPaymentSelector( 33 | /// configuration, paymentItems); 34 | /// ``` 35 | class PayMethodChannel extends PayPlatform { 36 | // The method channel used to send messages down the native pipe. 37 | final MethodChannel _channel = const MethodChannel('plugins.flutter.io/pay'); 38 | 39 | /// Determines whether a user can pay with the provider in the configuration. 40 | /// 41 | /// Completes with a [PlatformException] if the native call fails or otherwise 42 | /// returns a boolean for the [paymentConfiguration] specified. 43 | @override 44 | Future userCanPay(PaymentConfiguration paymentConfiguration) async { 45 | return await _channel.invokeMethod( 46 | 'userCanPay', jsonEncode(await paymentConfiguration.parameterMap())) 47 | as bool; 48 | } 49 | 50 | /// Shows the payment selector to complete the payment operation. 51 | /// 52 | /// Shows the payment selector with the [paymentItems] and 53 | /// [paymentConfiguration] specified, returning the payment method selected as 54 | /// a result if the operation completes successfully, or throws a 55 | /// [PlatformException] if there's an error on the native end. 56 | @override 57 | Future> showPaymentSelector( 58 | PaymentConfiguration paymentConfiguration, 59 | List paymentItems, 60 | ) async { 61 | final paymentResult = await _channel.invokeMethod('showPaymentSelector', { 62 | 'payment_profile': jsonEncode(await paymentConfiguration.parameterMap()), 63 | 'payment_items': paymentItems.map((item) => item.toMap()).toList(), 64 | }) as String; 65 | 66 | return jsonDecode(paymentResult) as Map; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pay_platform_interface/lib/pay_platform_interface.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'core/payment_configuration.dart'; 16 | import 'core/payment_item.dart'; 17 | 18 | /// A contract that defines the required actions for payment libraries that 19 | /// implement it. 20 | abstract class PayPlatform { 21 | /// Determines whether the caller can make a payment with a given 22 | /// configuration. 23 | /// 24 | /// Returns a [Future] that resolves to a boolean value with the result based 25 | /// on a given [paymentConfiguration]. 26 | Future userCanPay(PaymentConfiguration paymentConfiguration); 27 | 28 | /// Triggers the action to show the payment selector to complete a payment 29 | /// with the configuration and a list of [PaymentItem] that help determine 30 | /// the price elements to show in the payment selector. 31 | /// 32 | /// Returns a [Future] with the result of the selection for the 33 | /// [paymentConfiguration] and [paymentItems] specified. 34 | Future> showPaymentSelector( 35 | PaymentConfiguration paymentConfiguration, 36 | List paymentItems); 37 | } 38 | -------------------------------------------------------------------------------- /pay_platform_interface/lib/util/configurations.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import 'package:flutter/services.dart'; 16 | 17 | import 'package:yaml/yaml.dart'; 18 | import 'package:pay_platform_interface/core/payment_configuration.dart'; 19 | 20 | /// A utility class to handle configuration objects and metadata associated 21 | /// with this plugin. 22 | class Configurations { 23 | /// Complements the payment configuration object with metadata about the 24 | /// package. 25 | /// 26 | /// Takes the configuration included in [config] and returns and updated 27 | /// version of the object wrapped in a [Future] with additional metadata. 28 | static Future> extractParameters( 29 | Map configuration) async { 30 | PayProvider provider = 31 | PayProviders.fromString(configuration['provider'] as String)!; 32 | Map configurationParams = 33 | configuration['data'] as Map; 34 | 35 | switch (provider) { 36 | case PayProvider.apple_pay: 37 | return configurationParams; 38 | 39 | case PayProvider.google_pay: 40 | 41 | // Add information about the package. 42 | final updatedMerchantInfo = { 43 | ...(configurationParams['merchantInfo'] ?? {}) as Map, 44 | 'softwareInfo': { 45 | 'id': 'flutter/pay-plugin', 46 | 'version': (await _getPackageConfiguration())['version'] 47 | } 48 | }; 49 | 50 | final updatedPaymentConfiguration = Map.unmodifiable( 51 | {...configurationParams, 'merchantInfo': updatedMerchantInfo}); 52 | 53 | return updatedPaymentConfiguration; 54 | } 55 | } 56 | 57 | /// Retrieves package information from the `pubspec.yaml` file as a [Map]. 58 | static Future> _getPackageConfiguration() async { 59 | final configurationFile = await rootBundle 60 | .loadString('packages/pay_platform_interface/pubspec.yaml'); 61 | return loadYaml(configurationFile) as Map; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /pay_platform_interface/pubspec.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: pay_platform_interface 16 | description: A common platform interface for payment plugins for Flutter. 17 | version: 2.0.0 18 | homepage: https://github.com/google-pay/flutter-plugin 19 | 20 | environment: 21 | sdk: ">=3.0.0 <4.0.0" 22 | flutter: ">=3.10.0" 23 | 24 | flutter: 25 | assets: 26 | - pubspec.yaml 27 | 28 | dependencies: 29 | flutter: 30 | sdk: flutter 31 | yaml: ^3.1.0 32 | 33 | dev_dependencies: 34 | flutter_test: 35 | sdk: flutter 36 | flutter_lints: ^5.0.0 37 | -------------------------------------------------------------------------------- /pay_platform_interface/test/assets/google_pay_default_payment_profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "google_pay", 3 | "data": { 4 | "environment": "TEST", 5 | "apiVersion": 2, 6 | "apiVersionMinor": 0, 7 | "allowedPaymentMethods": [ 8 | { 9 | "type": "CARD", 10 | "tokenizationSpecification": { 11 | "type": "PAYMENT_GATEWAY", 12 | "parameters": { 13 | "gateway": "example", 14 | "gatewayMerchantId": "gatewayMerchantId" 15 | } 16 | }, 17 | "parameters": { 18 | "allowedCardNetworks": ["VISA", "MASTERCARD"], 19 | "allowedAuthMethods": ["PAN_ONLY", "CRYPTOGRAM_3DS"], 20 | "billingAddressRequired": true, 21 | "billingAddressParameters": { 22 | "format": "FULL", 23 | "phoneNumberRequired": true 24 | } 25 | } 26 | } 27 | ], 28 | "merchantInfo": { 29 | "merchantId": "01234567890123456789", 30 | "merchantName": "Example Merchant Name" 31 | }, 32 | "transactionInfo": { 33 | "countryCode": "US", 34 | "currencyCode": "USD" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pay_platform_interface/test/assets/google_pay_prod_payment_profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "google_pay", 3 | "data": { 4 | "environment": "PRODUCTION", 5 | "apiVersion": 2, 6 | "apiVersionMinor": 0 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /pay_platform_interface/test/assets/google_pay_test_payment_profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "google_pay", 3 | "data": { 4 | "environment": "TEST", 5 | "apiVersion": 2, 6 | "apiVersionMinor": 0 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /pay_platform_interface/test/pay_channel_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | @TestOn('vm') 16 | library; 17 | 18 | import 'package:flutter/services.dart'; 19 | import 'package:flutter_test/flutter_test.dart'; 20 | import 'package:pay_platform_interface/core/payment_configuration.dart'; 21 | 22 | import 'package:pay_platform_interface/pay_channel.dart'; 23 | 24 | void main() { 25 | TestWidgetsFlutterBinding.ensureInitialized(); 26 | 27 | late final PayMethodChannel mobilePlatform; 28 | const channel = MethodChannel('plugins.flutter.io/pay'); 29 | 30 | const providerApplePay = PayProvider.apple_pay; 31 | final payConfigString = 32 | '{"provider": "${providerApplePay.toSimpleString()}", "data": {}}'; 33 | final dummyConfig = PaymentConfiguration.fromJsonString(payConfigString); 34 | 35 | setUpAll(() async { 36 | mobilePlatform = PayMethodChannel(); 37 | }); 38 | 39 | group('Verify channel I/O for', () { 40 | final log = []; 41 | const testResponses = { 42 | 'userCanPay': true, 43 | 'showPaymentSelector': '{}', 44 | }; 45 | 46 | setUp(() { 47 | TestWidgetsFlutterBinding.ensureInitialized(); 48 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger 49 | .setMockMethodCallHandler(channel, (MethodCall methodCall) async { 50 | log.add(methodCall); 51 | final response = testResponses[methodCall.method]; 52 | if (response is Exception) { 53 | return Future.error(response); 54 | } 55 | return Future.value(response); 56 | }); 57 | }); 58 | 59 | test('userCanPay', () async { 60 | await mobilePlatform.userCanPay(dummyConfig); 61 | expect( 62 | log, 63 | [isMethodCall('userCanPay', arguments: '{}')], 64 | ); 65 | }); 66 | 67 | test('showPaymentSelector', () async { 68 | await mobilePlatform.showPaymentSelector(dummyConfig, []); 69 | expect( 70 | log, 71 | [ 72 | isMethodCall('showPaymentSelector', arguments: { 73 | 'payment_profile': '{}', 74 | 'payment_items': [], 75 | }) 76 | ], 77 | ); 78 | }); 79 | 80 | tearDown(() async { 81 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger 82 | .setMockMethodCallHandler(channel, null); 83 | log.clear(); 84 | }); 85 | }); 86 | 87 | tearDown(() async {}); 88 | } 89 | -------------------------------------------------------------------------------- /pay_platform_interface/test/payment_configuration_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | @TestOn('vm') 16 | library; 17 | 18 | import 'dart:io'; 19 | import 'dart:convert'; 20 | 21 | import 'package:flutter_test/flutter_test.dart'; 22 | import 'package:pay_platform_interface/core/payment_configuration.dart'; 23 | 24 | String _fixtureAsset(String name) { 25 | var currentPath = Directory.current.path; 26 | var dir = currentPath.endsWith('/test') 27 | ? Directory.current.parent.path 28 | : currentPath; 29 | 30 | return File('$dir/test/assets/$name').readAsStringSync(); 31 | } 32 | 33 | Future> _testProfileLoader( 34 | String paymentConfigurationAsset) async => 35 | jsonDecode(_fixtureAsset(paymentConfigurationAsset)) 36 | as Map; 37 | 38 | const PayProvider _providerGooglePay = PayProvider.google_pay; 39 | final String _payConfigString = 40 | '{"provider": "${_providerGooglePay.toSimpleString()}",' 41 | '"data": { "environment": "TEST", "apiVersion": 2, "apiVersionMinor": 0}}'; 42 | 43 | void main() { 44 | TestWidgetsFlutterBinding.ensureInitialized(); 45 | setUp(() async {}); 46 | 47 | test('Load payment configuration from a string', () async { 48 | final configuration = PaymentConfiguration.fromJsonString(_payConfigString); 49 | expect(configuration.provider, _providerGooglePay); 50 | expect(await configuration.parameterMap(), isNotEmpty); 51 | }); 52 | 53 | test('Load payment configuration from an asset', () async { 54 | final configuration = await PaymentConfiguration.fromAsset( 55 | 'google_pay_prod_payment_profile.json', 56 | profileLoader: _testProfileLoader); 57 | 58 | expect(configuration.provider, _providerGooglePay); 59 | expect(await configuration.parameterMap(), isNotEmpty); 60 | }); 61 | 62 | test('Check that software info is included in Google Pay requests', () async { 63 | final config = await PaymentConfiguration.fromAsset( 64 | 'google_pay_prod_payment_profile.json', 65 | profileLoader: _testProfileLoader); 66 | 67 | final configParams = await config.parameterMap(); 68 | expect(configParams.containsKey('merchantInfo'), isTrue); 69 | expect(configParams['merchantInfo'].containsKey('softwareInfo'), isTrue); 70 | 71 | Map softwareInfo = 72 | configParams['merchantInfo']['softwareInfo'] as Map; 73 | expect(softwareInfo.containsKey('id'), isTrue); 74 | expect(softwareInfo.containsKey('version'), isTrue); 75 | }); 76 | 77 | tearDown(() async {}); 78 | } 79 | --------------------------------------------------------------------------------