Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 87 additions & 8 deletions packages/stripe/lib/src/widgets/embedded_payment_element.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -109,6 +111,8 @@ class _EmbeddedPaymentElementState extends State<EmbeddedPaymentElement>
}

MethodChannel? _methodChannel;
Completer<Map<String, dynamic>?>? _pendingUpdate;
Completer<Map<String, dynamic>?>? _pendingConfirm;
double _currentHeight = 0;
bool _showPlatformView = true;

Expand Down Expand Up @@ -138,6 +142,10 @@ class _EmbeddedPaymentElementState extends State<EmbeddedPaymentElement>
if (widget.intentConfiguration.confirmTokenHandler != null) {
Stripe.instance.setConfirmTokenHandler(null);
}
_pendingUpdate?.complete(null);
_pendingUpdate = null;
_pendingConfirm?.complete(null);
_pendingConfirm = null;
super.dispose();
}

Expand All @@ -164,6 +172,8 @@ class _EmbeddedPaymentElementState extends State<EmbeddedPaymentElement>

@override
Future<Map<String, dynamic>?> confirm() async {
final channel = _methodChannel;
if (channel == null) return null;
if (widget.intentConfiguration.confirmHandler != null) {
Stripe.instance.setConfirmHandler(
widget.intentConfiguration.confirmHandler,
Expand All @@ -174,7 +184,14 @@ class _EmbeddedPaymentElementState extends State<EmbeddedPaymentElement>
widget.intentConfiguration.confirmTokenHandler,
);
}
final result = await _methodChannel?.invokeMethod('confirm');
if (defaultTargetPlatform == TargetPlatform.android) {
_completePendingConfirm({'status': 'canceled'});
final completer = Completer<Map<String, dynamic>?>();
_pendingConfirm = completer;
await channel.invokeMethod('confirm');
return completer.future;
}
final result = await channel.invokeMethod('confirm');
if (result is Map) {
return Map<String, dynamic>.from(result);
}
Expand Down Expand Up @@ -238,12 +255,16 @@ class _EmbeddedPaymentElementState extends State<EmbeddedPaymentElement>
final error = _parseLoadingError(call.arguments);
widget.onLoadingFailed?.call(error);
break;
case 'embeddedPaymentElementUpdateComplete':
_completePendingUpdate(call.arguments);
break;
case 'onFormSheetConfirmComplete':
case 'embeddedPaymentElementFormSheetConfirmComplete':
case 'onConfirmComplete':
final arguments = call.arguments as Map?;
if (arguments != null) {
final result = Map<String, dynamic>.from(arguments);
_completePendingConfirm(result);
widget.onFormSheetConfirmComplete?.call(result);
}
break;
Expand All @@ -257,6 +278,39 @@ class _EmbeddedPaymentElementState extends State<EmbeddedPaymentElement>
}
}

void _completePendingUpdate(dynamic payload) {
final completer = _pendingUpdate;
_pendingUpdate = null;
if (completer == null || completer.isCompleted) return;

completer.complete(
payload is Map ? Map<String, dynamic>.from(payload) : null,
);
}

void _completePendingConfirm(dynamic payload) {
final completer = _pendingConfirm;
_pendingConfirm = null;
if (completer == null || completer.isCompleted) return;

completer.complete(
payload is Map ? Map<String, dynamic>.from(payload) : null,
);
}

Map<String, dynamic> _intentConfigurationToJson(
IntentConfiguration intentConfiguration,
) {
final intentConfigurationJson = intentConfiguration.toJson();
if (intentConfiguration.confirmHandler != null) {
intentConfigurationJson['confirmHandler'] = true;
}
if (intentConfiguration.confirmTokenHandler != null) {
intentConfigurationJson['confirmationTokenConfirmHandler'] = true;
}
return intentConfigurationJson;
}

