Skip to content

Commit

Permalink
fix: apple pay (#1202)
Browse files Browse the repository at this point in the history
* fix: apple pay

* fix: add shipping method callback
  • Loading branch information
jamesblasco authored Apr 12, 2023
1 parent 49d03ca commit 9fbb7a4
Show file tree
Hide file tree
Showing 17 changed files with 756 additions and 475 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
import 'package:stripe_example/widgets/example_scaffold.dart';


class ApplePayCreatePaymentMethodScreen extends StatefulWidget {
@override
_ApplePayScreenState createState() => _ApplePayScreenState();
Expand Down Expand Up @@ -74,7 +73,6 @@ class _ApplePayScreenState extends State<ApplePayCreatePaymentMethodScreen> {
merchantCountryCode: 'Es',
currencyCode: 'EUR',
),
applePayPaymentMethodParams: ApplePayPaymentMethodParams(),
),
);

Expand Down
55 changes: 38 additions & 17 deletions example/lib/screens/wallets/apple_pay_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,48 @@ class _ApplePayScreenState extends State<ApplePayScreen> {
children: [
if (Stripe.instance.isPlatformPaySupportedListenable.value)
PlatformPayButton(
onDidSetShippingContact: (contact) {
onShippingContactSelected: (contact) async {
debugPrint('Shipping contact updated $contact');

// Mandatory after entering a shipping contact
Stripe.instance.updatePlatformSheet(
await Stripe.instance.updatePlatformSheet(
params: PlatformPaySheetUpdateParams.applePay(
summaryItems: items,
shippingMethods: shippingMethods,
errors: [],
),
);
},
onShippingMethodSelected: (method) {
debugPrint('Shipping contact updated $method');

Stripe.instance.updatePlatformSheet(
return;
},
onShippingMethodSelected: (method) async {
debugPrint('Shipping method updated $method');
// Mandatory after entering a shipping contact
await Stripe.instance.updatePlatformSheet(
params: PlatformPaySheetUpdateParams.applePay(
summaryItems: items,
shippingMethods: shippingMethods,
errors: [],
),
);

return;
},
onCouponCodeEntered: (couponCode) {
debugPrint('set coupon $couponCode');
},
onOrderTracking: () async {
debugPrint('set order tracking');

/// Provide a URL to your web service that will provide the order details
///
await Stripe.instance.configurePlatformOrderTracking(
orderDetails: PlatformPayOrderDetails.applePay(
orderTypeIdentifier: 'orderTypeIdentifier',
orderIdentifier: 'https://your-web-service.com/v1/orders/',
webServiceUrl: 'webServiceURL',
authenticationToken: 'token',
));
},
type: PlatformButtonType.buy,
appearance: PlatformButtonStyle.whiteOutline,
Expand Down Expand Up @@ -110,17 +130,18 @@ class _ApplePayScreenState extends State<ApplePayScreen> {
clientSecret: clientSecret,
confirmParams: PlatformPayConfirmParams.applePay(
applePay: ApplePayParams(
cartItems: items,
requiredShippingAddressFields: [
ApplePayContactFieldsType.name,
ApplePayContactFieldsType.postalAddress,
ApplePayContactFieldsType.emailAddress,
ApplePayContactFieldsType.phoneNumber,
],
shippingMethods: shippingMethods,
merchantCountryCode: 'Es',
currencyCode: 'EUR',
),
cartItems: items,
requiredShippingAddressFields: [
ApplePayContactFieldsType.name,
ApplePayContactFieldsType.postalAddress,
ApplePayContactFieldsType.emailAddress,
ApplePayContactFieldsType.phoneNumber,
],
shippingMethods: shippingMethods,
merchantCountryCode: 'Es',
currencyCode: 'EUR',
supportsCouponCode: true,
couponCode: 'Coupon'),
),
);
ScaffoldMessenger.of(context).showSnackBar(
Expand Down
28 changes: 28 additions & 0 deletions packages/stripe/lib/src/stripe.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:meta/meta.dart';
import 'package:stripe_platform_interface/stripe_platform_interface.dart';

/// [Stripe] is the facade of the library and exposes the operations that can be
Expand Down Expand Up @@ -226,16 +227,43 @@ class Stripe {
}
}

@internal
bool debugUpdatePlatformSheetCalled = false;

/// Updates the native payment sheet with new data **iOS-only.
///
/// For example this method is required to call when the user updates shippingmethod, shippingAddress and couponcode.
Future<void> updatePlatformSheet({
required PlatformPaySheetUpdateParams params,
}) async {
await _awaitForSettings();
assert(() {
debugUpdatePlatformSheetCalled = true;
return true;
}());
debugUpdatePlatformSheetCalled = true;
return _platform.updatePlatformSheet(params: params);
}

@internal
bool debugConfigurePlatformOrderTrackingCalled = false;

/// Updates the native payment sheet with new order tracking information
/// **iOS-only.
///
/// This method is required to call when the onOrderTracking is
/// called
Future<void> configurePlatformOrderTracking({
required PlatformPayOrderDetails orderDetails,
}) async {
await _awaitForSettings();
assert(() {
debugConfigurePlatformOrderTrackingCalled = true;
return true;
}());
return _platform.configurePlatformOrderTracking(orderDetails: orderDetails);
}

/// Creates a single-use token that represents an Apple Pay credit card’s details.
///
/// The [payment] param should be the data response from the `pay` plugin. It can
Expand Down
155 changes: 105 additions & 50 deletions packages/stripe/lib/src/widgets/apple_pay_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:stripe_platform_interface/stripe_platform_interface.dart';

import '../model/apple_pay_button.dart';
import 'package:flutter_stripe/flutter_stripe.dart';

const double _kApplePayButtonDefaultHeight = 48;

Expand All @@ -25,8 +23,8 @@ class ApplePayButton extends StatelessWidget {
double? height = _kApplePayButtonDefaultHeight,
BoxConstraints? constraints,
this.onShippingContactSelected,
this.onDidSetCoupon,
this.onShippingMethodSelected,
this.onCouponCodeEntered,
this.onOrderTracking,
}) : assert(constraints == null || constraints.debugAssertIsValid()),
constraints = (width != null || height != null)
Expand Down Expand Up @@ -57,23 +55,33 @@ class ApplePayButton extends StatelessWidget {
/// Callback that is executed when the button is pressed.
final VoidCallback? onPressed;

/// Callback that is executed when a shipping contact is selected
/// Additional constraints for the Apple pay button widget.
final BoxConstraints? constraints;

/// For iOS only, a callback that is executed when a shipping contact is
/// entered. If implemented this method requires to call
/// 'Stripe.instance.updatePlatformSheet' with the updated shipping details
final OnDidSetShippingContact? onShippingContactSelected;

/// Callback that is execyted when shipping method is selected
/// For iOS only, a callback that is executed when a shipping method is
/// selected. If implemented this method requires to call
/// 'Stripe.instance.updatePlatformSheet' with the updated price items
final OnDidSetShippingMethod? onShippingMethodSelected;

/// Callback that is execyted when shipping method is selected
final OnDidSetCoupon? onDidSetCoupon;

/// Additional constraints for the Apple pay button widget.
final BoxConstraints? constraints;
/// For iOS only, a callback that is executed when a shipping method is
/// selected. If implemented this method requires to call
/// 'Stripe.instance.updatePlatformSheet' with the updated price items
final OnCouponCodeEntered? onCouponCodeEntered;

/// Callback function for setting the order details (retrieved from your server) to give users the
/// ability to track and manage their purchases in Wallet. Stripe calls your implementation after the
/// payment is complete, but before iOS dismisses the Apple Pay sheet. You must call the `completion`
/// function, or else the Apple Pay sheet will hang.
final SetOrderTracking? onOrderTracking;
/// For iOS only. If implemented, the callback is executed when an order is
/// about to be completed and the developer needs to provide the tracking
/// information. This method needs to call
/// 'Stripe.instance.configurePlatformOrderTracking' with that info for
/// setting the order details (retrieved from your server) to give users the
/// ability to track and manage their purchases in Wallet
///
/// See https://stripe.com/docs/apple-pay?platform=ios&locale=es-ES#order-tracking
final OnOrderTracking? onOrderTracking;

@override
Widget build(BuildContext context) => ConstrainedBox(
Expand All @@ -91,9 +99,9 @@ class ApplePayButton extends StatelessWidget {
style: style,
cornerRadius: cornerRadius,
onPressed: onPressed,
onDidSetShippingContact: onShippingContactSelected,
onDidSetCoupon: onDidSetCoupon,
onShippingContactSelected: onShippingContactSelected,
onShippingMethodSelected: onShippingMethodSelected,
onCouponCodeEntered: onCouponCodeEntered,
onOrderTracking: onOrderTracking,
);
default:
Expand All @@ -110,8 +118,8 @@ class _UiKitApplePayButton extends StatefulWidget {
required this.type,
this.cornerRadius = 4.0,
this.onPressed,
this.onDidSetShippingContact,
this.onDidSetCoupon,
this.onShippingContactSelected,
this.onCouponCodeEntered,
this.onShippingMethodSelected,
this.onOrderTracking,
}) : super(key: key);
Expand All @@ -120,10 +128,10 @@ class _UiKitApplePayButton extends StatefulWidget {
final PlatformButtonType type;
final double cornerRadius;
final VoidCallback? onPressed;
final OnDidSetShippingContact? onDidSetShippingContact;
final OnDidSetShippingContact? onShippingContactSelected;
final OnDidSetShippingMethod? onShippingMethodSelected;
final OnDidSetCoupon? onDidSetCoupon;
final SetOrderTracking? onOrderTracking;
final OnCouponCodeEntered? onCouponCodeEntered;
final OnOrderTracking? onOrderTracking;
@override
_UiKitApplePayButtonState createState() => _UiKitApplePayButtonState();
}
Expand All @@ -143,47 +151,94 @@ class _UiKitApplePayButtonState extends State<_UiKitApplePayButton> {
},
onPlatformViewCreated: (viewId) {
methodChannel = MethodChannel('flutter.stripe/apple_pay/$viewId');
methodChannel?.setMethodCallHandler((call) async {
if (call.method == 'onPressed') {
widget.onPressed?.call();
}
if (call.method == 'onShippingContactSelected') {
final args =
_convertShippingContact(call.arguments['shippingContact']);
widget.onDidSetShippingContact?.call(args);
}
if (call.method == 'onShippingMethodSelected') {
final args = ApplePayShippingMethod.fromJson(
call.arguments['shippingMethod']);
widget.onShippingMethodSelected?.call(args);
}
if (call.method == 'onShippingContactSelected') {
widget.onDidSetCoupon?.call(call.arguments['couponCode']);
}
if (call.method == 'onOrderTracking') {
widget.onOrderTracking?.call(
call.arguments['orderIdentifier'],
call.arguments['orderTypeIdentifier'],
call.arguments['authenticationToken'],
call.arguments['webServiceUrl'],
);
}
return;
});
methodChannel?.setMethodCallHandler(callHandler);
_updateHandlers();
},
);
}

Future<void> callHandler(MethodCall call) async {
switch (call.method) {
case 'onPressed':
widget.onPressed?.call();
break;
case 'onShippingContactSelected':
if (widget.onShippingContactSelected == null) {
return;
}
assert(() {
Stripe.instance.debugUpdatePlatformSheetCalled = false;
return true;
}());

final args = _convertShippingContact(call.arguments['shippingContact']);
await widget.onShippingContactSelected?.call(args);
assert(
Stripe.instance.debugUpdatePlatformSheetCalled,
'You need to call Stripe.instance.updatePlatformSheet after onShippingContactSelected is called',
);
break;
case 'onShippingMethodSelected':
if (widget.onShippingMethodSelected == null) {
return;
}
assert(() {
Stripe.instance.debugUpdatePlatformSheetCalled = false;
return true;
}());
final args =
Map<String, dynamic>.from(call.arguments['shippingMethod']);

final newShippingMethod = ApplePayShippingMethod.fromJson(args);
await widget.onShippingMethodSelected!.call(newShippingMethod);
assert(
Stripe.instance.debugUpdatePlatformSheetCalled,
'You need to call Stripe.instance.updatePlatformSheet after onShippingMethodSelected is called',
);
break;
case 'onCouponCodeEntered':
await widget.onCouponCodeEntered?.call(call.arguments['couponCode']);
break;
case 'onOrderTracking':
if (widget.onOrderTracking == null) {
return;
}
assert(() {
Stripe.instance.debugConfigurePlatformOrderTrackingCalled = false;
return true;
}());

await widget.onOrderTracking?.call();
assert(
Stripe.instance.debugConfigurePlatformOrderTrackingCalled,
'You need to call Stripe.instance.configurePlatformOrderTracking after onOrderTracking is called',
);
break;
}
}

@override
void didUpdateWidget(covariant _UiKitApplePayButton oldWidget) {
if (widget.style != oldWidget.style || widget.type != oldWidget.type) {
methodChannel?.invokeMethod('updateStyle', {
'type': widget.type.id,
'style': widget.style.id,
});
_updateHandlers();
}

super.didUpdateWidget(oldWidget);
}

/// The platform layer needs to know if the callbacks are implemented or not
void _updateHandlers() {
methodChannel?.invokeMethod('updateHandlers', {
'onShippingContactSelected': widget.onShippingContactSelected != null,
'onShippingMethodSelected': widget.onShippingMethodSelected != null,
'onCouponCodeEntered': widget.onCouponCodeEntered != null,
'onOrderTracking': widget.onOrderTracking != null,
});
}
}

// For some reason json serializable cannot be cast
Expand Down
Loading

0 comments on commit 9fbb7a4

Please sign in to comment.