Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an option to troubleshoot in the error screen
Browse files Browse the repository at this point in the history
robertdalmeida committed Oct 29, 2024
1 parent 8e655b6 commit 917acac
Showing 10 changed files with 256 additions and 38 deletions.
30 changes: 22 additions & 8 deletions Adyen/Assets/Generated/LocalizationKey.swift
Original file line number Diff line number Diff line change
@@ -398,13 +398,13 @@ public struct LocalizationKey {
public static let threeds2DATouchID = LocalizationKey(key: "adyen.threeds2.DA.touchID")
/// OpticID
public static let threeds2DAOpticID = LocalizationKey(key: "adyen.threeds2.DA.opticID")
/// You can check out faster next time with this card
/// Check out faster next time with this card
public static let threeds2DARegistrationDescription = LocalizationKey(key: "adyen.threeds2.DA.registration.description")
/// Enjoy a faster checkout process.
/// Skip manual entry & speed up checkout
public static let threeds2DARegistrationFirstInfo = LocalizationKey(key: "adyen.threeds2.DA.registration.firstInfo")
/// Use %@ and Passcode to approve payments.
/// Pay with Face ID or passcode
public static let threeds2DARegistrationSecondInfo = LocalizationKey(key: "adyen.threeds2.DA.registration.secondInfo")
/// Remove your credentials at any time.
/// Edit or remove your details at any time.
public static let threeds2DARegistrationThirdInfo = LocalizationKey(key: "adyen.threeds2.DA.registration.thirdInfo")
/// Use secure checkout
public static let threeds2DARegistrationPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.registration.positiveButton")
@@ -420,24 +420,38 @@ public struct LocalizationKey {
public static let threeds2DAApprovalNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.approval.negativeButton")
/// Other options
public static let threeds2DAApprovalActionSheetTitle = LocalizationKey(key: "adyen.threeds2.DA.approval.actionSheet.title")
/// Approve differently
/// Approve another way
public static let threeds2DAApprovalActionSheetFallback = LocalizationKey(key: "adyen.threeds2.DA.approval.actionSheet.fallback")
/// Remove my credentials
public static let threeds2DAApprovalActionSheetRemove = LocalizationKey(key: "adyen.threeds2.DA.approval.actionSheet.remove")
/// Remove credentials
public static let threeds2DAApprovalRemoveAlertTitle = LocalizationKey(key: "adyen.threeds2.DA.approval.remove.alert.title")
/// Are you sure that you want to remove your Secure Checkout credentials?
/// Are you sure you want to remove your Secure Checkout credentials?
public static let threeds2DAApprovalRemoveAlertDescription = LocalizationKey(key: "adyen.threeds2.DA.approval.remove.alert.description")
/// Remove
public static let threeds2DAApprovalRemoveAlertPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.approval.remove.alert.positiveButton")
/// Cancel
public static let threeds2DAApprovalRemoveAlertNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.approval.remove.alert.negativeButton")
/// Authenticating…
public static let threeds2DAApprovalErrorTitle = LocalizationKey(key: "adyen.threeds2.DA.approval.error.title")
/// Authentication with Secure Checkout has failed, please attempt an alternative authentication.
/// Couldn’t approve payment with Secure Checkout
public static let threeds2DAApprovalErrorMessage = LocalizationKey(key: "adyen.threeds2.DA.approval.error.message")
/// Approve the transaction
/// Approve another way
public static let threeds2DAApprovalErrorButtonTitle = LocalizationKey(key: "adyen.threeds2.DA.approval.error.buttonTitle")
/// Troubleshooting
public static let threeds2DAErrorTroubleshootingTitle = LocalizationKey(key: "adyen.threeds2.DA.error.troubleshootingTitle")
/// Ongoing payment issues may be resolved by resetting your Secure Checkout details.
public static let threeds2DAErrorTroubleshootingDescription = LocalizationKey(key: "adyen.threeds2.DA.error.troubleshootingDescription")
/// Reset Secure Checkout
public static let threeds2DAErrorTroubleshootingButtonTitle = LocalizationKey(key: "adyen.threeds2.DA.error.troubleshootingButtonTitle")
/// Reset Secure Checkout?
public static let threeds2DAErrorResetAlertTitle = LocalizationKey(key: "adyen.threeds2.DA.error.reset.alert.title")
/// You will be redirected to complete this payment in a different way
public static let threeds2DAErrorResetAlertDescription = LocalizationKey(key: "adyen.threeds2.DA.error.reset.alert.description")
/// Reset
public static let threeds2DAErrorResetAlertPositiveButton = LocalizationKey(key: "adyen.threeds2.DA.error.reset.alert.positiveButton")
/// Cancel
public static let threeds2DAErrorResetAlertNegativeButton = LocalizationKey(key: "adyen.threeds2.DA.error.reset.alert.negativeButton")
/// Let’s try next time!
public static let threeds2DARegistrationErrorTitle = LocalizationKey(key: "adyen.threeds2.DA.registration.error.title")
/// Your payment has still been authenticated successfully but the Secure Checkout service was unavailable.
29 changes: 18 additions & 11 deletions Adyen/Assets/en-US.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -190,29 +190,36 @@
"adyen.twint.noAppsInstalled.message" = "No or an outdated version of TWINT is installed on this device. Please update or install the TWINT app.";
"adyen.threeds2.DA.registration.title" = "Secure checkout";
"adyen.threeds2.DA.biometrics" = "biometric";
"adyen.threeds2.DA.faceID" = "FaceID";
"adyen.threeds2.DA.touchID" = "TouchID";
"adyen.threeds2.DA.opticID" = "OpticID";
"adyen.threeds2.DA.registration.description" = "You can check out faster next time with this card";
"adyen.threeds2.DA.registration.firstInfo" = "Enjoy a faster checkout process.";
"adyen.threeds2.DA.registration.secondInfo" = "Use %@ and Passcode to approve payments.";
"adyen.threeds2.DA.registration.thirdInfo" = "Remove your credentials at any time.";
"adyen.threeds2.DA.faceID" = "Face ID";
"adyen.threeds2.DA.touchID" = "Touch ID";
"adyen.threeds2.DA.opticID" = "Optic ID";
"adyen.threeds2.DA.registration.description" = "Check out faster next time with this card";
"adyen.threeds2.DA.registration.firstInfo" = "Skip manual entry & speed up checkout";
"adyen.threeds2.DA.registration.secondInfo" = "Pay with %@ or passcode";
"adyen.threeds2.DA.registration.thirdInfo" = "Edit or remove your details at any time";
"adyen.threeds2.DA.registration.positiveButton" = "Use secure checkout";
"adyen.threeds2.DA.registration.negativeButton" = "Not now";
"adyen.threeds2.DA.approval.title" = "Approve transaction";
"adyen.threeds2.DA.approval.description" = "Approve this transaction to complete your purchase.";
"adyen.threeds2.DA.approval.positiveButton" = "Approve transaction";
"adyen.threeds2.DA.approval.negativeButton" = "Other options";
"adyen.threeds2.DA.approval.actionSheet.title" = "Other options";
"adyen.threeds2.DA.approval.actionSheet.fallback" = "Approve differently";
"adyen.threeds2.DA.approval.actionSheet.fallback" = "Approve another way";
"adyen.threeds2.DA.approval.actionSheet.remove" = "Remove my credentials";
"adyen.threeds2.DA.approval.remove.alert.title" = "Remove credentials";
"adyen.threeds2.DA.approval.remove.alert.description" = "Are you sure that you want to remove your Secure Checkout credentials?";
"adyen.threeds2.DA.approval.remove.alert.description" = "Are you sure you want to remove your Secure Checkout credentials?";
"adyen.threeds2.DA.approval.remove.alert.positiveButton" = "Remove";
"adyen.threeds2.DA.approval.remove.alert.negativeButton" = "Cancel";
"adyen.threeds2.DA.error.troubleshootingTitle" = "Troubleshooting";
"adyen.threeds2.DA.error.troubleshootingDescription" = "Ongoing payment issues may be resolved by resetting your Secure Checkout details.";
"adyen.threeds2.DA.error.troubleshootingButtonTitle" = "Reset Secure Checkout";
"adyen.threeds2.DA.error.reset.alert.title" = "Reset Secure Checkout";
"adyen.threeds2.DA.error.reset.alert.description" = "You will be redirected to complete this payment in a different way.";
"adyen.threeds2.DA.error.reset.alert.positiveButton" = "Reset";
"adyen.threeds2.DA.error.reset.alert.negativeButton" = "Cancel";
"adyen.threeds2.DA.approval.error.title" = "Authenticating…";
"adyen.threeds2.DA.approval.error.message" = "Authentication with Secure Checkout has failed, please attempt an alternative authentication.";
"adyen.threeds2.DA.approval.error.buttonTitle" = "Approve the transaction";
"adyen.threeds2.DA.approval.error.message" = "Couldn’t approve payment with Secure Checkout";
"adyen.threeds2.DA.approval.error.buttonTitle" = "Approve another way";
"adyen.threeds2.DA.registration.error.title" = "Let’s try next time!";
"adyen.threeds2.DA.registration.error.message" = "Your payment has still been authenticated successfully but the Secure Checkout service was unavailable.";
"adyen.threeds2.DA.registration.error.buttonTitle" = "Finish";
Original file line number Diff line number Diff line change
@@ -266,9 +266,17 @@ internal typealias VoidHandler = () -> Void
},
failedAuthenticationHandler: { [weak self] error in
guard let self else { return }
self.presenter.showAuthenticationError(component: self) {
completion(.failure(.authenticationServiceFailed(underlyingError: error)))
}

self.presenter.showAuthenticationError(
component: self,
handler: {
completion(.failure(.authenticationServiceFailed(underlyingError: error)))
},
troubleshootingHandler: { [weak self] in
try? self?.service(cardNumber: cardNumber).reset()
completion(.failure(.authenticationServiceFailed(underlyingError: error)))
}
)
}
)
}
Original file line number Diff line number Diff line change
@@ -28,7 +28,12 @@ internal protocol ThreeDS2PlusDAScreenPresenterProtocol {
)
// swiftlint:enable function_parameter_count