EmbeddedPaymentElementLoadingException _parseLoadingError(dynamic payload) {
if (payload is Map) {
final map = <String, dynamic>{};
Expand Down Expand Up @@ -304,13 +358,9 @@ class _EmbeddedPaymentElementState extends State<EmbeddedPaymentElement>
Widget build(BuildContext context) {
if (!_showPlatformView) return const SizedBox.shrink();

final intentConfiguration = widget.intentConfiguration.toJson();
if (widget.intentConfiguration.confirmHandler != null) {
intentConfiguration['confirmHandler'] = true;
}
if (widget.intentConfiguration.confirmTokenHandler != null) {
intentConfiguration['confirmationTokenConfirmHandler'] = true;
}
final intentConfiguration = _intentConfigurationToJson(
widget.intentConfiguration,
);

final creationParams = <String, dynamic>{
'intentConfiguration': intentConfiguration,
Expand Down Expand Up @@ -347,6 +397,35 @@ class _EmbeddedPaymentElementState extends State<EmbeddedPaymentElement>
),
);
}

@override
Future<Map<String, dynamic>?> update(
IntentConfiguration configuration,
) async {
final channel = _methodChannel;
if (channel == null) {
return null;
}
final map = _intentConfigurationToJson(configuration);
if (defaultTargetPlatform != TargetPlatform.android) {
final result = await channel.invokeMethod('update', {
'intentConfiguration': map,
});
return result is Map ? Map<String, dynamic>.from(result) : null;
}
_completePendingUpdate({'status': 'canceled'});
final completer = Completer<Map<String, dynamic>?>();
_pendingUpdate = completer;
try {
await channel.invokeMethod('update', {'intentConfiguration': map});
} catch (_) {
if (identical(_pendingUpdate, completer)) {
_pendingUpdate = null;
}
rethrow;
}
return completer.future;
}
}

