-
Notifications
You must be signed in to change notification settings - Fork 28
Expand file tree
/
Copy pathPSTCKFormEncoder.m
More file actions
228 lines (184 loc) · 11 KB
/
PSTCKFormEncoder.m
File metadata and controls
228 lines (184 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//
// PSTCKFormEncoder.m
// Paystack
//
#import "PSTCKFormEncoder.h"
#import "PSTCKCardParams.h"
#import "PSTCKTransactionParams.h"
#import "PSTCKFormEncodable.h"
FOUNDATION_EXPORT NSString * PSTCKPercentEscapedStringFromString(NSString *string);
FOUNDATION_EXPORT NSString * PSTCKQueryStringFromParameters(NSDictionary *parameters);
#pragma mark PSTCKQueryStringPair
@interface PSTCKQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;
- (instancetype)initWithField:(id)field value:(id)value;
- (NSString *)URLEncodedStringValue;
@end
@implementation PSTCKQueryStringPair
- (instancetype)initWithField:(id)field value:(id)value {
self = [super init];
if (!self) {
return nil;
}
_field = field;
_value = value;
return self;
}
- (NSString *)URLEncodedStringValue {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return PSTCKPercentEscapedStringFromString([self.field description]);
} else {
NSString *encoded= [NSString stringWithFormat:@"%@=%@", PSTCKPercentEscapedStringFromString([self.field description]), PSTCKPercentEscapedStringFromString([self.value description])];
// never send negative transaction_charge
if([encoded hasPrefix:@"transaction_charge=-"])
return @"";
return encoded;
}
}
@end
@implementation PSTCKFormEncoder
+ (NSString *)stringByReplacingSnakeCaseWithCamelCase:(NSString *)input {
NSArray *parts = [input componentsSeparatedByString:@"_"];
NSMutableString *camelCaseParam = [NSMutableString string];
[parts enumerateObjectsUsingBlock:^(NSString *part, NSUInteger idx, __unused BOOL *stop) {
[camelCaseParam appendString:(idx == 0 ? part : [part capitalizedString])];
}];
return [camelCaseParam copy];
}
+ (nonnull NSData *)formEncryptedDataForCard:(nonnull PSTCKCardParams *)card
andTransaction:(nonnull PSTCKTransactionParams *)transaction
usePublicKey:(nonnull NSString *)public_key
onThisDevice:(nonnull NSString *)device_id {
NSString *urlencodedcard = [PSTCKFormEncoder urlEncodedStringForObject:card];
NSString *urlencodedtransaction = [PSTCKFormEncoder urlEncodedStringForObject:transaction];
NSString *urlencodedpublickey = [[[PSTCKQueryStringPair alloc] initWithField:@"public_key" value:public_key] URLEncodedStringValue];
NSString *urlencodeddevice = [[[PSTCKQueryStringPair alloc] initWithField:@"device" value:device_id] URLEncodedStringValue];
return [[NSString stringWithFormat:@"%@&%@&%@&%@", urlencodedcard, urlencodedtransaction, urlencodedpublickey, urlencodeddevice] dataUsingEncoding:NSUTF8StringEncoding];
}
+ (nonnull NSData *)formEncryptedDataForCard:(nonnull PSTCKCardParams *)card
andTransaction:(nonnull PSTCKTransactionParams *)transaction
andHandle:(nonnull NSString *)handle
usePublicKey:(nonnull NSString *)public_key
onThisDevice:(nonnull NSString *)device_id {
NSString *urlencodedcard = [PSTCKFormEncoder urlEncodedStringForObject:card];
NSString *urlencodedtransaction = [PSTCKFormEncoder urlEncodedStringForObject:transaction];
NSString *urlencodedhandle = [[[PSTCKQueryStringPair alloc] initWithField:@"handle" value:handle] URLEncodedStringValue];
NSString *urlencodedpublickey = [[[PSTCKQueryStringPair alloc] initWithField:@"public_key" value:public_key] URLEncodedStringValue];
NSString *urlencodeddevice = [[[PSTCKQueryStringPair alloc] initWithField:@"device" value:device_id] URLEncodedStringValue];
return [[NSString stringWithFormat:@"%@&%@&%@&%@&%@", urlencodedcard, urlencodedtransaction, urlencodedhandle, urlencodedpublickey, urlencodeddevice] dataUsingEncoding:NSUTF8StringEncoding];
}
+ (NSData *)formEncryptedDataForDict:(NSDictionary *)dict usePublicKey:(NSString *)public_key onThisDevice:(NSString *)device_id {
NSString *encodedDict = PSTCKQueryStringFromParameters(dict);
NSString *urlencodedpublickey = [[[PSTCKQueryStringPair alloc] initWithField:@"public_key" value:public_key] URLEncodedStringValue];
NSString *urlencodeddevice = [[[PSTCKQueryStringPair alloc] initWithField:@"device" value:device_id] URLEncodedStringValue];
return [[NSString stringWithFormat:@"%@&%@&%@", encodedDict, urlencodedpublickey, urlencodeddevice] dataUsingEncoding:NSUTF8StringEncoding];
}
+ (nonnull NSData *)formEncodedDataForObject:(nonnull NSObject<PSTCKFormEncodable> *)object
usePublicKey:(nonnull NSString *)public_key
onThisDevice:(nonnull NSString *)device_id {
NSString *urlencodedobject = [PSTCKFormEncoder urlEncodedStringForObject:object];
NSString *urlencodedpublickey = [[[PSTCKQueryStringPair alloc] initWithField:@"public_key" value:public_key] URLEncodedStringValue];
NSString *urlencodeddevice = [[[PSTCKQueryStringPair alloc] initWithField:@"device" value:device_id] URLEncodedStringValue];
return [[NSString stringWithFormat:@"%@&%@&%@", urlencodedobject, urlencodedpublickey, urlencodeddevice] dataUsingEncoding:NSUTF8StringEncoding];
}
+ (nonnull NSString *)urlEncodedStringForObject:(nonnull NSObject<PSTCKFormEncodable> *)object {
NSDictionary *dict = @{
[object.class rootObjectName]: [self keyPairDictionaryForObject:object]
};
return PSTCKQueryStringFromParameters(dict) ;
}
+ (NSDictionary *)keyPairDictionaryForObject:(nonnull NSObject<PSTCKFormEncodable> *)object {
NSMutableDictionary *keyPairs = [NSMutableDictionary dictionary];
[[object.class propertyNamesToFormFieldNamesMapping] enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull propertyName, NSString * _Nonnull formFieldName, __unused BOOL * _Nonnull stop) {
id value = [self formEncodableValueForObject:[object valueForKey:propertyName]];
if (value) {
keyPairs[formFieldName] = value;
}
}];
[object.additionalAPIParameters enumerateKeysAndObjectsUsingBlock:^(id _Nonnull additionalFieldName, id _Nonnull additionalFieldValue, __unused BOOL * _Nonnull stop) {
id value = [self formEncodableValueForObject:additionalFieldValue];
if (value) {
keyPairs[additionalFieldName] = value;
}
}];
return [keyPairs copy];
}
+ (id)formEncodableValueForObject:(NSObject *)object {
if ([object conformsToProtocol:@protocol(PSTCKFormEncodable)]) {
return [self keyPairDictionaryForObject:(NSObject<PSTCKFormEncodable>*)object];
} else {
return object;
}
}
+ (NSString *)stringByURLEncoding:(NSString *)string {
return PSTCKPercentEscapedStringFromString(string);
}
@end
// This code is adapted from https://github.com/AFNetworking/AFNetworking/blob/master/AFNetworking/AFURLRequestSerialization.m . The only modifications are to replace the AF namespace with the PSTCK namespace to avoid collisions with apps that are using both Paystack and AFNetworking.
NSString * PSTCKPercentEscapedStringFromString(NSString *string) {
static NSString * const kPSTCKCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
static NSString * const kPSTCKCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowedCharacterSet removeCharactersInString:[kPSTCKCharactersGeneralDelimitersToEncode stringByAppendingString:kPSTCKCharactersSubDelimitersToEncode]];
// FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
// return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
static NSUInteger const batchSize = 50;
NSUInteger index = 0;
NSMutableString *escaped = @"".mutableCopy;
while (index < string.length) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wgnu"
NSUInteger length = MIN(string.length - index, batchSize);
#pragma GCC diagnostic pop
NSRange range = NSMakeRange(index, length);
// To avoid breaking up character sequences such as 👴🏻👮🏽
range = [string rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [string substringWithRange:range];
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[escaped appendString:encoded];
index += range.length;
}
return escaped;
}
#pragma mark -
FOUNDATION_EXPORT NSArray * PSTCKQueryStringPairsFromDictionary(NSDictionary *dictionary);
FOUNDATION_EXPORT NSArray * PSTCKQueryStringPairsFromKeyAndValue(NSString *key, id value);
NSString * PSTCKQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (PSTCKQueryStringPair *pair in PSTCKQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
NSArray * PSTCKQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return PSTCKQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * PSTCKQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
NSString *descriptionSelector = NSStringFromSelector(@selector(description));
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:descriptionSelector ascending:YES selector:@selector(compare:)];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
// Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:PSTCKQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@%@", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:PSTCKQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:PSTCKQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
[mutableQueryStringComponents addObject:[[PSTCKQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}