diff --git a/android/src/main/kotlin/com/adyen/adyen_checkout/CheckoutPlatformApi.kt b/android/src/main/kotlin/com/adyen/adyen_checkout/CheckoutPlatformApi.kt index 60efd063..272b0bd7 100644 --- a/android/src/main/kotlin/com/adyen/adyen_checkout/CheckoutPlatformApi.kt +++ b/android/src/main/kotlin/com/adyen/adyen_checkout/CheckoutPlatformApi.kt @@ -122,7 +122,13 @@ class CheckoutPlatformApi(private val checkoutFlutterApi: CheckoutFlutterApi?) : DropInPaymentMethodDeletionResultMessenger.sendResult(deleteStoredPaymentMethodResultDTO) } - private suspend fun createCheckoutSession( + override fun cleanUpDropIn() { + DropInServiceResultMessenger.instance().removeObservers(activity) + DropInPaymentMethodDeletionPlatformMessenger.instance().removeObservers(activity) + DropInAdditionalDetailsPlatformMessenger.instance().removeObservers(activity) + } + + private suspend fun createCheckoutSession( sessionModel: com.adyen.checkout.sessions.core.SessionModel, dropInConfiguration: com.adyen.checkout.dropin.DropInConfiguration, ): CheckoutSession { diff --git a/android/src/main/kotlin/com/adyen/adyen_checkout/PlatformApi.kt b/android/src/main/kotlin/com/adyen/adyen_checkout/PlatformApi.kt index fee8562d..23583a63 100644 --- a/android/src/main/kotlin/com/adyen/adyen_checkout/PlatformApi.kt +++ b/android/src/main/kotlin/com/adyen/adyen_checkout/PlatformApi.kt @@ -743,6 +743,7 @@ interface CheckoutPlatformInterface { fun onPaymentsResult(paymentsResult: DropInResultDTO) fun onPaymentsDetailsResult(paymentsDetailsResult: DropInResultDTO) fun onDeleteStoredPaymentMethodResult(deleteStoredPaymentMethodResultDTO: DeletedStoredPaymentMethodResultDTO) + fun cleanUpDropIn() companion object { /** The codec used by CheckoutPlatformInterface. */ @@ -885,6 +886,23 @@ interface CheckoutPlatformInterface { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.adyen_checkout.CheckoutPlatformInterface.cleanUpDropIn", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + var wrapped: List + try { + api.cleanUpDropIn() + wrapped = listOf(null) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } } } } diff --git a/android/src/main/kotlin/com/adyen/adyen_checkout/dropInAdvancedFlow/AdvancedFlowDropInService.kt b/android/src/main/kotlin/com/adyen/adyen_checkout/dropInAdvancedFlow/AdvancedFlowDropInService.kt index 9bc072a1..1ff5bcec 100644 --- a/android/src/main/kotlin/com/adyen/adyen_checkout/dropInAdvancedFlow/AdvancedFlowDropInService.kt +++ b/android/src/main/kotlin/com/adyen/adyen_checkout/dropInAdvancedFlow/AdvancedFlowDropInService.kt @@ -116,7 +116,8 @@ class AdvancedFlowDropInService : DropInService(), LifecycleOwner { return if (deleteStoredPaymentMethodResultDTO?.isSuccessfullyRemoved == true) { RecurringDropInServiceResult.PaymentMethodRemoved(deleteStoredPaymentMethodResultDTO.storedPaymentMethodId) } else { - RecurringDropInServiceResult.Error(errorDialog = ErrorDialog()) + //TODO - the error message should be provided by the native SDK + RecurringDropInServiceResult.Error(errorDialog = ErrorDialog(message = "Removal of the stored payment method failed. Please try again later.")) } } diff --git a/android/src/main/kotlin/com/adyen/adyen_checkout/dropInSession/SessionDropInService.kt b/android/src/main/kotlin/com/adyen/adyen_checkout/dropInSession/SessionDropInService.kt index 3fc8420c..95a1f1a4 100644 --- a/android/src/main/kotlin/com/adyen/adyen_checkout/dropInSession/SessionDropInService.kt +++ b/android/src/main/kotlin/com/adyen/adyen_checkout/dropInSession/SessionDropInService.kt @@ -48,7 +48,8 @@ class SessionDropInService : SessionDropInService(), LifecycleOwner { return if (deleteStoredPaymentMethodResultDTO?.isSuccessfullyRemoved == true) { RecurringDropInServiceResult.PaymentMethodRemoved(deleteStoredPaymentMethodResultDTO.storedPaymentMethodId) } else { - RecurringDropInServiceResult.Error(errorDialog = ErrorDialog()) + //TODO - the error message should be provided by the native SDK + RecurringDropInServiceResult.Error(errorDialog = ErrorDialog(message = "Removal of the stored payment method failed. Please try again later.")) } } diff --git a/ios/Classes/CheckoutPlatformApi.swift b/ios/Classes/CheckoutPlatformApi.swift index 013ab2e2..0065c2b2 100644 --- a/ios/Classes/CheckoutPlatformApi.swift +++ b/ios/Classes/CheckoutPlatformApi.swift @@ -43,7 +43,8 @@ class CheckoutPlatformApi : CheckoutPlatformInterface { let sessionConfiguration = AdyenSession.Configuration(sessionIdentifier: session.id, initialSessionData: session.sessionData, context: adyenContext) - self.dropInSessionStoredPaymentMethodsDelegate = DropInSessionsStoredPaymentMethodsDelegate(checkoutFlutterApi: self.checkoutFlutterApi) + dropInSessionStoredPaymentMethodsDelegate = DropInSessionsStoredPaymentMethodsDelegate(viewController: viewController, + checkoutFlutterApi: checkoutFlutterApi) AdyenSession.initialize(with: sessionConfiguration, delegate: dropInSessionDelegate!, @@ -89,9 +90,11 @@ class CheckoutPlatformApi : CheckoutPlatformInterface { context: adyenContext, configuration: configuration) dropInAdvancedFlowDelegate = DropInAdvancedFlowDelegate(checkoutFlutterApi: checkoutFlutterApi, component: dropInComponent) - dropInAdvancedFlowStoredPaymentMethodsDelegate = DropInAdvancedFlowStoredPaymentMethodsDelegate(checkoutFlutterApi: checkoutFlutterApi) dropInComponent.delegate = dropInAdvancedFlowDelegate + if (dropInConfigurationDTO.isRemoveStoredPaymentMethodEnabled == true) { + dropInAdvancedFlowStoredPaymentMethodsDelegate = DropInAdvancedFlowStoredPaymentMethodsDelegate(viewController:viewController, + checkoutFlutterApi: checkoutFlutterApi) dropInComponent.storedPaymentMethodsDelegate = dropInAdvancedFlowStoredPaymentMethodsDelegate } self.dropInComponent = dropInComponent @@ -119,6 +122,14 @@ class CheckoutPlatformApi : CheckoutPlatformInterface { dropInAdvancedFlowStoredPaymentMethodsDelegate?.handleDisableResult(isSuccessfullyRemoved: deleteStoredPaymentMethodResultDTO.isSuccessfullyRemoved) } + func cleanUpDropIn() { + dropInSessionDelegate = nil + dropInSessionPresentationDelegate = nil + dropInSessionStoredPaymentMethodsDelegate = nil + dropInAdvancedFlowDelegate = nil + dropInAdvancedFlowStoredPaymentMethodsDelegate = nil + } + private func getViewController() -> UIViewController? { var rootViewController = UIApplication.shared.adyen.mainKeyWindow?.rootViewController while let presentedViewController = rootViewController?.presentedViewController { @@ -173,15 +184,15 @@ class CheckoutPlatformApi : CheckoutPlatformInterface { } } catch let error { let paymentResult = PaymentResultDTO(type: PaymentResultEnum.error, reason: error.localizedDescription) - self.checkoutFlutterApi.onDropInAdvancedFlowPlatformCommunication(platformCommunicationModel: PlatformCommunicationModel(type: PlatformCommunicationType.result, paymentResult: paymentResult), completion: {}) - self.finalize(false, "\(error.localizedDescription)") + checkoutFlutterApi.onDropInAdvancedFlowPlatformCommunication(platformCommunicationModel: PlatformCommunicationModel(type: PlatformCommunicationType.result, paymentResult: paymentResult), completion: {}) + finalize(false, "\(error.localizedDescription)") } } private func onDropInResultFinished(dropInResult: DropInResultDTO) { let resultCode = ResultCode(rawValue: dropInResult.result ?? "") let success = resultCode == .authorised || resultCode == .received || resultCode == .pending - self.dropInComponent?.finalizeIfNeeded(with: success) { [weak self] in + dropInComponent?.finalizeIfNeeded(with: success) { [weak self] in self?.dropInComponent?.viewController.presentingViewController?.dismiss(animated: false, completion: { let paymentResult = PaymentResultDTO(type: PaymentResultEnum.finished, result: PaymentResultModelDTO(resultCode: resultCode?.rawValue)) self?.checkoutFlutterApi.onDropInAdvancedFlowPlatformCommunication(platformCommunicationModel: PlatformCommunicationModel(type: PlatformCommunicationType.result, paymentResult: paymentResult), completion: {}) @@ -192,13 +203,13 @@ class CheckoutPlatformApi : CheckoutPlatformInterface { private func onDropInResultAction(dropInResult: DropInResultDTO) throws { let jsonData = try JSONSerialization.data(withJSONObject: dropInResult.actionResponse as Any, options: []) let result = try JSONDecoder().decode(Action.self, from: jsonData) - self.dropInComponent?.handle(result) + dropInComponent?.handle(result) } private func onDropInResultError(dropInResult: DropInResultDTO) { let paymentResult = PaymentResultDTO(type: PaymentResultEnum.error, reason: dropInResult.error?.errorMessage) - self.checkoutFlutterApi.onDropInAdvancedFlowPlatformCommunication(platformCommunicationModel: PlatformCommunicationModel(type: PlatformCommunicationType.result, paymentResult: paymentResult), completion: {}) - self.finalize(false, dropInResult.error?.errorMessage ?? "") + checkoutFlutterApi.onDropInAdvancedFlowPlatformCommunication(platformCommunicationModel: PlatformCommunicationModel(type: PlatformCommunicationType.result, paymentResult: paymentResult), completion: {}) + finalize(false, dropInResult.error?.errorMessage ?? "") } private func finalize(_ success: Bool, _ message: String) { @@ -214,7 +225,7 @@ class CheckoutPlatformApi : CheckoutPlatformInterface { return PaymentMethods(regular: paymentMethods, stored: storedPaymentMethods) } - func sendSessionError(error: Error) { + private func sendSessionError(error: Error) { let platformCommunicationModel = PlatformCommunicationModel(type: PlatformCommunicationType.result, paymentResult: PaymentResultDTO(type: PaymentResultEnum.error, reason: error.localizedDescription)) checkoutFlutterApi.onDropInSessionPlatformCommunication(platformCommunicationModel: platformCommunicationModel, completion: {}) } diff --git a/ios/Classes/PlatformApi.swift b/ios/Classes/PlatformApi.swift index 5eaa3779..3075e01a 100644 --- a/ios/Classes/PlatformApi.swift +++ b/ios/Classes/PlatformApi.swift @@ -700,6 +700,7 @@ protocol CheckoutPlatformInterface { func onPaymentsResult(paymentsResult: DropInResultDTO) throws func onPaymentsDetailsResult(paymentsDetailsResult: DropInResultDTO) throws func onDeleteStoredPaymentMethodResult(deleteStoredPaymentMethodResultDTO: DeletedStoredPaymentMethodResultDTO) throws + func cleanUpDropIn() throws } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -815,6 +816,19 @@ class CheckoutPlatformInterfaceSetup { } else { onDeleteStoredPaymentMethodResultChannel.setMessageHandler(nil) } + let cleanUpDropInChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.adyen_checkout.CheckoutPlatformInterface.cleanUpDropIn", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + cleanUpDropInChannel.setMessageHandler { _, reply in + do { + try api.cleanUpDropIn() + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + cleanUpDropInChannel.setMessageHandler(nil) + } } } private class CheckoutFlutterApiCodecReader: FlutterStandardReader { diff --git a/ios/Classes/dropInAdvancedFlow/DropInAdvancedFlowStoredPaymentMethodsDelegate.swift b/ios/Classes/dropInAdvancedFlow/DropInAdvancedFlowStoredPaymentMethodsDelegate.swift index 9fecd2ba..f87557fd 100644 --- a/ios/Classes/dropInAdvancedFlow/DropInAdvancedFlowStoredPaymentMethodsDelegate.swift +++ b/ios/Classes/dropInAdvancedFlow/DropInAdvancedFlowStoredPaymentMethodsDelegate.swift @@ -1,11 +1,14 @@ +@_spi(AdyenInternal) import Adyen class DropInAdvancedFlowStoredPaymentMethodsDelegate : StoredPaymentMethodsDelegate { private let checkoutFlutterApi: CheckoutFlutterApi + private let viewController : UIViewController private var completionHandler: ((Bool) -> Void)? - init(checkoutFlutterApi: CheckoutFlutterApi) { + init(viewController: UIViewController, checkoutFlutterApi: CheckoutFlutterApi) { self.checkoutFlutterApi = checkoutFlutterApi + self.viewController = viewController } internal func disable(storedPaymentMethod: StoredPaymentMethod, completion: @escaping (Bool) -> Void) { @@ -15,6 +18,11 @@ class DropInAdvancedFlowStoredPaymentMethodsDelegate : StoredPaymentMethodsDeleg } func handleDisableResult(isSuccessfullyRemoved: Bool) { + if (isSuccessfullyRemoved == false) { + let errorAlert = TemporaryAlertHelper.buildPaymentMethodDeletionErrorAlert() + viewController.adyen.topPresenter.present(errorAlert, animated: true) + } + completionHandler?(isSuccessfullyRemoved) } } diff --git a/ios/Classes/dropInSessions/DropInSessionsStoredPaymentMethodsDelegate.swift b/ios/Classes/dropInSessions/DropInSessionsStoredPaymentMethodsDelegate.swift index d6825689..a388e8c8 100644 --- a/ios/Classes/dropInSessions/DropInSessionsStoredPaymentMethodsDelegate.swift +++ b/ios/Classes/dropInSessions/DropInSessionsStoredPaymentMethodsDelegate.swift @@ -1,11 +1,14 @@ +@_spi(AdyenInternal) import Adyen class DropInSessionsStoredPaymentMethodsDelegate : StoredPaymentMethodsDelegate { private let checkoutFlutterApi: CheckoutFlutterApi + private let viewController : UIViewController private var completionHandler: ((Bool) -> Void)? - init(checkoutFlutterApi: CheckoutFlutterApi) { + init(viewController: UIViewController, checkoutFlutterApi: CheckoutFlutterApi) { self.checkoutFlutterApi = checkoutFlutterApi + self.viewController = viewController } internal func disable(storedPaymentMethod: StoredPaymentMethod, completion: @escaping (Bool) -> Void) { @@ -15,6 +18,11 @@ class DropInSessionsStoredPaymentMethodsDelegate : StoredPaymentMethodsDelegate } func handleDisableResult(isSuccessfullyRemoved: Bool) { + if (isSuccessfullyRemoved == false) { + let errorAlert = TemporaryAlertHelper.buildPaymentMethodDeletionErrorAlert() + viewController.adyen.topPresenter.present(errorAlert, animated: true) + } + completionHandler?(isSuccessfullyRemoved) } } diff --git a/ios/Classes/utils/TemporaryAlertHelper.swift b/ios/Classes/utils/TemporaryAlertHelper.swift new file mode 100644 index 00000000..37bfe590 --- /dev/null +++ b/ios/Classes/utils/TemporaryAlertHelper.swift @@ -0,0 +1,9 @@ +class TemporaryAlertHelper { + + static func buildPaymentMethodDeletionErrorAlert() -> UIAlertController { + //TODO - this should be part of the native SDK and be translated there + let alertController = UIAlertController(title: "Error", message: "Removal of the stored payment method failed. Please try again later.", preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "OK", style: .cancel)) + return alertController; + } +} diff --git a/lib/src/adyen_checkout.dart b/lib/src/adyen_checkout.dart index 448d967a..5c23ab66 100644 --- a/lib/src/adyen_checkout.dart +++ b/lib/src/adyen_checkout.dart @@ -87,6 +87,7 @@ class AdyenCheckout implements AdyenCheckoutInterface { }); return dropInSessionCompleter.future.then((value) { + AdyenCheckoutPlatformInterface.instance.cleanUpDropIn(); _resultApi.dropInSessionPlatformCommunicationStream.close(); return value.fromDTO(); }); @@ -153,6 +154,7 @@ class AdyenCheckout implements AdyenCheckoutInterface { }); return dropInAdvancedFlowCompleter.future.then((value) { + AdyenCheckoutPlatformInterface.instance.cleanUpDropIn(); _resultApi.dropInAdvancedFlowPlatformCommunicationStream.close(); return value.fromDTO(); }); diff --git a/lib/src/generated/platform_api.g.dart b/lib/src/generated/platform_api.g.dart index a48907ec..cc80ee8b 100644 --- a/lib/src/generated/platform_api.g.dart +++ b/lib/src/generated/platform_api.g.dart @@ -903,6 +903,28 @@ class CheckoutPlatformInterface { return; } } + + Future cleanUpDropIn() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.adyen_checkout.CheckoutPlatformInterface.cleanUpDropIn', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send(null) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } } class _CheckoutFlutterApiCodec extends StandardMessageCodec { diff --git a/lib/src/platform/adyen_checkout_api.dart b/lib/src/platform/adyen_checkout_api.dart index 057a6e09..fdc082c7 100644 --- a/lib/src/platform/adyen_checkout_api.dart +++ b/lib/src/platform/adyen_checkout_api.dart @@ -44,4 +44,7 @@ class AdyenCheckoutApi implements AdyenCheckoutPlatformInterface { deleteStoredPaymentMethodResultDTO) => checkoutApi.onDeleteStoredPaymentMethodResult( deleteStoredPaymentMethodResultDTO); + + @override + void cleanUpDropIn() => checkoutApi.cleanUpDropIn(); } diff --git a/lib/src/platform/adyen_checkout_platform_interface.dart b/lib/src/platform/adyen_checkout_platform_interface.dart index 55059c56..6a25eb7a 100644 --- a/lib/src/platform/adyen_checkout_platform_interface.dart +++ b/lib/src/platform/adyen_checkout_platform_interface.dart @@ -35,4 +35,6 @@ abstract class AdyenCheckoutPlatformInterface extends PlatformInterface { void onDeleteStoredPaymentMethodResult( DeletedStoredPaymentMethodResultDTO deleteStoredPaymentMethodResultDTO); + + void cleanUpDropIn(); } diff --git a/pigeons/platform_api.dart b/pigeons/platform_api.dart index 8f88695f..9fccd788 100644 --- a/pigeons/platform_api.dart +++ b/pigeons/platform_api.dart @@ -315,6 +315,8 @@ abstract class CheckoutPlatformInterface { void onDeleteStoredPaymentMethodResult( DeletedStoredPaymentMethodResultDTO deleteStoredPaymentMethodResultDTO); + + void cleanUpDropIn(); } @FlutterApi() diff --git a/test/adyen_checkout_test.dart b/test/adyen_checkout_test.dart index d0b3f301..00a0afe0 100644 --- a/test/adyen_checkout_test.dart +++ b/test/adyen_checkout_test.dart @@ -42,6 +42,9 @@ class MockAdyenCheckoutPlatform @override void onDeleteStoredPaymentMethodResult( DeletedStoredPaymentMethodResultDTO deleteStoredPaymentMethodResultDTO) {} + + @override + void cleanUpDropIn() {} } void main() {