class _AndroidEmbeddedPaymentElement extends StatelessWidget {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_stripe/flutter_stripe.dart';

class EmbeddedPaymentElementController extends ChangeNotifier {
EmbeddedPaymentElementController();
Expand Down Expand Up @@ -29,6 +30,17 @@ class EmbeddedPaymentElementController extends ChangeNotifier {
return await _context?.confirm();
}

/// Updates the embedded element to use the new configuration without recreating the platform view.
Future<Map<String, dynamic>?> update(
IntentConfiguration configuration,
) async {
assert(
hasEmbeddedPaymentElement,
'Controller must be attached to an EmbeddedPaymentElement',
);
return await _context?.update(configuration);
}

Future<void> clearPaymentOption() async {
assert(
hasEmbeddedPaymentElement,
Expand All @@ -54,6 +66,7 @@ class EmbeddedPaymentElementController extends ChangeNotifier {

abstract class EmbeddedPaymentElementContext {
Future<Map<String, dynamic>?> confirm();
Future<Map<String, dynamic>?> update(IntentConfiguration configuration);
Future<void> clearPaymentOption();
Future<void> disposeView();
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.facebook.react.modules.core.DeviceEventManagerModule
import com.reactnativestripesdk.EmbeddedPaymentElementView
import com.reactnativestripesdk.EmbeddedPaymentElementViewManager
import com.reactnativestripesdk.StripeSdkModule
import com.reactnativestripesdk.buildIntentConfiguration
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.platform.PlatformView
Expand Down Expand Up @@ -54,6 +55,27 @@ class StripeSdkEmbeddedPaymentElementPlatformView(
viewManager.clearPaymentOption(embeddedView)
result.success(null)
}
"update" -> {
val intentConfiguration = asReadableMap(call.argument<Map<*, *>>("intentConfiguration"))
if (intentConfiguration == null) {
result.error("Failed", "Invalid configuration", null)
return
}
try {
val intentConfig = buildIntentConfiguration(intentConfiguration)
if (intentConfig == null) {
result.error("Failed", "Invalid configuration", null)
return
}
embeddedView.setUseConfirmationTokenCallback(
intentConfiguration.hasKey("confirmationTokenConfirmHandler")
)
embeddedView.update(intentConfig)
result.success(null)
} catch (e: Exception) {
result.error("Failed", e.localizedMessage ?: "Invalid configuration", null)
}
}
else -> {
result.notImplemented()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ class FlutterEmbeddedPaymentElementContainerView: UIView {

public class EmbeddedPaymentElementViewFactory: NSObject, FlutterPlatformViewFactory {
private var messenger: FlutterBinaryMessenger
private var stripeSdk: StripeSdkImpl

init(messenger: FlutterBinaryMessenger) {
init(messenger: FlutterBinaryMessenger, stripeSdk: StripeSdkImpl) {
self.messenger = messenger
self.stripeSdk = stripeSdk
super.init()
}

Expand All @@ -75,7 +77,8 @@ public class EmbeddedPaymentElementViewFactory: NSObject, FlutterPlatformViewFac
frame: frame,
viewIdentifier: viewId,
arguments: args,
binaryMessenger: messenger
binaryMessenger: messenger,
stripeSdk: stripeSdk
)
}

Expand All @@ -88,20 +91,22 @@ class EmbeddedPaymentElementPlatformView: NSObject, FlutterPlatformView {

private let embeddedView: FlutterEmbeddedPaymentElementContainerView
private let channel: FlutterMethodChannel
private let stripeSdk: StripeSdkImpl
private var delegate: FlutterEmbeddedPaymentElementDelegate?

init(
frame: CGRect,
viewIdentifier viewId: Int64,
arguments args: Any?,
binaryMessenger messenger: FlutterBinaryMessenger
binaryMessenger messenger: FlutterBinaryMessenger,
stripeSdk: StripeSdkImpl
) {
embeddedView = FlutterEmbeddedPaymentElementContainerView(frame: frame)
channel = FlutterMethodChannel(
name: "flutter.stripe/embedded_payment_element/\(viewId)",
binaryMessenger: messenger
)

self.stripeSdk = stripeSdk
super.init()
channel.setMethodCallHandler(handle)
StripePlugin.registerChannel(channel, forPrefix: "embeddedPaymentElement")
Expand Down Expand Up @@ -133,7 +138,7 @@ class EmbeddedPaymentElementPlatformView: NSObject, FlutterPlatformView {
mutableIntentConfig["confirmHandler"] = true
}

StripeSdkImpl.shared.createEmbeddedPaymentElement(
stripeSdk.createEmbeddedPaymentElement(
intentConfig: mutableIntentConfig,
configuration: configuration,
resolve: { [weak self] result in
Expand All @@ -153,7 +158,7 @@ class EmbeddedPaymentElementPlatformView: NSObject, FlutterPlatformView {
return
}

if let embeddedElement = StripeSdkImpl.shared.embeddedInstance {
if let embeddedElement = self.stripeSdk.embeddedInstance {
self.attachEmbeddedView(embeddedElement)
} else {
self.channel.invokeMethod("embeddedPaymentElementLoadingFailed", arguments: ["message": "Failed to create embedded payment element"])
Expand Down Expand Up @@ -213,7 +218,7 @@ class EmbeddedPaymentElementPlatformView: NSObject, FlutterPlatformView {
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "confirm":
guard let embeddedElement = StripeSdkImpl.shared.embeddedInstance else {
guard let embeddedElement = stripeSdk.embeddedInstance else {
result(FlutterError(
code: "Failed",
message: "Embedded payment element not available",
Expand Down Expand Up @@ -244,9 +249,27 @@ class EmbeddedPaymentElementPlatformView: NSObject, FlutterPlatformView {
}
case "clearPaymentOption":
DispatchQueue.main.async {
StripeSdkImpl.shared.embeddedInstance?.clearPaymentOption()
self.stripeSdk.embeddedInstance?.clearPaymentOption()
}
result(nil)
case "update":
guard let args = call.arguments as? [String: Any],
let intentConfiguration = args["intentConfiguration"] as? NSDictionary else {
result(FlutterError(code: "Failed", message: "Invalid configuration", details: nil ))
return
}
let mutableIntentConfig = intentConfiguration.mutableCopy() as! NSMutableDictionary
let hasConfirmHandler = mutableIntentConfig["confirmHandler"] != nil
let hasConfirmationTokenHandler = mutableIntentConfig["confirmationTokenConfirmHandler"] != nil
if !hasConfirmHandler && !hasConfirmationTokenHandler {
mutableIntentConfig["confirmHandler"] = true
}
stripeSdk.updateEmbeddedPaymentElement(intentConfig: mutableIntentConfig) { payload in
result(payload)
} reject: { code, message, error in
result(FlutterError(code: code, message: message, details: nil))
}

default:
result(FlutterMethodNotImplemented)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class StripePlugin: StripeSdkImpl, FlutterPlugin, ViewManagerDelegate {
registrar.register(addressSheetFactory, withId: "flutter.stripe/address_sheet")

// Embedded Payment Element
let embeddedPaymentElementFactory = EmbeddedPaymentElementViewFactory(messenger: registrar.messenger())
let embeddedPaymentElementFactory = EmbeddedPaymentElementViewFactory(messenger: registrar.messenger(), stripeSdk: instance)
registrar.register(embeddedPaymentElementFactory, withId: "flutter.stripe/embedded_payment_element")

}
Expand Down Expand Up @@ -890,7 +890,7 @@ extension StripePlugin {
return
}

StripeSdkImpl.shared.intentCreationCallback(
intentCreationCallback(
result: params,
resolver: resolver(for: result),
rejecter: rejecter(for: result)
Expand Down
Loading