func showAuthenticationError(component: Component, handler: @escaping () -> Void)
func showAuthenticationError(
component: Component,
handler: @escaping () -> Void,
troubleshootingHandler: @escaping () -> Void
)

func showRegistrationError(component: Component, handler: @escaping () -> Void)
func showDeletionConfirmation(component: Component, handler: @escaping () -> Void)

@@ -55,11 +60,16 @@ internal final class ThreeDS2PlusDAScreenPresenter: ThreeDS2PlusDAScreenPresente
self.localizedParameters = localizedParameters
}

internal func showAuthenticationError(component: Component, handler: @escaping () -> Void) {
internal func showAuthenticationError(
component: Component,
handler: @escaping () -> Void,
troubleshootingHandler: @escaping () -> Void
) {
let errorController = DAErrorViewController(
style: style,
screen: .authenticationFailed(localizationParameters: localizedParameters),
completion: handler
completion: handler,
troubleShootingHandler: troubleshootingHandler
)
let presentableComponent = PresentableComponentWrapper(
component: component,
@@ -69,11 +79,15 @@ internal final class ThreeDS2PlusDAScreenPresenter: ThreeDS2PlusDAScreenPresente
presentationDelegate?.present(component: presentableComponent)
}

internal func showRegistrationError(component: Component, handler: @escaping () -> Void) {
internal func showRegistrationError(
component: Component,
handler: @escaping () -> Void
) {
let errorController = DAErrorViewController(
style: style,
screen: .registrationFailed(localizationParameters: localizedParameters),
completion: handler
completion: handler,
troubleShootingHandler: nil
)
let presentableComponent = PresentableComponentWrapper(
component: component,
@@ -87,7 +101,8 @@ internal final class ThreeDS2PlusDAScreenPresenter: ThreeDS2PlusDAScreenPresente
let errorController = DAErrorViewController(
style: style,
screen: .deletionConfirmation(localizationParameters: localizedParameters),
completion: handler
completion: handler,
troubleShootingHandler: nil
)
let presentableComponent = PresentableComponentWrapper(
component: component,
Original file line number Diff line number Diff line change
@@ -100,7 +100,31 @@ public struct DelegatedAuthenticationComponentStyle {
color: UIColor.Adyen.componentSecondaryLabel,
textAlignment: .center
)


/// The title style for the troubleshooting message
public var troubleshootingTitleStyle = TextStyle(
font: .preferredFont(forTextStyle: .subheadline),
color: UIColor.Adyen.componentLabel,
textAlignment: .center
)

/// The description style for the troubleshooting message
public var troubleshootingDescriptionStyle = TextStyle(
font: .preferredFont(forTextStyle: .caption1),
color: UIColor.Adyen.componentSecondaryLabel,
textAlignment: .center
)

/// The button style for the troubleshooting message
public var troubleshootingButtonStyle = ButtonStyle(
title: TextStyle(
font: .preferredFont(forTextStyle: .headline),
color: UIColor.Adyen.defaultBlue
),
cornerRadius: 8,
background: .clear
)

/// The primary button style for the register & approve screens.
public var primaryButton = ButtonStyle(
title: TextStyle(
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ internal final class DAApprovalViewController: UIViewController {
let alertController = UIAlertController(
title: localizedString(.threeds2DAApprovalRemoveAlertTitle, localizationParameters),
message: localizedString(.threeds2DAApprovalRemoveAlertDescription, localizationParameters),
preferredStyle: .alert
preferredStyle: .actionSheet
)
let removeAction = UIAlertAction(
title: localizedString(.threeds2DAApprovalRemoveAlertPositiveButton, localizationParameters),
@@ -35,7 +35,7 @@ internal final class DAApprovalViewController: UIViewController {
)
let cancelAction = UIAlertAction(
title: localizedString(.threeds2DAApprovalRemoveAlertNegativeButton, localizationParameters),
style: .default,
style: .cancel,
handler: nil
)
alertController.addAction(cancelAction)
Original file line number Diff line number Diff line change
@@ -81,24 +81,94 @@ internal final class DAErrorViewController: UIViewController {
return localizedString(.threeds2DADeletionConfirmationButtonTitle, localizationParameters)
}
}

internal var troubleshootingSection: (
content: (title: String, description: String),
button: String
)? {
switch self {
case let .authenticationFailed(localizationParameters):
return (
content: (
title: localizedString(.threeds2DAErrorTroubleshootingTitle, localizationParameters),
description: localizedString(.threeds2DAErrorTroubleshootingDescription, localizationParameters)
),
button: localizedString(.threeds2DAErrorTroubleshootingButtonTitle, localizationParameters)
)
case .registrationFailed:
return nil
case .deletionConfirmation:
return nil // TODO: Robert: maybe it is required here.
}
}

internal var resetCredentialAlert: (
content: (title: String, description: String),
button: (positiveButtonTitle: String, negativeButtonTitle: String)
)? {
switch self {
case let .authenticationFailed(localizationParameters):
return (
content: (
title: localizedString(.threeds2DAErrorResetAlertTitle, localizationParameters),
description: localizedString(.threeds2DAErrorResetAlertDescription, localizationParameters)
),
button: (
positiveButtonTitle: localizedString(.threeds2DAErrorResetAlertPositiveButton, localizationParameters),
negativeButtonTitle: localizedString(.threeds2DAErrorResetAlertNegativeButton, localizationParameters)
)
)
case .registrationFailed, .deletionConfirmation:
return nil
}
}
}

private lazy var errorView: DelegatedAuthenticationErrorView = .init(style: style)
private let style: DelegatedAuthenticationComponentStyle
private let continueHandler: VoidHandler
private let troubleshootingHandler: VoidHandler?
private let screen: Screen

private lazy var resetCredentialAlert: UIAlertController? = {
guard let alert = screen.resetCredentialAlert else {
return nil
}
let alertController = UIAlertController(
title: alert.content.title,
message: alert.content.description,
preferredStyle: .actionSheet
)
let removeAction = UIAlertAction(
title: alert.button.positiveButtonTitle,
style: .destructive,
handler: { [weak self] _ in
self?.troubleshootingHandler?()
}
)
let cancelAction = UIAlertAction(
title: alert.button.negativeButtonTitle,
style: .cancel,
handler: nil
)

alertController.addAction(cancelAction)
alertController.addAction(removeAction)
return alertController
}()

// MARK: - Init

internal init(
style: DelegatedAuthenticationComponentStyle,
screen: Screen,
completion: @escaping () -> Void
completion: @escaping () -> Void,
troubleShootingHandler: (() -> Void)?
) {
self.style = style
self.screen = screen
self.continueHandler = completion

self.troubleshootingHandler = troubleShootingHandler
super.init(nibName: nil, bundle: Bundle(for: DAErrorViewController.self))
errorView.delegate = self
}
@@ -126,6 +196,14 @@ internal final class DAErrorViewController: UIViewController {
errorView.descriptionLabel.text = screen.message
errorView.firstButton.title = screen.buttonTitle
errorView.image.image = screen.image
if let troubleShootingSection = screen.troubleshootingSection {
errorView.troubleshootingStackView.isHidden = false
errorView.troubleshootingTitle.text = troubleShootingSection.content.title
errorView.troubleshootingDescription.text = troubleShootingSection.content.description
errorView.troubleShootingButton.title = troubleShootingSection.button
} else {
errorView.troubleshootingStackView.isHidden = true
}
}

private func buildUI() {
@@ -150,6 +228,13 @@ internal final class DAErrorViewController: UIViewController {

@available(iOS 16.0, *)
extension DAErrorViewController: DelegatedAuthenticationErrorViewDelegate {
internal func troubleshootingButtonTapped() {
guard let resetCredentialAlert else {
return
}
present(resetCredentialAlert, animated: true)
}

internal func firstButtonTapped() {
errorView.firstButton.showsActivityIndicator = true
continueHandler()
Loading

0 comments on commit 917acac

Please sign in to comment.