Skip to content

Commit 4ccad81

Browse files
authored
Merge pull request #7613 from woocommerce/issue/7591-toggle-cod-IPP-row-ui
[Mobile Payments] UI for Enable Pay in Person toggle switch in IPP settings
2 parents d0afcff + 6b7b4fb commit 4ccad81

19 files changed

+665
-119
lines changed

WooCommerce/Classes/Authentication/AuthenticationManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class AuthenticationManager: Authentication {
5252
wpcomScheme: ApiCredentials.dotcomAuthScheme,
5353
wpcomTermsOfServiceURL: WooConstants.URLs.termsOfService.rawValue,
5454
wpcomAPIBaseURL: Settings.wordpressApiBaseURL,
55-
whatIsWPComURL: WooConstants.URLs.whatIsWPComURL.rawValue,
55+
whatIsWPComURL: WooConstants.URLs.whatIsWPCom.rawValue,
5656
googleLoginClientId: ApiCredentials.googleClientId,
5757
googleLoginServerClientId: ApiCredentials.googleServerId,
5858
googleLoginScheme: ApiCredentials.googleAuthScheme,

WooCommerce/Classes/Authentication/Navigation Exceptions/NotWPAccountViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ private extension NotWPAccountViewModel {
7373
return
7474
}
7575
ServiceLocator.analytics.track(.whatIsWPComOnInvalidEmailScreenTapped)
76-
WebviewHelper.launch(WooConstants.URLs.whatIsWPComURL.asURL(), with: viewController)
76+
WebviewHelper.launch(WooConstants.URLs.whatIsWPCom.asURL(), with: viewController)
7777
}
7878

