Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding STPAddress containsContentFor...Fields: methods to check for any useful data #834

Merged
merged 6 commits into from
Nov 9, 2017
Merged
34 changes: 34 additions & 0 deletions Stripe/PublicHeaders/STPAddress.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
7 changes: 5 additions & 2 deletions Stripe/STPAddCardViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -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);
BOOL requiredFields = self.configuration.requiredBillingAddressFields;
BOOL needsAddress = requiredFields != STPBillingAddressFieldsNone && !self.addressViewModel.isValid;
BOOL buttonVisible = (needsAddress &&
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There wasn't actually a bug here (shippingAddress was still nil), but I chose to make a parallel change to keep things consistent and guard against future changes.

[self.shippingAddress containsContentForBillingAddressFields:requiredFields]
&& !self.hasUsedShippingAddress);
addressHeaderView.buttonHidden = !buttonVisible;
[addressHeaderView setNeedsLayout];
_addressHeaderView = addressHeaderView;
Expand Down
35 changes: 35 additions & 0 deletions Stripe/STPAddress.m
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
Expand All @@ -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) {
Expand Down
7 changes: 5 additions & 2 deletions Stripe/STPShippingAddressViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
120 changes: 120 additions & 0 deletions Tests/Tests/STPAddressTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down Expand Up @@ -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"]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe do something like NSStringFromSelector(@selector(line1)) instead of @"line1" for some more compiler safety?

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:PKAddressFieldName | PKAddressFieldEmail]);
XCTAssertTrue([address containsContentForShippingAddressFields:PKAddressFieldPhone | PKAddressFieldEmail]);
XCTAssertTrue([address containsContentForShippingAddressFields:PKAddressFieldAll]);

}


- (void)testShippingInfoForCharge {
STPAddress *address = [STPFixtures address];
PKShippingMethod *method = [[PKShippingMethod alloc] init];
Expand Down