Adds support for Contacts framework

If you are on the appropriate ios version, does work using CNContact or PKContact in places instead of ABAddressRef
This commit is contained in:
Brian Dorfman 2017-04-20 15:49:27 -07:00
parent 32d77727a2
commit 0e38aae137
8 changed files with 305 additions and 34 deletions

View File

@ -7,6 +7,7 @@ Pod::Spec.new do |s|
s.authors = { 'Jack Flintermann' => 'jack@stripe.com', 'Stripe' => 'support+github@stripe.com' }
s.source = { :git => 'https://github.com/stripe/stripe-ios.git', :tag => "v#{s.version}" }
s.frameworks = 'Foundation', 'Security', 'WebKit', 'PassKit', 'AddressBook'
s.weak_frameworks = 'Contacts'
s.requires_arc = true
s.platform = :ios
s.ios.deployment_target = '8.0'

View File

@ -596,6 +596,7 @@
F1510BBF1D5A8146000731AD /* stp_card_jcb_template.png in Resources */ = {isa = PBXBuildFile; fileRef = F1510BBC1D5A8146000731AD /* stp_card_jcb_template.png */; };
F1510BC21D5A8146000731AD /* stp_card_jcb_template@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F1510BBD1D5A8146000731AD /* stp_card_jcb_template@2x.png */; };
F1510BC51D5A8146000731AD /* stp_card_jcb_template@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = F1510BBE1D5A8146000731AD /* stp_card_jcb_template@3x.png */; };
F15232311EA93E6800D65C67 /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F15232301EA93E6800D65C67 /* Contacts.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
F15675401DB544D3004468E3 /* STPAddCardViewControllerLocalizationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F156753F1DB544D3004468E3 /* STPAddCardViewControllerLocalizationTests.m */; };
F15AC18E1DBA9CA90009EADE /* FBSnapshotTestCase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F15AC18D1DBA9CA90009EADE /* FBSnapshotTestCase.framework */; };
F15AC1901DBA9CC60009EADE /* FBSnapshotTestCase.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = F15AC18D1DBA9CA90009EADE /* FBSnapshotTestCase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@ -1048,6 +1049,7 @@
F1510BBC1D5A8146000731AD /* stp_card_jcb_template.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = stp_card_jcb_template.png; sourceTree = "<group>"; };
F1510BBD1D5A8146000731AD /* stp_card_jcb_template@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "stp_card_jcb_template@2x.png"; sourceTree = "<group>"; };
F1510BBE1D5A8146000731AD /* stp_card_jcb_template@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "stp_card_jcb_template@3x.png"; sourceTree = "<group>"; };
F15232301EA93E6800D65C67 /* Contacts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Contacts.framework; path = System/Library/Frameworks/Contacts.framework; sourceTree = SDKROOT; };
F156753F1DB544D3004468E3 /* STPAddCardViewControllerLocalizationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPAddCardViewControllerLocalizationTests.m; sourceTree = "<group>"; };
F15AC18D1DBA9CA90009EADE /* FBSnapshotTestCase.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FBSnapshotTestCase.framework; path = Carthage/Build/iOS/FBSnapshotTestCase.framework; sourceTree = "<group>"; };
F1852F911D80B6EC00367C86 /* STPStringUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STPStringUtils.h; sourceTree = "<group>"; };
@ -1109,11 +1111,12 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
F1D64B2E1D87686E001CDB7C /* WebKit.framework in Frameworks */,
F116E94B1D83404D0026A52A /* AddressBook.framework in Frameworks */,
F15232311EA93E6800D65C67 /* Contacts.framework in Frameworks */,
F116E94C1D83405E0026A52A /* Foundation.framework in Frameworks */,
04533E7D1A6877F400C7E52E /* PassKit.framework in Frameworks */,
F116E94D1D8340640026A52A /* Security.framework in Frameworks */,
F1D64B2E1D87686E001CDB7C /* WebKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1451,6 +1454,7 @@
11C74B9A164043050071C2CA /* Frameworks */ = {
isa = PBXGroup;
children = (
F15232301EA93E6800D65C67 /* Contacts.framework */,
C11B14961E8AE316000F760C /* OCMock.framework */,
F15AC18D1DBA9CA90009EADE /* FBSnapshotTestCase.framework */,
F1D64B2D1D87686E001CDB7C /* WebKit.framework */,

View File

@ -62,6 +62,20 @@ typedef void (^STPPaymentAuthorizationStatusCallback)(PKPaymentAuthorizationStat
});
}
- (void)paymentAuthorizationViewController:(__unused PKPaymentAuthorizationViewController *)controller
didSelectShippingContact:(PKContact *)contact
completion:(STPApplePayShippingAddressCompletionBlock)completion {
STPAddress *stpAddress = [[STPAddress alloc] initWithPKContact:contact];
self.onShippingAddressSelection(stpAddress, ^(STPShippingStatus status, NSArray<PKShippingMethod *>* shippingMethods, NSArray<PKPaymentSummaryItem*> *summaryItems) {
if (status == STPShippingStatusInvalid) {
completion(PKPaymentAuthorizationStatusInvalidShippingPostalAddress, shippingMethods, summaryItems);
}
else {
completion(PKPaymentAuthorizationStatusSuccess, shippingMethods, summaryItems);
}
});
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
- (void)paymentAuthorizationViewController:(__unused PKPaymentAuthorizationViewController *)controller

View File

@ -6,21 +6,21 @@
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#define FAUXPAS_IGNORED_IN_FILE(...)
FAUXPAS_IGNORED_IN_FILE(APIAvailability)
#define FAUXPAS_IGNORED_IN_METHOD(...)
#define FAUXPAS_IGNORED_ON_LINE(...)
#import <Foundation/Foundation.h>
#import <PassKit/PassKit.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
#import <AddressBook/AddressBook.h>
#pragma clang diagnostic pop
#define FAUXPAS_IGNORED_IN_METHOD(...)
#define FAUXPAS_IGNORED_ON_LINE(...)
#import <Foundation/Foundation.h>
#import <PassKit/PassKit.h>
#import "STPAPIResponseDecodable.h"
@class CNContact;
NS_ASSUME_NONNULL_BEGIN
/**
@ -100,12 +100,16 @@ typedef NS_ENUM(NSUInteger, STPBillingAddressFields) {
- (instancetype)initWithABRecord:(ABRecordRef)record;
- (ABRecordRef)ABRecordValue;
#pragma clang diagnostic pop
- (PKContact *)PKContactValue NS_AVAILABLE_IOS(9.0);
- (instancetype)initWithPKContact:(PKContact *)contact NS_AVAILABLE_IOS(9_0); FAUXPAS_IGNORED_ON_LINE(APIAvailability);
- (PKContact *)PKContactValue NS_AVAILABLE_IOS(9_0); FAUXPAS_IGNORED_ON_LINE(APIAvailability);
- (instancetype)initWithCNContact:(CNContact *)contact NS_AVAILABLE_IOS(9_0); FAUXPAS_IGNORED_ON_LINE(APIAvailability);
- (BOOL)containsRequiredFields:(STPBillingAddressFields)requiredFields;
- (BOOL)containsRequiredShippingAddressFields:(PKAddressField)requiredFields;
+ (PKAddressField)applePayAddressFieldsFromBillingAddressFields:(STPBillingAddressFields)billingAddressFields; FAUXPAS_IGNORED_ON_LINE(APIAvailability);
+ (PKAddressField)applePayAddressFieldsFromBillingAddressFields:(STPBillingAddressFields)billingAddressFields;
@end

View File

@ -24,14 +24,7 @@ FAUXPAS_IGNORED_IN_FILE(APIAvailability)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
+ (NSDictionary *)parametersForPayment:(PKPayment *)payment {
NSCAssert(payment != nil, @"Cannot create a token with a nil payment.");
NSString *paymentString =
[[NSString alloc] initWithData:payment.token.paymentData encoding:NSUTF8StringEncoding];
NSMutableDictionary *payload = [NSMutableDictionary new];
payload[@"pk_token"] = paymentString;
ABRecordRef billingAddress = payment.billingAddress;
+ (NSDictionary *)addressParamsFromABRecord:(ABRecordRef)billingAddress {
if (billingAddress) {
NSMutableDictionary *params = [NSMutableDictionary dictionary];
@ -66,10 +59,58 @@ FAUXPAS_IGNORED_IN_FILE(APIAvailability)
params[@"address_country"] = country;
}
CFRelease(dict);
payload[@"card"] = params;
}
CFRelease(addressValues);
}
return params;
}
else {
return nil;
}
}
#pragma clang diagnostic pop
+ (NSDictionary *)addressParamsFromPKContact:(PKContact *)billingContact {
if (billingContact) {
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSPersonNameComponents *nameComponents = billingContact.name;
if (nameComponents) {
params[@"name"] = [NSPersonNameComponentsFormatter localizedStringFromPersonNameComponents:nameComponents
style:NSPersonNameComponentsFormatterStyleDefault
options:(NSPersonNameComponentsFormatterOptions)0];
}
CNPostalAddress *address = billingContact.postalAddress;
if (address) {
params[@"address_line1"] = address.street;
params[@"address_city"] = address.city;
params[@"address_state"] = address.state;
params[@"address_zip"] = address.postalCode;
params[@"address_country"] = address.ISOCountryCode;
}
return params;
}
else {
return nil;
}
}
+ (NSDictionary *)parametersForPayment:(PKPayment *)payment {
NSCAssert(payment != nil, @"Cannot create a token with a nil payment.");
NSString *paymentString =
[[NSString alloc] initWithData:payment.token.paymentData encoding:NSUTF8StringEncoding];
NSMutableDictionary *payload = [NSMutableDictionary new];
payload[@"pk_token"] = paymentString;
if ([PKContact class]
&& [payment respondsToSelector:@selector(billingContact)]) {
payload[@"card"] = [self addressParamsFromPKContact:payment.billingContact];
}
else {
payload[@"card"] = [self addressParamsFromABRecord:payment.billingAddress];
}
NSString *paymentInstrumentName = payment.token.paymentInstrumentName;
@ -93,7 +134,6 @@ FAUXPAS_IGNORED_IN_FILE(APIAvailability)
return payload;
}
#pragma clang diagnostic pop
@end

View File

@ -13,13 +13,18 @@
#import "STPPhoneNumberValidator.h"
#import "STPPostalCodeValidator.h"
#import <Contacts/Contacts.h>
#define FAUXPAS_IGNORED_IN_FILE(...)
FAUXPAS_IGNORED_IN_FILE(APIAvailability)
NSString *stringIfHasContentsElseNil(NSString *string);
@interface STPAddress ()
@property (nonatomic, readwrite, nonnull, copy) NSDictionary *allResponseFields;
@property (nonatomic, readwrite, nullable, copy) NSString *givenName;
@property (nonatomic, readwrite, nullable, copy) NSString *familyName;
@end
@implementation STPAddress
@ -126,6 +131,61 @@ FAUXPAS_IGNORED_IN_FILE(APIAvailability)
#pragma clang diagnostic pop
- (NSString *)sanitizedPhoneStringFromCNPhoneNumber:(CNPhoneNumber *)phoneNumber {
NSString *phone = phoneNumber.stringValue;
if (phone) {
phone = [STPCardValidator sanitizedNumericStringForString:phone];
}
return stringIfHasContentsElseNil(phone);
}
- (instancetype)initWithCNContact:(CNContact *)contact {
self = [super init];
if (self) {
_givenName = stringIfHasContentsElseNil(contact.givenName);
_familyName = stringIfHasContentsElseNil(contact.familyName);
_name = stringIfHasContentsElseNil([CNContactFormatter stringFromContact:contact
style:CNContactFormatterStyleFullName]);
_email = stringIfHasContentsElseNil([contact.emailAddresses firstObject].value);
_phone = [self sanitizedPhoneStringFromCNPhoneNumber:contact.phoneNumbers.firstObject.value];
[self setAddressFromCNPostalAddress:contact.postalAddresses.firstObject.value];
}
return self;
}
- (instancetype)initWithPKContact:(PKContact *)contact {
self = [super init];
if (self) {
NSPersonNameComponents *nameComponents = contact.name;
if (nameComponents) {
_givenName = stringIfHasContentsElseNil(nameComponents.givenName);
_familyName = stringIfHasContentsElseNil(nameComponents.familyName);
_name = stringIfHasContentsElseNil([NSPersonNameComponentsFormatter localizedStringFromPersonNameComponents:nameComponents
style:NSPersonNameComponentsFormatterStyleDefault
options:(NSPersonNameComponentsFormatterOptions)0]);
}
_email = stringIfHasContentsElseNil(contact.emailAddress);
_phone = [self sanitizedPhoneStringFromCNPhoneNumber:contact.phoneNumber];
[self setAddressFromCNPostalAddress:contact.postalAddress];
}
return self;
}
- (void)setAddressFromCNPostalAddress:(CNPostalAddress *)address {
if (address) {
_line1 = stringIfHasContentsElseNil(address.street);
_city = stringIfHasContentsElseNil(address.city);
_state = stringIfHasContentsElseNil(address.state);
_postalCode = stringIfHasContentsElseNil(address.postalCode);
_country = stringIfHasContentsElseNil(address.ISOCountryCode.uppercaseString);
}
}
- (PKContact *)PKContactValue {
PKContact *contact = [PKContact new];
NSPersonNameComponents *name = [NSPersonNameComponents new];
@ -145,19 +205,29 @@ FAUXPAS_IGNORED_IN_FILE(APIAvailability)
}
- (NSString *)firstName {
NSArray<NSString *>*components = [self.name componentsSeparatedByString:@" "];
return [components firstObject];
if (self.givenName) {
return self.givenName;
}
else {
NSArray<NSString *>*components = [self.name componentsSeparatedByString:@" "];
return [components firstObject];
}
}
- (NSString *)lastName {
NSArray<NSString *>*components = [self.name componentsSeparatedByString:@" "];
NSString *firstName = [components firstObject];
NSString *lastName = [self.name stringByReplacingOccurrencesOfString:firstName withString:@""];
lastName = [lastName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([lastName length] == 0) {
lastName = nil;
if (self.familyName) {
return self.familyName;
}
else {
NSArray<NSString *>*components = [self.name componentsSeparatedByString:@" "];
NSString *firstName = [components firstObject];
NSString *lastName = [self.name stringByReplacingOccurrencesOfString:firstName withString:@""];
lastName = [lastName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([lastName length] == 0) {
lastName = nil;
}
return lastName;
}
return lastName;
}
- (NSString *)street {
@ -247,3 +317,12 @@ FAUXPAS_IGNORED_IN_FILE(APIAvailability)
@end
NSString *stringIfHasContentsElseNil(NSString *string) {
if (string.length > 0) {
return string;
}
else {
return nil;
}
}

View File

@ -8,6 +8,8 @@
#import <XCTest/XCTest.h>
#import <AddressBook/AddressBook.h>
#import <PassKit/PassKit.h>
#import <Contacts/Contacts.h>
#import "STPAddress.h"
@interface STPAddressTests : XCTestCase
@ -16,7 +18,134 @@
@implementation STPAddressTests
- (void)testInit {
- (void)testInitWithPKContact_complete {
PKContact *contact = [PKContact new];
{
NSPersonNameComponents *name = [NSPersonNameComponents new];
name.givenName = @"John";
name.familyName = @"Doe";
contact.name = name;
contact.emailAddress = @"foo@example.com";
contact.phoneNumber = [CNPhoneNumber phoneNumberWithStringValue:@"888-555-1212"];
CNMutablePostalAddress *address = [CNMutablePostalAddress new];
address.street = @"55 John St";
address.city = @"New York";
address.state = @"NY";
address.postalCode = @"10002";
address.ISOCountryCode = @"US";
address.country = @"United States";
contact.postalAddress = address.copy;
}
STPAddress *address = [[STPAddress alloc] initWithPKContact:contact];
XCTAssertEqualObjects(@"John Doe", address.name);
XCTAssertEqualObjects(@"8885551212", address.phone);
XCTAssertEqualObjects(@"foo@example.com", address.email);
XCTAssertEqualObjects(@"55 John St", address.line1);
XCTAssertEqualObjects(@"New York", address.city);
XCTAssertEqualObjects(@"NY", address.state);
XCTAssertEqualObjects(@"10002", address.postalCode);
XCTAssertEqualObjects(@"US", address.country);
}
- (void)testInitWithPKContact_partial {
PKContact *contact = [PKContact new];
{
NSPersonNameComponents *name = [NSPersonNameComponents new];
name.givenName = @"John";
contact.name = name;
CNMutablePostalAddress *address = [CNMutablePostalAddress new];
address.state = @"VA";
contact.postalAddress = address.copy;
}
STPAddress *address = [[STPAddress alloc] initWithPKContact:contact];
XCTAssertEqualObjects(@"John", address.name);
XCTAssertNil(address.phone);
XCTAssertNil(address.email);
XCTAssertNil(address.line1);
XCTAssertNil(address.city);
XCTAssertEqualObjects(@"VA", address.state);
XCTAssertNil(address.postalCode);
XCTAssertNil(address.country);
}
- (void)testInitWithCNContact_complete {
CNMutableContact *contact = [CNMutableContact new];
{
contact.givenName = @"John";
contact.familyName = @"Doe";
contact.emailAddresses = @[
[CNLabeledValue labeledValueWithLabel:CNLabelHome
value:@"foo@example.com"],
[CNLabeledValue labeledValueWithLabel:CNLabelWork
value:@"bar@example.com"],
];
contact.phoneNumbers = @[
[CNLabeledValue labeledValueWithLabel:CNLabelHome
value:[CNPhoneNumber phoneNumberWithStringValue:@"888-555-1212"]],
[CNLabeledValue labeledValueWithLabel:CNLabelWork
value:[CNPhoneNumber phoneNumberWithStringValue:@"555-555-5555"]],
];
CNMutablePostalAddress *address = [CNMutablePostalAddress new];
address.street = @"55 John St";
address.city = @"New York";
address.state = @"NY";
address.postalCode = @"10002";
address.ISOCountryCode = @"US";
address.country = @"United States";
contact.postalAddresses = @[
[CNLabeledValue labeledValueWithLabel:CNLabelHome
value:address],
];
}
STPAddress *address = [[STPAddress alloc] initWithCNContact:contact];
XCTAssertEqualObjects(@"John Doe", address.name);
XCTAssertEqualObjects(@"8885551212", address.phone);
XCTAssertEqualObjects(@"foo@example.com", address.email);
XCTAssertEqualObjects(@"55 John St", address.line1);
XCTAssertEqualObjects(@"New York", address.city);
XCTAssertEqualObjects(@"NY", address.state);
XCTAssertEqualObjects(@"10002", address.postalCode);
XCTAssertEqualObjects(@"US", address.country);
}
- (void)testInitWithCNContact_partial {
CNMutableContact *contact = [CNMutableContact new];
{
contact.givenName = @"John";
CNMutablePostalAddress *address = [CNMutablePostalAddress new];
address.state = @"VA";
contact.postalAddresses = @[
[CNLabeledValue labeledValueWithLabel:CNLabelHome
value:address],
];
}
STPAddress *address = [[STPAddress alloc] initWithCNContact:contact];
XCTAssertEqualObjects(@"John", address.name);
XCTAssertNil(address.phone);
XCTAssertNil(address.email);
XCTAssertNil(address.line1);
XCTAssertNil(address.city);
XCTAssertEqualObjects(@"VA", address.state);
XCTAssertNil(address.postalCode);
XCTAssertNil(address.country);
}
- (void)testInitWithABRecord_complete {
ABRecordRef record = ABPersonCreate();
ABRecordSetValue(record, kABPersonFirstNameProperty, CFSTR("John"), nil);
ABRecordSetValue(record, kABPersonLastNameProperty, CFSTR("Doe"), nil);
@ -50,7 +179,7 @@
XCTAssertEqualObjects(@"US", address.country);
}
- (void)testInit_partial {
- (void)testInitWithABRecord_partial {
ABRecordRef record = ABPersonCreate();
ABRecordSetValue(record, kABPersonFirstNameProperty, CFSTR("John"), nil);
ABMutableMultiValueRef addressRef = ABMultiValueCreateMutable(kABMultiDictionaryPropertyType);

View File

@ -26,11 +26,11 @@
@"address_city": @"New York",
@"address_state": @"NY",
@"address_zip": @"12345",
@"address_country": @"USA",
@"address_country": @"US",
@"last4": @"1234",
@"brand": @"Visa",
@"fingerprint": @"Fingolfin",
@"country": @"Japan",
@"country": @"JP",
};
NSDictionary *tokenDict = @{ @"id": @"id_for_token", @"object": @"token", @"livemode": @NO, @"created": @1353025450.0, @"used": @NO, @"card": cardDict };