Skip to content

Commit

Permalink
Add better coverage for the advanced integration with an example appl…
Browse files Browse the repository at this point in the history
…ication and additional information on the readme
  • Loading branch information
JlUgia committed Oct 10, 2024
1 parent 86d596a commit ddeef32
Show file tree
Hide file tree
Showing 2 changed files with 259 additions and 12 deletions.
48 changes: 36 additions & 12 deletions pay/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ dependencies:
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).
### Example
This snippet assumes the existence a payment configuration for Apple Pay ([`defaultApplePayConfigString`](https://github.com/google-pay/flutter-plugin/tree/main/pay/example/lib/payment_configurations.dart#L27)) and another one for Google Pay ([`defaultGooglePayConfigString`](https://github.com/google-pay/flutter-plugin/tree/main/pay/example/lib/payment_configurations.dart#L63)):
This snippet assumes the existence of a payment configuration for Apple Pay ([`defaultApplePayConfigString`](https://github.com/google-pay/flutter-plugin/tree/main/pay/example/lib/payment_configurations.dart#L27)) and another one for Google Pay ([`defaultGooglePayConfigString`](https://github.com/google-pay/flutter-plugin/tree/main/pay/example/lib/payment_configurations.dart#L63)):
```dart
import 'package:pay/pay.dart';
Expand Down Expand Up @@ -117,15 +117,10 @@ const _paymentItems = [
)
];
late final Pay _payClient;
// When you are ready to load your configuration
_payClient = Pay({
PayProvider.google_pay: PaymentConfiguration.fromJsonString(
payment_configurations.defaultGooglePay),
PayProvider.apple_pay: PaymentConfiguration.fromJsonString(
payment_configurations.defaultApplePay),
});
final Pay _payClient = Pay({
PayProvider.google_pay: payment_configurations.defaultGooglePayConfig,
PayProvider.apple_pay: payment_configurations.defaultApplePayConfig,
});
```

As you can see, you can add multiple configurations to your payment client, one for each payment provider supported.
Expand All @@ -141,8 +136,10 @@ Widget build(BuildContext context) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == true) {
return RawGooglePayButton(
paymentConfiguration:
payment_configurations.defaultGooglePayConfig,
type: GooglePayButtonType.buy,
onPressed: onGooglePayPressed);
onPressed: _onGooglePayPressed);
} else {
// userCanPay returned false
// Consider showing an alternative payment method
Expand All @@ -158,7 +155,7 @@ Widget build(BuildContext context) {
Finally, handle the `onPressed` event and trigger the payment selector as follows:

```dart
void onGooglePayPressed() async {
void _onGooglePayPressed() async {
final result = await _payClient.showPaymentSelector(
PayProvider.google_pay,
_paymentItems,
Expand All @@ -167,6 +164,33 @@ void onGooglePayPressed() async {
}
```

### Handling a payment result response (Android only)
On Android, payment results are received using an event channel, in order to eliminate the effect of lost references during activity recreation events. Because of that, calls to `Pay.showPaymentSelector` only initiate the payment process and don't return any result.

To subscribe to the result stream, create an `EventChannel` using the payment results channel name (`plugins.flutter.io/pay/payment_result`) and start listening to events:

```dart
static const eventChannel =
EventChannel('plugins.flutter.io/pay/payment_result');
final _paymentResultSubscription = eventChannel
.receiveBroadcastStream()
.map((result) => jsonDecode(result as String) as Map<String, dynamic>)
.listen((result) {
// TODO: Send the resulting Google Pay token to your server / PSP
}, onError: (error) {
// TODO: Handle errors
});
```

Make sure to cancel the subscription and clear the reference when it is not needed anymore:

```dart
_paymentResultSubscription.cancel();
_paymentResultSubscription = null;
```

See the [advanced example](https://github.com/google-pay/flutter-plugin/blob/main/pay/example/lib/advanced.dart) to see a working integration.

## Additional resources
Take a look at the following resources to manage your payment accounts and learn more about the APIs for the supported providers:

Expand Down
223 changes: 223 additions & 0 deletions pay/example/lib/advanced.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:pay/pay.dart';

import 'payment_configurations.dart' as payment_configurations;

void main() {
runApp(const PayAdvancedMaterialApp());
}

const googlePayEventChannelName = 'plugins.flutter.io/pay/payment_result';
const _paymentItems = [
PaymentItem(
label: 'Total',
amount: '99.99',
status: PaymentItemStatus.final_price,
)
];

class PayAdvancedMaterialApp extends StatelessWidget {
const PayAdvancedMaterialApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Pay for Flutter Advanced Integration Demo',
localizationsDelegates: const [
...GlobalMaterialLocalizations.delegates,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: const [
Locale('en', ''),
Locale('es', ''),
Locale('de', ''),
],
home: PayAdvancedSampleApp(),
);
}
}

class PayAdvancedSampleApp extends StatefulWidget {
final Pay payClient;

PayAdvancedSampleApp({super.key})
: payClient = Pay({
PayProvider.google_pay: payment_configurations.defaultGooglePayConfig,
PayProvider.apple_pay: payment_configurations.defaultApplePayConfig,
});

@override
State<PayAdvancedSampleApp> createState() => _PayAdvancedSampleAppState();
}

class _PayAdvancedSampleAppState extends State<PayAdvancedSampleApp> {
static const eventChannel = EventChannel(googlePayEventChannelName);
StreamSubscription<String>? _googlePayResultSubscription;

late final Future<bool> _canPayGoogleFuture;
late final Future<bool> _canPayAppleFuture;

// A method to listen to events coming from the event channel on Android
void _startListeningForPaymentResults() {
_googlePayResultSubscription = eventChannel
.receiveBroadcastStream()
.map((result) => result.toString())
.listen(debugPrint, onError: (error) => debugPrint(error.toString()));
}

@override
void initState() {
super.initState();
if (defaultTargetPlatform == TargetPlatform.android) {
_startListeningForPaymentResults();
}

// Initialize userCanPay futures
_canPayGoogleFuture = widget.payClient.userCanPay(PayProvider.google_pay);
_canPayAppleFuture = widget.payClient.userCanPay(PayProvider.apple_pay);
}

void _onGooglePayPressed() =>
_showPaymentSelectorForProvider(PayProvider.google_pay);

void _onApplePayPressed() =>
_showPaymentSelectorForProvider(PayProvider.apple_pay);

void _showPaymentSelectorForProvider(PayProvider provider) async {
try {
final result =
await widget.payClient.showPaymentSelector(provider, _paymentItems);
debugPrint(result.toString());
} catch (error) {
debugPrint(error.toString());
}
}

@override
void dispose() {
_googlePayResultSubscription?.cancel();
_googlePayResultSubscription = null;
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('T-shirt Shop'),
),
backgroundColor: Colors.white,
body: ListView(
padding: const EdgeInsets.symmetric(horizontal: 20),
children: [
Container(
margin: const EdgeInsets.symmetric(vertical: 20),
child: const Image(
image: AssetImage('assets/images/ts_10_11019a.jpg'),
height: 350,
),
),
const Text(
'Amanda\'s Polo Shirt',
style: TextStyle(
fontSize: 20,
color: Color(0xff333333),
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 5),
const Text(
'\$50.20',
style: TextStyle(
color: Color(0xff777777),
fontSize: 15,
),
),
const SizedBox(height: 15),
const Text(
'Description',
style: TextStyle(
fontSize: 15,
color: Color(0xff333333),
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 5),
const Text(
'A versatile full-zip that you can wear all day long and even...',
style: TextStyle(
color: Color(0xff777777),
fontSize: 15,
),
),

// Google Pay button
FutureBuilder<bool>(
future: _canPayGoogleFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == true) {
return RawGooglePayButton(
paymentConfiguration:
payment_configurations.defaultGooglePayConfig,
type: GooglePayButtonType.buy,
onPressed: _onGooglePayPressed);
} else {
// userCanPay returned false
// Consider showing an alternative payment method
}
} else {
// The operation hasn't finished loading
// Consider showing a loading indicator
}
// This example shows an empty box if userCanPay returns false
return const SizedBox.shrink();
},
),

// Apple Pay button
FutureBuilder<bool>(
future: _canPayAppleFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == true) {
return RawApplePayButton(
type: ApplePayButtonType.buy,
onPressed: _onApplePayPressed);
} else {
// userCanPay returned false
// Consider showing an alternative payment method
}
} else {
// The operation hasn't finished loading
// Consider showing a loading indicator
}
// This example shows an empty box if userCanPay returns false
return const SizedBox.shrink();
},
),
const SizedBox(height: 15)
],
),
);
}
}

0 comments on commit ddeef32

Please sign in to comment.