Skip to content

Commit

Permalink
Merge pull request #869 from stripe/bg-better-decoding
Browse files Browse the repository at this point in the history
Decoding improvements
  • Loading branch information
bg-stripe authored Dec 20, 2017
2 parents 3c70479 + 4dc5729 commit 5fddc2d
Show file tree
Hide file tree
Showing 20 changed files with 440 additions and 258 deletions.
20 changes: 18 additions & 2 deletions Stripe/NSDictionary+Stripe.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,28 @@ NS_ASSUME_NONNULL_BEGIN

@interface NSDictionary (Stripe)

- (nullable NSDictionary *)stp_dictionaryByRemovingNullsValidatingRequiredFields:(NSArray *)requiredFields;

- (NSDictionary *)stp_dictionaryByRemovingNulls;

- (NSDictionary<NSString *, NSString *> *)stp_dictionaryByRemovingNonStrings;

// Getters

- (nullable NSArray *)stp_arrayForKey:(NSString *)key;

- (BOOL)stp_boolForKey:(NSString *)key or:(BOOL)defaultValue;

- (nullable NSDate *)stp_dateForKey:(NSString *)key;

- (nullable NSDictionary *)stp_dictionaryForKey:(NSString *)key;

- (NSInteger)stp_intForKey:(NSString *)key or:(NSInteger)defaultValue;

- (nullable NSNumber *)stp_numberForKey:(NSString *)key;

- (nullable NSString *)stp_stringForKey:(NSString *)key;

- (nullable NSURL *)stp_urlForKey:(NSString *)key;

@end

NS_ASSUME_NONNULL_END
Expand Down
95 changes: 81 additions & 14 deletions Stripe/NSDictionary+Stripe.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,6 @@

@implementation NSDictionary (Stripe)

- (nullable NSDictionary *)stp_dictionaryByRemovingNullsValidatingRequiredFields:(NSArray *)requiredFields {
NSDictionary *result = [self stp_dictionaryByRemovingNulls];

for (NSString *key in requiredFields) {
if (![[result allKeys] containsObject:key]) {
// Result missing required field
return nil;
}
}

return result;
}

- (NSDictionary *)stp_dictionaryByRemovingNulls {
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];

Expand Down Expand Up @@ -66,8 +53,88 @@ - (NSDictionary *)stp_dictionaryByRemovingNulls {
return [result copy];
}

@end
#pragma mark - Getters

- (nullable NSArray *)stp_arrayForKey:(NSString *)key {
id value = self[key];
if (value && [value isKindOfClass:[NSArray class]]) {
return value;
}
return nil;
}

- (BOOL)stp_boolForKey:(NSString *)key or:(BOOL)defaultValue {
id value = self[key];
if (value) {
if ([value isKindOfClass:[NSNumber class]]) {
return [value boolValue];
}
if ([value isKindOfClass:[NSString class]]) {
NSString *string = [(NSString *)value lowercaseString];
// boolValue on NSString is true for "Y", "y", "T", "t", or 1-9
if ([string isEqualToString:@"true"] || [string boolValue]) {
return YES;
}
else {
return NO;
}
}
}
return defaultValue;
}

- (nullable NSDate *)stp_dateForKey:(NSString *)key {
id value = self[key];
if (value &&
([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]])) {
double timeInterval = [value doubleValue];
return [NSDate dateWithTimeIntervalSince1970:timeInterval];
}
return nil;
}

- (nullable NSDictionary *)stp_dictionaryForKey:(NSString *)key {
id value = self[key];
if (value && [value isKindOfClass:[NSDictionary class]]) {
return value;
}
return nil;
}

- (NSInteger)stp_intForKey:(NSString *)key or:(NSInteger)defaultValue {
id value = self[key];
if (value &&
([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]])) {
return [value integerValue];
}
return defaultValue;
}

- (nullable NSDictionary *)stp_numberForKey:(NSString *)key {
id value = self[key];
if (value && [value isKindOfClass:[NSNumber class]]) {
return value;
}
return nil;
}

- (nullable NSString *)stp_stringForKey:(NSString *)key {
id value = self[key];
if (value && [value isKindOfClass:[NSString class]]) {
return value;
}
return nil;
}

- (nullable NSURL *)stp_urlForKey:(NSString *)key {
id value = self[key];
if (value && [value isKindOfClass:[NSString class]]) {
return [NSURL URLWithString:value];
}
return nil;
}

