Skip to content

Commit 52e12a0

Browse files
committed
8089 Don’t report success when payment fails
This commit prevents us from going through the success flow when Tap to Pay on iPhone payments fail. Previously, if you locked the phone while the Apple payment screen was showing, it would still show a success notice even though the payment hadn’t been taken.
1 parent 8c8d46b commit 52e12a0

File tree

6 files changed

+91
-37
lines changed

6 files changed

+91
-37
lines changed

WooCommerce/Classes/ViewRelated/Orders/Collect Payments/CollectOrderPaymentUseCase.swift

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ protocol CollectOrderPaymentProtocol {
2222
/// - Parameter onCollect: Closure Invoked after the collect process has finished.
2323
/// - Parameter onCompleted: Closure Invoked after the flow has been totally completed.
2424
/// - Parameter onCancel: Closure invoked after the flow is cancelled
25-
func collectPayment(onCollect: @escaping (Result<Void, Error>) -> (), onCancel: @escaping () -> (), onCompleted: @escaping () -> ())
25+
func collectPayment(onFailure: @escaping (Error) -> (), onCancel: @escaping () -> (), onCompleted: @escaping () -> ())
2626
}
2727

2828
/// Use case to collect payments from an order.
@@ -129,15 +129,15 @@ final class CollectOrderPaymentUseCase: NSObject, CollectOrderPaymentProtocol {
129129
/// 7. Tracks payment analytics
130130
///
131131
///
132-
/// - Parameter onCollect: Closure invoked after the collect process has finished.
132+
/// - Parameter onFailure: Closure invoked after the payment process fails.
133133
/// - Parameter onCancel: Closure invoked after the flow is cancelled
134134
/// - Parameter onCompleted: Closure invoked after the flow has been totally completed, currently after merchant has handled the receipt.
135-
func collectPayment(onCollect: @escaping (Result<Void, Error>) -> (),
135+
func collectPayment(onFailure: @escaping (Error) -> (),
136136
onCancel: @escaping () -> (),
137137
onCompleted: @escaping () -> ()) {
138138
guard isTotalAmountValid() else {
139139
let error = totalAmountInvalidError()
140-
onCollect(.failure(error))
140+
onFailure(error)
141141
return handleTotalAmountInvalidError(totalAmountInvalidError(), onCompleted: onCancel)
142142
}
143143

@@ -158,16 +158,14 @@ final class CollectOrderPaymentUseCase: NSObject, CollectOrderPaymentProtocol {
158158
case .failure(CollectOrderPaymentUseCaseError.flowCanceledByUser):
159159
self.rootViewController.presentedViewController?.dismiss(animated: true)
160160
return onCancel()
161-
default:
162-
onCollect(result.map { _ in () }) // Transforms Result<CardPresentCapturedPaymentData, Error> to Result<Void, Error>
163-
}
164-
// Handle payment receipt
165-
guard let paymentData = try? result.get() else {
166-
return onCompleted()
161+
case .failure(let error):
162+
return onFailure(error)
163+
case .success(let paymentData):
164+
// Handle payment receipt
165+
self.presentReceiptAlert(receiptParameters: paymentData.receiptParameters,
166+
alertProvider: paymentAlertProvider,
167+
onCompleted: onCompleted)
167168
}
168-
self.presentReceiptAlert(receiptParameters: paymentData.receiptParameters,
169-
alertProvider: paymentAlertProvider,
170-
onCompleted: onCompleted)
171169
})
172170
case .canceled:
173171
self.handlePaymentCancellation()

WooCommerce/Classes/ViewRelated/Orders/Collect Payments/LegacyCollectOrderPaymentUseCase.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,24 @@ import MessageUI
55
import WooFoundation
66
import protocol Storage.StorageManagerType
77

8+
9+
/// Protocol to abstract the `LegacyCollectOrderPaymentUseCase`.
10+
/// Currently only used to facilitate unit tests.
11+
///
12+
protocol LegacyCollectOrderPaymentProtocol {
13+
/// Starts the collect payment flow.
14+
///
15+
///
16+
/// - Parameter onCollect: Closure Invoked after the collect process has finished.
17+
/// - Parameter onCompleted: Closure Invoked after the flow has been totally completed.
18+
/// - Parameter onCancel: Closure invoked after the flow is cancelled
19+
func collectPayment(onCollect: @escaping (Result<Void, Error>) -> (), onCancel: @escaping () -> (), onCompleted: @escaping () -> ())
20+
}
21+
822
/// Use case to collect payments from an order.
923
/// Orchestrates reader connection, payment, UI alerts, receipt handling and analytics.
1024
///
11-
final class LegacyCollectOrderPaymentUseCase: NSObject, CollectOrderPaymentProtocol {
25+
final class LegacyCollectOrderPaymentUseCase: NSObject, LegacyCollectOrderPaymentProtocol {
1226
/// Currency Formatter
1327
///
1428
private let currencyFormatter = CurrencyFormatter(currencySettings: ServiceLocator.currencySettings)

WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ struct PaymentMethodsView: View {
6060
Divider()
6161

6262
MethodRow(icon: .creditCardImage, title: Localization.card, accessibilityID: Accessibility.cardMethod) {
63-
viewModel.collectPayment(on: rootViewController, onSuccess: dismiss)
63+
viewModel.collectPayment(on: rootViewController, onSuccess: dismiss, onFailure: dismiss)
6464
}
6565
}
6666

WooCommerce/Classes/ViewRelated/Orders/Payment Methods/PaymentMethodsViewModel.swift

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ final class PaymentMethodsViewModel: ObservableObject {
9999

100100
/// Retains the use-case so it can perform all of its async tasks.
101101
///
102-
private var legacyCollectPaymentsUseCase: CollectOrderPaymentProtocol?
102+
private var legacyCollectPaymentsUseCase: LegacyCollectOrderPaymentProtocol?
103103

104104
private var collectPaymentsUseCase: CollectOrderPaymentProtocol?
105105

@@ -112,6 +112,8 @@ final class PaymentMethodsViewModel: ObservableObject {
112112
configuration: upsellCardReadersCampaign.configuration)
113113
}
114114

115+
private let isTapToPayOnIPhoneEnabled: Bool
116+
115117
struct Dependencies {
116118
let presentNoticeSubject: PassthroughSubject<SimplePaymentsNotice, Never>
117119
let cardPresentPaymentsOnboardingPresenter: CardPresentPaymentsOnboardingPresenting
@@ -141,12 +143,14 @@ final class PaymentMethodsViewModel: ObservableObject {
141143
paymentLink: URL? = nil,
142144
formattedTotal: String,
143145
flow: WooAnalyticsEvent.PaymentsFlow.Flow,
146+
isTapToPayOnIPhoneEnabled: Bool = ServiceLocator.generalAppSettings.settings.isTapToPayOnIPhoneSwitchEnabled,
144147
dependencies: Dependencies = Dependencies()) {
145148
self.siteID = siteID
146149
self.orderID = orderID
147150
self.paymentLink = paymentLink
148151
self.formattedTotal = formattedTotal
149152
self.flow = flow
153+
self.isTapToPayOnIPhoneEnabled = isTapToPayOnIPhoneEnabled
150154
presentNoticeSubject = dependencies.presentNoticeSubject
151155
cardPresentPaymentsOnboardingPresenter = dependencies.cardPresentPaymentsOnboardingPresenter
152156
stores = dependencies.stores
@@ -199,19 +203,21 @@ final class PaymentMethodsViewModel: ObservableObject {
199203
/// - parameter useCase: Assign a custom useCase object for testing purposes. If not provided `CollectOrderPaymentUseCase` will be used.
200204
///
201205
func collectPayment(on rootViewController: UIViewController?,
202-
useCase: CollectOrderPaymentProtocol? = nil,
203-
onSuccess: @escaping () -> ()) {
204-
switch ServiceLocator.generalAppSettings.settings.isTapToPayOnIPhoneSwitchEnabled {
206+
useCase: LegacyCollectOrderPaymentProtocol? = nil,
207+
onSuccess: @escaping () -> (),
208+
onFailure: @escaping () -> ()) {
209+
switch isTapToPayOnIPhoneEnabled {
205210
case true:
206-
newCollectPayment(on: rootViewController, useCase: useCase, onSuccess: onSuccess)
211+
newCollectPayment(on: rootViewController, onSuccess: onSuccess, onFailure: onFailure)
207212
case false:
208213
legacyCollectPayment(on: rootViewController, useCase: useCase, onSuccess: onSuccess)
209214
}
210215
}
211216

212217
func newCollectPayment(on rootViewController: UIViewController?,
213-
useCase: CollectOrderPaymentProtocol? = nil,
214-
onSuccess: @escaping () -> ()) {
218+
useCase: CollectOrderPaymentProtocol? = nil,
219+
onSuccess: @escaping () -> (),
220+
onFailure: @escaping () -> ()) {
215221
trackCollectIntention(method: .card)
216222

217223
guard let rootViewController = rootViewController else {
@@ -243,9 +249,12 @@ final class PaymentMethodsViewModel: ObservableObject {
243249
configuration: CardPresentConfigurationLoader().configuration)
244250

245251
self.collectPaymentsUseCase?.collectPayment(
246-
onCollect: { [weak self] result in
247-
guard result.isFailure else { return }
252+
onFailure: { [weak self] error in
248253
self?.trackFlowFailed()
254+
// Update order in case its status and/or other details are updated after a failed in-person payment
255+
self?.updateOrderAsynchronously()
256+
257+
onFailure()
249258
},
250259
onCancel: {
251260
// No tracking required because the flow remains on screen to choose other payment methods.
@@ -273,8 +282,8 @@ final class PaymentMethodsViewModel: ObservableObject {
273282
}
274283

275284
func legacyCollectPayment(on rootViewController: UIViewController?,
276-
useCase: CollectOrderPaymentProtocol? = nil,
277-
onSuccess: @escaping () -> ()) {
285+
useCase: LegacyCollectOrderPaymentProtocol? = nil,
286+
onSuccess: @escaping () -> ()) {
278287
trackCollectIntention(method: .card)
279288

280289
guard let rootViewController = rootViewController else {

0 commit comments

Comments
 (0)