Skip to content

Commit

Permalink
Make states drop down pickers for US and Canada (#1395)
Browse files Browse the repository at this point in the history
* Make US and Canada have dropdowns for states

* Make dropdown hide after autofill

* Fix default value in playground

* update test

* Add StringDataElement

* update address ui test

* fix UI tests

* update test again

* Update StripeUICore/StripeUICore/Source/Elements/Factories/Address/AddressSpec.swift

Co-authored-by: Yuki <[email protected]>

* Clean up function for state dropdown

* StringDataElement -> RawDataElement

* Rename to TextOrDropdownElement

Co-authored-by: Yuki <[email protected]>
  • Loading branch information
porter-stripe and yuki-stripe authored Sep 14, 2022
1 parent d10d8fb commit 784a787
Show file tree
Hide file tree
Showing 14 changed files with 267 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ class PaymentSheetTestPlayground: UIViewController {
configuration.defaultValues = .init(
address: .init(
city: "San Francisco",
country: "CA",
country: "US",
line1: "510 Townsend St.",
postalCode: "94102",
state: "California"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ class PaymentSheet_AddressTests: XCTestCase {
app.textFields["City"].tap()
app.typeText("San Francisco")
app.textFields["State"].tap()
app.typeText("California")
app.pickerWheels.firstMatch.adjust(toPickerWheelValue: "California")
app.toolbars.buttons["Done"].tap()
// The save address button should still be disabled until we fill in all required fields
XCTAssertFalse(saveAddressButton.isEnabled)
app.textFields["ZIP"].tap()
Expand All @@ -55,7 +56,7 @@ class PaymentSheet_AddressTests: XCTestCase {
saveAddressButton.tap()

// The merchant app should get back the expected address
XCTAssertEqual(shippingButton.label, "Jane Doe, 510 Townsend St, Apt 152, San Francisco California 94102, US, +15555555555")
XCTAssertEqual(shippingButton.label, "Jane Doe, 510 Townsend St, Apt 152, San Francisco CA 94102, US, +15555555555")

// Opening the shipping address back up...
shippingButton.tap()
Expand Down Expand Up @@ -86,7 +87,7 @@ class PaymentSheet_AddressTests: XCTestCase {
saveAddressButton.tap()

// The merchant app should get back the expected address
XCTAssertEqual(shippingButton.label, "Jane Doe, 510 Townsend St., San Francisco California 94102, CA, +15555555555")
XCTAssertEqual(shippingButton.label, "Jane Doe, 510 Townsend St., San Francisco CA 94102, US, +15555555555")
}

func testAddressAutoComplete_UnitedStates() throws {
Expand All @@ -113,7 +114,7 @@ class PaymentSheet_AddressTests: XCTestCase {
XCTAssertEqual(app.textFields["Address line 1"].value as! String, "4 Pennsylvania Plaza")
XCTAssertEqual(app.textFields["Address line 2"].value as! String, "")
XCTAssertEqual(app.textFields["City"].value as! String, "New York")
XCTAssertEqual(app.textFields["State"].value as! String, "NY")
XCTAssertEqual(app.textFields["State"].value as! String, "New York")
XCTAssertEqual(app.textFields["ZIP"].value as! String, "10001")

// Type in phone number
Expand Down
5 changes: 3 additions & 2 deletions Stripe/AddressViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import UIKit
line1: line1,
line2: addressSection.line2?.text.nonEmpty,
postalCode: addressSection.postalCode?.text.nonEmpty,
state: addressSection.state?.text.nonEmpty
state: addressSection.state?.rawData.nonEmpty
)
return .init(
address: address,
Expand Down Expand Up @@ -348,7 +348,8 @@ extension AddressViewController: AutoCompleteViewControllerDelegate {
addressSection.line1?.setText(address.line1 ?? "")
addressSection.city?.setText(address.city ?? "")
addressSection.postalCode?.setText(address.postalCode ?? "")
addressSection.state?.setText(address.state ?? "")
addressSection.state?.setRawData(address.state ?? "")
addressSection.state?.view.resignFirstResponder()

self.selectedAutoCompleteResult = address
}
Expand Down
2 changes: 1 addition & 1 deletion Stripe/PaymentSheetFormFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ extension PaymentSheetFormFactory {
params.paymentMethodParams.nonnil_billingDetails.nonnil_address.city = city.text
}
if let state = section.state {
params.paymentMethodParams.nonnil_billingDetails.nonnil_address.state = state.text
params.paymentMethodParams.nonnil_billingDetails.nonnil_address.state = state.rawData
}
if let postalCode = section.postalCode {
params.paymentMethodParams.nonnil_billingDetails.nonnil_address.postalCode = postalCode.text
Expand Down
4 changes: 4 additions & 0 deletions StripeUICore/StripeUICore.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
36E1894A26FBAFC700A57EF4 /* InputFormColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E1894926FBAFC700A57EF4 /* InputFormColors.swift */; };
36E1894C26FBD60200A57EF4 /* PhoneNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E1894B26FBD60200A57EF4 /* PhoneNumber.swift */; };
36E1895027067A8700A57EF4 /* String+CountryEmoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E1894F27067A8700A57EF4 /* String+CountryEmoji.swift */; };
61078DA628C28D2D007C7001 /* TextOrDropdownElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61078DA528C28D2D007C7001 /* TextOrDropdownElement.swift */; };
614E0763284FDCE100FB70F4 /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = 614E0762284FDCE100FB70F4 /* [email protected] */; };
61A6294527E401C900C8DF08 /* CALayer+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A6294427E401C900C8DF08 /* CALayer+StripeUICore.swift */; };
6B573C9727C5BEF00082C0B3 /* TextFieldElement+AccountFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B573C9627C5BEF00082C0B3 /* TextFieldElement+AccountFactory.swift */; };
Expand Down Expand Up @@ -132,6 +133,7 @@
36E1894926FBAFC700A57EF4 /* InputFormColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputFormColors.swift; sourceTree = "<group>"; };
36E1894B26FBD60200A57EF4 /* PhoneNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumber.swift; sourceTree = "<group>"; };
36E1894F27067A8700A57EF4 /* String+CountryEmoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+CountryEmoji.swift"; sourceTree = "<group>"; };
61078DA528C28D2D007C7001 /* TextOrDropdownElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextOrDropdownElement.swift; sourceTree = "<group>"; };
614E0762284FDCE100FB70F4 /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
61A6294427E401C900C8DF08 /* CALayer+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CALayer+StripeUICore.swift"; sourceTree = "<group>"; };
6B573C9627C5BEF00082C0B3 /* TextFieldElement+AccountFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextFieldElement+AccountFactory.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -452,6 +454,7 @@
E6752D8D26F420F70062B821 /* Section */,
E6752D9326F420F70062B821 /* StaticElement.swift */,
E6752D9426F420F70062B821 /* TextField */,
61078DA528C28D2D007C7001 /* TextOrDropdownElement.swift */,
);
path = Elements;
sourceTree = "<group>";
Expand Down Expand Up @@ -808,6 +811,7 @@
files = (
E61ADAC5270E4916004ED998 /* AddressSpecProvider.swift in Sources */,
D0CF912A273B104A00EE2E60 /* Button.swift in Sources */,
61078DA628C28D2D007C7001 /* TextOrDropdownElement.swift in Sources */,
D08FD70B270BD28D009FE060 /* UIBarButtonItem+StripeUICore.swift in Sources */,
D0BEB3FC273CA7C60031D677 /* UIFont+StripeUICore.swift in Sources */,
367199DC280756C800773613 /* UIStackView+StripeUICore.swift in Sources */,
Expand Down
162 changes: 160 additions & 2 deletions StripeUICore/StripeUICore/Resources/JSON/localized_address_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,37 @@
"CA":{
"fmt":"%N%n%O%n%A%n%C %S %Z",
"require":"ACSZ",
"zip":"[ABCEGHJKLMNPRSTVXY]\\d[ABCEGHJ-NPRSTV-Z] ?\\d[ABCEGHJ-NPRSTV-Z]\\d"
"zip":"[ABCEGHJKLMNPRSTVXY]\\d[ABCEGHJ-NPRSTV-Z] ?\\d[ABCEGHJ-NPRSTV-Z]\\d",
"sub_keys": [
"AB",
"BC",
"MB",
"NB",
"NL",
"NT",
"NS",
"NU",
"ON",
"PE",
"QC",
"SK",
"YT",
],
"sub_labels": [
"Alberta",
"British Columbia",
"Manitoba",
"New Brunswick",
"Newfoundland and Labrador",
"Northwest Territories",
"Nova Scotia",
"Nunavut",
"Ontario",
"Prince Edward Island",
"Quebec",
"Saskatchewan",
"Yukon",
],
},
"CD":{
"fmt":"%C"
Expand Down Expand Up @@ -967,7 +997,135 @@
"require":"ACSZ",
"state_name_type":"state",
"zip":"\\d{5}",
"zip_name_type":"zip"
"zip_name_type":"zip",
"sub_keys": [
"AL",
"AK",
"AS",
"AZ",
"AR",
"AA",
"AE",
"AP",
"CA",
"CO",
"CT",
"DE",
"DC",
"FL",
"GA",
"GU",
"HI",
"ID",
"IL",
"IN",
"IA",
"KS",
"KY",
"LA",
"ME",
"MH",
"MD",
"MA",
"MI",
"FM",
"MN",
"MS",
"MO",
"MT",
"NE",
"NV",
"NH",
"NJ",
"NM",
"NY",
"NC",
"ND",
"MP",
"OH",
"OK",
"OR",
"PW",
"PA",
"PR",
"RI",
"SC",
"SD",
"TN",
"TX",
"UT",
"VT",
"VI",
"VA",
"WA",
"WV",
"WI",
"WY",
],
"sub_labels": [
"Alabama",
"Alaska",
"American Samoa",
"Arizona",
"Arkansas",
"Armed Forces (AA)",
"Armed Forces (AE)",
"Armed Forces (AP)",
"California",
"Colorado",
"Connecticut",
"Delaware",
"District of Columbia",
"Florida",
"Georgia",
"Guam",
"Hawaii",
"Idaho",
"Illinois",
"Indiana",
"Iowa",
"Kansas",
"Kentucky",
"Louisiana",
"Maine",
"Marshall Islands",
"Maryland",
"Massachusetts",
"Michigan",
"Micronesia",
"Minnesota",
"Mississippi",
"Missouri",
"Montana",
"Nebraska",
"Nevada",
"New Hampshire",
"New Jersey",
"New Mexico",
"New York",
"North Carolina",
"North Dakota",
"Northern Mariana Islands",
"Ohio",
"Oklahoma",
"Oregon",
"Palau",
"Pennsylvania",
"Puerto Rico",
"Rhode Island",
"South Carolina",
"South Dakota",
"Tennessee",
"Texas",
"Utah",
"Vermont",
"Virgin Islands",
"Virginia",
"Washington",
"West Virginia",
"Wisconsin",
"Wyoming",
]
},
"UY":{
"fmt":"%N%n%O%n%A%n%Z %C %S",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ import UIKit
public private(set) var line1: TextFieldElement?
public private(set) var line2: TextFieldElement?
public private(set) var city: TextFieldElement?
public private(set) var state: TextFieldElement?
public private(set) var state: TextOrDropdownElement?
public private(set) var postalCode: TextFieldElement?
public let sameAsCheckbox: CheckboxElement

Expand All @@ -127,7 +127,7 @@ import UIKit
return countryCodes[country.selectedIndex]
}
var addressDetails: AddressDetails {
let address = AddressDetails.Address(city: city?.text, country: selectedCountryCode, line1: line1?.text, line2: line2?.text, postalCode: postalCode?.text, state: state?.text)
let address = AddressDetails.Address(city: city?.text, country: selectedCountryCode, line1: line1?.text, line2: line2?.text, postalCode: postalCode?.text, state: state?.rawData)
return .init(name: name?.text, phone: phone?.phoneNumber?.string(as: .e164), address: address)
}
public let countryCodes: [String]
Expand Down Expand Up @@ -271,7 +271,7 @@ import UIKit
line1: line1?.text,
line2: line2?.text,
postalCode: postalCode?.text,
state: state?.text
state: state?.rawData
)

// Get the address spec for the country and filter out unused fields
Expand Down Expand Up @@ -304,12 +304,14 @@ import UIKit
city = fieldOrdering.contains(.city) ?
spec.makeCityElement(defaultValue: address.city, theme: theme) : nil
state = fieldOrdering.contains(.state) ?
spec.makeStateElement(defaultValue: address.state, theme: theme) : nil
spec.makeStateElement(defaultValue: address.state,
stateDict: Dictionary(uniqueKeysWithValues: zip(spec.subKeys ?? [], spec.subLabels ?? [])),
theme: theme) : nil
postalCode = fieldOrdering.contains(.postal) ?
spec.makePostalElement(countryCode: countryCode, defaultValue: address.postalCode, theme: theme) : nil

// Order the address fields according to `fieldOrdering`
let addressFields: [TextFieldElement?] = fieldOrdering.reduce([]) { partialResult, fieldType in
let addressFields: [TextOrDropdownElement?] = fieldOrdering.reduce([]) { partialResult, fieldType in
// This should be a flatMap but I'm having trouble satisfying the compiler
switch fieldType {
case .line:
Expand Down Expand Up @@ -344,7 +346,7 @@ import UIKit
if let postalCode = postalCode, postalCode.text.nonEmpty != address.postalCode?.nonEmpty {
allDisplayedFieldsEqual = false
}
if let state = state, state.text.nonEmpty != address.state?.nonEmpty {
if let state = state, state.rawData.nonEmpty != address.state?.nonEmpty {
allDisplayedFieldsEqual = false
}
return allDisplayedFieldsEqual
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,30 @@ extension AddressSpec {
).makeElement(theme: theme)
}

func makeStateElement(defaultValue: String?, theme: ElementsUITheme = .default) -> TextFieldElement {
return TextFieldElement.Address.StateConfiguration(
label: stateNameType.localizedLabel,
defaultValue: defaultValue,
isOptional: !requiredFields.contains(.state)
).makeElement(theme: theme)
func makeStateElement(defaultValue: String?, stateDict: [String: String], theme: ElementsUITheme = .default) -> TextOrDropdownElement {
// If no state dict just use a textfield for state
if stateDict.isEmpty {
return TextFieldElement.Address.StateConfiguration(
label: stateNameType.localizedLabel,
defaultValue: defaultValue,
isOptional: !requiredFields.contains(.state)
).makeElement(theme: theme)
}

// Otherwise create a dropdown with the provided states
let items = stateDict.map({DropdownFieldElement.DropdownItem(pickerDisplayName: $0.value,
labelDisplayName: $0.value,
accessibilityLabel: $0.value,
rawData: $0.key)}).sorted {$0.pickerDisplayName < $1.pickerDisplayName}

let defaultIndex = items.firstIndex(where: {$0.rawData.lowercased() == defaultValue?.lowercased()
|| $0.pickerDisplayName.lowercased() == defaultValue?.lowercased()}) ?? 0

return DropdownFieldElement(items: items,
defaultIndex: defaultIndex,
label: stateNameType.localizedLabel,
theme: theme,
didUpdate: nil)
}

func makePostalElement(countryCode: String, defaultValue: String?, theme: ElementsUITheme = .default) -> TextFieldElement {
Expand Down
Loading

0 comments on commit 784a787

Please sign in to comment.