7979
func needHelpFindingEmailButtonTapped() {

WooCommerce/Classes/System/WooConstants.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ extension WooConstants {
5555
///
5656
/// Displayed by the Authenticator in the Continue with WordPress.com flow.
5757
///
58-
case whatIsWPComURL = "https://woocommerce.com/document/what-is-a-wordpress-com-account/"
58+
case whatIsWPCom = "https://woocommerce.com/document/what-is-a-wordpress-com-account/"
5959

6060
/// Terms of Service Website. Displayed by the Authenticator (when / if needed).
6161
///
@@ -186,11 +186,11 @@ extension WooConstants {
186186
#endif
187187
/// URL for the Enable Cash on Delivery (or Pay in Person) onboarding step's learn more link using the Stripe plugin
188188
///
189-
case stripeCashOnDeliveryLearnMoreUrl = "https://woocommerce.com/document/stripe/accept-in-person-payments-with-stripe/#section-8"
189+
case stripeCashOnDeliveryLearnMore = "https://woocommerce.com/document/stripe/accept-in-person-payments-with-stripe/#section-8"
190190

191191
/// URL for the Enable Cash on Delivery (or Pay in Person) onboarding step's learn more link using the WCPay plugin
192192
///
193-
case wcPayCashOnDeliveryLearnMoreUrl =
193+
case wcPayCashOnDeliveryLearnMore =
194194
"https://woocommerce.com/document/payments/getting-started-with-in-person-payments-with-woocommerce-payments/#add-cod-payment-method"
195195

196196
/// Returns the URL version of the receiver

WooCommerce/Classes/ViewRelated/Dashboard/Settings/CardReadersV2/CardReaderSettingsSearchingViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ struct CardReaderSettingsSearchingView: View {
150150
InPersonPaymentsLearnMore()
151151
.customOpenURL(action: { url in
152152
switch url {
153-
case InPersonPaymentsLearnMore.learnMoreURL:
153+
case LearnMoreViewModel.learnMoreURL:
154154
if let url = learnMoreUrl {
155155
showURL?(url)
156156
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Foundation
2+
import Yosemite
3+
4+
extension CardPresentPaymentsPlugin {
5+
var cashOnDeliveryLearnMoreURL: URL {
6+
switch self {
7+
case .wcPay:
8+
return WooConstants.URLs.wcPayCashOnDeliveryLearnMore.asURL()
9+
case .stripe:
10+
return WooConstants.URLs.stripeCashOnDeliveryLearnMore.asURL()
11+
}
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Foundation
2+
import Yosemite
3+
4+
extension PaymentGateway {
5+
/// Creates a PaymentGateway with default settings suitable for enabling Pay in Person on a store.
6+
/// This provides customer-facing strings to update the store's gateway.
7+
/// The gateway is enabled.
8+
static func defaultPayInPersonGateway(siteID: Int64) -> PaymentGateway {
9+
PaymentGateway(siteID: siteID,
10+
gatewayID: Constants.cashOnDeliveryGatewayID,
11+
title: Localization.cashOnDeliveryCheckoutTitle,
12+
description: Localization.cashOnDeliveryCheckoutDescription,
13+
enabled: true,
14+
features: [.products],
15+
instructions: Localization.cashOnDeliveryCheckoutInstructions)
16+
}
17+
18+
enum Constants {
19+
static let cashOnDeliveryGatewayID = "cod"
20+
}
21+
22+
private enum Localization {
23+
static let cashOnDeliveryCheckoutTitle = NSLocalizedString(
24+
"Pay in Person",
25+
comment: "Customer-facing title for the payment option added to the store checkout when the merchant enables " +
26+
"Pay in Person")
27+
28+
static let cashOnDeliveryCheckoutDescription = NSLocalizedString(
29+
"Pay by card or another accepted payment method",
30+
comment: "Customer-facing description showing more details about the Pay in Person option which is added to " +
31+
"the store checkout when the merchant enables Pay in Person")
32+
33+
static let cashOnDeliveryCheckoutInstructions = NSLocalizedString(
34+
"Pay by card or another accepted payment method",
35+
comment: "Customer-facing instructions shown on Order Thank-you pages and confirmation emails, showing more " +
36+
"details about the Pay in Person option added to the store checkout when the merchant enables Pay in Person")
37+
}
38+
}

WooCommerce/Classes/ViewRelated/Dashboard/Settings/In-Person Payments/InPersonPaymentsLearnMore.swift

Lines changed: 6 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
11
import SwiftUI
22

33
struct InPersonPaymentsLearnMore: View {
4-
static let learnMoreURL = URL(string: "woocommerce://in-person-payments/learn-more")!
54
@Environment(\.customOpenURL) var customOpenURL
65

7-
let url: URL
8-
let linkText: String
9-
let formatText: String
10-
let analyticReason: String?
6+
private let viewModel: LearnMoreViewModel
117

12-
init(url: URL = learnMoreURL,
13-
linkText: String = Localization.learnMoreLink,
14-
formatText: String = Localization.learnMoreText,
15-
analyticReason: String? = nil) {
16-
self.url = url
17-
self.linkText = linkText
18-
self.formatText = formatText
19-
self.analyticReason = analyticReason
8+
init(viewModel: LearnMoreViewModel = LearnMoreViewModel()) {
9+
self.viewModel = viewModel
2010
}
2111

2212
private let cardPresentConfiguration = CardPresentConfigurationLoader().configuration
@@ -27,38 +17,18 @@ struct InPersonPaymentsLearnMore: View {
2717
.resizable()
2818
.foregroundColor(Color(.neutral(.shade60)))
2919
.frame(width: iconSize, height: iconSize)
30-
AttributedText(learnMoreAttributedString)
20+
AttributedText(viewModel.learnMoreAttributedString)
3121
}
3222
.padding(.vertical, Constants.verticalPadding)
3323
.onTapGesture {
34-
ServiceLocator.analytics.track(
35-
event: WooAnalyticsEvent.InPersonPayments.cardPresentOnboardingLearnMoreTapped(
36-
reason: analyticReason ?? "",
37-
countryCode: cardPresentConfiguration.countryCode))
38-
customOpenURL?(url)
24+
viewModel.learnMoreTapped()
25+
customOpenURL?(viewModel.url)
3926
}
4027
}
4128

4229
var iconSize: CGFloat {
4330
UIFontMetrics(forTextStyle: .subheadline).scaledValue(for: 20)
4431
}
45-
46-
private var learnMoreAttributedString: NSAttributedString {
47-
let result = NSMutableAttributedString(
48-
string: .localizedStringWithFormat(formatText, linkText),
49-
attributes: [.foregroundColor: UIColor.textSubtle]
50-
)
51-
result.replaceFirstOccurrence(
52-
of: linkText,
53-
with: NSAttributedString(
54-
string: linkText,
55-
attributes: [.foregroundColor: UIColor.textLink]
56-
))
57-
58-
// https://github.com/gonzalezreal/AttributedText/issues/11
59-
result.addAttribute(.font, value: UIFont.footnote, range: NSRange(location: 0, length: result.length))
60-
return result
61-
}
6232
}
6333

6434
private enum Constants {
@@ -76,14 +46,6 @@ private enum Localization {
7646
comment: "Generic error message when In-Person Payments is unavailable"
7747
)
7848

79-
static let learnMoreLink = NSLocalizedString(
80-
"Learn more",
81-
comment: """
82-
A label prompting users to learn more about card readers.
83-
This part is the link to the website, and forms part of a longer sentence which it should be considered a part of.
84-
"""
85-
)
86-
8749
static let learnMoreText = NSLocalizedString(
8850
"%1$@ about accepting payments with your mobile device and ordering card readers",
8951
comment: """

WooCommerce/Classes/ViewRelated/Dashboard/Settings/In-Person Payments/InPersonPaymentsMenuViewController.swift

Lines changed: 87 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ final class InPersonPaymentsMenuViewController: UIViewController {
1212
private let featureFlagService: FeatureFlagService
1313
private let cardPresentPaymentsOnboardingUseCase: CardPresentPaymentsOnboardingUseCase
1414
private var cancellables: Set<AnyCancellable> = []
15+
private lazy var learnMoreViewModel: LearnMoreViewModel = {
16+
LearnMoreViewModel(url: WooConstants.URLs.wcPayCashOnDeliveryLearnMore.asURL(),
17+
linkText: Localization.toggleEnableCashOnDeliveryLearnMoreLink,
18+
formatText: Localization.toggleEnableCashOnDeliveryLearnMoreFormat,
19+
tappedAnalyticEvent: WooAnalyticsEvent.InPersonPayments.cardPresentOnboardingLearnMoreTapped(
20+
reason: "reason",
21+
countryCode: configurationLoader.configuration.countryCode))
22+
}()
23+
private let inPersonPaymentsMenuViewModel: InPersonPaymentsMenuViewModel
1524

1625
/// No Manuals to be shown in a country where IPP is not supported
1726
///
@@ -44,6 +53,7 @@ final class InPersonPaymentsMenuViewController: UIViewController {
4453
self.featureFlagService = featureFlagService
4554
self.cardPresentPaymentsOnboardingUseCase = CardPresentPaymentsOnboardingUseCase()
4655
configurationLoader = CardPresentConfigurationLoader()
56+
self.inPersonPaymentsMenuViewModel = InPersonPaymentsMenuViewModel()
4757

4858
super.init(nibName: nil, bundle: nil)
4959
}
@@ -58,6 +68,7 @@ final class InPersonPaymentsMenuViewController: UIViewController {
5868
configureSections()
5969
configureTableView()
6070
registerTableViewCells()
71+
configureTableReload()
6172
runCardPresentPaymentsOnboarding()
6273
}
6374
}
@@ -77,7 +88,7 @@ private extension InPersonPaymentsMenuViewController {
7788
}
7889

7990
func refreshAfterNewOnboardingState(_ state: CardPresentPaymentOnboardingState) {
80-
self.pluginState = nil
91+
pluginState = nil
8192

8293
guard state != .loading else {
8394
self.activityIndicator?.startAnimating()
@@ -86,22 +97,24 @@ private extension InPersonPaymentsMenuViewController {
8697

8798
switch state {
8899
case let .completed(newPluginState):
89-
self.pluginState = newPluginState
90-
self.dismissCardPresentPaymentsOnboardingNoticeIfPresent()
91-
self.dismissOnboardingIfPresented()
100+
pluginState = newPluginState
101+
dismissCardPresentPaymentsOnboardingNoticeIfPresent()
102+
dismissOnboardingIfPresented()
92103
case let .selectPlugin(pluginSelectionWasCleared):
93104
// If it was cleared it means that we triggered it manually (e.g by tapping in this view on the plugin selection row)
94105
// No need to show the onboarding notice
95106
if !pluginSelectionWasCleared {
96-
self.showCardPresentPaymentsOnboardingNotice()
107+
showCardPresentPaymentsOnboardingNotice()
97108
}
98109
default:
99-
self.showCardPresentPaymentsOnboardingNotice()
110+
showCardPresentPaymentsOnboardingNotice()
100111
}
101112

102-
self.activityIndicator?.stopAnimating()
103-
self.configureSections()
104-
self.tableView.reloadData()
113+
updateViewModelSelectedPlugin(state: state)
114+
115+
activityIndicator?.stopAnimating()
116+
configureSections()
117+
tableView.reloadData()
105118
}
106119

107120
func showCardPresentPaymentsOnboardingNotice() {
@@ -119,6 +132,17 @@ private extension InPersonPaymentsMenuViewController {
119132
permanentNoticePresenter.dismiss()
120133
}
121134

135+
func updateViewModelSelectedPlugin(state: CardPresentPaymentOnboardingState) {
136+
switch state {
137+
case let .completed(pluginState):
138+
inPersonPaymentsMenuViewModel.selectedPlugin = pluginState.preferred
139+
case let .codPaymentGatewayNotSetUp(plugin):
140+
inPersonPaymentsMenuViewModel.selectedPlugin = plugin
141+
default:
142+
inPersonPaymentsMenuViewModel.selectedPlugin = nil
143+
}
144+
}
145+
122146
func showOnboarding() {
123147
// Instead of using `CardPresentPaymentsOnboardingPresenter` we create the view directly because we already have the onboarding state in the use case.
124148
// That way we avoid triggering the onboarding check again that comes with the presenter.
@@ -147,7 +171,7 @@ private extension InPersonPaymentsMenuViewController {
147171
}
148172

149173
var actionsSection: Section? {
150-
return Section(header: Localization.paymentActionsSectionTitle, rows: [.collectPayment])
174+
return Section(header: Localization.paymentActionsSectionTitle, rows: [.collectPayment, .toggleEnableCashOnDelivery])
151175
}
152176

153177
var cardReadersSection: Section? {
@@ -211,6 +235,8 @@ private extension InPersonPaymentsMenuViewController {
211235
configureCardReaderManuals(cell: cell)
212236
case let cell as LeftImageTableViewCell where row == .collectPayment:
213237
configureCollectPayment(cell: cell)
238+
case let cell as LeftImageTitleSubtitleToggleTableViewCell where row == .toggleEnableCashOnDelivery:
239+
configureToggleEnableCashOnDelivery(cell: cell)
214240
default:
215241
fatalError()
216242
}
@@ -259,11 +285,32 @@ private extension InPersonPaymentsMenuViewController {
259285
updateEnabledState(in: cell)
260286
}
261287

288+
func configureToggleEnableCashOnDelivery(cell: LeftImageTitleSubtitleToggleTableViewCell) {
289+
cell.leftImageView?.tintColor = .text
290+
cell.accessoryType = .none
291+
cell.selectionStyle = .none
292+
cell.configure(image: .creditCardIcon,
293+
text: Localization.toggleEnableCashOnDelivery,
294+
subtitle: learnMoreViewModel.learnMoreAttributedString,
295+
switchState: inPersonPaymentsMenuViewModel.cashOnDeliveryEnabledState,
296+
switchAction: inPersonPaymentsMenuViewModel.updateCashOnDeliverySetting(enabled:),
297+
subtitleTapAction: { [weak self] in
298+
guard let self = self else { return }
299+
self.inPersonPaymentsMenuViewModel.learnMoreTapped(from: self)
300+
})
301+
}
302+
262303
func updateEnabledState(in cell: UITableViewCell, shouldBeEnabled: Bool = true) {
263304
let alpha = shouldBeEnabled ? 1 : 0.3
264305
cell.imageView?.alpha = alpha
265306
cell.textLabel?.alpha = alpha
266307
}
308+
309+
func configureTableReload() {
310+
inPersonPaymentsMenuViewModel.$cashOnDeliveryEnabledState.sink { [weak self] _ in
311+
self?.tableView.reloadData()
312+
}.store(in: &cancellables)
313+
}
267314
}
268315

269316
// MARK: - Convenience methods
@@ -375,6 +422,17 @@ extension InPersonPaymentsMenuViewController: UITableViewDelegate {
375422
managePaymentGatewaysWasPressed()
376423
case .collectPayment:
377424
collectPaymentWasPressed()
425+
case .toggleEnableCashOnDelivery:
426+
break
427+
}
428+
}
429+
430+
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
431+
switch rowAtIndexPath(indexPath) {
432+
case .toggleEnableCashOnDelivery:
433+
return nil
434+
default:
435+
return indexPath
378436
}
379437
}
380438
}
@@ -410,6 +468,22 @@ private extension InPersonPaymentsMenuViewController {
410468
comment: "Navigates to Payment Gateway management screen"
411469
)
412470

471+
static let toggleEnableCashOnDelivery = NSLocalizedString(
472+
"Enable Pay in Person",
473+
comment: "Title for a switch on the In-Person Payments menu to enable Cash on Delivery"
474+
)
475+
476+
static let toggleEnableCashOnDeliveryLearnMoreFormat = NSLocalizedString(
477+
"Pay in Person lets you accept card or cash payments on collection or delivery. %1$@",
478+
comment: "A label prompting users to learn more about adding Pay in Person to their checkout. " +
479+
"%1$@ is a placeholder that always replaced with \"Learn more\" string, " +
480+
"which should be translated separately and considered part of this sentence.")
481+
482+
static let toggleEnableCashOnDeliveryLearnMoreLink = NSLocalizedString(
483+
"Learn more",
484+
comment: "The \"Learn more\" string replaces the placeholder in a label prompting users to learn " +
485+
"more about adding Pay in Person to their checkout. ")
486+
413487
static let cardReaderManuals = NSLocalizedString(
414488
"Card Reader Manuals",
415489
comment: "Navigates to Card Reader Manuals screen"
@@ -443,11 +517,14 @@ private enum Row: CaseIterable {
443517
case cardReaderManuals
444518
case managePaymentGateways
445519
case collectPayment
520+
case toggleEnableCashOnDelivery
446521

447522
var type: UITableViewCell.Type {
448523
switch self {
449524
case .managePaymentGateways:
450525
return LeftImageTitleSubtitleTableViewCell.self
526+
case .toggleEnableCashOnDelivery:
527+
return LeftImageTitleSubtitleToggleTableViewCell.self
451528
default:
452529
return LeftImageTableViewCell.self
453530
}

0 commit comments

Comments
 (0)