Skip to content

Commit 7bdefa5

Browse files
authored
Merge pull request #6035 from woocommerce/issue/6034-ipp-ca-experimental-feature
[Mobile Payments] Add experimental flag for In-Person Payments in Canada
2 parents b497e69 + 7f592ed commit 7bdefa5

File tree

10 files changed

+185
-16
lines changed

10 files changed

+185
-16
lines changed

Storage/Storage/Model/Copiable/Models+Copiable.generated.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ extension GeneralAppSettings {
1111
isViewAddOnsSwitchEnabled: CopiableProp<Bool> = .copy,
1212
isOrderCreationSwitchEnabled: CopiableProp<Bool> = .copy,
1313
isStripeInPersonPaymentsSwitchEnabled: CopiableProp<Bool> = .copy,
14+
isCanadaInPersonPaymentsSwitchEnabled: CopiableProp<Bool> = .copy,
1415
isProductSKUInputScannerSwitchEnabled: CopiableProp<Bool> = .copy,
1516
knownCardReaders: CopiableProp<[String]> = .copy,
1617
lastEligibilityErrorInfo: NullableCopiableProp<EligibilityErrorInfo> = .copy,
@@ -21,6 +22,7 @@ extension GeneralAppSettings {
2122
let isViewAddOnsSwitchEnabled = isViewAddOnsSwitchEnabled ?? self.isViewAddOnsSwitchEnabled
2223
let isOrderCreationSwitchEnabled = isOrderCreationSwitchEnabled ?? self.isOrderCreationSwitchEnabled
2324
let isStripeInPersonPaymentsSwitchEnabled = isStripeInPersonPaymentsSwitchEnabled ?? self.isStripeInPersonPaymentsSwitchEnabled
25+
let isCanadaInPersonPaymentsSwitchEnabled = isCanadaInPersonPaymentsSwitchEnabled ?? self.isCanadaInPersonPaymentsSwitchEnabled
2426
let isProductSKUInputScannerSwitchEnabled = isProductSKUInputScannerSwitchEnabled ?? self.isProductSKUInputScannerSwitchEnabled
2527
let knownCardReaders = knownCardReaders ?? self.knownCardReaders
2628
let lastEligibilityErrorInfo = lastEligibilityErrorInfo ?? self.lastEligibilityErrorInfo
@@ -32,6 +34,7 @@ extension GeneralAppSettings {
3234
isViewAddOnsSwitchEnabled: isViewAddOnsSwitchEnabled,
3335
isOrderCreationSwitchEnabled: isOrderCreationSwitchEnabled,
3436
isStripeInPersonPaymentsSwitchEnabled: isStripeInPersonPaymentsSwitchEnabled,
37+
isCanadaInPersonPaymentsSwitchEnabled: isCanadaInPersonPaymentsSwitchEnabled,
3538
isProductSKUInputScannerSwitchEnabled: isProductSKUInputScannerSwitchEnabled,
3639
knownCardReaders: knownCardReaders,
3740
lastEligibilityErrorInfo: lastEligibilityErrorInfo,

Storage/Storage/Model/GeneralAppSettings.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ public struct GeneralAppSettings: Codable, Equatable, GeneratedCopiable {
3232
///
3333
public let isStripeInPersonPaymentsSwitchEnabled: Bool
3434

35+
/// The state for the In-Person Payments in Canada feature switch
36+
///
37+
public let isCanadaInPersonPaymentsSwitchEnabled: Bool
38+
3539
/// The state(`true` or `false`) for the Product SKU Input Scanner feature switch.
3640
///
3741
public let isProductSKUInputScannerSwitchEnabled: Bool
@@ -53,6 +57,7 @@ public struct GeneralAppSettings: Codable, Equatable, GeneratedCopiable {
5357
isViewAddOnsSwitchEnabled: Bool,
5458
isOrderCreationSwitchEnabled: Bool,
5559
isStripeInPersonPaymentsSwitchEnabled: Bool,
60+
isCanadaInPersonPaymentsSwitchEnabled: Bool,
5661
isProductSKUInputScannerSwitchEnabled: Bool,
5762
knownCardReaders: [String],
5863
lastEligibilityErrorInfo: EligibilityErrorInfo? = nil,
@@ -62,6 +67,7 @@ public struct GeneralAppSettings: Codable, Equatable, GeneratedCopiable {
6267
self.isViewAddOnsSwitchEnabled = isViewAddOnsSwitchEnabled
6368
self.isOrderCreationSwitchEnabled = isOrderCreationSwitchEnabled
6469
self.isStripeInPersonPaymentsSwitchEnabled = isStripeInPersonPaymentsSwitchEnabled
70+
self.isCanadaInPersonPaymentsSwitchEnabled = isCanadaInPersonPaymentsSwitchEnabled
6571
self.isProductSKUInputScannerSwitchEnabled = isProductSKUInputScannerSwitchEnabled
6672
self.knownCardReaders = knownCardReaders
6773
self.lastEligibilityErrorInfo = lastEligibilityErrorInfo
@@ -91,6 +97,7 @@ public struct GeneralAppSettings: Codable, Equatable, GeneratedCopiable {
9197
isViewAddOnsSwitchEnabled: isViewAddOnsSwitchEnabled,
9298
isOrderCreationSwitchEnabled: isOrderCreationSwitchEnabled,
9399
isStripeInPersonPaymentsSwitchEnabled: isStripeInPersonPaymentsSwitchEnabled,
100+
isCanadaInPersonPaymentsSwitchEnabled: isCanadaInPersonPaymentsSwitchEnabled,
94101
isProductSKUInputScannerSwitchEnabled: isProductSKUInputScannerSwitchEnabled,
95102
knownCardReaders: knownCardReaders,
96103
lastEligibilityErrorInfo: lastEligibilityErrorInfo
@@ -110,6 +117,7 @@ extension GeneralAppSettings {
110117
self.isViewAddOnsSwitchEnabled = try container.decodeIfPresent(Bool.self, forKey: .isViewAddOnsSwitchEnabled) ?? false
111118
self.isOrderCreationSwitchEnabled = try container.decodeIfPresent(Bool.self, forKey: .isOrderCreationSwitchEnabled) ?? false
112119
self.isStripeInPersonPaymentsSwitchEnabled = try container.decodeIfPresent(Bool.self, forKey: .isStripeInPersonPaymentsSwitchEnabled) ?? false
120+
self.isCanadaInPersonPaymentsSwitchEnabled = try container.decodeIfPresent(Bool.self, forKey: .isCanadaInPersonPaymentsSwitchEnabled) ?? false
113121
self.isProductSKUInputScannerSwitchEnabled = try container.decodeIfPresent(Bool.self, forKey: .isProductSKUInputScannerSwitchEnabled) ?? false
114122
self.knownCardReaders = try container.decodeIfPresent([String].self, forKey: .knownCardReaders) ?? []
115123
self.lastEligibilityErrorInfo = try container.decodeIfPresent(EligibilityErrorInfo.self, forKey: .lastEligibilityErrorInfo)

Storage/StorageTests/Model/AppSettings/GeneralAppSettingsTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ final class GeneralAppSettingsTests: XCTestCase {
6363
isViewAddOnsSwitchEnabled: true,
6464
isOrderCreationSwitchEnabled: true,
6565
isStripeInPersonPaymentsSwitchEnabled: true,
66+
isCanadaInPersonPaymentsSwitchEnabled: true,
6667
isProductSKUInputScannerSwitchEnabled: true,
6768
knownCardReaders: readers,
6869
lastEligibilityErrorInfo: eligibilityInfo,
@@ -84,6 +85,7 @@ final class GeneralAppSettingsTests: XCTestCase {
8485
assertEqual(newSettings.isViewAddOnsSwitchEnabled, false)
8586
assertEqual(newSettings.isOrderCreationSwitchEnabled, true)
8687
assertEqual(newSettings.isStripeInPersonPaymentsSwitchEnabled, true)
88+
assertEqual(newSettings.isCanadaInPersonPaymentsSwitchEnabled, true)
8789
assertEqual(newSettings.isProductSKUInputScannerSwitchEnabled, true)
8890
assertEqual(newSettings.lastJetpackBenefitsBannerDismissedTime, jetpackBannerDismissedDate)
8991
}
@@ -95,6 +97,7 @@ private extension GeneralAppSettingsTests {
9597
isViewAddOnsSwitchEnabled: Bool = false,
9698
isOrderCreationSwitchEnabled: Bool = false,
9799
isStripeInPersonPaymentsSwitchEnabled: Bool = false,
100+
isCanadaInPersonPaymentsSwitchEnabled: Bool = false,
98101
isProductSKUInputScannerSwitchEnabled: Bool = false,
99102
knownCardReaders: [String] = [],
100103
lastEligibilityErrorInfo: EligibilityErrorInfo? = nil,
@@ -104,6 +107,7 @@ private extension GeneralAppSettingsTests {
104107
isViewAddOnsSwitchEnabled: isViewAddOnsSwitchEnabled,
105108
isOrderCreationSwitchEnabled: isOrderCreationSwitchEnabled,
106109
isStripeInPersonPaymentsSwitchEnabled: isStripeInPersonPaymentsSwitchEnabled,
110+
isCanadaInPersonPaymentsSwitchEnabled: isCanadaInPersonPaymentsSwitchEnabled,
107111
isProductSKUInputScannerSwitchEnabled: isProductSKUInputScannerSwitchEnabled,
108112
knownCardReaders: knownCardReaders,
109113
lastEligibilityErrorInfo: lastEligibilityErrorInfo,

WooCommerce/Classes/ViewRelated/Dashboard/Settings/Beta features/BetaFeaturesViewController.swift

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,21 @@ private extension BetaFeaturesViewController {
9595
}
9696

9797
func inPersonPaymentsSection() -> Section? {
98-
guard ServiceLocator.featureFlagService.isFeatureFlagEnabled(.stripeExtensionInPersonPayments) else {
98+
var rows: [Row] = []
99+
100+
if ServiceLocator.featureFlagService.isFeatureFlagEnabled(.stripeExtensionInPersonPayments) {
101+
rows += [.stripeExtensionInPersonPayments, .stripeExtensionInPersonPaymentsDescription]
102+
}
103+
104+
if ServiceLocator.featureFlagService.isFeatureFlagEnabled(.canadaInPersonPayments) {
105+
rows += [.canadaInPersonPayments, .canadaInPersonPaymentsDescription]
106+
}
107+
108+
guard rows.isNotEmpty else {
99109
return nil
100110
}
101111

102-
return Section(rows: [.stripeExtensionInPersonPayments,
103-
.stripeExtensionInPersonPaymentsDescription])
112+
return Section(rows: rows)
104113
}
105114

106115
func productSKUInputScannerSection() -> Section? {
@@ -144,6 +153,11 @@ private extension BetaFeaturesViewController {
144153
configureStripeExtensionInPersonPaymentsSwitch(cell: cell)
145154
case let cell as BasicTableViewCell where row == .stripeExtensionInPersonPaymentsDescription:
146155
configureStripeExtensionInPersonPaymentsDescription(cell: cell)
156+
// In-Person Payments in Canada
157+
case let cell as SwitchTableViewCell where row == .canadaInPersonPayments:
158+
configureCanadaInPersonPaymentsSwitch(cell: cell)
159+
case let cell as BasicTableViewCell where row == .canadaInPersonPaymentsDescription:
160+
configureCanadaInPersonPaymentsDescription(cell: cell)
147161
// Product SKU Input Scanner
148162
case let cell as SwitchTableViewCell where row == .productSKUInputScanner:
149163
configureProductSKUInputScannerSwitch(cell: cell)
@@ -251,6 +265,37 @@ private extension BetaFeaturesViewController {
251265
cell.textLabel?.text = Localization.stripeExtensionInPersonPaymentsDescription
252266
}
253267

268+
func configureCanadaInPersonPaymentsSwitch(cell: SwitchTableViewCell) {
269+
configureCommonStylesForSwitchCell(cell)
270+
cell.title = Localization.canadaExtensionInPersonPaymentsTitle
271+
272+
// Fetch switch's state stored value.
273+
let action = AppSettingsAction.loadCanadaInPersonPaymentsSwitchState { result in
274+
guard let isEnabled = try? result.get() else {
275+
return cell.isOn = false
276+
}
277+
cell.isOn = isEnabled
278+
}
279+
ServiceLocator.stores.dispatch(action)
280+
281+
// Change switch's state stored value
282+
cell.onChange = { isSwitchOn in
283+
let action = AppSettingsAction.setCanadaInPersonPaymentsSwitchState(isEnabled: isSwitchOn, onCompletion: { result in
284+
// Roll back toggle if an error occurred
285+
if result.isFailure {
286+
cell.isOn.toggle()
287+
}
288+
})
289+
ServiceLocator.stores.dispatch(action)
290+
}
291+
cell.accessibilityIdentifier = "beta-features-canada-in-person-payments-cell"
292+
}
293+
294+
func configureCanadaInPersonPaymentsDescription(cell: BasicTableViewCell) {
295+
configureCommonStylesForDescriptionCell(cell)
296+
cell.textLabel?.text = Localization.canadaExtensionInPersonPaymentsDescription
297+
}
298+
254299
func configureProductSKUInputScannerSwitch(cell: SwitchTableViewCell) {
255300
configureCommonStylesForSwitchCell(cell)
256301
cell.title = Localization.productSKUInputScannerTitle
@@ -353,15 +398,20 @@ private enum Row: CaseIterable {
353398
case stripeExtensionInPersonPayments
354399
case stripeExtensionInPersonPaymentsDescription
355400

401+
// In-Person Payments in Canada
402+
case canadaInPersonPayments
403+
case canadaInPersonPaymentsDescription
404+
356405
// Product SKU Input Scanner
357406
case productSKUInputScanner
358407
case productSKUInputScannerDescription
359408

360409
var type: UITableViewCell.Type {
361410
switch self {
362-
case .orderAddOns, .orderCreation, .stripeExtensionInPersonPayments, .productSKUInputScanner:
411+
case .orderAddOns, .orderCreation, .stripeExtensionInPersonPayments, .canadaInPersonPayments, .productSKUInputScanner:
363412
return SwitchTableViewCell.self
364-
case .orderAddOnsDescription, .orderCreationDescription, .stripeExtensionInPersonPaymentsDescription, .productSKUInputScannerDescription:
413+
case .orderAddOnsDescription, .orderCreationDescription, .stripeExtensionInPersonPaymentsDescription, .canadaInPersonPaymentsDescription,
414+
.productSKUInputScannerDescription:
365415
return BasicTableViewCell.self
366416
}
367417
}
@@ -396,6 +446,13 @@ private extension BetaFeaturesViewController {
396446
comment: "Cell description on beta features screen to enable accepting in-person payments for stores with " +
397447
"the WooCommerce Stripe Payment Gateway extension")
398448

449+
static let canadaExtensionInPersonPaymentsTitle = NSLocalizedString(
450+
"In-Person Payments in Canada",
451+
comment: "Cell title on beta features screen to enable accepting in-person payments for stores in Canada ")
452+
static let canadaExtensionInPersonPaymentsDescription = NSLocalizedString(
453+
"Test out In-Person Payments in Canada",
454+
comment: "Cell description on beta features screen to enable accepting in-person payments for stores in Canada")
455+
399456
static let productSKUInputScannerTitle = NSLocalizedString(
400457
"Product SKU Scanner",
401458
comment: "Cell title on beta features screen to enable product SKU input scanner in inventory settings.")

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

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ final class CardPresentPaymentsOnboardingUseCase: CardPresentPaymentsOnboardingU
3131
let storageManager: StorageManagerType
3232
let stores: StoresManager
3333
private var stripeGatewayIPPEnabled: Bool?
34+
private var canadaIPPEnabled: Bool?
3435

3536
@Published var state: CardPresentPaymentOnboardingState = .loading
3637

@@ -44,16 +45,31 @@ final class CardPresentPaymentsOnboardingUseCase: CardPresentPaymentsOnboardingU
4445
) {
4546
self.storageManager = storageManager
4647
self.stores = stores
47-
let action = AppSettingsAction.loadStripeInPersonPaymentsSwitchState(onCompletion: { [weak self] result in
48+
49+
let stripeAction = AppSettingsAction.loadStripeInPersonPaymentsSwitchState(onCompletion: { [weak self] result in
4850
switch result {
4951
case .success(let stripeGatewayIPPEnabled):
5052
self?.stripeGatewayIPPEnabled = stripeGatewayIPPEnabled
5153
default:
5254
break
5355
}
54-
self?.updateState()
5556
})
56-
stores.dispatch(action)
57+
stores.dispatch(stripeAction)
58+
59+
let canadaAction = AppSettingsAction.loadCanadaInPersonPaymentsSwitchState(onCompletion: { [weak self] result in
60+
switch result {
61+
case .success(let canadaIPPEnabled):
62+
self?.canadaIPPEnabled = canadaIPPEnabled
63+
default:
64+
break
65+
}
66+
})
67+
stores.dispatch(canadaAction)
68+
69+
// At the time of writing, actions are dispatched and processed synchronously, so the completion blocks for
70+
// loadStripeInPersonPaymentsSwitchState and loadCanadaInPersonPaymentsSwitchState should have been called already.
71+
// We defer updating the state until all settings are read to prevent unnecessary checks.
72+
updateState()
5773
}
5874

5975
func refresh() {
@@ -257,7 +273,7 @@ private extension CardPresentPaymentsOnboardingUseCase {
257273
}
258274

259275
func isCountrySupported(plugin: CardPresentPaymentsPlugins, countryCode: String) -> Bool {
260-
return plugin.supportedCountryCodes.contains(countryCode)
276+
return supportedCountryCodes(plugin: plugin).contains(countryCode)
261277
}
262278

263279
func getWCPayPlugin() -> SystemPlugin? {
@@ -363,13 +379,13 @@ private extension PaymentGatewayAccount {
363379
}
364380
}
365381

366-
private extension CardPresentPaymentsPlugins {
382+
private extension CardPresentPaymentsOnboardingUseCase {
367383
// This was moved from Yosemite so it can check the feature flag
368-
// In a future iteration, we might want to store all the configuration in the WooCommerce layer
369-
var supportedCountryCodes: [String] {
370-
switch self {
384+
// In a future iteration, we might want to store all the configuration in a better place
385+
func supportedCountryCodes(plugin: CardPresentPaymentsPlugins) -> [String] {
386+
switch plugin {
371387
case .wcPay:
372-
if ServiceLocator.featureFlagService.isFeatureFlagEnabled(.canadaInPersonPayments) {
388+
if canadaIPPEnabled == true {
373389
return ["US", "CA"]
374390
} else {
375391
return ["US"]

WooCommerce/WooCommerceTests/ViewRelated/CardPresentPayments/CardPresentPaymentsOnboardingUseCaseTests.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class CardPresentPaymentsOnboardingUseCaseTests: XCTestCase {
2424
switch action {
2525
case .loadStripeInPersonPaymentsSwitchState(let completion):
2626
completion(.success(true))
27+
case .loadCanadaInPersonPaymentsSwitchState(let completion):
28+
completion(.success(true))
2729
default:
2830
break
2931
}
@@ -323,7 +325,11 @@ class CardPresentPaymentsOnboardingUseCaseTests: XCTestCase {
323325
_ = CardPresentPaymentsOnboardingUseCase(storageManager: storageManager, stores: stores)
324326

325327
// Then
326-
XCTAssertEqual(stores.receivedActions.count, 2)
328+
329+
// AppSettingsAction.loadStripeInPersonPaymentsSwitchState
330+
// + AppSettingsAction.loadCanadaInPersonPaymentsSwitchState
331+
// + CardPresentPaymentAction.use
332+
XCTAssertEqual(stores.receivedActions.count, 3)
327333
let action = try XCTUnwrap(stores.receivedActions.last as? CardPresentPaymentAction)
328334

329335
switch action {
@@ -369,7 +375,11 @@ class CardPresentPaymentsOnboardingUseCaseTests: XCTestCase {
369375
_ = CardPresentPaymentsOnboardingUseCase(storageManager: storageManager, stores: stores)
370376

371377
// Then
372-
XCTAssertEqual(stores.receivedActions.count, 2)
378+
379+
// AppSettingsAction.loadStripeInPersonPaymentsSwitchState
380+
// + AppSettingsAction.loadCanadaInPersonPaymentsSwitchState
381+
// + CardPresentPaymentAction.use
382+
XCTAssertEqual(stores.receivedActions.count, 3)
373383
let action = try XCTUnwrap(stores.receivedActions.last as? CardPresentPaymentAction)
374384

375385
switch action {

Yosemite/Yosemite/Actions/AppSettingsAction.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,14 @@ public enum AppSettingsAction: Action {
136136
///
137137
case setStripeInPersonPaymentsSwitchState(isEnabled: Bool, onCompletion: (Result<Void, Error>) -> Void)
138138

139+
/// Loads the most recent state for the In-Person Payments in Canada beta feature switch
140+
///
141+
case loadCanadaInPersonPaymentsSwitchState(onCompletion: (Result<Bool, Error>) -> Void)
142+
143+
/// Sets the state for the In-Person Payments in Canada beta feature switch
144+
///
145+
case setCanadaInPersonPaymentsSwitchState(isEnabled: Bool, onCompletion: (Result<Void, Error>) -> Void)
146+
139147
/// Sets the state for the Product SKU Input Scanner beta feature switch.
140148
///
141149
case setProductSKUInputScannerFeatureSwitchState(isEnabled: Bool, onCompletion: (Result<Void, Error>) -> Void)

0 commit comments

Comments
 (0)