diff --git a/Stripe/PublicHeaders/STPAddress.h b/Stripe/PublicHeaders/STPAddress.h index d62193db919..0e720efb473 100644 --- a/Stripe/PublicHeaders/STPAddress.h +++ b/Stripe/PublicHeaders/STPAddress.h @@ -166,6 +166,24 @@ typedef NS_ENUM(NSUInteger, STPBillingAddressFields) { */ - (BOOL)containsRequiredFields:(STPBillingAddressFields)requiredFields; +/** + Checks if this STPAddress has any content (possibly invalid) in any of the + desired billing address fields. + + Where `containsRequiredFields:` validates that this STPAddress contains valid data in + all of the required fields, this method checks for the existence of *any* data. + + For example, if `desiredFields` is `STPBillingAddressFieldsZip`, this will check + if the postalCode is empty. + + Note: When `desiredFields == STPBillingAddressFieldsNone`, this method always returns + NO. + + @parameter desiredFields The billing address information the caller is interested in. + @return YES if there is any data in this STPAddress that's relevant for those fields. + */ +- (BOOL)containsContentForBillingAddressFields:(STPBillingAddressFields)desiredFields; + /** Checks if this STPAddress has the level of valid address information required by the passed in setting. @@ -177,6 +195,22 @@ typedef NS_ENUM(NSUInteger, STPBillingAddressFields) { */ - (BOOL)containsRequiredShippingAddressFields:(PKAddressField)requiredFields; +/** + Checks if this STPAddress has any content (possibly invalid) in any of the + desired shipping address fields. + + Where `containsRequiredShippingAddressFields:` validates that this STPAddress + contains valid data in all of the required fields, this method checks for the + existence of *any* data. + + Note: When `desiredFields == PKAddressFieldNone`, this method always returns + NO. + + @parameter desiredFields The shipping address information the caller is interested in. + @return YES if there is any data in this STPAddress that's relevant for those fields. + */ +- (BOOL)containsContentForShippingAddressFields:(PKAddressField)desiredFields; + /** Converts an STPBillingAddressFields enum value into the closest equivalent representation of PKAddressField options diff --git a/Stripe/STPAddCardViewController.m b/Stripe/STPAddCardViewController.m index a54b3175c8b..c6e2b6ee4a1 100644 --- a/Stripe/STPAddCardViewController.m +++ b/Stripe/STPAddCardViewController.m @@ -145,8 +145,11 @@ - (void)createAndSetupViews { } [addressHeaderView.button addTarget:self action:@selector(useShippingAddress:) forControlEvents:UIControlEventTouchUpInside]; - BOOL needsAddress = self.configuration.requiredBillingAddressFields != STPBillingAddressFieldsNone && !self.addressViewModel.isValid; - BOOL buttonVisible = (needsAddress && self.shippingAddress != nil && !self.hasUsedShippingAddress); + STPBillingAddressFields requiredFields = self.configuration.requiredBillingAddressFields; + BOOL needsAddress = requiredFields != STPBillingAddressFieldsNone && !self.addressViewModel.isValid; + BOOL buttonVisible = (needsAddress && + [self.shippingAddress containsContentForBillingAddressFields:requiredFields] + && !self.hasUsedShippingAddress); addressHeaderView.buttonHidden = !buttonVisible; [addressHeaderView setNeedsLayout]; _addressHeaderView = addressHeaderView; diff --git a/Stripe/STPAddress.m b/Stripe/STPAddress.m index fc723c8c9ff..b8067716960 100644 --- a/Stripe/STPAddress.m +++ b/Stripe/STPAddress.m @@ -275,6 +275,19 @@ - (BOOL)containsRequiredFields:(STPBillingAddressFields)requiredFields { return containsFields; } +- (BOOL)containsContentForBillingAddressFields:(STPBillingAddressFields)desiredFields { + switch (desiredFields) { + case STPBillingAddressFieldsNone: + return NO; + case STPBillingAddressFieldsZip: + return self.postalCode.length > 0; + case STPBillingAddressFieldsFull: + return [self hasPartialPostalAddress]; + } + + return NO; +} + - (BOOL)containsRequiredShippingAddressFields:(PKAddressField)requiredFields { BOOL containsFields = YES; if (requiredFields & PKAddressFieldName) { @@ -292,6 +305,13 @@ - (BOOL)containsRequiredShippingAddressFields:(PKAddressField)requiredFields { return containsFields; } +- (BOOL)containsContentForShippingAddressFields:(PKAddressField)desiredFields { + return (((desiredFields & PKAddressFieldName) && self.name.length > 0) + || ((desiredFields & PKAddressFieldEmail) && self.email.length > 0) + || ((desiredFields & PKAddressFieldPhone) && self.phone.length > 0) + || ((desiredFields & PKAddressFieldPostalAddress) && [self hasPartialPostalAddress])); +} + - (BOOL)hasValidPostalAddress { return (self.line1.length > 0 && self.city.length > 0 @@ -301,6 +321,21 @@ - (BOOL)hasValidPostalAddress { countryCode:self.country] == STPCardValidationStateValid)); } +/** + Does this STPAddress contain any data in the postal address fields? + + If they are all empty or nil, returns NO. Even a single character in a + single field will return YES. + */ +- (BOOL)hasPartialPostalAddress { + return (self.line1.length > 0 + || self.line2.length > 0 + || self.city.length > 0 + || self.country.length > 0 + || self.state.length > 0 + || self.postalCode.length > 0); +} + + (PKAddressField)applePayAddressFieldsFromBillingAddressFields:(STPBillingAddressFields)billingAddressFields { FAUXPAS_IGNORED_IN_METHOD(APIAvailability); switch (billingAddressFields) { diff --git a/Stripe/STPShippingAddressViewController.m b/Stripe/STPShippingAddressViewController.m index a6fe42f3b9b..91edd9642d0 100644 --- a/Stripe/STPShippingAddressViewController.m +++ b/Stripe/STPShippingAddressViewController.m @@ -137,8 +137,11 @@ - (void)createAndSetupViews { forState:UIControlStateNormal]; [headerView.button addTarget:self action:@selector(useBillingAddress:) forControlEvents:UIControlEventTouchUpInside]; - BOOL needsAddress = self.configuration.requiredShippingAddressFields & PKAddressFieldPostalAddress && !self.addressViewModel.isValid; - BOOL buttonVisible = (needsAddress && self.billingAddress != nil && !self.hasUsedBillingAddress); + PKAddressField requiredFields = self.configuration.requiredShippingAddressFields; + BOOL needsAddress = (requiredFields & PKAddressFieldPostalAddress) && !self.addressViewModel.isValid; + BOOL buttonVisible = (needsAddress + && [self.billingAddress containsContentForShippingAddressFields:requiredFields] + && !self.hasUsedBillingAddress); headerView.button.alpha = buttonVisible ? 1 : 0; [headerView setNeedsLayout]; _addressHeaderView = headerView; diff --git a/Tests/Tests/STPAddCardViewControllerLocalizationTests.m b/Tests/Tests/STPAddCardViewControllerLocalizationTests.m index af62b6d5e32..9839a28b62f 100644 --- a/Tests/Tests/STPAddCardViewControllerLocalizationTests.m +++ b/Tests/Tests/STPAddCardViewControllerLocalizationTests.m @@ -51,6 +51,7 @@ - (void)performSnapshotTestForLanguage:(NSString *)language delivery:(BOOL)deliv STPAddCardViewController *addCardVC = [[STPAddCardViewController alloc] initWithConfiguration:config theme:[STPTheme defaultTheme]]; addCardVC.shippingAddress = [STPAddress new]; + addCardVC.shippingAddress.line1 = @"1"; // trigger "use shipping address" button UINavigationController *navController = [UINavigationController new]; navController.view.frame = CGRectMake(0, 0, 320, 750); diff --git a/Tests/Tests/STPAddressTests.m b/Tests/Tests/STPAddressTests.m index 999bef8adb6..85b9c459681 100644 --- a/Tests/Tests/STPAddressTests.m +++ b/Tests/Tests/STPAddressTests.m @@ -428,6 +428,51 @@ - (void)testContainsRequiredFieldsFull { XCTAssertTrue([address containsRequiredFields:STPBillingAddressFieldsFull]); } +- (void)testContainsContentForBillingAddressFields { + STPAddress *address = [STPAddress new]; + + // Empty address should return false for everything + XCTAssertFalse([address containsContentForBillingAddressFields:STPBillingAddressFieldsNone]); + XCTAssertFalse([address containsContentForBillingAddressFields:STPBillingAddressFieldsZip]); + XCTAssertFalse([address containsContentForBillingAddressFields:STPBillingAddressFieldsFull]); + + // 1+ characters in postalCode will return true for .Zip && .Full + address.postalCode = @"0"; + XCTAssertFalse([address containsContentForBillingAddressFields:STPBillingAddressFieldsNone]); + XCTAssertTrue([address containsContentForBillingAddressFields:STPBillingAddressFieldsZip]); + XCTAssertTrue([address containsContentForBillingAddressFields:STPBillingAddressFieldsFull]); + // empty string returns false + address.postalCode = @""; + XCTAssertFalse([address containsContentForBillingAddressFields:STPBillingAddressFieldsNone]); + XCTAssertFalse([address containsContentForBillingAddressFields:STPBillingAddressFieldsZip]); + XCTAssertFalse([address containsContentForBillingAddressFields:STPBillingAddressFieldsFull]); + address.postalCode = nil; + + // Test every other property that contributes to the full address, ensuring it returns True for .Full only + // This is *not* refactoring-safe, but I think it's better than a bunch of duplicated code + for (NSString *propertyName in @[@"line1", @"line2", @"city", @"state", @"country"]) { + for (NSString *testValue in @[@"a", @"0", @"Foo Bar"]) { + [address setValue:testValue forKey:propertyName]; + XCTAssertFalse([address containsContentForBillingAddressFields:STPBillingAddressFieldsNone]); + XCTAssertFalse([address containsContentForBillingAddressFields:STPBillingAddressFieldsZip]); + XCTAssertTrue([address containsContentForBillingAddressFields:STPBillingAddressFieldsFull]); + [address setValue:nil forKey:propertyName]; + } + + // Make sure that empty string is treated like nil, and returns false for these properties + [address setValue:@"" forKey:propertyName]; + XCTAssertFalse([address containsContentForBillingAddressFields:STPBillingAddressFieldsNone]); + XCTAssertFalse([address containsContentForBillingAddressFields:STPBillingAddressFieldsZip]); + XCTAssertFalse([address containsContentForBillingAddressFields:STPBillingAddressFieldsFull]); + [address setValue:nil forKey:propertyName]; + } + + // ensure it still returns false for everything since it has been cleared + XCTAssertFalse([address containsContentForBillingAddressFields:STPBillingAddressFieldsNone]); + XCTAssertFalse([address containsContentForBillingAddressFields:STPBillingAddressFieldsZip]); + XCTAssertFalse([address containsContentForBillingAddressFields:STPBillingAddressFieldsFull]); +} + - (void)testContainsRequiredShippingAddressFields { STPAddress *address = [STPAddress new]; XCTAssertTrue([address containsRequiredShippingAddressFields:PKAddressFieldNone]); @@ -458,6 +503,81 @@ - (void)testContainsRequiredShippingAddressFields { XCTAssertTrue([address containsRequiredShippingAddressFields:PKAddressFieldAll]); } +- (void)testContainsContentForShippingAddressFields { + STPAddress *address = [STPAddress new]; + + // Empty address should return false for everything + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldNone]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldName]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldPhone]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldEmail]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldPostalAddress]); + + // Name + address.name = @"Smith"; + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldNone]); + XCTAssertTrue([address containsContentForShippingAddressFields:PKAddressFieldName]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldPhone]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldEmail]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldPostalAddress]); + address.name = @""; + + // Phone + address.phone = @"1"; + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldNone]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldName]); + XCTAssertTrue([address containsContentForShippingAddressFields:PKAddressFieldPhone]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldEmail]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldPostalAddress]); + address.phone = @""; + + // Email + address.email = @"f"; + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldNone]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldName]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldPhone]); + XCTAssertTrue([address containsContentForShippingAddressFields:PKAddressFieldEmail]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldPostalAddress]); + address.email = @""; + + // Test every property that contributes to the full address + // This is *not* refactoring-safe, but I think it's better than a bunch more duplicated code + for (NSString *propertyName in @[@"line1", @"line2", @"city", @"state", @"postalCode", @"country"]) { + for (NSString *testValue in @[@"a", @"0", @"Foo Bar"]) { + [address setValue:testValue forKey:propertyName]; + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldNone]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldName]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldPhone]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldEmail]); + XCTAssertTrue([address containsContentForShippingAddressFields:PKAddressFieldPostalAddress]); + [address setValue:@"" forKey:propertyName]; + } + } + + // ensure it still returns false for everything with empty strings + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldNone]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldName]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldPhone]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldEmail]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldPostalAddress]); + + // Try a hybrid address, and make sure some bitwise combinations work + address.name = @"a"; + address.phone = @"1"; + address.line1 = @"_"; + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldNone]); + XCTAssertTrue([address containsContentForShippingAddressFields:PKAddressFieldName]); + XCTAssertTrue([address containsContentForShippingAddressFields:PKAddressFieldPhone]); + XCTAssertFalse([address containsContentForShippingAddressFields:PKAddressFieldEmail]); + XCTAssertTrue([address containsContentForShippingAddressFields:PKAddressFieldPostalAddress]); + + XCTAssertTrue([address containsContentForShippingAddressFields:(PKAddressField)(PKAddressFieldName|PKAddressFieldEmail)]); + XCTAssertTrue([address containsContentForShippingAddressFields:(PKAddressField)(PKAddressFieldPhone|PKAddressFieldEmail)]); + XCTAssertTrue([address containsContentForShippingAddressFields:PKAddressFieldAll]); + +} + + - (void)testShippingInfoForCharge { STPAddress *address = [STPFixtures address]; PKShippingMethod *method = [[PKShippingMethod alloc] init]; diff --git a/Tests/Tests/STPShippingAddressViewControllerLocalizationTests.m b/Tests/Tests/STPShippingAddressViewControllerLocalizationTests.m index 57fdf2e54f6..01cd0058cca 100644 --- a/Tests/Tests/STPShippingAddressViewControllerLocalizationTests.m +++ b/Tests/Tests/STPShippingAddressViewControllerLocalizationTests.m @@ -47,6 +47,7 @@ - (void)performSnapshotTestForLanguage:(NSString *)language shippingType:(STPShi [STPLocalizationUtils overrideLanguageTo:language]; STPUserInformation *info = [STPUserInformation new]; info.billingAddress = [STPAddress new]; + info.billingAddress.email = @"@"; // trigger "use billing address" button STPShippingAddressViewController *shippingVC = [[STPShippingAddressViewController alloc] initWithConfiguration:config theme:[STPTheme defaultTheme]