Skip to content

Commit

Permalink
Show both message and alert for payment capture error based on the la…
Browse files Browse the repository at this point in the history
…test design suggestion.
  • Loading branch information
jaclync committed Jul 1, 2024
1 parent 432ea08 commit 7679334
Show file tree
Hide file tree
Showing 14 changed files with 182 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ private extension CardPresentPaymentCollectOrderPaymentUseCaseAdaptor {
case .preparingForPayment(cancelPayment: let cancelPayment),
.tapSwipeOrInsertCard(_, cancelPayment: let cancelPayment),
.paymentError(_, _, cancelPayment: let cancelPayment),
.paymentErrorNonRetryable(_, cancelPayment: let cancelPayment):
.paymentErrorNonRetryable(_, cancelPayment: let cancelPayment),
.paymentCaptureError(cancelPayment: let cancelPayment):
cancelPayment()
case .processing, /// if cancellation fails here, which is likely, we may need a new order. But we can disable going back to make it unlikely.
.displayReaderMessage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ enum CardPresentPaymentEventDetails {
cancelPayment: () -> Void)
case paymentErrorNonRetryable(error: any Error,
cancelPayment: () -> Void)
case paymentCaptureError(cancelPayment: () -> Void)
case processing
case displayReaderMessage(message: String)
case cancelledOnReader
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
import Combine
import enum Yosemite.ServerSidePaymentCaptureError

final class CardPresentPaymentsAlertPresenterAdaptor: CardPresentPaymentAlertsPresenting {
typealias AlertDetails = CardPresentPaymentEventDetails
Expand All @@ -18,6 +19,14 @@ final class CardPresentPaymentsAlertPresenterAdaptor: CardPresentPaymentAlertsPr
case .paymentError(error: CollectOrderPaymentUseCaseError.orderAlreadyPaid, _, _),
.paymentErrorNonRetryable(error: CollectOrderPaymentUseCaseError.orderAlreadyPaid, _):
paymentEventSubject.send(.show(eventDetails: .paymentSuccess(done: {})))
case .paymentError(error: ServerSidePaymentCaptureError.paymentGateway(.otherError), _, let cancelPayment),
.paymentErrorNonRetryable(error: ServerSidePaymentCaptureError.paymentGateway(.otherError), let cancelPayment):
paymentEventSubject.send(.show(
eventDetails: .paymentCaptureError(cancelPayment: { [weak self] in
cancelPayment()
self?.paymentEventSubject.send(.idle)
})
))
case .paymentError(let error, let tryAgain, let cancelPayment):
paymentEventSubject.send(.show(
eventDetails: .paymentError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ enum PointOfSaleCardPresentPaymentAlertType {
case connectingFailedChargeReader(viewModel: PointOfSaleCardPresentPaymentConnectingFailedChargeReaderAlertViewModel)
case connectingFailedUpdateAddress(viewModel: PointOfSaleCardPresentPaymentConnectingFailedUpdateAddressAlertViewModel)
case connectingFailedUpdatePostalCode(viewModel: PointOfSaleCardPresentPaymentConnectingFailedUpdatePostalCodeAlertViewModel)
case paymentCaptureFailed(viewModel: PointOfSaleCardPresentPaymentCaptureFailedAlertViewModel)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Foundation
import SwiftUI

struct PointOfSaleCardPresentPaymentCaptureFailedAlertViewModel {
let title = Localization.title
let image = Image(uiImage: .paymentErrorImage)
let errorDetails = Localization.errorDetails
let cancelButtonTitle = Localization.cancel
}

private extension PointOfSaleCardPresentPaymentCaptureFailedAlertViewModel {
enum Localization {
static let title = NSLocalizedString(
"pointOfSale.cardPresentPayment.alert.paymentCaptureError.title",
value: "Please check order payment status",
comment: "Title of the alert presented when payment capture fails."
)

static let errorDetails = NSLocalizedString(
"pointOfSale.cardPresentPayment.alert.paymentCaptureError.errorDetails",
value: "Due to an error from capturing payment and refreshing order, we couldn't load complete order information. " +
"To avoid undercharging or double charging, please check the latest order separately before proceeding.",
comment: "Subtitle of the alert presented when payment capture fails."
)

static let cancel = NSLocalizedString(
"pointOfSale.cardPresentPayment.alert.paymentCaptureError.cancel.button.title",
value: "I understand that order should be checked",
comment: "Button to dismiss the alert presented when payment capture fails."
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import Foundation
enum PointOfSaleCardPresentPaymentEventPresentationStyle {
case message(PointOfSaleCardPresentPaymentMessageType)
case alert(PointOfSaleCardPresentPaymentAlertType)
/// In-line message with an alert on top for important messages, e.g. payment capture error.
case messageAndAlert(PointOfSaleCardPresentPaymentMessageType, PointOfSaleCardPresentPaymentAlertType)
}

extension CardPresentPaymentEventDetails {
Expand Down Expand Up @@ -136,6 +138,12 @@ extension CardPresentPaymentEventDetails {
error: error,
cancelButtonAction: cancelPayment)))

case .paymentCaptureError(let cancelPayment):
return .messageAndAlert(.paymentCaptureError(
viewModel: PointOfSaleCardPresentPaymentCaptureErrorMessageViewModel(cancelButtonAction: cancelPayment)),
.paymentCaptureFailed(
viewModel: PointOfSaleCardPresentPaymentCaptureFailedAlertViewModel()))

case .processing:
return .message(.processing(viewModel: PointOfSaleCardPresentPaymentProcessingMessageViewModel()))

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Foundation
import enum Yosemite.CardReaderServiceError

struct PointOfSaleCardPresentPaymentCaptureErrorMessageViewModel {
let title = Localization.title
let message = Localization.message
let cancelButtonViewModel: CardPresentPaymentsModalButtonViewModel

init(cancelButtonAction: @escaping () -> Void) {
self.cancelButtonViewModel = CardPresentPaymentsModalButtonViewModel(
title: Localization.cancel,
actionHandler: cancelButtonAction)
}
}

private extension PointOfSaleCardPresentPaymentCaptureErrorMessageViewModel {
enum Localization {
static let title = NSLocalizedString(
"pointOfSale.cardPresent.paymentCaptureError.title",
value: "Payment status unknown",
comment: "Error message. Presented to users after collecting a payment fails from payment capture error on the Point of Sale Checkout"
)

static let message = NSLocalizedString(
"pointOfSale.cardPresent.paymentCaptureError.message",
value: "Due to an error from capturing payment and refreshing order, we couldn't load complete order information. " +
"Please check the latest order separately.",
comment: "Error message. Presented to users after collecting a payment fails from payment capture error on the Point of Sale Checkout"
)

static let cancel = NSLocalizedString(
"pointOfSale.cardPresent.paymentCaptureError.cancel.button.title",
value: "I understand that order should be checked",
comment: "Button to dismiss payment capture error message. " +
"Presented to users after collecting a payment fails from payment capture error on the Point of Sale Checkout"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ enum PointOfSaleCardPresentPaymentMessageType {
case paymentSuccess(viewModel: PointOfSaleCardPresentPaymentSuccessMessageViewModel)
case paymentError(viewModel: PointOfSaleCardPresentPaymentErrorMessageViewModel)
case paymentErrorNonRetryable(viewModel: PointOfSaleCardPresentPaymentNonRetryableErrorMessageViewModel)
case paymentCaptureError(viewModel: PointOfSaleCardPresentPaymentCaptureErrorMessageViewModel)
case cancelledOnReader(viewModel: PointOfSaleCardPresentPaymentCancelledOnReaderMessageViewModel)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import SwiftUI

struct PointOfSaleCardPresentPaymentCaptureFailedView: View {
private let viewModel: PointOfSaleCardPresentPaymentCaptureFailedAlertViewModel
@Environment(\.dismiss) private var dismiss

init(viewModel: PointOfSaleCardPresentPaymentCaptureFailedAlertViewModel) {
self.viewModel = viewModel
}

var body: some View {
VStack {
Text(viewModel.title)

viewModel.image

Text(viewModel.errorDetails)

Button(viewModel.cancelButtonTitle,
action: {
dismiss()
})
.buttonStyle(SecondaryButtonStyle())
}
}
}

#Preview {
PointOfSaleCardPresentPaymentCaptureFailedView(
viewModel: PointOfSaleCardPresentPaymentCaptureFailedAlertViewModel())
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ struct PointOfSaleCardPresentPaymentAlert: View {
PointOfSaleCardPresentPaymentConnectingFailedUpdateAddressView(viewModel: alertViewModel)
case .connectingFailedUpdatePostalCode(let alertViewModel):
PointOfSaleCardPresentPaymentConnectingFailedUpdatePostalCodeView(viewModel: alertViewModel)
case .paymentCaptureFailed(let viewModel):
PointOfSaleCardPresentPaymentCaptureFailedView(viewModel: viewModel)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ struct PointOfSaleCardPresentPaymentInLineMessage: View {
PointOfSaleCardPresentPaymentErrorMessageView(viewModel: viewModel)
case .paymentErrorNonRetryable(let viewModel):
PointOfSaleCardPresentPaymentNonRetryableErrorMessageView(viewModel: viewModel)
case .paymentCaptureError(let viewModel):
PointOfSaleCardPresentPaymentCaptureErrorMessageView(viewModel: viewModel)
case .cancelledOnReader:
Text("Payment cancelled on reader")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import SwiftUI
import enum Yosemite.CardReaderServiceError

struct PointOfSaleCardPresentPaymentCaptureErrorMessageView: View {
let viewModel: PointOfSaleCardPresentPaymentCaptureErrorMessageViewModel

var body: some View {
HStack {
VStack {
Text(viewModel.title)
Text(viewModel.message)
}

Button(viewModel.cancelButtonViewModel.title,
action: viewModel.cancelButtonViewModel.actionHandler)
}
}
}

#Preview {
PointOfSaleCardPresentPaymentCaptureErrorMessageView(
viewModel: PointOfSaleCardPresentPaymentCaptureErrorMessageViewModel(
cancelButtonAction: {}))
}
Original file line number Diff line number Diff line change
Expand Up @@ -249,19 +249,29 @@ private extension PointOfSaleDashboardViewModel {
cardPresentPaymentService.paymentEventPublisher
.map { event -> PointOfSaleCardPresentPaymentAlertType? in
guard case let .show(eventDetails) = event,
case let .alert(alertType) = eventDetails.pointOfSalePresentationStyle else {
let presentationStyle = eventDetails.pointOfSalePresentationStyle else {
return nil
}
return alertType
switch presentationStyle {
case .alert(let alertType), .messageAndAlert(_, let alertType):
return alertType
default:
return nil
}
}
.assign(to: &$cardPresentPaymentAlertViewModel)
cardPresentPaymentService.paymentEventPublisher
.map { event -> PointOfSaleCardPresentPaymentMessageType? in
guard case let .show(eventDetails) = event,
case let .message(messageType) = eventDetails.pointOfSalePresentationStyle else {
let presentationStyle = eventDetails.pointOfSalePresentationStyle else {
return nil
}
return messageType
switch presentationStyle {
case .message(let messageType), .messageAndAlert(let messageType, _):
return messageType
default:
return nil
}
}
.assign(to: &$cardPresentPaymentInlineMessage)
cardPresentPaymentService.paymentEventPublisher.map { event in
Expand All @@ -270,7 +280,7 @@ private extension PointOfSaleDashboardViewModel {
return false
case .show(let eventDetails):
switch eventDetails.pointOfSalePresentationStyle {
case .alert:
case .alert, .messageAndAlert:
return true
case .message, .none:
return false
Expand Down
Loading

0 comments on commit 7679334

Please sign in to comment.