Skip to content

Commit 88abf89

Browse files
committed
Restore link signup customer input
1 parent 4dba38d commit 88abf89

File tree

10 files changed

+82
-36
lines changed

10 files changed

+82
-36
lines changed

Stripe/StripeiOSTests/LinkInlineSignupElementSnapshotTests.swift

+1
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ extension LinkInlineSignupElementSnapshotTests {
148148
configuration: configuration,
149149
showCheckbox: showCheckbox,
150150
accountService: MockAccountService(),
151+
previousCustomerInput: nil,
151152
linkAccount: linkAccount,
152153
country: country
153154
)

Stripe/StripeiOSTests/LinkSignupViewModelTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import StripeCoreTestUtils
1212
import XCTest
1313

1414
@testable@_spi(STP) import Stripe
15-
@testable@_spi(STP) import StripeCore
1615
@testable@_spi(STP) import StripePayments
1716
@testable@_spi(STP) import StripePaymentSheet
1817
import StripePaymentsTestUtils
@@ -208,6 +207,7 @@ extension LinkInlineSignupViewModelTests {
208207
configuration: PaymentSheet.Configuration(),
209208
showCheckbox: showCheckbox,
210209
accountService: MockAccountService(shouldFailLookup: shouldFailLookup),
210+
previousCustomerInput: nil,
211211
linkAccount: linkAccount,
212212
country: country
213213
)

StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Elements/InlineSignup/LinkInlineSignupElement.swift

+13-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import UIKit
1111