@end
void linkNSDictionaryCategory(void){}

NS_ASSUME_NONNULL_END
6 changes: 0 additions & 6 deletions Stripe/PublicHeaders/STPAPIResponseDecodable.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@
*/
@protocol STPAPIResponseDecodable <NSObject>

/**
These fields are required to be present in the API response. If any of them are
nil, `decodedObjectFromAPIResponse` should also return nil.
*/
+ (nonnull NSArray *)requiredFields;

/**
Parses an response from the Stripe API (in JSON format; represented as
an `NSDictionary`) into an instance of the class.
Expand Down
19 changes: 8 additions & 11 deletions Stripe/STPAddress.m
Original file line number Diff line number Diff line change
Expand Up @@ -293,24 +293,21 @@ + (PKAddressField)pkAddressFieldsFromStripeContactFields:(NSSet<STPContactField>

#pragma mark STPAPIResponseDecodable

+ (NSArray *)requiredFields {
return @[];
}

+ (instancetype)decodedObjectFromAPIResponse:(NSDictionary *)response {
NSDictionary *dict = [response stp_dictionaryByRemovingNullsValidatingRequiredFields:[self requiredFields]];
NSDictionary *dict = [response stp_dictionaryByRemovingNulls];
if (!dict) {
return nil;
}

STPAddress *address = [self new];
address.allResponseFields = dict;
address.city = dict[@"city"];
address.country = dict[@"country"];
address.line1 = dict[@"line1"];
address.line2 = dict[@"line2"];
address.postalCode = dict[@"postal_code"];
address.state = dict[@"state"];
/// all properties are nullable
address.city = [dict stp_stringForKey:@"city"];
address.country = [dict stp_stringForKey:@"country"];
address.line1 = [dict stp_stringForKey:@"line1"];
address.line2 = [dict stp_stringForKey:@"line2"];
address.postalCode = [dict stp_stringForKey:@"postal_code"];
address.state = [dict stp_stringForKey:@"state"];
return address;
}

Expand Down
47 changes: 24 additions & 23 deletions Stripe/STPBankAccount.m
Original file line number Diff line number Diff line change
Expand Up @@ -115,43 +115,44 @@ - (NSString *)description {

#pragma mark - STPAPIResponseDecodable

+ (NSArray *)requiredFields {
return @[
@"id",
@"last4",
@"bank_name",
@"country",
@"currency",
@"status",
];
}

+ (nullable instancetype)decodedObjectFromAPIResponse:(nullable NSDictionary *)response {
NSDictionary *dict = [response stp_dictionaryByRemovingNullsValidatingRequiredFields:[self requiredFields]];
NSDictionary *dict = [response stp_dictionaryByRemovingNulls];
if (!dict) {
return nil;
}

// required fields
NSString *stripeId = [dict stp_stringForKey:@"id"];
NSString *last4 = [dict stp_stringForKey:@"last4"];
NSString *bankName = [dict stp_stringForKey:@"bank_name"];
NSString *country = [dict stp_stringForKey:@"country"];
NSString *currency = [dict stp_stringForKey:@"currency"];
NSString *rawStatus = [dict stp_stringForKey:@"status"];
if (!stripeId || !last4 || !bankName || !country || !currency || !rawStatus) {
return nil;
}

STPBankAccount *bankAccount = [self new];

// Identifier
bankAccount.stripeID = dict[@"id"];
bankAccount.stripeID = stripeId;

// Basic account details
bankAccount.routingNumber = dict[@"routing_number"];
bankAccount.last4 = dict[@"last4"];
bankAccount.routingNumber = [dict stp_stringForKey:@"routing_number"];
bankAccount.last4 = last4;

// Additional account details (alphabetical)
bankAccount.bankName = dict[@"bank_name"];
bankAccount.country = dict[@"country"];
bankAccount.currency = dict[@"currency"];
bankAccount.fingerprint = dict[@"fingerprint"];
bankAccount.metadata = [dict[@"metadata"] stp_dictionaryByRemovingNonStrings];
bankAccount.status = [self statusFromString:dict[@"status"]];
bankAccount.bankName = bankName;
bankAccount.country = country;
bankAccount.currency = currency;
bankAccount.fingerprint = [dict stp_stringForKey:@"fingerprint"];
bankAccount.metadata = [[dict stp_dictionaryForKey:@"metadata"] stp_dictionaryByRemovingNonStrings];
bankAccount.status = [self statusFromString:rawStatus];

// Owner details
bankAccount.accountHolderName = dict[@"account_holder_name"];
bankAccount.accountHolderType = [STPBankAccountParams accountHolderTypeFromString:dict[@"account_holder_type"]];
bankAccount.accountHolderName = [dict stp_stringForKey:@"account_holder_name"];
NSString *rawAccountHolderType = [dict stp_stringForKey:@"account_holder_type"];
bankAccount.accountHolderType = [STPBankAccountParams accountHolderTypeFromString:rawAccountHolderType];

bankAccount.allResponseFields = dict;

Expand Down
51 changes: 29 additions & 22 deletions Stripe/STPCard.m
Original file line number Diff line number Diff line change
Expand Up @@ -169,44 +169,51 @@ - (NSString *)stripeObject {
return @"card";
}

+ (NSArray *)requiredFields {
return @[@"id", @"last4", @"brand", @"exp_month", @"exp_year"];
}

+ (nullable instancetype)decodedObjectFromAPIResponse:(nullable NSDictionary *)response {
NSDictionary *dict = [response stp_dictionaryByRemovingNullsValidatingRequiredFields:[self requiredFields]];
NSDictionary *dict = [response stp_dictionaryByRemovingNulls];
if (!dict) {
return nil;
}

// required fields
NSString *stripeId = [dict stp_stringForKey:@"id"];
NSString *last4 = [dict stp_stringForKey:@"last4"];
NSString *rawBrand = [dict stp_stringForKey:@"brand"];
NSNumber *rawExpMonth = [dict stp_numberForKey:@"exp_month"];
NSNumber *rawExpYear = [dict stp_numberForKey:@"exp_year"];
if (!stripeId || !last4 || !rawBrand || !rawExpMonth || !rawExpYear) {
return nil;
}

STPCard *card = [self new];
card.address = [STPAddress new];

card.stripeID = dict[@"id"];
card.name = dict[@"name"];
card.last4 = dict[@"last4"];
card.dynamicLast4 = dict[@"dynamic_last4"];
card.brand = [self.class brandFromString:dict[@"brand"]];
card.stripeID = stripeId;
card.name = [dict stp_stringForKey:@"name"];
card.last4 = last4;
card.dynamicLast4 = [dict stp_stringForKey:@"dynamic_last4"];
card.brand = [self.class brandFromString:rawBrand];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
// This is only intended to be deprecated publicly.
// When removed from public header, can remove these pragmas
card.funding = [self.class fundingFromString:dict[@"funding"]];
NSString *rawFunding = [dict stp_stringForKey:@"funding"];
card.funding = [self.class fundingFromString:rawFunding];
#pragma clang diagnostic pop

card.country = dict[@"country"];
card.currency = dict[@"currency"];
card.expMonth = [dict[@"exp_month"] intValue];
card.expYear = [dict[@"exp_year"] intValue];
card.metadata = [dict[@"metadata"] stp_dictionaryByRemovingNonStrings];
card.country = [dict stp_stringForKey:@"country"];
card.currency = [dict stp_stringForKey:@"currency"];
card.expMonth = [dict stp_intForKey:@"exp_month" or:0];
card.expYear = [dict stp_intForKey:@"exp_year" or:0];
card.metadata = [[dict stp_dictionaryForKey:@"metadata"] stp_dictionaryByRemovingNonStrings];

card.address.name = card.name;
card.address.line1 = dict[@"address_line1"];
card.address.line2 = dict[@"address_line2"];
card.address.city = dict[@"address_city"];
card.address.state = dict[@"address_state"];
card.address.postalCode = dict[@"address_zip"];
card.address.country = dict[@"address_country"];
card.address.line1 = [dict stp_stringForKey:@"address_line1"];
card.address.line2 = [dict stp_stringForKey:@"address_line2"];
card.address.city = [dict stp_stringForKey:@"address_city"];
card.address.state = [dict stp_stringForKey:@"address_state"];
card.address.postalCode = [dict stp_stringForKey:@"address_zip"];
card.address.country = [dict stp_stringForKey:@"address_country"];

card.allResponseFields = dict;
return card;
Expand Down
Loading

0 comments on commit 5fddc2d

Please sign in to comment.