Skip to content

Commit

Permalink
Send receipts after failed payments via the API (#14485)
Browse files Browse the repository at this point in the history
  • Loading branch information
staskus authored Nov 22, 2024
2 parents 60b8cab + 7dd66ad commit 13afd68
Show file tree
Hide file tree
Showing 18 changed files with 483 additions and 215 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ final class CardPresentModalBuiltInSuccess: CardPresentPaymentsModalViewModel {
}

func didTapSecondaryButton(in viewController: UIViewController?) {
viewController?.dismiss(animated: true, completion: { [weak self] in
self?.emailReceiptAction()
})
emailReceiptAction()
}

func didTapAuxiliaryButton(in viewController: UIViewController?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import UIKit
/// Modal presented on error
final class CardPresentModalError: CardPresentPaymentsModalViewModel {
/// A closure to execute when the primary button is tapped
private let primaryAction: () -> Void
private let tryAgainAction: () -> Void

/// A closure to execute after the secondary button is tapped to dismiss the modal
/// A closure to execute when the secondary button is tapped
private let emailReceiptAction: () -> Void

/// A closure to execute after the auxilary button is tapped to dismiss the modal
private let dismissCompletion: () -> Void

let textMode: PaymentsModalTextMode = .reducedBottomInfo
let actionsMode: PaymentsModalActionsMode = .twoAction
let actionsMode: PaymentsModalActionsMode = .twoActionAndAuxiliary

let topTitle: String

Expand All @@ -19,9 +22,9 @@ final class CardPresentModalError: CardPresentPaymentsModalViewModel {

let primaryButtonTitle: String?

let secondaryButtonTitle: String?
let secondaryButtonTitle: String? = Localization.emailReceipt

let auxiliaryButtonTitle: String? = nil
let auxiliaryButtonTitle: String?

let bottomTitle: String?

Expand All @@ -37,28 +40,32 @@ final class CardPresentModalError: CardPresentPaymentsModalViewModel {
init(errorDescription: String?,
transactionType: CardPresentTransactionType,
image: UIImage = .paymentErrorImage,
primaryAction: @escaping () -> Void,
tryAgainAction: @escaping () -> Void,
emailReceiptAction: @escaping () -> Void,
dismissCompletion: @escaping () -> Void) {
self.topTitle = Localization.paymentFailed(transactionType: transactionType)
self.bottomTitle = errorDescription
self.image = image
self.primaryButtonTitle = Localization.tryAgain(transactionType: transactionType)
self.secondaryButtonTitle = Localization.noThanks(transactionType: transactionType)
self.primaryAction = primaryAction
self.auxiliaryButtonTitle = Localization.noThanks(transactionType: transactionType)
self.tryAgainAction = tryAgainAction
self.emailReceiptAction = emailReceiptAction
self.dismissCompletion = dismissCompletion
}

func didTapPrimaryButton(in viewController: UIViewController?) {
primaryAction()
tryAgainAction()
}

func didTapSecondaryButton(in viewController: UIViewController?) {
emailReceiptAction()
}

func didTapAuxiliaryButton(in viewController: UIViewController?) {
viewController?.dismiss(animated: true) { [weak self] in
self?.dismissCompletion()
}
}

func didTapAuxiliaryButton(in viewController: UIViewController?) { }
}

extension CardPresentModalError {
Expand Down Expand Up @@ -113,5 +120,11 @@ extension CardPresentModalError {
value: "A receipt has been sent to %1$@",
comment: "Message informing the user that a receipt has been sent to their email address. %1$@ is the email address"
)

static let emailReceipt = NSLocalizedString(
"cardPresentPaymentsModal.error.emailReceipt",
value: "Email receipt",
comment: "Button to email receipts. Presented to users after a payment processing has failed"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import UIKit
/// Modal presented on error. Shows email address which the receipt was sent to.
final class CardPresentModalErrorEmailSent: CardPresentPaymentsModalViewModel {
/// A closure to execute when the primary button is tapped
private let primaryAction: () -> Void
private let tryAgainAction: () -> Void

/// A closure to execute after the secondary button is tapped to dismiss the modal
private let dismissCompletion: () -> Void
Expand Down Expand Up @@ -40,14 +40,14 @@ final class CardPresentModalErrorEmailSent: CardPresentPaymentsModalViewModel {
transactionType: CardPresentTransactionType,
image: UIImage = .paymentErrorImage,
email: String,
primaryAction: @escaping () -> Void,
tryAgainAction: @escaping () -> Void,
dismissCompletion: @escaping () -> Void) {
self.topTitle = CardPresentModalError.Localization.paymentFailed(transactionType: transactionType)
self.bottomTitle = errorDescription
self.image = image
self.primaryButtonTitle = CardPresentModalError.Localization.tryAgain(transactionType: transactionType)
self.secondaryButtonTitle = CardPresentModalError.Localization.noThanks(transactionType: transactionType)
self.primaryAction = primaryAction
self.tryAgainAction = tryAgainAction
self.dismissCompletion = dismissCompletion

let formattedMessage = String(format: CardPresentModalError.Localization.receiptMessage, email)
Expand All @@ -60,7 +60,7 @@ final class CardPresentModalErrorEmailSent: CardPresentPaymentsModalViewModel {
}

func didTapPrimaryButton(in viewController: UIViewController?) {
primaryAction()
tryAgainAction()
}

func didTapSecondaryButton(in viewController: UIViewController?) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import UIKit

/// Modal presented on error
final class CardPresentModalErrorWithoutEmail: CardPresentPaymentsModalViewModel {
/// A closure to execute when the primary button is tapped
private let tryAgainAction: () -> Void

/// A closure to execute after the secondary button is tapped to dismiss the modal
private let dismissCompletion: () -> Void

let textMode: PaymentsModalTextMode = .reducedBottomInfo
let actionsMode: PaymentsModalActionsMode = .twoAction

let topTitle: String

var topSubtitle: String? = nil

let image: UIImage

let primaryButtonTitle: String?

let secondaryButtonTitle: String?

let auxiliaryButtonTitle: String? = nil

let bottomTitle: String?

let bottomSubtitle: String? = nil

var accessibilityLabel: String? {
guard let bottomTitle = bottomTitle else {
return topTitle
}
return topTitle + bottomTitle
}

init(errorDescription: String?,
transactionType: CardPresentTransactionType,
image: UIImage = .paymentErrorImage,
tryAgainAction: @escaping () -> Void,
dismissCompletion: @escaping () -> Void) {
self.topTitle = CardPresentModalError.Localization.paymentFailed(transactionType: transactionType)
self.bottomTitle = errorDescription
self.image = image
self.primaryButtonTitle = CardPresentModalError.Localization.tryAgain(transactionType: transactionType)
self.secondaryButtonTitle = CardPresentModalError.Localization.noThanks(transactionType: transactionType)
self.tryAgainAction = tryAgainAction
self.dismissCompletion = dismissCompletion
}

func didTapPrimaryButton(in viewController: UIViewController?) {
tryAgainAction()
}

func didTapSecondaryButton(in viewController: UIViewController?) {
viewController?.dismiss(animated: true) { [weak self] in
self?.dismissCompletion()
}
}

func didTapAuxiliaryButton(in viewController: UIViewController?) { }
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ final class CardPresentModalNonRetryableError: CardPresentPaymentsModalViewModel
/// Called when the view is dismissed
private let onDismiss: () -> Void

/// A closure to execute when the secondary button is tapped
private let emailReceiptAction: () -> Void

let textMode: PaymentsModalTextMode = .reducedBottomInfo
let actionsMode: PaymentsModalActionsMode = .oneAction
let actionsMode: PaymentsModalActionsMode = .twoAction

let topTitle: String = Localization.paymentFailed

Expand All @@ -22,7 +25,7 @@ final class CardPresentModalNonRetryableError: CardPresentPaymentsModalViewModel

let primaryButtonTitle: String? = Localization.dismiss

let secondaryButtonTitle: String? = nil
let secondaryButtonTitle: String? = CardPresentModalError.Localization.emailReceipt

let auxiliaryButtonTitle: String? = nil

Expand All @@ -41,15 +44,23 @@ final class CardPresentModalNonRetryableError: CardPresentPaymentsModalViewModel
init(amount: String,
errorDescription: String?,
image: UIImage = .paymentErrorImage,
onDismiss: @escaping () -> Void) {
onDismiss: @escaping () -> Void,
emailReceiptAction: @escaping () -> Void) {
self.amount = amount
self.bottomTitle = errorDescription
self.image = image
self.onDismiss = onDismiss
self.emailReceiptAction = emailReceiptAction
}

convenience init(amount: String, error: Error, onDismiss: @escaping () -> Void) {
self.init(amount: amount, errorDescription: error.localizedDescription, onDismiss: onDismiss)
convenience init(amount: String,
error: Error,
onDismiss: @escaping () -> Void,
emailReceiptAction: @escaping () -> Void) {
self.init(amount: amount,
errorDescription: error.localizedDescription,
onDismiss: onDismiss,
emailReceiptAction: emailReceiptAction)
}

func didTapPrimaryButton(in viewController: UIViewController?) {
Expand All @@ -61,7 +72,9 @@ final class CardPresentModalNonRetryableError: CardPresentPaymentsModalViewModel
}
}

func didTapSecondaryButton(in viewController: UIViewController?) { }
func didTapSecondaryButton(in viewController: UIViewController?) {
emailReceiptAction()
}

func didTapAuxiliaryButton(in viewController: UIViewController?) { }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import UIKit

/// Modal presented on error. Does not provide a retry action.
final class CardPresentModalNonRetryableErrorWithoutEmail: CardPresentPaymentsModalViewModel {

/// Amount charged
private let amount: String

/// Called when the view is dismissed
private let onDismiss: () -> Void

let textMode: PaymentsModalTextMode = .reducedBottomInfo
let actionsMode: PaymentsModalActionsMode = .oneAction

let topTitle: String = CardPresentModalNonRetryableError.Localization.paymentFailed

var topSubtitle: String? {
amount
}

let image: UIImage

let primaryButtonTitle: String? = CardPresentModalNonRetryableError.Localization.dismiss

let secondaryButtonTitle: String? = nil

let auxiliaryButtonTitle: String? = nil

let bottomTitle: String?

let bottomSubtitle: String? = nil

var accessibilityLabel: String? {
guard let bottomTitle = bottomTitle else {
return topTitle
}

return topTitle + bottomTitle
}

init(amount: String,
errorDescription: String?,
image: UIImage = .paymentErrorImage,
onDismiss: @escaping () -> Void) {
self.amount = amount
self.bottomTitle = errorDescription
self.image = image
self.onDismiss = onDismiss
}

convenience init(amount: String, error: Error, onDismiss: @escaping () -> Void) {
self.init(amount: amount, errorDescription: error.localizedDescription, onDismiss: onDismiss)
}

func didTapPrimaryButton(in viewController: UIViewController?) {
guard let viewController else {
return onDismiss()
}
viewController.dismiss(animated: true) { [weak self] in
self?.onDismiss()
}
}

func didTapSecondaryButton(in viewController: UIViewController?) { }

func didTapAuxiliaryButton(in viewController: UIViewController?) { }
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ final class CardPresentModalSuccess: CardPresentPaymentsModalViewModel {
}

func didTapSecondaryButton(in viewController: UIViewController?) {
viewController?.dismiss(animated: true, completion: { [weak self] in
self?.emailReceiptAction()
})
emailReceiptAction()
}

func didTapAuxiliaryButton(in viewController: UIViewController?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,19 @@ final class BluetoothCardReaderPaymentAlertsProvider: CardReaderTransactionAlert
return CardPresentModalErrorEmailSent(errorDescription: errorDescription,
transactionType: transactionType,
email: email,
primaryAction: tryAgain,
tryAgainAction: tryAgain,
dismissCompletion: dismissCompletion)
default:
case let .promptToSendEmailReceipt(emailReceiptAction):
return CardPresentModalError(errorDescription: errorDescription,
transactionType: transactionType,
primaryAction: tryAgain,
tryAgainAction: tryAgain,
emailReceiptAction: emailReceiptAction,
dismissCompletion: dismissCompletion)
case .noEmailReceipt:
return CardPresentModalErrorWithoutEmail(errorDescription: errorDescription,
transactionType: transactionType,
tryAgainAction: tryAgain,
dismissCompletion: dismissCompletion)

}
}
Expand All @@ -109,15 +115,22 @@ final class BluetoothCardReaderPaymentAlertsProvider: CardReaderTransactionAlert
switch receiptState {
case let .paymentSuccessEmailSent(email):
CardPresentModalNonRetryableErrorEmailSent(amount: amount, error: error, email: email, onDismiss: dismissCompletion)
default:
CardPresentModalNonRetryableError(amount: amount, error: error, onDismiss: dismissCompletion)
case let .promptToSendEmailReceipt(emailReceiptAction):
CardPresentModalNonRetryableError(amount: amount,
error: error,
onDismiss: dismissCompletion,
emailReceiptAction: emailReceiptAction)
case .noEmailReceipt:
CardPresentModalNonRetryableErrorWithoutEmail(amount: amount,
error: error,
onDismiss: dismissCompletion)
}
}

func cancelledOnReader() -> CardPresentPaymentsModalViewModel? {
CardPresentModalNonRetryableError(amount: amount,
error: CardReaderServiceError.paymentMethodCollection(underlyingError: .commandCancelled(from: .reader)),
onDismiss: { })
CardPresentModalNonRetryableErrorWithoutEmail(amount: amount,
error: CardReaderServiceError.paymentMethodCollection(underlyingError: .commandCancelled(from: .reader)),
onDismiss: { })
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct BluetoothReaderConnectionAlertsProvider: BluetoothReaderConnnectionAlerts
}

func connectingFailedNonRetryable(error: Error, close: @escaping () -> Void) -> CardPresentPaymentsModalViewModel {
CardPresentModalNonRetryableError(amount: "", error: error, onDismiss: close)
CardPresentModalNonRetryableErrorWithoutEmail(amount: "", error: error, onDismiss: close)
}

func connectingFailedIncompleteAddress(wcSettingsAdminURL: URL?,
Expand Down
Loading

0 comments on commit 13afd68

Please sign in to comment.