1212
// TODO: Refactor this to be a ContainerElement and contain its sub-elements.
13-
final class LinkInlineSignupElement: Element {
13+
final class LinkInlineSignupElement: PaymentMethodElement {
1414
let collectsUserInput: Bool = true
1515

1616
let signupView: LinkInlineSignupView
@@ -40,12 +40,14 @@ final class LinkInlineSignupElement: Element {
4040
configuration: PaymentSheet.Configuration,
4141
linkAccount: PaymentSheetLinkAccount?,
4242
country: String?,
43-
showCheckbox: Bool
43+
showCheckbox: Bool,
44+
previousCustomerInput: IntentConfirmParams?
4445
) {
4546
self.init(viewModel: LinkInlineSignupViewModel(
4647
configuration: configuration,
4748
showCheckbox: showCheckbox,
4849
accountService: LinkAccountService(apiClient: configuration.apiClient),
50+
previousCustomerInput: previousCustomerInput?.linkInlineSignupCustomerInput,
4951
linkAccount: linkAccount,
5052
country: country
5153
))
@@ -56,6 +58,15 @@ final class LinkInlineSignupElement: Element {
5658
self.signupView.delegate = self
5759
}
5860

61+
func updateParams(params: IntentConfirmParams) -> IntentConfirmParams? {
62+
params.linkInlineSignupCustomerInput = .init(
63+
phoneNumber: signupView.phoneNumberElement.phoneNumber,
64+
name: signupView.nameElement.text,
65+
email: signupView.emailElement.emailAddressString,
66+
checkboxSelected: signupView.checkboxElement.isChecked
67+
)
68+
return params
69+
}
5970
}
6071

6172
extension LinkInlineSignupElement: LinkInlineSignupViewDelegate {

StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Elements/InlineSignup/LinkInlineSignupView-CheckboxElement.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ extension LinkInlineSignupView {
2121
private let appearance: PaymentSheet.Appearance
2222
/// Controls the stroke color of the checkbox
2323
private let borderColor: UIColor
24+
let initialIsSelectedValue: Bool
2425

2526
var view: UIView {
2627
return checkboxButton
@@ -56,15 +57,16 @@ extension LinkInlineSignupView {
5657

5758
let checkbox = CheckboxButton(text: text, description: description, theme: appearanceCopy.asElementsTheme)
5859
checkbox.addTarget(self, action: #selector(didToggleCheckbox), for: .touchUpInside)
59-
checkbox.isSelected = false
60+
checkbox.isSelected = initialIsSelectedValue
6061

6162
return checkbox
6263
}()
6364

64-
init(merchantName: String, appearance: PaymentSheet.Appearance, borderColor: UIColor) {
65+
init(merchantName: String, appearance: PaymentSheet.Appearance, borderColor: UIColor, isSelected: Bool) {
6566
self.merchantName = merchantName
6667
self.appearance = appearance
6768
self.borderColor = borderColor
69+
self.initialIsSelectedValue = isSelected
6870
}
6971

7072
func setUserInteraction(isUserInteractionEnabled: Bool) {

StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Elements/InlineSignup/LinkInlineSignupView.swift

+21-7
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ final class LinkInlineSignupView: UIView {
3131
private(set) lazy var checkboxElement = CheckboxElement(
3232
merchantName: viewModel.configuration.merchantDisplayName,
3333
appearance: viewModel.configuration.appearance,
34-
borderColor: borderColor
34+
borderColor: borderColor,
35+
isSelected: viewModel.saveCheckboxChecked
3536
)
3637

3738
private(set) lazy var emailElement: LinkEmailElement = {
38-
let element = LinkEmailElement(defaultValue: viewModel.emailAddress,
39+
let element = LinkEmailElement(defaultValue: viewModel.initialEmail ?? viewModel.emailAddress,
3940
isOptional: viewModel.isEmailOptional,
4041
showLogo: viewModel.mode != .textFieldsOnlyPhoneFirst,
4142
theme: theme)
@@ -44,7 +45,7 @@ final class LinkInlineSignupView: UIView {
4445
}()
4546

4647
private(set) lazy var nameElement: TextFieldElement = {
47-
let configuration = TextFieldElement.NameConfiguration(type: .full, defaultValue: viewModel.legalName)
48+
let configuration = TextFieldElement.NameConfiguration(type: .full, defaultValue: viewModel.initialName ?? viewModel.legalName)
4849
return TextFieldElement(configuration: configuration, theme: theme)
4950
}()
5051

@@ -53,15 +54,28 @@ final class LinkInlineSignupView: UIView {
5354
// Otherwise, we'd imply consumer consent when it hasn't occurred.
5455
switch viewModel.mode {
5556
case .checkbox:
57+
let defaultCountryCode = viewModel.initialPhoneNumber?.countryCode ?? viewModel.configuration.defaultBillingDetails.address.country
58+
let defaultPhoneNumber = viewModel.initialPhoneNumber?.number ?? viewModel.configuration.defaultBillingDetails.phone
5659
return PhoneNumberElement(
57-
defaultCountryCode: viewModel.configuration.defaultBillingDetails.address.country,
58-
defaultPhoneNumber: viewModel.configuration.defaultBillingDetails.phone,
60+
defaultCountryCode: defaultCountryCode,
61+
defaultPhoneNumber: defaultPhoneNumber,
5962
theme: theme
6063
)
6164
case .textFieldsOnlyEmailFirst:
62-
return PhoneNumberElement(isOptional: viewModel.isPhoneNumberOptional, theme: theme)
65+
return PhoneNumberElement(
66+
defaultCountryCode: viewModel.initialPhoneNumber?.countryCode,
67+
defaultPhoneNumber: viewModel.initialPhoneNumber?.number,
68+
isOptional: viewModel.isPhoneNumberOptional,
69+
theme: theme
70+
)
6371
case .textFieldsOnlyPhoneFirst:
64-
return PhoneNumberElement(isOptional: viewModel.isPhoneNumberOptional, infoView: LinkMoreInfoView(), theme: theme)
72+
return PhoneNumberElement(
73+
defaultCountryCode: viewModel.initialPhoneNumber?.countryCode,
74+
defaultPhoneNumber: viewModel.initialPhoneNumber?.number,
75+
isOptional: viewModel.isPhoneNumberOptional,
76+
infoView: LinkMoreInfoView(),
77+
theme: theme
78+
)
6579
}
6680
}()
6781

StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/ViewModels/LinkInlineSignupViewModel.swift

+16-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ protocol LinkInlineSignupViewModelDelegate: AnyObject {
1515
func signupViewModelDidUpdate(_ viewModel: LinkInlineSignupViewModel)
1616
}
1717

18+
struct LinkInlineSignupCustomerInput: Equatable {
19+
let phoneNumber: PhoneNumber?
20+
let name: String?
21+
let email: String?
22+
let checkboxSelected: Bool?
23+
}
24+
1825
final class LinkInlineSignupViewModel {
1926
enum Action: Equatable {
2027
case signupAndPay(account: PaymentSheetLinkAccount, phoneNumber: PhoneNumber, legalName: String?)
@@ -38,8 +45,11 @@ final class LinkInlineSignupViewModel {
3845
let configuration: PaymentSheet.Configuration
3946

4047
let mode: Mode
48+
let initialEmail: String?
49+
let initialPhoneNumber: PhoneNumber?
50+
let initialName: String?
4151

42-
var saveCheckboxChecked: Bool = false {
52+
var saveCheckboxChecked: Bool {
4353
didSet {
4454
if saveCheckboxChecked != oldValue {
4555
notifyUpdate()
@@ -291,13 +301,18 @@ final class LinkInlineSignupViewModel {
291301
configuration: PaymentSheet.Configuration,
292302
showCheckbox: Bool,
293303
accountService: LinkAccountServiceProtocol,
304+
previousCustomerInput: LinkInlineSignupCustomerInput?,
294305
linkAccount: PaymentSheetLinkAccount? = nil,
295306
country: String? = nil
296307
) {
297308
self.configuration = configuration
298309
self.accountService = accountService
299310
self.linkAccount = linkAccount
300311
self.emailAddress = linkAccount?.email
312+
self.saveCheckboxChecked = previousCustomerInput?.checkboxSelected ?? false
313+
self.initialEmail = previousCustomerInput?.email
314+
self.initialPhoneNumber = previousCustomerInput?.phoneNumber
315+
self.initialName = previousCustomerInput?.name
301316
if let email = self.emailAddress,
302317
!email.isEmpty {
303318
emailWasPrefilled = true

StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/IntentConfirmParams.swift

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ final class IntentConfirmParams {
3636

3737
var financialConnectionsLinkedBank: FinancialConnectionsLinkedBank?
3838
var instantDebitsLinkedBank: InstantDebitsLinkedBank?
39+
/// Hack: Contains the customer input in the link inline signup element (e.g. email, checkbox state) so that it can be preserved across `FlowController.update` etc.
40+
var linkInlineSignupCustomerInput: LinkInlineSignupCustomerInput?
3941

4042
var paymentSheetLabel: String {
4143
if let last4 = (financialConnectionsLinkedBank?.last4 ?? instantDebitsLinkedBank?.last4) {

StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Card.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ extension PaymentSheetFormFactory {
9393
configuration: configuration,
9494
linkAccount: linkAccount,
9595
country: countryCode,
96-
showCheckbox: !shouldDisplaySaveCheckbox
96+
showCheckbox: !shouldDisplaySaveCheckbox,
97+
previousCustomerInput: previousCustomerInput
9798
)
9899
elements.append(inlineSignupElement)
99100
}

StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CardSectionElementTest.swift

+17-21
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@
55
// Created by Yuki Tokuhiro on 10/2/24.
66
//
77

8-
import XCTest
98
@testable@_spi(STP) import StripeCore
109
@testable@_spi(STP) import StripePayments
1110
@testable@_spi(STP) import StripePaymentSheet
1211
@testable@_spi(STP) import StripePaymentsTestUtils
1312
@testable@_spi(STP) import StripePaymentsUI
1413
@testable@_spi(STP) import StripeUICore
14+
import XCTest
1515

1616
@MainActor
1717
final class CardSectionElementTest: XCTestCase {
1818
let window: UIWindow = UIWindow(frame: .init(x: 0, y: 0, width: 428, height: 926))
1919

20-
func testPreservesPreviousCustomerInput() async {
20+
func testLinkSignupPreservesPreviousCustomerInput() async {
2121
await PaymentSheetLoader.loadMiscellaneousSingletons()
2222
func makeForm(previousCustomerInput: IntentConfirmParams?) -> PaymentMethodElement {
2323
let intent: Intent = ._testPaymentIntent(paymentMethodTypes: [.card])
@@ -27,7 +27,7 @@ final class CardSectionElementTest: XCTestCase {
2727
elementsSession: ._testValue(paymentMethodTypes: ["card"], isLinkPassthroughModeEnabled: true),
2828
previousCustomerInput: previousCustomerInput,
2929
formCache: .init(),
30-
configuration: configuration,
30+
configuration: .init(),
3131
headerView: nil,
3232
analyticsHelper: ._testValue(),
3333
delegate: self
@@ -40,29 +40,25 @@ final class CardSectionElementTest: XCTestCase {
4040
formVC.viewDidAppear(false)
4141
return formVC.form
4242
}
43-
var configuration = PaymentSheet.Configuration()
44-
configuration.customer = .init(id: "id", ephemeralKeySecret: "sec")
4543
let form = makeForm(previousCustomerInput: nil)
46-
let checkbox = form.getCheckboxElement(startingWith: "Save payment details")!
4744
let linkInlineSignupElement: LinkInlineSignupElement = form.getElement()!
4845
let linkInlineView = linkInlineSignupElement.signupView
49-
50-
XCTAssertNotNil(checkbox) // Checkbox should appear since this is a PI w/ customer
46+
XCTAssertNotNil(linkInlineView.checkboxElement) // Checkbox should appear since this is a PI w/o customer
5147
form.getTextFieldElement("Card number")?.setText("4242424242424242")
5248
form.getTextFieldElement("MM / YY").setText("1232")
5349
form.getTextFieldElement("CVC").setText("123")
5450
form.getTextFieldElement("ZIP").setText("65432")
55-
56-
XCTAssertEqual(form.getAllUnwrappedSubElements().count, 14)
57-
// XCTAssertNotNil(form.mandateString)
51+
5852
// Simulate selecting checkbox
59-
checkbox.isSelected = true
60-
checkbox.didToggleCheckbox()
61-
53+
linkInlineView.checkboxElement.isChecked = true
54+
linkInlineView.checkboxElement.didToggleCheckbox()
55+
6256
// Set the email & phone number
63-
linkInlineView.emailElement.emailAddressElement.setText("\(UUID().uuidString)@foo.com")
57+
let email = "\(UUID().uuidString)@foo.com"
58+
linkInlineView.emailElement.emailAddressElement.setText(email)
6459
linkInlineView.phoneNumberElement.countryDropdownElement.setRawData("GB")
6560
linkInlineView.phoneNumberElement.textFieldElement.setText("1234567890")
61+
linkInlineView.nameElement.setText("John Doe")
6662

6763
// Generate params from the form
6864
guard let intentConfirmParams = form.updateParams(params: IntentConfirmParams(type: .stripe(.card))) else {
@@ -72,17 +68,17 @@ final class CardSectionElementTest: XCTestCase {
7268

7369
// Re-generate the form and validate that it carries over all previous customer input
7470
let regeneratedForm = makeForm(previousCustomerInput: intentConfirmParams)
71+
let regeneratedLinkInlineSignupElement: LinkInlineSignupElement = regeneratedForm.getElement()!
72+
let regeneratedLinkInlineView = linkInlineSignupElement.signupView
7573
guard let regeneratedIntentConfirmParams = regeneratedForm.updateParams(params: IntentConfirmParams(type: .stripe(.card))) else {
7674
XCTFail("Regenerated form failed to create params. Validation state: \(regeneratedForm.validationState) \n Form: \(regeneratedForm)")
7775
return
7876
}
79-
// Ensure checkbox remains selected
80-
XCTAssertTrue(regeneratedForm.getCheckboxElement(startingWith: "Save payment details")!.isSelected)
8177
XCTAssertEqual(regeneratedIntentConfirmParams, intentConfirmParams)
82-
let linkInlineSignupElement2: LinkInlineSignupElement = regeneratedForm.getElement()!
83-
let linkInlineView2 = linkInlineSignupElement2.signupView
84-
print(linkInlineView2)
85-
78+
XCTAssertTrue(regeneratedLinkInlineSignupElement.isChecked)
79+
XCTAssertEqual(regeneratedLinkInlineView.emailElement.emailAddressString, email)
80+
XCTAssertEqual(regeneratedLinkInlineView.nameElement.text, "John Doe")
81+
XCTAssertEqual(regeneratedLinkInlineView.phoneNumberElement.phoneNumber, PhoneNumber(number: "1234567890", countryCode: "GB"))
8682
}
8783
}
8884

StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/PaymentSheetLPMConfirmFlowTests.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -979,11 +979,15 @@ extension IntentConfirmParams: Equatable {
979979
print("Instant debits linked banks not equal: \(lhs.instantDebitsLinkedBank.debugDescription) vs \(rhs.instantDebitsLinkedBank.debugDescription)")
980980
return false
981981
}
982+
if lhs.linkInlineSignupCustomerInput != rhs.linkInlineSignupCustomerInput {
983+
print("Link inline signup customer input not equal: \(lhs.linkInlineSignupCustomerInput.debugDescription) vs \(rhs.linkInlineSignupCustomerInput.debugDescription)")
984+
return false
985+
}
982986

983987
// Sanity check to make sure when we add new properties, we check them here
984988
let mirror = Mirror(reflecting: lhs)
985989
let propertyCount = mirror.children.count
986-
XCTAssertEqual(propertyCount, 7)
990+
XCTAssertEqual(propertyCount, 8)
987991

988992
return true
989993
}

0 commit comments

Comments
 (0)