Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
//
// NSDictionary+Stripe.h
// Stripe
//
// Created by Jack Flintermann on 10/15/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSDictionary (Stripe)
- (nullable NSDictionary *)stp_dictionaryByRemovingNullsValidatingRequiredFields:(nonnull NSArray *)requiredFields;
@end
void linkNSDictionaryCategory(void);
+30
View File
@@ -0,0 +1,30 @@
//
// NSDictionary+Stripe.m
// Stripe
//
// Created by Jack Flintermann on 10/15/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import "NSDictionary+Stripe.h"
@implementation NSDictionary (Stripe)
- (nullable NSDictionary *)stp_dictionaryByRemovingNullsValidatingRequiredFields:(nonnull NSArray *)requiredFields {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, __unused BOOL *stop) {
if (obj != [NSNull null]) {
dict[key] = obj;
}
}];
for (NSString *key in requiredFields) {
if (![[dict allKeys] containsObject:key]) {
return nil;
}
}
return [dict copy];
}
@end
void linkNSDictionaryCategory(void){}
+19
View File
@@ -0,0 +1,19 @@
//
// NSString+Stripe.h
// Stripe
//
// Created by Jack Flintermann on 10/16/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSString (Stripe)
- (NSString *)stp_safeSubstringToIndex:(NSUInteger)index;
- (NSString *)stp_safeSubstringFromIndex:(NSUInteger)index;
- (NSString *)stp_reversedString;
@end
void linkNSStringCategory(void);
+33
View File
@@ -0,0 +1,33 @@
//
// NSString+Stripe.m
// Stripe
//
// Created by Jack Flintermann on 10/16/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import "NSString+Stripe.h"
@implementation NSString (Stripe)
- (NSString *)stp_safeSubstringToIndex:(NSUInteger)index {
return [self substringToIndex:MIN(self.length, index)];
}
- (NSString *)stp_safeSubstringFromIndex:(NSUInteger)index {
return (index > self.length) ? @"" : [self substringFromIndex:index];
}
- (NSString *)stp_reversedString {
NSMutableString *mutableReversedString = [NSMutableString stringWithCapacity:self.length];
[self enumerateSubstringsInRange:NSMakeRange(0, self.length)
options:NSStringEnumerationReverse | NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, __unused BOOL *stop) {
[mutableReversedString appendString:substring];
}];
return [mutableReversedString copy];
}
@end
void linkNSStringCategory(void){}
+18
View File
@@ -0,0 +1,18 @@
//
// NSString+Stripe_CardBrands.h
// Stripe
//
// Created by Jack Flintermann on 1/15/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "STPCardBrand.h"
@interface NSString (Stripe_CardBrands)
+ (nonnull instancetype)stp_stringWithCardBrand:(STPCardBrand)brand;
@end
void linkNSStringCardBrandsCategory(void);
+28
View File
@@ -0,0 +1,28 @@
//
// NSString+Stripe_CardBrands.m
// Stripe
//
// Created by Jack Flintermann on 1/15/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import "NSString+Stripe_CardBrands.h"
@implementation NSString (Stripe_CardBrands)
+ (nonnull instancetype)stp_stringWithCardBrand:(STPCardBrand)brand {
switch (brand) {
case STPCardBrandAmex: return @"American Express";
case STPCardBrandDinersClub: return @"Diners Club";
case STPCardBrandDiscover: return @"Discover";
case STPCardBrandJCB: return @"JCB";
case STPCardBrandMasterCard: return @"MasterCard";
case STPCardBrandUnknown: return @"Unknown";
case STPCardBrandVisa: return @"Visa";
case STPCardBrandOther: return @"Other";
}
}
@end
void linkNSStringCardBrandsCategory(void){}
+20
View File
@@ -0,0 +1,20 @@
//
// PKPayment+Stripe.h
// Stripe
//
// Created by Ben Guo on 7/2/15.
//
#import <PassKit/PassKit.h>
@interface PKPayment (Stripe)
/// Returns true if the instance is a payment from the simulator.
- (BOOL)stp_isSimulated;
/// Returns a fake transaction identifier with the expected ~-separated format.
+ (NSString *)stp_testTransactionIdentifier;
@end
void linkPKPaymentCategory(void);
+35
View File
@@ -0,0 +1,35 @@
//
// PKPayment+Stripe.m
// Stripe
//
// Created by Ben Guo on 7/2/15.
//
#import "PKPayment+Stripe.h"
@implementation PKPayment (Stripe)
- (BOOL)stp_isSimulated {
return [self.token.transactionIdentifier isEqualToString:@"Simulated Identifier"];
}
+ (NSString *)stp_testTransactionIdentifier {
NSString *uuid = [[NSUUID UUID] UUIDString];
uuid = [uuid stringByReplacingOccurrencesOfString:@"~" withString:@""
options:0
range:NSMakeRange(0, uuid.length)];
// Simulated cards don't have enough info yet. For now, use a fake Visa number
NSString *number = @"4242424242424242";
// Without the original PKPaymentRequest, we'll need to use fake data here.
NSDecimalNumber *amount = [NSDecimalNumber decimalNumberWithString:@"0"];
NSString *cents = [@([[amount decimalNumberByMultiplyingByPowerOf10:2] integerValue]) stringValue];
NSString *currency = @"USD";
NSString *identifier = [@[@"ApplePayStubs", number, cents, currency, uuid] componentsJoinedByString:@"~"];
return identifier;
}
@end
void linkPKPaymentCategory(void){}
+104
View File
@@ -0,0 +1,104 @@
//
// STPAPIClient+ApplePay.m
// Stripe
//
// Created by Jack Flintermann on 12/19/14.
//
#import <AddressBook/AddressBook.h>
#import "STPAPIClient+ApplePay.h"
#import "PKPayment+Stripe.h"
#import "STPAPIClient+Private.h"
FAUXPAS_IGNORED_IN_FILE(APIAvailability)
@implementation STPAPIClient (ApplePay)
- (void)createTokenWithPayment:(PKPayment *)payment completion:(STPTokenCompletionBlock)completion {
[self createTokenWithData:[self.class formEncodedDataForPayment:payment]
completion:completion];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
+ (NSData *)formEncodedDataForPayment:(PKPayment *)payment {
NSCAssert(payment != nil, @"Cannot create a token with a nil payment.");
NSMutableCharacterSet *set = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[set removeCharactersInString:@"+="];
NSString *paymentString =
[[[NSString alloc] initWithData:payment.token.paymentData encoding:NSUTF8StringEncoding] stringByAddingPercentEncodingWithAllowedCharacters:set];
__block NSString *payloadString = [@"pk_token=" stringByAppendingString:paymentString];
ABRecordRef billingAddress = payment.billingAddress;
if (billingAddress) {
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSString *firstName = (__bridge_transfer NSString*)ABRecordCopyValue(billingAddress, kABPersonFirstNameProperty);
NSString *lastName = (__bridge_transfer NSString*)ABRecordCopyValue(billingAddress, kABPersonLastNameProperty);
if (firstName.length && lastName.length) {
params[@"name"] = [NSString stringWithFormat:@"%@ %@", firstName, lastName];
}
ABMultiValueRef addressValues = ABRecordCopyValue(billingAddress, kABPersonAddressProperty);
if (addressValues != NULL) {
if (ABMultiValueGetCount(addressValues) > 0) {
CFDictionaryRef dict = ABMultiValueCopyValueAtIndex(addressValues, 0);
NSString *line1 = CFDictionaryGetValue(dict, kABPersonAddressStreetKey);
if (line1) {
params[@"address_line1"] = line1;
}
NSString *city = CFDictionaryGetValue(dict, kABPersonAddressCityKey);
if (city) {
params[@"address_city"] = city;
}
NSString *state = CFDictionaryGetValue(dict, kABPersonAddressStateKey);
if (state) {
params[@"address_state"] = state;
}
NSString *zip = CFDictionaryGetValue(dict, kABPersonAddressZIPKey);
if (zip) {
params[@"address_zip"] = zip;
}
NSString *country = CFDictionaryGetValue(dict, kABPersonAddressCountryKey);
if (country) {
params[@"address_country"] = country;
}
CFRelease(dict);
[params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, __unused BOOL *stop) {
NSString *param = [NSString stringWithFormat:@"&card[%@]=%@", key, [obj stringByAddingPercentEncodingWithAllowedCharacters:set]];
payloadString = [payloadString stringByAppendingString:param];
}];
}
CFRelease(addressValues);
}
}
NSString *paymentInstrumentName = payment.token.paymentInstrumentName;
if (paymentInstrumentName) {
NSString *param = [NSString stringWithFormat:@"&pk_token_instrument_name=%@", paymentInstrumentName];
payloadString = [payloadString stringByAppendingString:param];
}
NSString *paymentNetwork = payment.token.paymentNetwork;
if (paymentNetwork) {
NSString *param = [NSString stringWithFormat:@"&pk_token_payment_network=%@", paymentNetwork];
payloadString = [payloadString stringByAppendingString:param];
}
NSString *transactionIdentifier = payment.token.transactionIdentifier;
if (transactionIdentifier) {
if ([payment stp_isSimulated]) {
transactionIdentifier = [PKPayment stp_testTransactionIdentifier];
}
NSString *param = [NSString stringWithFormat:@"&pk_token_transaction_id=%@", transactionIdentifier];
payloadString = [payloadString stringByAppendingString:param];
}
return [payloadString dataUsingEncoding:NSUTF8StringEncoding];
}
#pragma clang diagnostic pop
@end
void linkSTPAPIClientApplePayCategory(void){}
+26
View File
@@ -0,0 +1,26 @@
//
// STPAPIClient+Private.h
// Stripe
//
// Created by Jack Flintermann on 10/14/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface STPAPIClient()
- (instancetype)initWithPublishableKey:(NSString *)publishableKey
baseURL:(NSString *)baseURL;
- (void)createTokenWithData:(NSData *)data
completion:(STPTokenCompletionBlock)completion;
@property (nonatomic, readwrite) NSURL *apiURL;
@property (nonatomic, readwrite) NSURLSession *urlSession;
@end
NS_ASSUME_NONNULL_END
+309
View File
@@ -0,0 +1,309 @@
//
// STPAPIClient.m
// StripeExample
//
// Created by Jack Flintermann on 12/18/14.
// Copyright (c) 2014 Stripe. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <sys/utsname.h>
#import "STPAPIClient.h"
#import "STPAPIClient+ApplePay.h"
#import "STPFormEncoder.h"
#import "STPBankAccount.h"
#import "STPCard.h"
#import "STPToken.h"
#import "STPAPIPostRequest.h"
#import "STPPaymentConfiguration.h"
#import "NSString+Stripe_CardBrands.h"
#if __has_include("Fabric.h")
#import "Fabric+FABKits.h"
#import "FABKitProtocol.h"
#endif
#ifdef STP_STATIC_LIBRARY_BUILD
#import "STPCategoryLoader.h"
#endif
#define FAUXPAS_IGNORED_IN_METHOD(...)
FAUXPAS_IGNORED_IN_FILE(APIAvailability)
static NSString *const apiURLBase = @"api.stripe.com/v1";
static NSString *const tokenEndpoint = @"tokens";
static NSString *const stripeAPIVersion = @"2015-10-12";
@implementation Stripe
+ (void)setDefaultPublishableKey:(NSString *)publishableKey {
[STPPaymentConfiguration sharedConfiguration].publishableKey = publishableKey;
}
+ (NSString *)defaultPublishableKey {
return [STPPaymentConfiguration sharedConfiguration].publishableKey;
}
+ (void)disableAnalytics {
}
@end
#if __has_include("Fabric.h")
@interface STPAPIClient ()<FABKit>
#else
@interface STPAPIClient()
#endif
@property (nonatomic, readwrite) NSURL *apiURL;
@property (nonatomic, readwrite) NSURLSession *urlSession;
@end
@implementation STPAPIClient
+ (NSString *)stringWithCardBrand:(STPCardBrand)brand {
return [NSString stp_stringWithCardBrand:brand];
}
+ (void)initialize {
#ifdef STP_STATIC_LIBRARY_BUILD
[STPCategoryLoader loadCategories];
#endif
}
+ (instancetype)sharedClient {
static id sharedClient;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ sharedClient = [[self alloc] init]; });
return sharedClient;
}
- (instancetype)init {
return [self initWithConfiguration:[STPPaymentConfiguration sharedConfiguration]];
}
- (instancetype)initWithPublishableKey:(NSString *)publishableKey {
STPPaymentConfiguration *config = [[STPPaymentConfiguration alloc] init];
config.publishableKey = [publishableKey copy];
[self.class validateKey:publishableKey];
return [self initWithConfiguration:config];
}
- (instancetype)initWithConfiguration:(STPPaymentConfiguration *)configuration {
self = [super init];
if (self) {
_apiURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", apiURLBase]];
_configuration = configuration;
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSString *auth = [@"Bearer " stringByAppendingString:self.publishableKey];
sessionConfiguration.HTTPAdditionalHeaders = @{
@"X-Stripe-User-Agent": [self.class stripeUserAgentDetails],
@"Stripe-Version": stripeAPIVersion,
@"Authorization": auth,
};
_urlSession = [NSURLSession sessionWithConfiguration:sessionConfiguration];
}
return self;
}
- (instancetype)initWithPublishableKey:(NSString *)publishableKey
baseURL:(NSString *)baseURL {
self = [self initWithPublishableKey:publishableKey];
if (self) {
_apiURL = [NSURL URLWithString:baseURL];
}
return self;
}
- (void)setPublishableKey:(NSString *)publishableKey {
self.configuration.publishableKey = [publishableKey copy];
}
- (NSString *)publishableKey {
return self.configuration.publishableKey;
}
- (void)createTokenWithData:(NSData *)data
completion:(STPTokenCompletionBlock)completion {
NSCAssert(data != nil, @"'data' is required to create a token");
NSCAssert(completion != nil, @"'completion' is required to use the token that is created");
[STPAPIPostRequest<STPToken *> startWithAPIClient:self
endpoint:tokenEndpoint
postData:data
serializer:[STPToken new]
completion:^(STPToken *object, NSHTTPURLResponse *response, NSError *error) {
completion(object, error);
}];
}
- (void)createTokenWithCard:(STPCard *)card completion:(STPTokenCompletionBlock)completion {
NSData *data = [STPFormEncoder formEncodedDataForObject:card];
[self createTokenWithData:data completion:completion];
}
#pragma mark - private helpers
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-variable"
+ (void)validateKey:(NSString *)publishableKey {
NSCAssert(publishableKey != nil && ![publishableKey isEqualToString:@""],
@"You must use a valid publishable key to create a token. For more info, see https://stripe.com/docs/stripe.js");
BOOL secretKey = [publishableKey hasPrefix:@"sk_"];
NSCAssert(!secretKey,
@"You are using a secret key to create a token, instead of the publishable one. For more info, see https://stripe.com/docs/stripe.js");
#ifndef DEBUG
if ([publishableKey.lowercaseString hasPrefix:@"pk_test"]) {
FAUXPAS_IGNORED_IN_METHOD(NSLogUsed);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"️ You're using your Stripe testmode key. Make sure to use your livemode key when submitting to the App Store!");
});
}
#endif
}
#pragma clang diagnostic pop
#pragma mark Utility methods -
+ (NSString *)stripeUserAgentDetails {
NSMutableDictionary *details = [@{
@"lang": @"objective-c",
@"bindings_version": STPSDKVersion,
} mutableCopy];
NSString *version = [UIDevice currentDevice].systemVersion;
if (version) {
details[@"os_version"] = version;
}
struct utsname systemInfo;
uname(&systemInfo);
NSString *deviceType = @(systemInfo.machine);
if (deviceType) {
details[@"type"] = deviceType;
}
NSString *model = [UIDevice currentDevice].localizedModel;
if (model) {
details[@"model"] = model;
}
if ([[UIDevice currentDevice] respondsToSelector:@selector(identifierForVendor)]) {
NSString *vendorIdentifier = [[[UIDevice currentDevice] performSelector:@selector(identifierForVendor)] performSelector:@selector(UUIDString)];
if (vendorIdentifier) {
details[@"vendor_identifier"] = vendorIdentifier;
}
}
return [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:[details copy] options:0 error:NULL] encoding:NSUTF8StringEncoding];
}
#pragma mark Fabric
#if __has_include("Fabric.h")
+ (NSString *)bundleIdentifier {
return @"com.stripe.stripe-ios";
}
+ (NSString *)kitDisplayVersion {
return STPSDKVersion;
}
+ (void)initializeIfNeeded {
Class fabric = NSClassFromString(@"Fabric");
if (fabric) {
// The app must be using Fabric, as it exists at runtime. We fetch our default publishable key from Fabric.
NSDictionary *fabricConfiguration = [fabric configurationDictionaryForKitClass:[STPAPIClient class]];
NSString *publishableKey = fabricConfiguration[@"publishable"];
if (!publishableKey) {
NSLog(@"Configuration dictionary returned by Fabric was nil, or doesn't have publishableKey. Can't initialize Stripe.");
return;
}
[self validateKey:publishableKey];
[Stripe setDefaultPublishableKey:publishableKey];
} else {
NSCAssert(fabric, @"initializeIfNeeded method called from a project that doesn't have Fabric.");
}
}
#endif
@end
#pragma mark - Bank Accounts
@implementation STPAPIClient (BankAccounts)
- (void)createTokenWithBankAccount:(STPBankAccountParams *)bankAccount
completion:(STPTokenCompletionBlock)completion {
NSData *data = [STPFormEncoder formEncodedDataForObject:bankAccount];
[self createTokenWithData:data completion:completion];
}
@end
#pragma mark - Credit Cards
@implementation Stripe (ApplePay)
+ (BOOL)canSubmitPaymentRequest:(PKPaymentRequest *)paymentRequest {
if (paymentRequest == nil) {
return NO;
}
if (paymentRequest.merchantIdentifier == nil) {
return NO;
}
return [[[paymentRequest.paymentSummaryItems lastObject] amount] floatValue] > 0;
}
+ (NSArray<NSString *> *)supportedPKPaymentNetworks {
NSArray *supportedNetworks = @[PKPaymentNetworkAmex, PKPaymentNetworkMasterCard, PKPaymentNetworkVisa];
supportedNetworks = [supportedNetworks arrayByAddingObject:PKPaymentNetworkDiscover];
return supportedNetworks;
}
+ (PKPaymentRequest *)paymentRequestWithMerchantIdentifier:(NSString *)merchantIdentifier {
if (![PKPaymentRequest class]) {
return nil;
}
PKPaymentRequest *paymentRequest = [PKPaymentRequest new];
[paymentRequest setMerchantIdentifier:merchantIdentifier];
[paymentRequest setSupportedNetworks:[self supportedPKPaymentNetworks]];
[paymentRequest setMerchantCapabilities:PKMerchantCapability3DS];
[paymentRequest setCountryCode:@"US"];
[paymentRequest setCurrencyCode:@"USD"];
return paymentRequest;
}
+ (void)createTokenWithPayment:(PKPayment *)payment
completion:(STPTokenCompletionBlock)handler {
[[STPAPIClient sharedClient] createTokenWithPayment:payment completion:handler];
}
@end
@implementation Stripe (Deprecated)
+ (id)alloc {
NSCAssert(NO, @"'Stripe' is a static class and cannot be instantiated.");
return nil;
}
#pragma mark Shorthand methods -
+ (void)createTokenWithCard:(STPCard *)card completion:(STPCompletionBlock)handler {
[[STPAPIClient sharedClient] createTokenWithCard:card completion:handler];
}
+ (void)createTokenWithCard:(STPCard *)card publishableKey:(NSString *)publishableKey completion:(STPCompletionBlock)handler {
STPPaymentConfiguration *config = [STPPaymentConfiguration new];
config.publishableKey = publishableKey;
[[[STPAPIClient alloc] initWithConfiguration:config] createTokenWithCard:card completion:handler];
}
+ (void)createTokenWithBankAccount:(STPBankAccount *)bankAccount completion:(STPCompletionBlock)handler {
[[STPAPIClient sharedClient] createTokenWithBankAccount:bankAccount completion:handler];
}
+ (void)createTokenWithBankAccount:(STPBankAccount *)bankAccount publishableKey:(NSString *)publishableKey completion:(STPCompletionBlock)handler {
STPPaymentConfiguration *config = [STPPaymentConfiguration new];
config.publishableKey = publishableKey;
[[[STPAPIClient alloc] initWithConfiguration:config] createTokenWithBankAccount:bankAccount completion:handler];
}
@end
+23
View File
@@ -0,0 +1,23 @@
//
// STPAPIPostRequest.h
// Stripe
//
// Created by Jack Flintermann on 10/14/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "STPAPIResponseDecodable.h"
@class STPAPIClient;
@interface STPAPIPostRequest<__covariant ResponseType:id<STPAPIResponseDecodable>> : NSObject
typedef void(^STPAPIPostResponseBlock)(ResponseType object, NSHTTPURLResponse *response, NSError *error);
+ (void)startWithAPIClient:(STPAPIClient *)apiClient
endpoint:(NSString *)endpoint
postData:(NSData *)postData
serializer:(ResponseType)serializer
completion:(STPAPIPostResponseBlock)completion;
@end
+51
View File
@@ -0,0 +1,51 @@
//
// STPAPIPostRequest.m
// Stripe
//
// Created by Jack Flintermann on 10/14/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import "STPAPIPostRequest.h"
#import "STPAPIClient.h"
#import "STPAPIClient+Private.h"
#import "StripeError.h"
#import "STPDispatchFunctions.h"
@implementation STPAPIPostRequest
+ (void)startWithAPIClient:(STPAPIClient *)apiClient
endpoint:(NSString *)endpoint
postData:(NSData *)postData
serializer:(id<STPAPIResponseDecodable>)serializer
completion:(STPAPIPostResponseBlock)completion {
NSURL *url = [apiClient.apiURL URLByAppendingPathComponent:endpoint];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
request.HTTPMethod = @"POST";
request.HTTPBody = postData;
[[apiClient.urlSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable body, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *jsonDictionary = body ? [NSJSONSerialization JSONObjectWithData:body options:0 error:NULL] : nil;
id<STPAPIResponseDecodable> responseObject = [[serializer class] decodedObjectFromAPIResponse:jsonDictionary];
NSError *returnedError = [NSError stp_errorFromStripeResponse:jsonDictionary] ?: error;
if ((!responseObject || ![response isKindOfClass:[NSHTTPURLResponse class]]) && !returnedError) {
returnedError = [NSError stp_genericFailedToParseResponseError];
}
NSHTTPURLResponse *httpResponse;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
httpResponse = (NSHTTPURLResponse *)response;
}
stpDispatchToMainThreadIfNecessary(^{
if (returnedError) {
completion(nil, httpResponse, returnedError);
} else {
completion(responseObject, httpResponse, nil);
}
});
}] resume];
}
@end
+106
View File
@@ -0,0 +1,106 @@
//
// STPAddress.m
// Stripe
//
// Created by Ben Guo on 4/13/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import "STPAddress.h"
#import "STPCardValidator.h"
#import "STPPostalCodeValidator.h"
@implementation STPAddress
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
- (instancetype)initWithABRecord:(ABRecordRef)record {
self = [super init];
if (self) {
NSString *firstName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonFirstNameProperty);
NSString *lastName = (__bridge_transfer NSString*)ABRecordCopyValue(record, kABPersonLastNameProperty);
NSString *first = firstName ?: @"";
NSString *last = lastName ?: @"";
_name = [@[first, last] componentsJoinedByString:@" "];
ABMultiValueRef emailValues = ABRecordCopyValue(record, kABPersonEmailProperty);
_email = (__bridge_transfer NSString *)(ABMultiValueCopyValueAtIndex(emailValues, 0));
CFRelease(emailValues);
ABMultiValueRef phoneValues = ABRecordCopyValue(record, kABPersonPhoneProperty);
NSString *phone = (__bridge_transfer NSString *)(ABMultiValueCopyValueAtIndex(phoneValues, 0));
CFRelease(phoneValues);
_phone = [STPCardValidator sanitizedNumericStringForString:phone];
ABMultiValueRef addressValues = ABRecordCopyValue(record, kABPersonAddressProperty);
if (addressValues != NULL) {
if (ABMultiValueGetCount(addressValues) > 0) {
CFDictionaryRef dict = ABMultiValueCopyValueAtIndex(addressValues, 0);
NSString *street = CFDictionaryGetValue(dict, kABPersonAddressStreetKey);
if (street) {
_line1 = street;
}
NSString *city = CFDictionaryGetValue(dict, kABPersonAddressCityKey);
if (city) {
_city = city;
}
NSString *state = CFDictionaryGetValue(dict, kABPersonAddressStateKey);
if (state) {
_state = state;
}
NSString *zip = CFDictionaryGetValue(dict, kABPersonAddressZIPKey);
if (zip) {
_postalCode = zip;
}
NSString *country = CFDictionaryGetValue(dict, kABPersonAddressCountryCodeKey);
if (country) {
_country = country;
}
CFRelease(dict);
}
CFRelease(addressValues);
}
}
return self;
}
#pragma clang diagnostic pop
- (BOOL)containsRequiredFields:(STPBillingAddressFields)requiredFields {
BOOL containsFields = YES;
switch (requiredFields) {
case STPBillingAddressFieldsNone:
return YES;
case STPBillingAddressFieldsZip:
return [STPPostalCodeValidator stringIsValidPostalCode:self.postalCode
countryCode:self.country];
case STPBillingAddressFieldsFull:
return [self hasValidPostalAddress];
}
return containsFields;
}
- (BOOL)hasValidPostalAddress {
return (self.line1.length > 0
&& self.city.length > 0
&& self.country.length > 0
&& (self.state.length > 0 || ![self.country isEqualToString:@"US"])
&& [STPPostalCodeValidator stringIsValidPostalCode:self.postalCode
countryCode:self.country]);
}
/*+ (PKAddressField)applePayAddressFieldsFromBillingAddressFields:(STPBillingAddressFields)billingAddressFields {
FAUXPAS_IGNORED_IN_METHOD(APIAvailability);
switch (billingAddressFields) {
case STPBillingAddressFieldsNone:
return PKAddressFieldNone;
case STPBillingAddressFieldsZip:
case STPBillingAddressFieldsFull:
return PKAddressFieldPostalAddress;
}
}*/
@end
+117
View File
@@ -0,0 +1,117 @@
//
// STPBINRange.m
// Stripe
//
// Created by Jack Flintermann on 5/24/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import "STPBINRange.h"
#import "NSString+Stripe.h"
@interface STPBINRange()
@property(nonatomic)NSUInteger length;
@property(nonatomic)NSString *qRangeLow;
@property(nonatomic)NSString *qRangeHigh;
@property(nonatomic)STPCardBrand brand;
- (BOOL)matchesNumber:(NSString *)number;
@end
@implementation STPBINRange
+ (NSArray<STPBINRange *> *)allRanges {
static NSArray<STPBINRange *> *STPBINRangeAllRanges;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSArray *ranges = @[
// Catch-all values
@[@"", @"", @16, @(STPCardBrandUnknown)],
@[@"34", @"34", @15, @(STPCardBrandAmex)],
@[@"37", @"37", @15, @(STPCardBrandAmex)],
@[@"30", @"30", @14, @(STPCardBrandDinersClub)],
@[@"36", @"36", @14, @(STPCardBrandDinersClub)],
@[@"38", @"39", @14, @(STPCardBrandDinersClub)],
@[@"6011", @"6011", @16, @(STPCardBrandDiscover)],
@[@"622", @"622", @16, @(STPCardBrandDiscover)],
@[@"64", @"65", @16, @(STPCardBrandDiscover)],
@[@"35", @"35", @16, @(STPCardBrandJCB)],
@[@"5", @"5", @16, @(STPCardBrandMasterCard)],
@[@"4", @"4", @16, @(STPCardBrandVisa)],
// Specific known BIN ranges
@[@"222100", @"272099", @16, @(STPCardBrandMasterCard)],
@[@"413600", @"413600", @13, @(STPCardBrandVisa)],
@[@"444509", @"444509", @13, @(STPCardBrandVisa)],
@[@"444509", @"444509", @13, @(STPCardBrandVisa)],
@[@"444550", @"444550", @13, @(STPCardBrandVisa)],
@[@"450603", @"450603", @13, @(STPCardBrandVisa)],
@[@"450617", @"450617", @13, @(STPCardBrandVisa)],
@[@"450628", @"450629", @13, @(STPCardBrandVisa)],
@[@"450636", @"450636", @13, @(STPCardBrandVisa)],
@[@"450640", @"450641", @13, @(STPCardBrandVisa)],
@[@"450662", @"450662", @13, @(STPCardBrandVisa)],
@[@"463100", @"463100", @13, @(STPCardBrandVisa)],
@[@"476142", @"476142", @13, @(STPCardBrandVisa)],
@[@"476143", @"476143", @13, @(STPCardBrandVisa)],
@[@"492901", @"492902", @13, @(STPCardBrandVisa)],
@[@"492920", @"492920", @13, @(STPCardBrandVisa)],
@[@"492923", @"492923", @13, @(STPCardBrandVisa)],
@[@"492928", @"492930", @13, @(STPCardBrandVisa)],
@[@"492937", @"492937", @13, @(STPCardBrandVisa)],
@[@"492939", @"492939", @13, @(STPCardBrandVisa)],
@[@"492960", @"492960", @13, @(STPCardBrandVisa)],
@[@"8600", @"8600", @16, @(STPCardBrandOther)],
@[@"9860", @"9860", @16, @(STPCardBrandOther)],
@[@"2", @"2", @16, @(STPCardBrandUnknown)],
];
NSMutableArray *binRanges = [NSMutableArray array];
for (NSArray *range in ranges) {
STPBINRange *binRange = [self.class new];
binRange.qRangeLow = range[0];
binRange.qRangeHigh = range[1];
binRange.length = [range[2] unsignedIntegerValue];
binRange.brand = [range[3] integerValue];
[binRanges addObject:binRange];
}
STPBINRangeAllRanges = [binRanges copy];
});
return STPBINRangeAllRanges;
}
- (BOOL)matchesNumber:(NSString *)number {
NSString *low = [number stringByPaddingToLength:self.qRangeLow.length withString:@"0" startingAtIndex:0];
NSString *high = [number stringByPaddingToLength:self.qRangeHigh.length withString:@"0" startingAtIndex:0];
return self.qRangeLow.integerValue <= low.integerValue && self.qRangeHigh.integerValue >= high.integerValue;
}
- (NSComparisonResult)compare:(STPBINRange *)other {
return [@(self.qRangeLow.length) compare:@(other.qRangeLow.length)];
}
+ (NSArray<STPBINRange *> *)binRangesForNumber:(NSString *)number {
return [[self allRanges] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(STPBINRange *range, __unused NSDictionary *bindings) {
return [range matchesNumber:number];
}]];
}
+ (instancetype)mostSpecificBINRangeForNumber:(NSString *)number {
NSArray *validRanges = [[self allRanges] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(STPBINRange *range, __unused NSDictionary *bindings) {
return [range matchesNumber:number];
}]];
return [[validRanges sortedArrayUsingSelector:@selector(compare:)] lastObject];
}
+ (NSArray<STPBINRange *> *)binRangesForBrand:(STPCardBrand)brand {
return [[self allRanges] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(STPBINRange *range, __unused NSDictionary *bindings) {
return range.brand == brand;
}]];
}
@end
+113
View File
@@ -0,0 +1,113 @@
//
// STPBankAccount.m
// Stripe
//
// Created by Charles Scalesse on 10/1/14.
//
//
#import "STPBankAccount.h"
#import "NSDictionary+Stripe.h"
@interface STPBankAccount ()
@property (nonatomic, readwrite) NSString *bankAccountId;
@property (nonatomic, readwrite) NSString *last4;
@property (nonatomic, readwrite) NSString *bankName;
@property (nonatomic, readwrite) NSString *fingerprint;
@property (nonatomic) STPBankAccountStatus status;
@property (nonatomic, readwrite, nonnull, copy) NSDictionary *allResponseFields;
@end
@implementation STPBankAccount
@synthesize routingNumber, country, currency, accountHolderName, accountHolderType;
- (void)setAccountNumber:(NSString *)accountNumber {
[super setAccountNumber:accountNumber];
}
- (NSString *)last4 {
return _last4 ?: [super last4];
}
#pragma mark - Equality
- (BOOL)isEqual:(STPBankAccount *)bankAccount {
return [self isEqualToBankAccount:bankAccount];
}
- (NSUInteger)hash {
return [self.bankAccountId hash];
}
- (BOOL)isEqualToBankAccount:(STPBankAccount *)bankAccount {
if (self == bankAccount) {
return YES;
}
if (!bankAccount || ![bankAccount isKindOfClass:self.class]) {
return NO;
}
return [self.bankAccountId isEqualToString:bankAccount.bankAccountId];
}
- (BOOL)validated {
return self.status == STPBankAccountStatusValidated;
}
- (BOOL)disabled {
return self.status == STPBankAccountStatusErrored;
}
#pragma mark STPAPIResponseDecodable
+ (NSArray *)requiredFields {
return @[
@"id",
@"last4",
@"bank_name",
@"country",
@"currency",
@"status",
];
}
+ (instancetype)decodedObjectFromAPIResponse:(NSDictionary *)response {
NSDictionary *dict = [response stp_dictionaryByRemovingNullsValidatingRequiredFields:[self requiredFields]];
if (!dict) {
return nil;
}
STPBankAccount *bankAccount = [self new];
bankAccount.bankAccountId = dict[@"id"];
bankAccount.last4 = dict[@"last4"];
bankAccount.bankName = dict[@"bank_name"];
bankAccount.country = dict[@"country"];
bankAccount.fingerprint = dict[@"fingerprint"];
bankAccount.currency = dict[@"currency"];
bankAccount.accountHolderName = dict[@"account_holder_name"];
NSString *accountHolderType = dict[@"account_holder_type"];
if ([accountHolderType isEqualToString:@"individual"]) {
bankAccount.accountHolderType = STPBankAccountHolderTypeIndividual;
} else if ([accountHolderType isEqualToString:@"company"]) {
bankAccount.accountHolderType = STPBankAccountHolderTypeCompany;
}
NSString *status = dict[@"status"];
if ([status isEqual: @"new"]) {
bankAccount.status = STPBankAccountStatusNew;
} else if ([status isEqual: @"validated"]) {
bankAccount.status = STPBankAccountStatusValidated;
} else if ([status isEqual: @"verified"]) {
bankAccount.status = STPBankAccountStatusVerified;
} else if ([status isEqual: @"errored"]) {
bankAccount.status = STPBankAccountStatusErrored;
}
bankAccount.allResponseFields = dict;
return bankAccount;
}
@end
+61
View File
@@ -0,0 +1,61 @@
//
// STPBankAccountParams.m
// Stripe
//
// Created by Jack Flintermann on 10/4/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import "STPBankAccountParams.h"
#define FAUXPAS_IGNORED_ON_LINE(...)
@interface STPBankAccountParams()
@property(nonatomic, readonly)NSString *accountHolderTypeString;
@end
@implementation STPBankAccountParams
@synthesize additionalAPIParameters = _additionalAPIParameters;
- (instancetype)init {
self = [super init];
if (self) {
_additionalAPIParameters = @{};
_accountHolderType = STPBankAccountHolderTypeIndividual;
}
return self;
}
- (NSString *)last4 {
if (self.accountNumber && self.accountNumber.length >= 4) {
return [self.accountNumber substringFromIndex:(self.accountNumber.length - 4)];
} else {
return nil;
}
}
- (NSString *)accountHolderTypeString { FAUXPAS_IGNORED_ON_LINE(UnusedMethod)
switch (self.accountHolderType) {
case STPBankAccountHolderTypeCompany:
return @"company";
case STPBankAccountHolderTypeIndividual:
return @"individual";
}
}
+ (NSString *)rootObjectName {
return @"bank_account";
}
+ (NSDictionary *)propertyNamesToFormFieldNamesMapping {
return @{
@"accountNumber": @"account_number",
@"routingNumber": @"routing_number",
@"country": @"country",
@"currency": @"currency",
@"accountHolderName": @"account_holder_name",
@"accountHolderTypeString": @"account_holder_type",
};
}
@end
+204
View File
@@ -0,0 +1,204 @@
//
// STPCard.m
// Stripe
//
// Created by Saikat Chakrabarti on 11/2/12.
//
//
#import "STPCard.h"
#import "NSDictionary+Stripe.h"
#import "NSString+Stripe_CardBrands.h"
#import "STPImageLibrary.h"
#import "STPImageLibrary+Private.h"
@interface STPCard ()
@property (nonatomic, readwrite) NSString *cardId;
@property (nonatomic, readwrite) NSString *last4;
@property (nonatomic, readwrite) NSString *dynamicLast4;
@property (nonatomic, readwrite) STPCardBrand brand;
@property (nonatomic, readwrite) STPCardFundingType funding;
@property (nonatomic, readwrite) NSString *fingerprint;
@property (nonatomic, readwrite) NSString *country;
@property (nonatomic, readwrite, nonnull, copy) NSDictionary *allResponseFields;
@end
@implementation STPCard
@dynamic number, cvc, expMonth, expYear, currency, name, addressLine1, addressLine2, addressCity, addressState, addressZip, addressCountry;
- (instancetype)initWithID:(NSString *)stripeID
brand:(STPCardBrand)brand
last4:(NSString *)last4
expMonth:(NSUInteger)expMonth
expYear:(NSUInteger)expYear
funding:(STPCardFundingType)funding {
self = [super init];
if (self) {
_cardId = stripeID;
_brand = brand;
_last4 = last4;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
self.expMonth = expMonth;
self.expYear = expYear;
#pragma clang diagnostic pop
_funding = funding;
}
return self;
}
+ (STPCardBrand)brandFromString:(NSString *)string {
NSString *brand = [string lowercaseString];
if ([brand isEqualToString:@"visa"]) {
return STPCardBrandVisa;
} else if ([brand isEqualToString:@"american express"]) {
return STPCardBrandAmex;
} else if ([brand isEqualToString:@"mastercard"]) {
return STPCardBrandMasterCard;
} else if ([brand isEqualToString:@"discover"]) {
return STPCardBrandDiscover;
} else if ([brand isEqualToString:@"jcb"]) {
return STPCardBrandJCB;
} else if ([brand isEqualToString:@"diners club"]) {
return STPCardBrandDinersClub;
} else if ([brand isEqualToString:@"other"]) {
return STPCardBrandOther;
} else {
return STPCardBrandUnknown;
}
}
+ (STPCardFundingType)fundingFromString:(NSString *)string {
NSString *funding = [string lowercaseString];
if ([funding isEqualToString:@"credit"]) {
return STPCardFundingTypeCredit;
} else if ([funding isEqualToString:@"debit"]) {
return STPCardFundingTypeDebit;
} else if ([funding isEqualToString:@"prepaid"]) {
return STPCardFundingTypePrepaid;
} else {
return STPCardFundingTypeOther;
}
}
- (instancetype)init {
self = [super init];
if (self) {
_brand = STPCardBrandUnknown;
_funding = STPCardFundingTypeOther;
}
return self;
}
- (NSString *)last4 {
return _last4 ?: [super last4];
}
- (BOOL)isApplePayCard {
return [self.allResponseFields[@"tokenization_method"] isEqualToString:@"apple_pay"];
}
- (NSString *)type {
switch (self.brand) {
case STPCardBrandAmex:
return @"American Express";
case STPCardBrandDinersClub:
return @"Diners Club";
case STPCardBrandDiscover:
return @"Discover";
case STPCardBrandJCB:
return @"JCB";
case STPCardBrandMasterCard:
return @"MasterCard";
case STPCardBrandVisa:
return @"Visa";
case STPCardBrandOther:
return @"Other";
default:
return @"Unknown";
}
}
- (BOOL)isEqual:(id)other {
return [self isEqualToCard:other];
}
- (NSUInteger)hash {
return [self.cardId hash];
}
- (BOOL)isEqualToCard:(STPCard *)other {
if (self == other) {
return YES;
}
if (!other || ![other isKindOfClass:self.class]) {
return NO;
}
return [self.cardId isEqualToString:other.cardId];
}
#pragma mark STPAPIResponseDecodable
+ (NSArray *)requiredFields {
return @[@"id", @"last4", @"brand", @"exp_month", @"exp_year"];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
+ (instancetype)decodedObjectFromAPIResponse:(NSDictionary *)response {
NSDictionary *dict = [response stp_dictionaryByRemovingNullsValidatingRequiredFields:[self requiredFields]];
if (!dict) {
return nil;
}
STPCard *card = [self new];
card.cardId = dict[@"id"];
card.name = dict[@"name"];
card.last4 = dict[@"last4"];
card.dynamicLast4 = dict[@"dynamic_last4"];
NSString *brand = [dict[@"brand"] lowercaseString];
card.brand = [self.class brandFromString:brand];
NSString *funding = dict[@"funding"];
card.funding = [self.class fundingFromString:funding];
card.fingerprint = dict[@"fingerprint"];
card.country = dict[@"country"];
card.currency = dict[@"currency"];
card.expMonth = [dict[@"exp_month"] intValue];
card.expYear = [dict[@"exp_year"] intValue];
card.addressLine1 = dict[@"address_line1"];
card.addressLine2 = dict[@"address_line2"];
card.addressCity = dict[@"address_city"];
card.addressState = dict[@"address_state"];
card.addressZip = dict[@"address_zip"];
card.addressCountry = dict[@"address_country"];
card.allResponseFields = dict;
return card;
}
#pragma clang diagnostic pop
#pragma mark - STPSource
- (NSString *)stripeID {
return self.cardId;
}
- (NSString *)label {
NSString *brand = [NSString stp_stringWithCardBrand:self.brand];
return [NSString stringWithFormat:@"%@ %@", brand, self.last4];
}
- (UIImage *)image {
return [STPImageLibrary brandImageForCardBrand:self.brand];
}
- (UIImage *)templateImage {
return [STPImageLibrary templatedBrandImageForCardBrand:self.brand];
}
@end
+199
View File
@@ -0,0 +1,199 @@
//
// STPCardParams.m
// Stripe
//
// Created by Jack Flintermann on 10/4/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import "STPCardParams.h"
#import "STPCardValidator.h"
#import "StripeError.h"
@implementation STPCardParams
@synthesize additionalAPIParameters = _additionalAPIParameters;
- (instancetype)init {
self = [super init];
if (self) {
_additionalAPIParameters = @{};
}
return self;
}
- (NSString *)last4 {
if (self.number && self.number.length >= 4) {
return [self.number substringFromIndex:(self.number.length - 4)];
} else {
return nil;
}
}
#if TARGET_OS_IPHONE
- (STPAddress *)address {
STPAddress *address = [STPAddress new];
address.name = self.name;
address.line1 = self.addressLine1;
address.line2 = self.addressLine2;
address.city = self.addressCity;
address.state = self.addressState;
address.postalCode = self.addressZip;
address.country = self.addressCountry;
return address;
}
- (void)setAddress:(STPAddress *)address {
self.name = address.name;
self.addressLine1 = address.line1;
self.addressLine2 = address.line2;
self.addressCity = address.city;
self.addressState = address.state;
self.addressZip = address.postalCode;
self.addressCountry = address.country;
}
#endif
- (BOOL)validateNumber:(id *)ioValue error:(NSError **)outError {
if (*ioValue == nil) {
return [self.class handleValidationErrorForParameter:@"number" error:outError];
}
NSString *ioValueString = (NSString *)*ioValue;
if ([STPCardValidator validationStateForNumber:ioValueString validatingCardBrand:NO] != STPCardValidationStateValid) {
return [self.class handleValidationErrorForParameter:@"number" error:outError];
}
return YES;
}
- (BOOL)validateCvc:(id *)ioValue error:(NSError **)outError {
if (*ioValue == nil) {
return [self.class handleValidationErrorForParameter:@"number" error:outError];
}
NSString *ioValueString = (NSString *)*ioValue;
STPCardBrand brand = [STPCardValidator brandForNumber:self.number];
if ([STPCardValidator validationStateForCVC:ioValueString cardBrand:brand] != STPCardValidationStateValid) {
return [self.class handleValidationErrorForParameter:@"cvc" error:outError];
}
return YES;
}
- (BOOL)validateExpMonth:(id *)ioValue error:(NSError **)outError {
if (*ioValue == nil) {
return [self.class handleValidationErrorForParameter:@"expMonth" error:outError];
}
NSString *ioValueString = [(NSString *)*ioValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([STPCardValidator validationStateForExpirationMonth:ioValueString] != STPCardValidationStateValid) {
return [self.class handleValidationErrorForParameter:@"expMonth" error:outError];
}
return YES;
}
- (BOOL)validateExpYear:(id *)ioValue error:(NSError **)outError {
if (*ioValue == nil) {
return [self.class handleValidationErrorForParameter:@"expYear" error:outError];
}
NSString *ioValueString = [(NSString *)*ioValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSString *monthString = [@(self.expMonth) stringValue];
if ([STPCardValidator validationStateForExpirationYear:ioValueString inMonth:monthString cardBrand:[STPCardValidator brandForNumber:self.number]] != STPCardValidationStateValid) {
return [self.class handleValidationErrorForParameter:@"expYear" error:outError];
}
return YES;
}
- (BOOL)validateCardReturningError:(NSError **)outError {
// Order matters here
NSString *numberRef = [self number];
NSString *expMonthRef = [NSString stringWithFormat:@"%02lu", (unsigned long)[self expMonth]];
NSString *expYearRef = [NSString stringWithFormat:@"%02lu", (unsigned long)[self expYear]];
NSString *cvcRef = [self cvc];
// Make sure expMonth, expYear, and number are set. Validate CVC if it is provided
return [self validateNumber:&numberRef error:outError] && [self validateExpYear:&expYearRef error:outError] &&
[self validateExpMonth:&expMonthRef error:outError] && (cvcRef == nil || [self validateCvc:&cvcRef error:outError]);
}
#pragma mark Private Helpers
+ (BOOL)handleValidationErrorForParameter:(NSString *)parameter error:(NSError **)outError {
if (outError != nil) {
if ([parameter isEqualToString:@"number"]) {
*outError = [self createErrorWithMessage:[NSError stp_cardErrorInvalidNumberUserMessage]
parameter:parameter
cardErrorCode:STPInvalidNumber
devErrorMessage:@"Card number must be between 10 and 19 digits long and Luhn valid."];
} else if ([parameter isEqualToString:@"cvc"]) {
*outError = [self createErrorWithMessage:[NSError stp_cardInvalidCVCUserMessage]
parameter:parameter
cardErrorCode:STPInvalidCVC
devErrorMessage:@"Card CVC must be numeric, 3 digits for Visa, Discover, MasterCard, JCB, and Discover cards, and 3 or 4 "
@"digits for American Express cards."];
} else if ([parameter isEqualToString:@"expMonth"]) {
*outError = [self createErrorWithMessage:[NSError stp_cardErrorInvalidExpMonthUserMessage]
parameter:parameter
cardErrorCode:STPInvalidExpMonth
devErrorMessage:@"expMonth must be less than 13"];
} else if ([parameter isEqualToString:@"expYear"]) {
*outError = [self createErrorWithMessage:[NSError stp_cardErrorInvalidExpYearUserMessage]
parameter:parameter
cardErrorCode:STPInvalidExpYear
devErrorMessage:@"expYear must be this year or a year in the future"];
} else {
// This should not be possible since this is a private method so we
// know exactly how it is called. We use STPAPIError for all errors
// that are unexpected within the bindings as well.
*outError = [[NSError alloc] initWithDomain:StripeDomain
code:STPAPIError
userInfo:@{
NSLocalizedDescriptionKey: [NSError stp_unexpectedErrorMessage],
STPErrorMessageKey: @"There was an error within the Stripe client library when trying to generate the "
@"proper validation error. Contact support@stripe.com if you see this."
}];
}
}
return NO;
}
+ (NSError *)createErrorWithMessage:(NSString *)userMessage
parameter:(NSString *)parameter
cardErrorCode:(NSString *)cardErrorCode
devErrorMessage:(NSString *)devMessage {
return [[NSError alloc] initWithDomain:StripeDomain
code:STPCardError
userInfo:@{
NSLocalizedDescriptionKey: userMessage,
STPErrorParameterKey: parameter,
STPCardErrorCodeKey: cardErrorCode,
STPErrorMessageKey: devMessage
}];
}
#pragma mark - STPFormEncodable
+ (NSString *)rootObjectName {
return @"card";
}
+ (NSDictionary *)propertyNamesToFormFieldNamesMapping {
return @{
@"number": @"number",
@"cvc": @"cvc",
@"name": @"name",
@"addressLine1": @"address_line1",
@"addressLine2": @"address_line2",
@"addressCity": @"address_city",
@"addressState": @"address_state",
@"addressZip": @"address_zip",
@"addressCountry": @"address_country",
@"expMonth": @"exp_month",
@"expYear": @"exp_year",
@"currency": @"currency",
};
}
@end
+309
View File
@@ -0,0 +1,309 @@
//
// STPCardValidator.m
// Stripe
//
// Created by Jack Flintermann on 7/15/15.
// Copyright (c) 2015 Stripe, Inc. All rights reserved.
//
#import "STPCardValidator.h"
#import "STPBINRange.h"
@implementation STPCardValidator
+ (NSString *)sanitizedNumericStringForString:(NSString *)string {
return stringByRemovingCharactersFromSet(string, invertedAsciiDigitCharacterSet());
}
static NSCharacterSet *invertedAsciiDigitCharacterSet() {
static NSCharacterSet *cs;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cs = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet];
});
return cs;
}
+ (NSString *)stringByRemovingSpacesFromString:(NSString *)string {
NSCharacterSet *set = [NSCharacterSet whitespaceCharacterSet];
return stringByRemovingCharactersFromSet(string, set);
}
static NSString * _Nonnull stringByRemovingCharactersFromSet(NSString * _Nonnull string, NSCharacterSet * _Nonnull cs) {
NSRange range = [string rangeOfCharacterFromSet:cs];
if (range.location != NSNotFound) {
NSMutableString *newString = [[string substringWithRange:NSMakeRange(0, range.location)] mutableCopy];
NSUInteger lastPosition = NSMaxRange(range);
while (lastPosition < string.length) {
range = [string rangeOfCharacterFromSet:cs options:0 range:NSMakeRange(lastPosition, string.length - lastPosition)];
if (range.location == NSNotFound) break;
if (range.location != lastPosition) {
[newString appendString:[string substringWithRange:NSMakeRange(lastPosition, range.location - lastPosition)]];
}
lastPosition = NSMaxRange(range);
}
if (lastPosition != string.length) {
[newString appendString:[string substringWithRange:NSMakeRange(lastPosition, string.length - lastPosition)]];
}
return newString;
} else {
return string;
}
}
+ (BOOL)stringIsNumeric:(NSString *)string {
return [string rangeOfCharacterFromSet:invertedAsciiDigitCharacterSet()].location == NSNotFound;
}
+ (STPCardValidationState)validationStateForExpirationMonth:(NSString *)expirationMonth {
NSString *sanitizedExpiration = [self stringByRemovingSpacesFromString:expirationMonth];
if (![self stringIsNumeric:sanitizedExpiration]) {
return STPCardValidationStateInvalid;
}
switch (sanitizedExpiration.length) {
case 0:
return STPCardValidationStateIncomplete;
case 1:
return ([sanitizedExpiration isEqualToString:@"0"] || [sanitizedExpiration isEqualToString:@"1"]) ? STPCardValidationStateIncomplete : STPCardValidationStateValid;
case 2:
return (0 < sanitizedExpiration.integerValue && sanitizedExpiration.integerValue <= 12) ? STPCardValidationStateValid : STPCardValidationStateInvalid;
default:
return STPCardValidationStateInvalid;
}
}
+ (STPCardValidationState)validationStateForExpirationYear:(NSString *)expirationYear inMonth:(NSString *)expirationMonth inCurrentYear:(NSInteger)currentYear currentMonth:(NSInteger)currentMonth cardBrand:(STPCardBrand)cardBrand {
NSInteger moddedYear = currentYear % 100;
if (![self stringIsNumeric:expirationMonth] || ![self stringIsNumeric:expirationYear]) {
return STPCardValidationStateInvalid;
}
NSString *sanitizedMonth = [self sanitizedNumericStringForString:expirationMonth];
NSString *sanitizedYear = [self sanitizedNumericStringForString:expirationYear];
switch (sanitizedYear.length) {
case 0:
case 1:
return STPCardValidationStateIncomplete;
case 2: {
switch (cardBrand) {
case STPCardBrandVisa:
case STPCardBrandMasterCard:
case STPCardBrandOther:
return STPCardValidationStateValid;
default:
break;
}
if (sanitizedYear.integerValue == moddedYear) {
return sanitizedMonth.integerValue >= currentMonth ? STPCardValidationStateValid : STPCardValidationStateInvalid;
} else {
return sanitizedYear.integerValue > moddedYear ? STPCardValidationStateValid : STPCardValidationStateInvalid;
}
}
default:
return STPCardValidationStateInvalid;
}
}
+ (STPCardValidationState)validationStateForExpirationYear:(NSString *)expirationYear
inMonth:(NSString *)expirationMonth cardBrand:(STPCardBrand)cardBrand {
return [self validationStateForExpirationYear:expirationYear
inMonth:expirationMonth
inCurrentYear:[self currentYear]
currentMonth:[self currentMonth] cardBrand:cardBrand];
}
+ (STPCardValidationState)validationStateForCVC:(NSString *)cvc cardBrand:(STPCardBrand)brand {
if (![self stringIsNumeric:cvc]) {
return STPCardValidationStateInvalid;
}
NSString *sanitizedCvc = [self sanitizedNumericStringForString:cvc];
NSUInteger minLength = [self minCVCLength];
NSUInteger maxLength = [self maxCVCLengthForCardBrand:brand];
if (sanitizedCvc.length < minLength) {
return STPCardValidationStateIncomplete;
}
else if (sanitizedCvc.length > maxLength) {
return STPCardValidationStateInvalid;
}
else {
return STPCardValidationStateValid;
}
}
+ (STPCardValidationState)validationStateForNumber:(nonnull NSString *)cardNumber
validatingCardBrand:(BOOL)validatingCardBrand {
NSString *sanitizedNumber = [self stringByRemovingSpacesFromString:cardNumber];
if (![self stringIsNumeric:sanitizedNumber]) {
return STPCardValidationStateInvalid;
}
if (sanitizedNumber.length == 0) {
return STPCardValidationStateIncomplete;
}
//BOOL isValidLuhn = [self stringIsValidLuhn:sanitizedNumber];
//return isValidLuhn ? STPCardValidationStateValid : STPCardValidationStateInvalid;
STPBINRange *binRange = [STPBINRange mostSpecificBINRangeForNumber:sanitizedNumber];
if (binRange.brand == STPCardBrandUnknown && validatingCardBrand) {
//return STPCardValidationStateInvalid;
}
if (sanitizedNumber.length == binRange.length) {
BOOL isValidLuhn = [self stringIsValidLuhn:sanitizedNumber];
return isValidLuhn ? STPCardValidationStateValid : STPCardValidationStateInvalid;
} else if (sanitizedNumber.length > binRange.length) {
return STPCardValidationStateInvalid;
} else {
return STPCardValidationStateIncomplete;
}
}
+ (STPCardValidationState)validationStateForCard:(nonnull STPCardParams *)card inCurrentYear:(NSInteger)currentYear currentMonth:(NSInteger)currentMonth {
STPCardValidationState numberValidation = [self validationStateForNumber:card.number validatingCardBrand:YES];
NSString *expMonthString = [NSString stringWithFormat:@"%02lu", (unsigned long)card.expMonth];
STPCardValidationState expMonthValidation = [self validationStateForExpirationMonth:expMonthString];
NSString *expYearString = [NSString stringWithFormat:@"%02lu", (unsigned long)card.expYear%100];
STPCardValidationState expYearValidation = [self validationStateForExpirationYear:expYearString
inMonth:expMonthString
inCurrentYear:currentYear
currentMonth:currentMonth cardBrand:[STPCardValidator brandForNumber:card.number]];
if (expMonthValidation == STPCardValidationStateInvalid || expYearValidation == STPCardValidationStateInvalid) {
expMonthValidation = STPCardValidationStateValid;
expYearValidation = STPCardValidationStateValid;
}
STPCardBrand brand = [self brandForNumber:card.number];
STPCardValidationState cvcValidation = [self validationStateForCVC:card.cvc cardBrand:brand];
NSArray<NSNumber *> *states = @[@(numberValidation),
@(expMonthValidation),
@(expYearValidation),
@(cvcValidation)];
BOOL incomplete = NO;
for (NSNumber *boxedState in states) {
STPCardValidationState state = [boxedState integerValue];
if (state == STPCardValidationStateInvalid) {
return state;
}
else if (state == STPCardValidationStateIncomplete) {
incomplete = YES;
}
}
return incomplete ? STPCardValidationStateIncomplete : STPCardValidationStateValid;
}
+ (STPCardValidationState)validationStateForCard:(STPCardParams *)card {
return [self validationStateForCard:card
inCurrentYear:[self currentYear]
currentMonth:[self currentMonth]];
}
+ (NSUInteger)minCVCLength {
return 3;
}
+ (NSUInteger)maxCVCLengthForCardBrand:(STPCardBrand)brand {
switch (brand) {
case STPCardBrandAmex:
case STPCardBrandUnknown:
return 4;
default:
return 3;
}
}
+ (STPCardBrand)brandForNumber:(NSString *)cardNumber {
NSString *sanitizedNumber = [self sanitizedNumericStringForString:cardNumber];
NSSet *brands = [self possibleBrandsForNumber:sanitizedNumber];
if (brands.count == 1) {
return (STPCardBrand)[brands.anyObject integerValue];
}
return STPCardBrandUnknown;
}
+ (NSSet *)possibleBrandsForNumber:(NSString *)cardNumber {
NSArray<STPBINRange *> *binRanges = [STPBINRange binRangesForNumber:cardNumber];
NSMutableSet *possibleBrands = [NSMutableSet setWithArray:[binRanges valueForKeyPath:@"brand"]];
[possibleBrands removeObject:@(STPCardBrandUnknown)];
return [possibleBrands copy];
}
+ (NSSet<NSNumber *>*)lengthsForCardBrand:(STPCardBrand)brand {
NSMutableSet *set = [NSMutableSet set];
NSArray<STPBINRange *> *binRanges = [STPBINRange binRangesForBrand:brand];
for (STPBINRange *binRange in binRanges) {
[set addObject:@(binRange.length)];
}
return [set copy];
}
+ (NSInteger)lengthForCardBrand:(STPCardBrand)brand {
return [self maxLengthForCardBrand:brand];
}
+ (NSInteger)maxLengthForCardBrand:(STPCardBrand)brand {
NSInteger maxLength = -1;
for (NSNumber *length in [self lengthsForCardBrand:brand]) {
if (length.integerValue > maxLength) {
maxLength = length.integerValue;
}
}
return maxLength;
}
+ (NSInteger)fragmentLengthForCardBrand:(STPCardBrand)brand {
switch (brand) {
case STPCardBrandAmex:
return 5;
case STPCardBrandDinersClub:
return 2;
default:
return 4;
}
}
+ (BOOL)stringIsValidLuhn:(NSString *)number {
BOOL odd = true;
int sum = 0;
NSMutableArray *digits = [NSMutableArray arrayWithCapacity:number.length];
for (int i = 0; i < (NSInteger)number.length; i++) {
[digits addObject:[number substringWithRange:NSMakeRange(i, 1)]];
}
for (NSString *digitStr in [digits reverseObjectEnumerator]) {
int digit = [digitStr intValue];
if ((odd = !odd)) digit *= 2;
if (digit > 9) digit -= 9;
sum += digit;
}
return sum % 10 == 0;
}
+ (NSInteger)currentYear {
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *dateComponents = [calendar components:NSCalendarUnitYear fromDate:[NSDate date]];
return dateComponents.year % 100;
}
+ (NSInteger)currentMonth {
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *dateComponents = [calendar components:NSCalendarUnitMonth fromDate:[NSDate date]];
return dateComponents.month;
}
@end
+101
View File
@@ -0,0 +1,101 @@
//
// STPCustomer.m
// Stripe
//
// Created by Jack Flintermann on 6/9/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import "STPCustomer.h"
#import "StripeError.h"
#import "STPCard.h"
@interface STPCustomer()
@property(nonatomic, copy)NSString *stripeID;
@property(nonatomic) id<STPSource> defaultSource;
@property(nonatomic) NSArray<id<STPSource>> *sources;
@end
@implementation STPCustomer
+ (instancetype)customerWithStripeID:(NSString *)stripeID
defaultSource:(id<STPSource>)defaultSource
sources:(NSArray<id<STPSource>> *)sources {
STPCustomer *customer = [self new];
customer.stripeID = stripeID;
customer.defaultSource = defaultSource;
customer.sources = sources;
return customer;
}
@end
@interface STPCustomerDeserializer()
@property(nonatomic, nullable)STPCustomer *customer;
@property(nonatomic, nullable)NSError *error;
@end
@implementation STPCustomerDeserializer
- (instancetype)initWithData:(nullable NSData *)data
urlResponse:(nullable __unused NSURLResponse *)urlResponse
error:(nullable NSError *)error {
if (error) {
return [self initWithError:error];
}
NSError *jsonError;
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
if (!json) {
return [self initWithError:jsonError];
}
return [self initWithJSONResponse:json];
}
- (instancetype)initWithError:(NSError *)error {
self = [super init];
if (self) {
_error = error;
}
return self;
}
- (instancetype)initWithJSONResponse:(id)json {
self = [super init];
if (self) {
if (![json isKindOfClass:[NSDictionary class]] || ![json[@"id"] isKindOfClass:[NSString class]]) {
_error = [NSError stp_genericFailedToParseResponseError];
return self;
}
STPCustomer *customer = [STPCustomer new];
customer.stripeID = json[@"id"];
NSString *defaultSourceId;
if ([json[@"default_source"] isKindOfClass:[NSString class]]) {
defaultSourceId = json[@"default_source"];
}
NSMutableArray *sources = [NSMutableArray array];
if ([json[@"sources"] isKindOfClass:[NSDictionary class]] && [json[@"sources"][@"data"] isKindOfClass:[NSArray class]]) {
for (id contents in json[@"sources"][@"data"]) {
if ([contents isKindOfClass:[NSDictionary class]]) {
// eventually support other source types
STPCard *card = [STPCard decodedObjectFromAPIResponse:contents];
// ignore apple pay cards from the response
if (card && !card.isApplePayCard) {
[sources addObject:card];
if (defaultSourceId && [card.stripeID isEqualToString:defaultSourceId]) {
customer.defaultSource = card;
}
}
}
}
customer.sources = sources;
}
_customer = customer;
}
return self;
}
@end
+16
View File
@@ -0,0 +1,16 @@
//
// STPDelegateProxy.h
// Stripe
//
// Created by Jack Flintermann on 10/20/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface STPDelegateProxy<__covariant DelegateType:NSObject<NSObject> *> : NSObject
@property(nonatomic, weak)DelegateType delegate;
- (instancetype)init;
@end
+41
View File
@@ -0,0 +1,41 @@
//
// STPDelegateProxy.m
// Stripe
//
// Created by Jack Flintermann on 10/20/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import "STPDelegateProxy.h"
@implementation STPDelegateProxy
- (instancetype)init {
return self;
}
- (BOOL)respondsToSelector:(SEL)selector {
return [super respondsToSelector:selector] || [_delegate respondsToSelector:selector];
}
- (id)forwardingTargetForSelector:(SEL)selector {
if ([self respondsToSelector:selector]) {
return self;
}
if ([_delegate respondsToSelector:selector]) {
return _delegate;
}
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [super methodSignatureForSelector:selector] ?: [_delegate methodSignatureForSelector:selector];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
if ([_delegate respondsToSelector:invocation.selector]) {
[invocation invokeWithTarget:_delegate];
}
}
@end
+11
View File
@@ -0,0 +1,11 @@
//
// STPDispatchFunctions.h
// Stripe
//
// Created by Brian Dorfman on 10/24/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#include <Foundation/Foundation.h>
void stpDispatchToMainThreadIfNecessary(dispatch_block_t block);
+18
View File
@@ -0,0 +1,18 @@
//
// STPDispatchFunctions.m
// Stripe
//
// Created by Brian Dorfman on 10/24/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#include "STPDispatchFunctions.h"
void stpDispatchToMainThreadIfNecessary(dispatch_block_t block) {
if ([NSThread isMainThread]) {
block();
}
else {
dispatch_async(dispatch_get_main_queue(), block);
}
}
+24
View File
@@ -0,0 +1,24 @@
//
// STPFormEncoder.h
// Stripe
//
// Created by Jack Flintermann on 1/8/15.
// Copyright (c) 2015 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class STPCardParams, STPBankAccountParams;
@protocol STPFormEncodable;
@interface STPFormEncoder : NSObject
+ (nonnull NSData *)formEncodedDataForObject:(nonnull NSObject<STPFormEncodable> *)object;
+ (nonnull NSString *)stringByURLEncoding:(nonnull NSString *)string;
+ (nonnull NSString *)stringByReplacingSnakeCaseWithCamelCase:(nonnull NSString *)input;
+ (nonnull NSString *)queryStringFromParameters:(nonnull NSDictionary *)parameters;
@end
+188
View File
@@ -0,0 +1,188 @@
//
// STPFormEncoder.m
// Stripe
//
// Created by Jack Flintermann on 1/8/15.
// Copyright (c) 2015 Stripe, Inc. All rights reserved.
//
#import "STPFormEncoder.h"
#import "STPCardParams.h"
FOUNDATION_EXPORT NSString * STPPercentEscapedStringFromString(NSString *string);
FOUNDATION_EXPORT NSString * STPQueryStringFromParameters(NSDictionary *parameters);
@implementation STPFormEncoder
+ (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 *)formEncodedDataForObject:(nonnull NSObject<STPFormEncodable> *)object {
NSDictionary *keyPairs = [self keyPairDictionaryForObject:object];
NSString *rootObjectName = [object.class rootObjectName];
NSDictionary *dict = rootObjectName != nil ? @{ rootObjectName: keyPairs } : keyPairs;
return [STPQueryStringFromParameters(dict) dataUsingEncoding:NSUTF8StringEncoding];
}
+ (NSDictionary *)keyPairDictionaryForObject:(nonnull NSObject<STPFormEncodable> *)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(STPFormEncodable)]) {
return [self keyPairDictionaryForObject:(NSObject<STPFormEncodable>*)object];
} else {
return object;
}
}
+ (NSString *)stringByURLEncoding:(NSString *)string {
return STPPercentEscapedStringFromString(string);
}
+ (NSString *)queryStringFromParameters:(NSDictionary *)parameters {
return STPQueryStringFromParameters(parameters);
}
@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 STP namespace to avoid collisions with apps that are using both Stripe and AFNetworking.
NSString * STPPercentEscapedStringFromString(NSString *string) {
static NSString * const kSTPCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
static NSString * const kSTPCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowedCharacterSet removeCharactersInString:[kSTPCharactersGeneralDelimitersToEncode stringByAppendingString:kSTPCharactersSubDelimitersToEncode]];
// 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 -
@interface STPQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;
- (instancetype)initWithField:(id)field value:(id)value;
- (NSString *)URLEncodedStringValue;
@end
@implementation STPQueryStringPair
- (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 STPPercentEscapedStringFromString([self.field description]);
} else {
return [NSString stringWithFormat:@"%@=%@", STPPercentEscapedStringFromString([self.field description]), STPPercentEscapedStringFromString([self.value description])];
}
}
@end
#pragma mark -
FOUNDATION_EXPORT NSArray * STPQueryStringPairsFromDictionary(NSDictionary *dictionary);
FOUNDATION_EXPORT NSArray * STPQueryStringPairsFromKeyAndValue(NSString *key, id value);
NSString * STPQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (STPQueryStringPair *pair in STPQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
NSArray * STPQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return STPQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * STPQueryStringPairsFromKeyAndValue(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:STPQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:STPQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:STPQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
[mutableQueryStringComponents addObject:[[STPQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}
+314
View File
@@ -0,0 +1,314 @@
//
// STPFormTextField.m
// Stripe
//
// Created by Jack Flintermann on 7/24/15.
// Copyright (c) 2015 Stripe, Inc. All rights reserved.
//
#import "STPFormTextField.h"
#import "STPCardValidator.h"
#import "STPPhoneNumberValidator.h"
#import "NSString+Stripe.h"
#import "STPDelegateProxy.h"
#import "STPWeakStrongMacros.h"
#define FAUXPAS_IGNORED_IN_METHOD(...)
@interface STPTextFieldDelegateProxy : STPDelegateProxy<UITextFieldDelegate>
@property(nonatomic, assign)STPFormTextFieldAutoFormattingBehavior autoformattingBehavior;
@property(nonatomic, assign)BOOL selectionEnabled;
@end
@implementation STPTextFieldDelegateProxy
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
BOOL deleting = (range.location == textField.text.length - 1 && range.length == 1 && [string isEqualToString:@""]);
NSString *inputText;
if (deleting) {
NSString *sanitized = [self unformattedStringForString:textField.text];
inputText = [sanitized stp_safeSubstringToIndex:sanitized.length - 1];
} else {
NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSString *sanitized = [self unformattedStringForString:newString];
inputText = sanitized;
}
UITextPosition *beginning = textField.beginningOfDocument;
UITextPosition *start = [textField positionFromPosition:beginning offset:range.location];
if ([textField.text isEqualToString:inputText]) {
return NO;
}
textField.text = inputText;
if (self.autoformattingBehavior == STPFormTextFieldAutoFormattingBehaviorNone && self.selectionEnabled) {
// this will be the new cursor location after insert/paste/typing
NSInteger cursorOffset = [textField offsetFromPosition:beginning toPosition:start] + string.length;
UITextPosition *newCursorPosition = [textField positionFromPosition:textField.beginningOfDocument offset:cursorOffset];
UITextRange *newSelectedRange = [textField textRangeFromPosition:newCursorPosition toPosition:newCursorPosition];
[textField setSelectedTextRange:newSelectedRange];
}
return NO;
}
- (NSString *)unformattedStringForString:(NSString *)string {
switch (self.autoformattingBehavior) {
case STPFormTextFieldAutoFormattingBehaviorNone:
return string;
case STPFormTextFieldAutoFormattingBehaviorCardNumbers:
case STPFormTextFieldAutoFormattingBehaviorPhoneNumbers:
case STPFormTextFieldAutoFormattingBehaviorExpiration:
return [STPCardValidator sanitizedNumericStringForString:string];
}
}
@end
typedef NSAttributedString* (^STPFormTextTransformationBlock)(NSAttributedString *inputText);
@interface STPFormTextField()
@property(nonatomic)STPTextFieldDelegateProxy *delegateProxy;
@property(nonatomic, copy)STPFormTextTransformationBlock textFormattingBlock;
// This property only exists to disable keyboard loading in Travis CI due to a crash that occurs while trying to load the keyboard. Don't use it outside of tests.
@property(nonatomic)BOOL skipsReloadingInputViews;
@end
@implementation STPFormTextField
@synthesize placeholderColor = _placeholderColor;
- (void)reloadInputViews {
if (self.skipsReloadingInputViews) {
return;
}
[super reloadInputViews];
}
+ (NSDictionary *)attributesForAttributedString:(NSAttributedString *)attributedString {
if (attributedString.length == 0) {
return @{};
}
return [attributedString attributesAtIndex:0 longestEffectiveRange:nil inRange:NSMakeRange(0, attributedString.length)];
}
- (void)setSelectionEnabled:(BOOL)selectionEnabled {
_selectionEnabled = selectionEnabled;
self.delegateProxy.selectionEnabled = selectionEnabled;
}
- (void)setAutoFormattingBehavior:(STPFormTextFieldAutoFormattingBehavior)autoFormattingBehavior {
_autoFormattingBehavior = autoFormattingBehavior;
self.delegateProxy.autoformattingBehavior = autoFormattingBehavior;
switch (autoFormattingBehavior) {
case STPFormTextFieldAutoFormattingBehaviorNone:
case STPFormTextFieldAutoFormattingBehaviorExpiration:
self.textFormattingBlock = nil;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
if ([self respondsToSelector:@selector(setTextContentType:)]) {
self.textContentType = nil;
}
#endif
break;
case STPFormTextFieldAutoFormattingBehaviorCardNumbers:
self.textFormattingBlock = ^NSAttributedString *(NSAttributedString *inputString) {
if (![STPCardValidator stringIsNumeric:inputString.string]) {
return [inputString copy];
}
NSMutableAttributedString *attributedString = [inputString mutableCopy];
NSArray *cardSpacing;
STPCardBrand currentBrand = [STPCardValidator brandForNumber:attributedString.string];
if (currentBrand == STPCardBrandAmex) {
cardSpacing = @[@3, @9];
} else {
cardSpacing = @[@3, @7, @11];
}
for (NSUInteger i = 0; i < attributedString.length; i++) {
if ([cardSpacing containsObject:@(i)]) {
[attributedString addAttribute:NSKernAttributeName value:@(5)
range:NSMakeRange(i, 1)];
} else {
[attributedString addAttribute:NSKernAttributeName value:@(0)
range:NSMakeRange(i, 1)];
}
}
return [attributedString copy];
};
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
if ([self respondsToSelector:@selector(setTextContentType:)]) {
self.textContentType = UITextContentTypeCreditCardNumber;
}
#endif
break;
case STPFormTextFieldAutoFormattingBehaviorPhoneNumbers: {
WEAK(self);
self.textFormattingBlock = ^NSAttributedString *(NSAttributedString *inputString) {
if (![STPCardValidator stringIsNumeric:inputString.string]) {
return [inputString copy];
}
STRONG(self);
NSString *phoneNumber = [STPPhoneNumberValidator formattedSanitizedPhoneNumberForString:inputString.string];
NSDictionary *attributes = [[self class] attributesForAttributedString:inputString];
return [[NSAttributedString alloc] initWithString:phoneNumber attributes:attributes];
};
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
if ([self respondsToSelector:@selector(setTextContentType:)]) {
self.textContentType = UITextContentTypeTelephoneNumber;
}
#endif
break;
}
}
}
- (void)setFormDelegate:(id<STPFormTextFieldDelegate>)formDelegate {
_formDelegate = formDelegate;
self.delegate = formDelegate;
}
- (void)insertText:(NSString *)text {
[self setText:[self.text stringByAppendingString:text]];
}
- (void)deleteBackward {
[super deleteBackward];
if (self.text.length == 0) {
if ([self.formDelegate respondsToSelector:@selector(formTextFieldDidBackspaceOnEmpty:)]) {
[self.formDelegate formTextFieldDidBackspaceOnEmpty:self];
}
}
}
- (CGSize)measureTextSize {
return self.attributedText.size;
}
- (void)setText:(NSString *)text {
NSString *nonNilText = text ?: @"";
NSAttributedString *attributed = [[NSAttributedString alloc] initWithString:nonNilText attributes:self.defaultTextAttributes];
[self setAttributedText:attributed];
}
- (void)setAttributedText:(NSAttributedString *)attributedText {
NSAttributedString *oldValue = [self attributedText];
BOOL shouldModify = self.formDelegate && [self.formDelegate respondsToSelector:@selector(formTextField:modifyIncomingTextChange:)];
NSAttributedString *modified = shouldModify ?
[self.formDelegate formTextField:self modifyIncomingTextChange:attributedText] :
attributedText;
NSAttributedString *transformed = self.textFormattingBlock ? self.textFormattingBlock(modified) : modified;
[super setAttributedText:transformed];
[self sendActionsForControlEvents:UIControlEventEditingChanged];
if ([self.formDelegate respondsToSelector:@selector(formTextFieldTextDidChange:)]) {
if (![transformed isEqualToAttributedString:oldValue]) {
[self.formDelegate formTextFieldTextDidChange:self];
}
}
}
- (void)setPlaceholder:(NSString *)placeholder {
NSString *nonNilPlaceholder = placeholder ?: @"";
NSAttributedString *attributedPlaceholder = [[NSAttributedString alloc] initWithString:nonNilPlaceholder attributes:[self placeholderTextAttributes]];
[self setAttributedPlaceholder:attributedPlaceholder];
}
- (void)setAttributedPlaceholder:(NSAttributedString *)attributedPlaceholder {
NSAttributedString *transformed = self.textFormattingBlock ? self.textFormattingBlock(attributedPlaceholder) : attributedPlaceholder;
[super setAttributedPlaceholder:transformed];
}
- (NSDictionary *)placeholderTextAttributes {
NSMutableDictionary *defaultAttributes = [[self defaultTextAttributes] mutableCopy];
if (self.placeholderColor) {
defaultAttributes[NSForegroundColorAttributeName] = self.placeholderColor;
}
return [defaultAttributes copy];
}
- (void)setDefaultColor:(UIColor *)defaultColor {
_defaultColor = defaultColor;
[self updateColor];
}
- (void)setErrorColor:(UIColor *)errorColor {
_errorColor = errorColor;
[self updateColor];
}
- (void)setValidText:(BOOL)validText {
_validText = validText;
[self updateColor];
}
- (void)setPlaceholderColor:(UIColor *)placeholderColor {
_placeholderColor = placeholderColor;
[self setPlaceholder:self.placeholder]; //explicitly rebuild attributed placeholder
}
- (void)updateColor {
self.textColor = _validText ? self.defaultColor : self.errorColor;
}
// Workaround for http://www.openradar.appspot.com/19374610
- (CGRect)editingRectForBounds:(CGRect)bounds {
if (UIDevice.currentDevice.systemVersion.integerValue != 8) {
return [self textRectForBounds:bounds];
}
CGFloat const scale = UIScreen.mainScreen.scale;
CGFloat const preferred = self.attributedText.size.height;
CGFloat const delta = (CGFloat)ceil(preferred) - preferred;
CGFloat const adjustment = (CGFloat)floor(delta * scale) / scale;
CGRect const textRect = [self textRectForBounds:bounds];
CGRect const editingRect = CGRectOffset(textRect, 0.0, adjustment);
return editingRect;
}
// Fixes a weird issue related to our custom override of deleteBackwards. This only affects the simulator and iPads with custom keyboards.
- (NSArray *)keyCommands {
FAUXPAS_IGNORED_IN_METHOD(APIAvailability);
return @[[UIKeyCommand keyCommandWithInput:@"\b" modifierFlags:UIKeyModifierCommand action:@selector(commandDeleteBackwards)]];
}
- (void)commandDeleteBackwards {
self.text = @"";
}
- (UITextPosition *)closestPositionToPoint:(CGPoint)point {
if (self.selectionEnabled) {
return [super closestPositionToPoint:point];
}
return [self positionFromPosition:self.beginningOfDocument offset:self.text.length];
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
return [super canPerformAction:action withSender:sender] && action == @selector(paste:);
}
- (void)paste:(id)sender {
if (self.preservesContentsOnPaste) {
[super paste:sender];
} else {
self.text = [UIPasteboard generalPasteboard].string;
}
}
- (void)setDelegate:(id <UITextFieldDelegate>)delegate {
STPTextFieldDelegateProxy *delegateProxy = [[STPTextFieldDelegateProxy alloc] init];
delegateProxy.autoformattingBehavior = self.autoFormattingBehavior;
delegateProxy.selectionEnabled = self.selectionEnabled;
delegateProxy.delegate = delegate;
self.delegateProxy = delegateProxy;
[super setDelegate:delegateProxy];
}
- (id <UITextFieldDelegate>)delegate {
return self.delegateProxy;
}
@end
+35
View File
@@ -0,0 +1,35 @@
//
// STPImageLibrary+Private.h
// Stripe
//
// Created by Jack Flintermann on 6/30/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import "STPImageLibrary.h"
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface STPImageLibrary (Private)
+ (UIImage *)addIcon;
+ (UIImage *)leftChevronIcon;
+ (UIImage *)smallRightChevronIcon;
+ (UIImage *)checkmarkIcon;
+ (UIImage *)largeCardFrontImage;
+ (UIImage *)largeCardBackImage;
+ (UIImage *)largeCardApplePayImage;
+ (UIImage *)safeImageNamed:(NSString *)imageName
templateIfAvailable:(BOOL)templateIfAvailable;
+ (UIImage *)brandImageForCardBrand:(STPCardBrand)brand
template:(BOOL)isTemplate;
+ (UIImage *)imageWithTintColor:(UIColor *)color
forImage:(UIImage *)image;
+ (UIImage *)paddedImageWithInsets:(UIEdgeInsets)insets
forImage:(UIImage *)image;
@end
NS_ASSUME_NONNULL_END
+79
View File
@@ -0,0 +1,79 @@
//
// STPImages.h
// Stripe
//
// Created by Jack Flintermann on 6/30/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <Stripe/STPCardBrand.h>
NS_ASSUME_NONNULL_BEGIN
/**
* This class lets you access card icons used by the Stripe SDK. All icons are 32 x 20 points.
*/
@interface STPImageLibrary : NSObject
/**
* An icon representing Apple Pay.
*/
+ (UIImage *)applePayCardImage;
/**
* An icon representing American Express.
*/
+ (UIImage *)amexCardImage;
/**
* An icon representing Diners Club.
*/
+ (UIImage *)dinersClubCardImage;
/**
* An icon representing Discover.
*/
+ (UIImage *)discoverCardImage;
/**
* An icon representing JCB.
*/
+ (UIImage *)jcbCardImage;
/**
* An icon representing MasterCard.
*/
+ (UIImage *)masterCardCardImage;
/**
* An icon representing Visa.
*/
+ (UIImage *)visaCardImage;
/**
* An icon to use when the type of the card is unknown.
*/
+ (UIImage *)unknownCardCardImage;
/**
* This returns the appropriate icon for the specified card brand.
*/
+ (UIImage *)brandImageForCardBrand:(STPCardBrand)brand;
/**
* This returns the appropriate icon for the specified card brand as a
* single color template that can be tinted
*/
+ (UIImage *)templatedBrandImageForCardBrand:(STPCardBrand)brand;
/**
* This returns a small icon indicating the CVC location for the given card brand.
*/
+ (UIImage *)cvcImageForCardBrand:(STPCardBrand)brand;
@end
NS_ASSUME_NONNULL_END
+182
View File
@@ -0,0 +1,182 @@
//
// STPImages.m
// Stripe
//
// Created by Jack Flintermann on 6/30/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import "STPImageLibrary.h"
#import "STPImageLibrary+Private.h"
#define FAUXPAS_IGNORED_IN_METHOD(...)
// Dummy class for locating the framework bundle
@implementation STPImageLibrary
+ (UIImage *)applePayCardImage {
return [self safeImageNamed:@"stp_card_applepay"];
}
+ (UIImage *)amexCardImage {
return [self brandImageForCardBrand:STPCardBrandAmex];
}
+ (UIImage *)dinersClubCardImage {
return [self brandImageForCardBrand:STPCardBrandDinersClub];
}
+ (UIImage *)discoverCardImage {
return [self brandImageForCardBrand:STPCardBrandDiscover];
}
+ (UIImage *)jcbCardImage {
return [self brandImageForCardBrand:STPCardBrandJCB];
}
+ (UIImage *)masterCardCardImage {
return [self brandImageForCardBrand:STPCardBrandMasterCard];
}
+ (UIImage *)visaCardImage {
return [self brandImageForCardBrand:STPCardBrandVisa];
}
+ (UIImage *)unknownCardCardImage {
return [self brandImageForCardBrand:STPCardBrandUnknown];
}
+ (UIImage *)otherCardCardImage {
return [self brandImageForCardBrand:STPCardBrandUnknown];
}
+ (UIImage *)brandImageForCardBrand:(STPCardBrand)brand {
return [self brandImageForCardBrand:brand template:NO];
}
+ (UIImage *)templatedBrandImageForCardBrand:(STPCardBrand)brand {
return [self brandImageForCardBrand:brand template:YES];
}
+ (UIImage *)cvcImageForCardBrand:(STPCardBrand)brand {
NSString *imageName = brand == STPCardBrandAmex ? @"stp_card_cvc_amex" : @"stp_card_cvc";
return [self safeImageNamed:imageName];
}
+ (UIImage *)safeImageNamed:(NSString *)imageName {
return [self safeImageNamed:imageName templateIfAvailable:NO];
}
@end
@implementation STPImageLibrary (Private)
+ (UIImage *)addIcon {
return [self safeImageNamed:@"stp_icon_add" templateIfAvailable:YES];
}
+ (UIImage *)leftChevronIcon {
return [self safeImageNamed:@"stp_icon_chevron_left" templateIfAvailable:YES];
}
+ (UIImage *)smallRightChevronIcon {
return [self safeImageNamed:@"stp_icon_chevron_right_small" templateIfAvailable:YES];
}
+ (UIImage *)checkmarkIcon {
return [self safeImageNamed:@"stp_icon_checkmark" templateIfAvailable:YES];
}
+ (UIImage *)largeCardFrontImage {
return [self safeImageNamed:@"stp_card_form_front" templateIfAvailable:YES];
}
+ (UIImage *)largeCardBackImage {
return [self safeImageNamed:@"stp_card_form_back" templateIfAvailable:YES];
}
+ (UIImage *)largeCardApplePayImage {
return [self safeImageNamed:@"stp_card_form_applepay" templateIfAvailable:YES];
}
+ (UIImage *)safeImageNamed:(NSString *)imageName
templateIfAvailable:(BOOL)templateIfAvailable {
FAUXPAS_IGNORED_IN_METHOD(APIAvailability);
UIImage *image = nil;
if ([UIImage respondsToSelector:@selector(imageNamed:inBundle:compatibleWithTraitCollection:)]) {
image = [UIImage imageNamed:imageName inBundle:[NSBundle bundleForClass:[STPImageLibrary class]] compatibleWithTraitCollection:nil];
}
if (image == nil) {
image = [UIImage imageNamed:imageName];
}
if (templateIfAvailable) {
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}
return image;
}
+ (UIImage *)brandImageForCardBrand:(STPCardBrand)brand
template:(BOOL)isTemplate {
BOOL shouldUseTemplate = isTemplate;
FAUXPAS_IGNORED_IN_METHOD(APIAvailability);
NSString *imageName;
switch (brand) {
case STPCardBrandAmex:
imageName = shouldUseTemplate ? @"stp_card_amex_template" : @"stp_card_amex";
break;
case STPCardBrandDinersClub:
imageName = shouldUseTemplate ? @"stp_card_diners_template" : @"stp_card_diners";
break;
case STPCardBrandDiscover:
imageName = shouldUseTemplate ? @"stp_card_discover_template" : @"stp_card_discover";
break;
case STPCardBrandJCB:
imageName = shouldUseTemplate ? @"stp_card_jcb_template" : @"stp_card_jcb";
break;
case STPCardBrandMasterCard:
imageName = shouldUseTemplate ? @"stp_card_mastercard_template" : @"stp_card_mastercard";
break;
case STPCardBrandUnknown:
shouldUseTemplate = YES;
imageName = @"stp_card_placeholder_template";
break;
case STPCardBrandVisa:
imageName = shouldUseTemplate ? @"stp_card_visa_template" : @"stp_card_visa";
case STPCardBrandOther:
shouldUseTemplate = YES;
imageName = @"stp_card_placeholder_template";
break;
}
UIImage *image = [self safeImageNamed:imageName
templateIfAvailable:shouldUseTemplate];
return image;
}
+ (UIImage *)imageWithTintColor:(UIColor *)color
forImage:(UIImage *)image {
UIImage *newImage;
UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
[color set];
UIImage *templateImage = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[templateImage drawInRect:CGRectMake(0, 0, templateImage.size.width, templateImage.size.height)];
newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
+ (UIImage *)paddedImageWithInsets:(UIEdgeInsets)insets
forImage:(UIImage *)image {
CGSize size = CGSizeMake(image.size.width + insets.left + insets.right,
image.size.height + insets.top + insets.bottom);
UIGraphicsBeginImageContextWithOptions(size, NO, image.scale);
CGPoint origin = CGPointMake(insets.left, insets.top);
[image drawAtPoint:origin];
UIImage *imageWithInsets = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
imageWithInsets = [imageWithInsets imageWithRenderingMode:image.renderingMode];
return imageWithInsets;
}
@end
+845
View File
@@ -0,0 +1,845 @@
//
// STPPaymentCardTextField.m
// Stripe
//
// Created by Jack Flintermann on 7/16/15.
// Copyright (c) 2015 Stripe, Inc. All rights reserved.
//
#import <UIKit/UIKit.h>
//#import "Stripe.h"
#import "STPPaymentCardTextField.h"
#import "STPPaymentCardTextFieldViewModel.h"
#import "STPFormTextField.h"
#import "STPImageLibrary.h"
#import "STPWeakStrongMacros.h"
#define FAUXPAS_IGNORED_IN_METHOD(...)
@interface STPPaymentCardTextField()<STPFormTextFieldDelegate>
@property(nonatomic, readwrite, strong)STPFormTextField *sizingField;
@property(nonatomic, readwrite, weak)UIImageView *brandImageView;
@property(nonatomic, readwrite, weak)UIView *fieldsView;
@property(nonatomic, readwrite, weak)STPFormTextField *numberField;
@property(nonatomic, readwrite, weak)STPFormTextField *expirationField;
@property(nonatomic, readwrite, weak)STPFormTextField *cvcField;
@property(nonatomic, readwrite, strong)STPPaymentCardTextFieldViewModel *viewModel;
@property(nonatomic, readonly, weak)UITextField *currentFirstResponderField;
@property(nonatomic, assign)BOOL numberFieldShrunk;
@property(nonatomic, readwrite, strong)STPCardParams *internalCardParams;
@end
@implementation STPPaymentCardTextField
@synthesize font = _font;
@synthesize textColor = _textColor;
@synthesize textErrorColor = _textErrorColor;
@synthesize placeholderColor = _placeholderColor;
@synthesize borderColor = _borderColor;
@synthesize borderWidth = _borderWidth;
@synthesize cornerRadius = _cornerRadius;
@dynamic enabled;
CGFloat const STPPaymentCardTextFieldDefaultPadding = 13;
#if CGFLOAT_IS_DOUBLE
#define stp_roundCGFloat(x) round(x)
#else
#define stp_roundCGFloat(x) roundf(x)
#endif
#pragma mark initializers
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit {
// We're using ivars here because UIAppearance tracks when setters are
// called, and won't override properties that have already been customized
_borderColor = [self.class placeholderGrayColor];
_cornerRadius = 5.0f;
_borderWidth = 1.0f;
self.layer.borderColor = [[_borderColor copy] CGColor];
self.layer.cornerRadius = _cornerRadius;
self.layer.borderWidth = _borderWidth;
self.clipsToBounds = YES;
_internalCardParams = [STPCardParams new];
_viewModel = [STPPaymentCardTextFieldViewModel new];
_sizingField = [self buildTextField];
_sizingField.formDelegate = nil;
UIImageView *brandImageView = [[UIImageView alloc] initWithImage:self.brandImage];
brandImageView.contentMode = UIViewContentModeCenter;
brandImageView.backgroundColor = [UIColor clearColor];
if ([brandImageView respondsToSelector:@selector(setTintColor:)]) {
brandImageView.tintColor = self.placeholderColor;
}
self.brandImageView = brandImageView;
STPFormTextField *numberField = [self buildTextField];
numberField.autoFormattingBehavior = STPFormTextFieldAutoFormattingBehaviorCardNumbers;
numberField.tag = STPCardFieldTypeNumber;
numberField.accessibilityLabel = NSLocalizedString(@"card number", @"accessibility label for text field");
self.numberField = numberField;
self.numberPlaceholder = [self.viewModel defaultPlaceholder];
STPFormTextField *expirationField = [self buildTextField];
expirationField.autoFormattingBehavior = STPFormTextFieldAutoFormattingBehaviorExpiration;
expirationField.tag = STPCardFieldTypeExpiration;
expirationField.alpha = 0;
expirationField.accessibilityLabel = NSLocalizedString(@"expiration date", @"accessibility label for text field");
self.expirationField = expirationField;
self.expirationPlaceholder = @"MM/YY";
STPFormTextField *cvcField = [self buildTextField];
cvcField.tag = STPCardFieldTypeCVC;
cvcField.alpha = 0;
self.cvcField = cvcField;
self.cvcPlaceholder = @"CVC";
self.cvcField.accessibilityLabel = self.cvcPlaceholder;
UIView *fieldsView = [[UIView alloc] init];
fieldsView.clipsToBounds = YES;
fieldsView.backgroundColor = [UIColor clearColor];
self.fieldsView = fieldsView;
[self addSubview:self.fieldsView];
[self.fieldsView addSubview:cvcField];
[self.fieldsView addSubview:expirationField];
[self.fieldsView addSubview:numberField];
[self addSubview:brandImageView];
}
- (STPPaymentCardTextFieldViewModel *)viewModel {
if (_viewModel == nil) {
_viewModel = [STPPaymentCardTextFieldViewModel new];
}
return _viewModel;
}
#pragma mark appearance properties
+ (UIColor *)placeholderGrayColor {
return [UIColor lightGrayColor];
}
- (void)setBackgroundColor:(UIColor *)backgroundColor {
[super setBackgroundColor:[backgroundColor copy]];
self.numberField.backgroundColor = self.backgroundColor;
}
- (UIColor *)backgroundColor {
return [super backgroundColor] ?: [UIColor whiteColor];
}
- (void)setFont:(UIFont *)font {
_font = [font copy];
for (UITextField *field in [self allFields]) {
field.font = _font;
}
self.sizingField.font = _font;
[self setNeedsLayout];
}
- (UIFont *)font {
return _font ?: [UIFont systemFontOfSize:18];
}
- (void)setTextColor:(UIColor *)textColor {
_textColor = [textColor copy];
for (STPFormTextField *field in [self allFields]) {
field.defaultColor = _textColor;
}
}
- (void)setContentVerticalAlignment:(UIControlContentVerticalAlignment)contentVerticalAlignment {
[super setContentVerticalAlignment:contentVerticalAlignment];
for (UITextField *field in [self allFields]) {
field.contentVerticalAlignment = contentVerticalAlignment;
}
switch (contentVerticalAlignment) {
case UIControlContentVerticalAlignmentCenter:
self.brandImageView.contentMode = UIViewContentModeCenter;
break;
case UIControlContentVerticalAlignmentBottom:
self.brandImageView.contentMode = UIViewContentModeBottom;
break;
case UIControlContentVerticalAlignmentFill:
self.brandImageView.contentMode = UIViewContentModeTop;
break;
case UIControlContentVerticalAlignmentTop:
self.brandImageView.contentMode = UIViewContentModeTop;
break;
}
}
- (UIColor *)textColor {
return _textColor ?: [UIColor blackColor];
}
- (void)setTextErrorColor:(UIColor *)textErrorColor {
_textErrorColor = [textErrorColor copy];
for (STPFormTextField *field in [self allFields]) {
field.errorColor = _textErrorColor;
}
}
- (UIColor *)textErrorColor {
return _textErrorColor ?: [UIColor redColor];
}
- (void)setPlaceholderColor:(UIColor *)placeholderColor {
_placeholderColor = [placeholderColor copy];
if ([self.brandImageView respondsToSelector:@selector(setTintColor:)]) {
self.brandImageView.tintColor = placeholderColor;
}
for (STPFormTextField *field in [self allFields]) {
field.placeholderColor = _placeholderColor;
}
}
- (UIColor *)placeholderColor {
return _placeholderColor ?: [self.class placeholderGrayColor];
}
- (void)setNumberPlaceholder:(NSString * __nullable)numberPlaceholder {
_numberPlaceholder = [numberPlaceholder copy];
self.numberField.placeholder = _numberPlaceholder;
}
- (void)setExpirationPlaceholder:(NSString * __nullable)expirationPlaceholder {
_expirationPlaceholder = [expirationPlaceholder copy];
self.expirationField.placeholder = _expirationPlaceholder;
}
- (void)setCvcPlaceholder:(NSString * __nullable)cvcPlaceholder {
_cvcPlaceholder = [cvcPlaceholder copy];
self.cvcField.placeholder = _cvcPlaceholder;
}
- (void)setCursorColor:(UIColor *)cursorColor {
self.tintColor = cursorColor;
}
- (UIColor *)cursorColor {
return self.tintColor;
}
- (void)setBorderColor:(UIColor * __nullable)borderColor {
_borderColor = borderColor;
if (borderColor) {
self.layer.borderColor = [[borderColor copy] CGColor];
}
else {
self.layer.borderColor = [[UIColor clearColor] CGColor];
}
}
- (UIColor * __nullable)borderColor {
return _borderColor;
}
- (void)setCornerRadius:(CGFloat)cornerRadius {
_cornerRadius = cornerRadius;
self.layer.cornerRadius = cornerRadius;
}
- (CGFloat)cornerRadius {
return _cornerRadius;
}
- (void)setBorderWidth:(CGFloat)borderWidth {
_borderWidth = borderWidth;
self.layer.borderWidth = borderWidth;
}
- (CGFloat)borderWidth {
return _borderWidth;
}
- (void)setKeyboardAppearance:(UIKeyboardAppearance)keyboardAppearance {
_keyboardAppearance = keyboardAppearance;
for (STPFormTextField *field in [self allFields]) {
field.keyboardAppearance = keyboardAppearance;
}
}
- (void)setInputAccessoryView:(UIView *)inputAccessoryView {
_inputAccessoryView = inputAccessoryView;
for (STPFormTextField *field in [self allFields]) {
field.inputAccessoryView = inputAccessoryView;
}
}
#pragma mark UIControl
- (void)setEnabled:(BOOL)enabled {
[super setEnabled:enabled];
for (STPFormTextField *textField in [self allFields]) {
textField.enabled = enabled;
};
}
#pragma mark UIResponder & related methods
- (BOOL)isFirstResponder {
return [self.currentFirstResponderField isFirstResponder];
}
- (BOOL)canBecomeFirstResponder {
return [[self nextFirstResponderField] canBecomeFirstResponder];
}
- (BOOL)becomeFirstResponder {
return [[self nextFirstResponderField] becomeFirstResponder];
}
- (STPFormTextField *)nextFirstResponderField {
if ([self.viewModel validationStateForField:STPCardFieldTypeNumber] != STPCardValidationStateValid) {
return self.numberField;
} else if ([self.viewModel validationStateForField:STPCardFieldTypeExpiration] != STPCardValidationStateValid) {
return self.expirationField;
} else {
return self.cvcField;
}
}
- (STPFormTextField *)currentFirstResponderField {
for (STPFormTextField *textField in [self allFields]) {
if ([textField isFirstResponder]) {
return textField;
}
}
return nil;
}
- (BOOL)canResignFirstResponder {
return [self.currentFirstResponderField canResignFirstResponder];
}
- (BOOL)resignFirstResponder {
[super resignFirstResponder];
BOOL success = [self.currentFirstResponderField resignFirstResponder];
[self setNumberFieldShrunk:[self shouldShrinkNumberField] animated:YES completion:nil];
[self updateImageForFieldType:STPCardFieldTypeNumber];
return success;
}
- (STPFormTextField *)previousField {
if (self.currentFirstResponderField == self.cvcField) {
return self.expirationField;
} else if (self.currentFirstResponderField == self.expirationField) {
return self.numberField;
}
return nil;
}
#pragma mark public convenience methods
- (void)clear {
for (STPFormTextField *field in [self allFields]) {
field.text = @"";
}
self.viewModel = [STPPaymentCardTextFieldViewModel new];
[self onChange];
[self updateImageForFieldType:STPCardFieldTypeNumber];
WEAK(self);
[self setNumberFieldShrunk:NO animated:YES completion:^(__unused BOOL completed){
STRONG(self);
if ([self isFirstResponder]) {
[[self numberField] becomeFirstResponder];
}
}];
}
- (BOOL)isValid {
return [self.viewModel isValid];
}
- (BOOL)valid {
return self.isValid;
}
#pragma mark readonly variables
- (NSString *)cardNumber {
return self.viewModel.cardNumber;
}
- (NSUInteger)expirationMonth {
return [self.viewModel.expirationMonth integerValue];
}
- (NSUInteger)expirationYear {
return [self.viewModel.expirationYear integerValue];
}
- (NSString *)formattedExpirationMonth {
return self.viewModel.expirationMonth;
}
- (NSString *)formattedExpirationYear {
return self.viewModel.expirationYear;
}
- (NSString *)cvc {
return self.viewModel.cvc;
}
- (STPCardParams *)cardParams {
self.internalCardParams.number = self.cardNumber;
self.internalCardParams.expMonth = self.expirationMonth;
self.internalCardParams.expYear = self.expirationYear;
self.internalCardParams.cvc = self.cvc;
return self.internalCardParams;
}
- (void)setCardParams:(STPCardParams *)cardParams {
self.internalCardParams = cardParams;
[self setText:cardParams.number inField:STPCardFieldTypeNumber];
BOOL expirationPresent = cardParams.expMonth && cardParams.expYear;
if (expirationPresent) {
NSString *text = [NSString stringWithFormat:@"%02lu%02lu",
(unsigned long)cardParams.expMonth,
(unsigned long)cardParams.expYear%100];
[self setText:text inField:STPCardFieldTypeExpiration];
}
else {
[self setText:@"" inField:STPCardFieldTypeExpiration];
}
[self setText:cardParams.cvc inField:STPCardFieldTypeCVC];
BOOL shrinkNumberField = [self shouldShrinkNumberField];
[self setNumberFieldShrunk:shrinkNumberField animated:NO completion:nil];
if ([self isFirstResponder]) {
[[self nextFirstResponderField] becomeFirstResponder];
}
// update the card image, falling back to the number field image if not editing
if ([self.expirationField isFirstResponder]) {
[self updateImageForFieldType:STPCardFieldTypeExpiration];
}
else if ([self.cvcField isFirstResponder]) {
[self updateImageForFieldType:STPCardFieldTypeCVC];
}
else {
[self updateImageForFieldType:STPCardFieldTypeNumber];
}
}
- (STPCardParams *)card {
if (!self.isValid) { return nil; }
return self.cardParams;
}
- (void)setCard:(STPCardParams *)card {
[self setCardParams:card];
}
- (void)setText:(NSString *)text inField:(STPCardFieldType)field {
NSString *nonNilText = text ?: @"";
STPFormTextField *textField = nil;
switch (field) {
case STPCardFieldTypeNumber:
textField = self.numberField;
break;
case STPCardFieldTypeExpiration:
textField = self.expirationField;
break;
case STPCardFieldTypeCVC:
textField = self.cvcField;
break;
}
textField.text = nonNilText;
}
- (CGSize)intrinsicContentSize {
CGSize imageSize = self.brandImage.size;
self.sizingField.text = self.viewModel.defaultPlaceholder;
CGFloat textHeight = [self.sizingField measureTextSize].height;
CGFloat imageHeight = imageSize.height + (STPPaymentCardTextFieldDefaultPadding);
CGFloat height = stp_roundCGFloat((MAX(MAX(imageHeight, textHeight), 44)));
CGFloat width = stp_roundCGFloat([self widthForCardNumber:self.viewModel.defaultPlaceholder] + imageSize.width + (STPPaymentCardTextFieldDefaultPadding * 3));
return CGSizeMake(width, height);
}
- (CGRect)brandImageRectForBounds:(CGRect)bounds {
return CGRectMake(STPPaymentCardTextFieldDefaultPadding, 0, self.brandImageView.image.size.width, bounds.size.height - 1);
}
- (CGRect)fieldsRectForBounds:(CGRect)bounds {
CGRect brandImageRect = [self brandImageRectForBounds:bounds];
return CGRectMake(CGRectGetMaxX(brandImageRect), 0, CGRectGetWidth(bounds) - CGRectGetMaxX(brandImageRect), CGRectGetHeight(bounds));
}
- (CGRect)numberFieldRectForBounds:(CGRect)bounds {
CGFloat placeholderWidth = [self widthForCardNumber:self.numberField.placeholder] - 4;
CGFloat numberWidth = [self widthForCardNumber:self.viewModel.defaultPlaceholder] - 4;
CGFloat numberFieldWidth = MAX(placeholderWidth, numberWidth);
CGFloat nonFragmentWidth = [self widthForCardNumber:[self.viewModel numberWithoutLastDigits]] - 12;
CGFloat numberFieldX = self.numberFieldShrunk ? STPPaymentCardTextFieldDefaultPadding - nonFragmentWidth : 8;
return CGRectMake(numberFieldX, 0, numberFieldWidth, CGRectGetHeight(bounds));
}
- (CGRect)cvcFieldRectForBounds:(CGRect)bounds {
CGRect fieldsRect = [self fieldsRectForBounds:bounds];
CGFloat cvcWidth = MAX([self widthForText:self.cvcField.placeholder], [self widthForText:@"8888"]);
CGFloat cvcX = self.numberFieldShrunk ?
CGRectGetWidth(fieldsRect) - cvcWidth - STPPaymentCardTextFieldDefaultPadding / 2 :
CGRectGetWidth(fieldsRect);
return CGRectMake(cvcX, 0, cvcWidth, CGRectGetHeight(bounds));
}
- (CGRect)expirationFieldRectForBounds:(CGRect)bounds {
CGRect numberFieldRect = [self numberFieldRectForBounds:bounds];
CGRect cvcRect = [self cvcFieldRectForBounds:bounds];
CGFloat expirationWidth = MAX([self widthForText:self.expirationField.placeholder], [self widthForText:@"88/88"]);
CGFloat expirationX = (CGRectGetMaxX(numberFieldRect) + CGRectGetMinX(cvcRect) - expirationWidth) / 2;
return CGRectMake(expirationX, 0, expirationWidth, CGRectGetHeight(bounds));
}
- (void)layoutSubviews {
[super layoutSubviews];
CGRect bounds = self.bounds;
self.brandImageView.frame = [self brandImageRectForBounds:bounds];
self.fieldsView.frame = [self fieldsRectForBounds:bounds];
self.numberField.frame = [self numberFieldRectForBounds:bounds];
self.cvcField.frame = [self cvcFieldRectForBounds:bounds];
self.expirationField.frame = [self expirationFieldRectForBounds:bounds];
}
#pragma mark - private helper methods
- (STPFormTextField *)buildTextField {
STPFormTextField *textField = [[STPFormTextField alloc] initWithFrame:CGRectZero];
textField.backgroundColor = [UIColor clearColor];
textField.keyboardType = UIKeyboardTypePhonePad;
textField.font = self.font;
textField.defaultColor = self.textColor;
textField.errorColor = self.textErrorColor;
textField.placeholderColor = self.placeholderColor;
textField.formDelegate = self;
textField.validText = true;
return textField;
}
- (NSArray *)allFields {
NSMutableArray *mutable = [NSMutableArray array];
if (self.numberField) {
[mutable addObject:self.numberField];
}
if (self.expirationField) {
[mutable addObject:self.expirationField];
}
if (self.cvcField) {
[mutable addObject:self.cvcField];
}
return [mutable copy];
}
typedef void (^STPNumberShrunkCompletionBlock)(BOOL completed);
- (void)setNumberFieldShrunk:(BOOL)shrunk animated:(BOOL)animated
completion:(STPNumberShrunkCompletionBlock)completion {
if (_numberFieldShrunk == shrunk) {
if (completion) {
completion(YES);
}
return;
}
_numberFieldShrunk = shrunk;
void (^animations)() = ^void() {
for (UIView *view in @[self.expirationField, self.cvcField]) {
view.alpha = 1.0f * shrunk;
}
[self layoutSubviews];
};
FAUXPAS_IGNORED_IN_METHOD(APIAvailability);
NSTimeInterval duration = animated * 0.3;
if ([UIView respondsToSelector:@selector(animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:)]) {
[UIView animateWithDuration:duration
delay:0
usingSpringWithDamping:0.85f
initialSpringVelocity:0
options:0
animations:animations
completion:completion];
} else {
[UIView animateWithDuration:duration
animations:animations
completion:completion];
}
}
- (BOOL)shouldShrinkNumberField {
return [self.viewModel validationStateForField:STPCardFieldTypeNumber] == STPCardValidationStateValid;
}
- (CGFloat)widthForText:(NSString *)text {
self.sizingField.autoFormattingBehavior = STPFormTextFieldAutoFormattingBehaviorNone;
[self.sizingField setText:text];
return [self.sizingField measureTextSize].width + 8;
}
- (CGFloat)widthForCardNumber:(NSString *)cardNumber {
self.sizingField.autoFormattingBehavior = STPFormTextFieldAutoFormattingBehaviorCardNumbers;
[self.sizingField setText:cardNumber];
return [self.sizingField measureTextSize].width + 20;
}
#pragma mark STPFormTextFieldDelegate
- (void)formTextFieldDidBackspaceOnEmpty:(__unused STPFormTextField *)formTextField {
STPFormTextField *previous = [self previousField];
[previous becomeFirstResponder];
[previous deleteBackward];
}
- (NSAttributedString *)formTextField:(STPFormTextField *)formTextField
modifyIncomingTextChange:(NSAttributedString *)input {
STPCardFieldType fieldType = formTextField.tag;
switch (fieldType) {
case STPCardFieldTypeNumber:
self.viewModel.cardNumber = input.string;
break;
case STPCardFieldTypeExpiration: {
self.viewModel.rawExpiration = input.string;
break;
}
case STPCardFieldTypeCVC:
self.viewModel.cvc = input.string;
break;
}
switch (fieldType) {
case STPCardFieldTypeNumber:
return [[NSAttributedString alloc] initWithString:self.viewModel.cardNumber
attributes:self.numberField.defaultTextAttributes];
case STPCardFieldTypeExpiration:
return [[NSAttributedString alloc] initWithString:self.viewModel.rawExpiration
attributes:self.expirationField.defaultTextAttributes];
case STPCardFieldTypeCVC:
return [[NSAttributedString alloc] initWithString:self.viewModel.cvc
attributes:self.cvcField.defaultTextAttributes];
}
}
- (void)formTextFieldTextDidChange:(STPFormTextField *)formTextField {
STPCardFieldType fieldType = formTextField.tag;
if (fieldType == STPCardFieldTypeNumber) {
[self updateImageForFieldType:fieldType];
}
STPCardValidationState state = [self.viewModel validationStateForField:fieldType];
formTextField.validText = YES;
switch (state) {
case STPCardValidationStateInvalid:
formTextField.validText = NO;
break;
case STPCardValidationStateIncomplete:
break;
case STPCardValidationStateValid: {
[[self nextFirstResponderField] becomeFirstResponder];
break;
}
}
[self onChange];
}
- (void)textFieldDidBeginEditing:(UITextField *)textField {
switch ((STPCardFieldType)textField.tag) {
case STPCardFieldTypeNumber:
[self setNumberFieldShrunk:NO animated:YES completion:nil];
if ([self.delegate respondsToSelector:@selector(paymentCardTextFieldDidBeginEditingNumber:)]) {
[self.delegate paymentCardTextFieldDidBeginEditingNumber:self];
}
break;
case STPCardFieldTypeCVC:
[self setNumberFieldShrunk:YES animated:YES completion:nil];
if ([self.delegate respondsToSelector:@selector(paymentCardTextFieldDidBeginEditingCVC:)]) {
[self.delegate paymentCardTextFieldDidBeginEditingCVC:self];
}
break;
case STPCardFieldTypeExpiration:
[self setNumberFieldShrunk:YES animated:YES completion:nil];
if ([self.delegate respondsToSelector:@selector(paymentCardTextFieldDidBeginEditingExpiration:)]) {
[self.delegate paymentCardTextFieldDidBeginEditingExpiration:self];
}
break;
}
[self updateImageForFieldType:textField.tag];
}
- (BOOL)textFieldShouldEndEditing:(__unused UITextField *)textField {
[self updateImageForFieldType:STPCardFieldTypeNumber];
return YES;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
switch ((STPCardFieldType)textField.tag) {
case STPCardFieldTypeNumber:
if ([self.delegate respondsToSelector:@selector(paymentCardTextFieldDidEndEditingNumber:)]) {
[self.delegate paymentCardTextFieldDidEndEditingNumber:self];
}
break;
case STPCardFieldTypeCVC:
if ([self.delegate respondsToSelector:@selector(paymentCardTextFieldDidEndEditingCVC:)]) {
[self.delegate paymentCardTextFieldDidEndEditingCVC:self];
}
break;
case STPCardFieldTypeExpiration:
if ([self.delegate respondsToSelector:@selector(paymentCardTextFieldDidEndEditingExpiration:)]) {
[self.delegate paymentCardTextFieldDidEndEditingExpiration:self];
}
break;
}
}
- (UIImage *)brandImage {
if (self.currentFirstResponderField) {
return [self brandImageForFieldType:self.currentFirstResponderField.tag];
} else {
return [self brandImageForFieldType:STPCardFieldTypeNumber];
}
}
+ (UIImage *)cvcImageForCardBrand:(STPCardBrand)cardBrand {
return [STPImageLibrary cvcImageForCardBrand:cardBrand];
}
+ (UIImage *)brandImageForCardBrand:(STPCardBrand)cardBrand {
return [STPImageLibrary brandImageForCardBrand:cardBrand];
}
- (UIImage *)brandImageForFieldType:(STPCardFieldType)fieldType {
if (fieldType == STPCardFieldTypeCVC) {
return [self.class cvcImageForCardBrand:self.viewModel.brand];
}
return [self.class brandImageForCardBrand:self.viewModel.brand];
}
- (void)updateImageForFieldType:(STPCardFieldType)fieldType {
UIImage *image = [self brandImageForFieldType:fieldType];
if (image != self.brandImageView.image) {
self.brandImageView.image = image;
CATransition *transition = [CATransition animation];
transition.duration = 0.2f;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade;
[self.brandImageView.layer addAnimation:transition forKey:nil];
[self setNeedsLayout];
}
}
- (void)onChange {
if ([self.delegate respondsToSelector:@selector(paymentCardTextFieldDidChange:)]) {
[self.delegate paymentCardTextFieldDidChange:self];
}
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
#pragma mark UIKeyInput
- (BOOL)hasText {
return self.numberField.hasText || self.expirationField.hasText || self.cvcField.hasText;
}
- (void)insertText:(NSString *)text {
[self.currentFirstResponderField insertText:text];
}
- (void)deleteBackward {
[self.currentFirstResponderField deleteBackward];
}
@end
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-implementations"
@implementation PTKCard
@end
@interface PTKView()
@property(nonatomic, weak)id<PTKViewDelegate>internalDelegate;
@end
@implementation PTKView
@dynamic delegate, card;
- (void)setDelegate:(id<PTKViewDelegate> __nullable)delegate {
self.internalDelegate = delegate;
}
- (id<PTKViewDelegate> __nullable)delegate {
return self.internalDelegate;
}
- (void)onChange {
[super onChange];
[self.internalDelegate paymentView:self withCard:[self card] isValid:self.isValid];
}
- (PTKCard * __nonnull)card {
PTKCard *card = [[PTKCard alloc] init];
card.number = self.cardNumber;
card.expMonth = self.expirationMonth;
card.expYear = self.expirationYear;
card.cvc = self.cvc;
return card;
}
@end
#pragma clang diagnostic pop
@@ -0,0 +1,105 @@
//
// STPPaymentCardTextFieldViewModel.m
// Stripe
//
// Created by Jack Flintermann on 7/21/15.
// Copyright (c) 2015 Stripe, Inc. All rights reserved.
//
#import "STPPaymentCardTextFieldViewModel.h"
#import "NSString+Stripe.h"
#define FAUXPAS_IGNORED_IN_METHOD(...)
@implementation STPPaymentCardTextFieldViewModel
- (void)setCardNumber:(NSString *)cardNumber {
NSString *sanitizedNumber = [STPCardValidator sanitizedNumericStringForString:cardNumber];
STPCardBrand brand = [STPCardValidator brandForNumber:sanitizedNumber];
NSInteger maxLength = [STPCardValidator maxLengthForCardBrand:brand];
_cardNumber = [sanitizedNumber stp_safeSubstringToIndex:maxLength];
}
// This might contain slashes.
- (void)setRawExpiration:(NSString *)expiration {
NSString *sanitizedExpiration = [STPCardValidator sanitizedNumericStringForString:expiration];
self.expirationMonth = [sanitizedExpiration stp_safeSubstringToIndex:2];
self.expirationYear = [[sanitizedExpiration stp_safeSubstringFromIndex:2] stp_safeSubstringToIndex:2];
}
- (NSString *)rawExpiration {
NSMutableArray *array = [@[] mutableCopy];
if (self.expirationMonth && ![self.expirationMonth isEqualToString:@""]) {
[array addObject:self.expirationMonth];
}
if ([STPCardValidator validationStateForExpirationMonth:self.expirationMonth] == STPCardValidationStateValid) {
[array addObject:self.expirationYear];
}
return [array componentsJoinedByString:@"/"];
}
- (void)setExpirationMonth:(NSString *)expirationMonth {
NSString *sanitizedExpiration = [STPCardValidator sanitizedNumericStringForString:expirationMonth];
if (sanitizedExpiration.length == 1 && ![sanitizedExpiration isEqualToString:@"0"] && ![sanitizedExpiration isEqualToString:@"1"]) {
sanitizedExpiration = [@"0" stringByAppendingString:sanitizedExpiration];
}
_expirationMonth = [sanitizedExpiration stp_safeSubstringToIndex:2];
}
- (void)setExpirationYear:(NSString *)expirationYear {
_expirationYear = [[STPCardValidator sanitizedNumericStringForString:expirationYear] stp_safeSubstringToIndex:2];
}
- (void)setCvc:(NSString *)cvc {
NSInteger maxLength = [STPCardValidator maxCVCLengthForCardBrand:self.brand];
_cvc = [[STPCardValidator sanitizedNumericStringForString:cvc] stp_safeSubstringToIndex:maxLength];
}
- (STPCardBrand)brand {
return [STPCardValidator brandForNumber:self.cardNumber];
}
- (STPCardValidationState)validationStateForField:(STPCardFieldType)fieldType {
switch (fieldType) {
case STPCardFieldTypeNumber:
return [STPCardValidator validationStateForNumber:self.cardNumber validatingCardBrand:YES];
break;
case STPCardFieldTypeExpiration: {
STPCardValidationState monthState = [STPCardValidator validationStateForExpirationMonth:self.expirationMonth];
STPCardValidationState yearState = [STPCardValidator validationStateForExpirationYear:self.expirationYear inMonth:self.expirationMonth cardBrand:[STPCardValidator brandForNumber:self.cardNumber]];
if (monthState == STPCardValidationStateValid && yearState == STPCardValidationStateValid) {
return STPCardValidationStateValid;
} else if (monthState == STPCardValidationStateInvalid || yearState == STPCardValidationStateInvalid) {
return STPCardValidationStateInvalid;
} else {
return STPCardValidationStateIncomplete;
}
break;
}
case STPCardFieldTypeCVC:
return [STPCardValidator validationStateForCVC:self.cvc cardBrand:self.brand];
}
}
- (BOOL)isValid {
return ([self validationStateForField:STPCardFieldTypeNumber] == STPCardValidationStateValid &&
[self validationStateForField:STPCardFieldTypeExpiration] == STPCardValidationStateValid &&
[self validationStateForField:STPCardFieldTypeCVC] == STPCardValidationStateValid);
}
- (NSString *)defaultPlaceholder {
return @"1234567812345678";
}
- (NSString *)numberWithoutLastDigits {
NSUInteger length = [STPCardValidator fragmentLengthForCardBrand:[STPCardValidator brandForNumber:self.cardNumber]];
NSUInteger toIndex = self.cardNumber.length - length;
return (toIndex < self.cardNumber.length) ?
[self.cardNumber substringToIndex:toIndex] :
[self.defaultPlaceholder stp_safeSubstringToIndex:[self defaultPlaceholder].length - length];
}
@end
@@ -0,0 +1,16 @@
//
// STPPaymentConfiguration+Private.h
// Stripe
//
// Created by Jack Flintermann on 6/9/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import "STPPaymentConfiguration.h"
@interface STPPaymentConfiguration (Private)
@property(nonatomic, readonly)BOOL applePayEnabled;
@end
+57
View File
@@ -0,0 +1,57 @@
//
// STPPaymentConfiguration.m
// Stripe
//
// Created by Jack Flintermann on 5/18/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import "STPPaymentConfiguration.h"
#import "STPPaymentConfiguration+Private.h"
#import "STPAPIClient.h"
#import "STPAPIClient+ApplePay.h"
@implementation STPPaymentConfiguration
+ (instancetype)sharedConfiguration {
static STPPaymentConfiguration *sharedConfiguration;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedConfiguration = [self new];
});
return sharedConfiguration;
}
- (instancetype)init {
self = [super init];
if (self) {
_additionalPaymentMethods = STPPaymentMethodTypeAll;
_requiredBillingAddressFields = STPBillingAddressFieldsNone;
_companyName = @"Telegram";
_smsAutofillDisabled = NO;
}
return self;
}
- (id)copyWithZone:(__unused NSZone *)zone {
STPPaymentConfiguration *copy = [self.class new];
copy.publishableKey = self.publishableKey;
copy.additionalPaymentMethods = self.additionalPaymentMethods;
copy.requiredBillingAddressFields = self.requiredBillingAddressFields;
copy.companyName = self.companyName;
copy.appleMerchantIdentifier = self.appleMerchantIdentifier;
copy.smsAutofillDisabled = self.smsAutofillDisabled;
return copy;
}
@end
@implementation STPPaymentConfiguration (Private)
- (BOOL)applePayEnabled {
return self.appleMerchantIdentifier &&
(self.additionalPaymentMethods & STPPaymentMethodTypeApplePay);
}
@end
+107
View File
@@ -0,0 +1,107 @@
//
// STPPhoneNumberValidator.m
// Stripe
//
// Created by Jack Flintermann on 10/16/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import "STPPhoneNumberValidator.h"
#import "STPCardValidator.h"
#import "NSString+Stripe.h"
@implementation STPPhoneNumberValidator
+ (NSString *)countryCodeOrCurrentLocaleCountryFromString:(nullable NSString *)nillableCode {
NSString *countryCode = nillableCode;
if (!countryCode) {
countryCode = [[NSLocale autoupdatingCurrentLocale] objectForKey:NSLocaleCountryCode];
}
return countryCode;
}
+ (BOOL)stringIsValidPartialPhoneNumber:(NSString *)string {
return [self stringIsValidPartialPhoneNumber:string forCountryCode:nil];
}
+ (BOOL)stringIsValidPhoneNumber:(NSString *)string {
return [self stringIsValidPhoneNumber:string forCountryCode:nil];
}
+ (BOOL)stringIsValidPartialPhoneNumber:(NSString *)string
forCountryCode:(nullable NSString *)nillableCode {
NSString *countryCode = [self countryCodeOrCurrentLocaleCountryFromString:nillableCode];
if ([countryCode isEqualToString:@"US"]) {
return [STPCardValidator sanitizedNumericStringForString:string].length <= 10;
}
else {
return YES;
}
}
+ (BOOL)stringIsValidPhoneNumber:(NSString *)string
forCountryCode:(nullable NSString *)nillableCode {
NSString *countryCode = [self countryCodeOrCurrentLocaleCountryFromString:nillableCode];
if ([countryCode isEqualToString:@"US"]) {
return [STPCardValidator sanitizedNumericStringForString:string].length == 10;
}
else {
return YES;
}
}
+ (NSString *)formattedSanitizedPhoneNumberForString:(NSString *)string {
return [self formattedSanitizedPhoneNumberForString:string
forCountryCode:nil];
}
+ (NSString *)formattedSanitizedPhoneNumberForString:(NSString *)string
forCountryCode:(nullable NSString *)nillableCode {
NSString *countryCode = [self countryCodeOrCurrentLocaleCountryFromString:nillableCode];
NSString *sanitized = [STPCardValidator sanitizedNumericStringForString:string];
return [self formattedPhoneNumberForString:sanitized
forCountryCode:countryCode];
}
+ (NSString *)formattedRedactedPhoneNumberForString:(NSString *)string {
return [self formattedRedactedPhoneNumberForString:string
forCountryCode:nil];
}
+ (NSString *)formattedRedactedPhoneNumberForString:(NSString *)string
forCountryCode:(nullable NSString *)nillableCode {
NSString *countryCode = [self countryCodeOrCurrentLocaleCountryFromString:nillableCode];
NSScanner *scanner = [NSScanner scannerWithString:string];
NSMutableString *prefix = [NSMutableString stringWithCapacity:string.length];
[scanner scanUpToString:@"*" intoString:&prefix];
NSString *number = [string stringByReplacingOccurrencesOfString:prefix withString:@""];
number = [number stringByReplacingOccurrencesOfString:@"*" withString:@""];
number = [self formattedPhoneNumberForString:number
forCountryCode:countryCode];
return [NSString stringWithFormat:@"%@ %@", prefix, number];
}
+ (NSString *)formattedPhoneNumberForString:(NSString *)string
forCountryCode:(NSString *)countryCode {
if (![countryCode isEqualToString:@"US"]) {
return string;
}
if (string.length >= 6) {
return [NSString stringWithFormat:@"(%@) %@-%@",
[string stp_safeSubstringToIndex:3],
[[string stp_safeSubstringToIndex:6] stp_safeSubstringFromIndex:3],
[[string stp_safeSubstringToIndex:10] stp_safeSubstringFromIndex:6]
];
} else if (string.length >= 3) {
return [NSString stringWithFormat:@"(%@) %@",
[string stp_safeSubstringToIndex:3],
[string stp_safeSubstringFromIndex:3]
];
}
return string;
}
@end
+117
View File
@@ -0,0 +1,117 @@
//
// STPPostalCodeValidator.m
// Stripe
//
// Created by Ben Guo on 4/14/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import "STPPostalCodeValidator.h"
#import "STPCardValidator.h"
#import "STPPhoneNumberValidator.h"
@implementation STPPostalCodeValidator
+ (BOOL)stringIsValidPostalCode:(nullable NSString *)string
type:(STPPostalCodeType)postalCodeType {
switch (postalCodeType) {
case STPCountryPostalCodeTypeNumericOnly:
return [STPCardValidator sanitizedNumericStringForString:string].length > 0;
case STPCountryPostalCodeTypeAlphanumeric:
return string.length > 0;
case STPCountryPostalCodeTypeNotRequired:
return YES;
}
}
+ (BOOL)stringIsValidPostalCode:(nullable NSString *)string
countryCode:(nullable NSString *)countryCode {
return [self stringIsValidPostalCode:string
type:[self postalCodeTypeForCountryCode:countryCode]];
}
+ (STPPostalCodeType)postalCodeTypeForCountryCode:(NSString *)countryCode {
if ([countryCode isEqualToString:@"US"]) {
return STPCountryPostalCodeTypeNumericOnly;
}
else if ([[self countriesWithNoPostalCodes] containsObject:countryCode]) {
return STPCountryPostalCodeTypeNotRequired;
}
else {
return STPCountryPostalCodeTypeAlphanumeric;
}
}
+ (NSArray *)countriesWithNoPostalCodes {
return @[ @"AE",
@"AG",
@"AN",
@"AO",
@"AW",
@"BF",
@"BI",
@"BJ",
@"BO",
@"BS",
@"BW",
@"BZ",
@"CD",
@"CF",
@"CG",
@"CI",
@"CK",
@"CM",
@"DJ",
@"DM",
@"ER",
@"FJ",
@"GD",
@"GH",
@"GM",
@"GN",
@"GQ",
@"GY",
@"HK",
@"IE",
@"JM",
@"KE",
@"KI",
@"KM",
@"KN",
@"KP",
@"LC",
@"ML",
@"MO",
@"MR",
@"MS",
@"MU",
@"MW",
@"NR",
@"NU",
@"PA",
@"QA",
@"RW",
@"SA",
@"SB",
@"SC",
@"SL",
@"SO",
@"SR",
@"ST",
@"SY",
@"TF",
@"TK",
@"TL",
@"TO",
@"TT",
@"TV",
@"TZ",
@"UG",
@"VU",
@"YE",
@"ZA",
@"ZW"
];
}
@end
+108
View File
@@ -0,0 +1,108 @@
//
// STPToken.m
// Stripe
//
// Created by Saikat Chakrabarti on 11/5/12.
//
//
#import "STPToken.h"
#import "STPCard.h"
#import "STPBankAccount.h"
#import "NSDictionary+Stripe.h"
@interface STPToken()
@property (nonatomic, nonnull) NSString *tokenId;
@property (nonatomic) BOOL livemode;
@property (nonatomic, nullable) STPCard *card;
@property (nonatomic, nullable) STPBankAccount *bankAccount;
@property (nonatomic, nullable) NSDate *created;
@property (nonatomic, readwrite, nonnull, copy) NSDictionary *allResponseFields;
@end
@implementation STPToken
- (instancetype)init {
self = [super init];
if (self) {
}
return self;
}
- (NSString *)description {
return self.tokenId ?: @"Unknown token";
}
- (NSString *)debugDescription {
NSString *token = self.tokenId ?: @"Unknown token";
NSString *livemode = self.livemode ? @"live mode" : @"test mode";
return [NSString stringWithFormat:@"%@ (%@)", token, livemode];
}
- (BOOL)isEqual:(id)object {
return [self isEqualToToken:object];
}
- (NSUInteger)hash {
return [self.tokenId hash];
}
- (BOOL)isEqualToToken:(STPToken *)object {
if (self == object) {
return YES;
}
if (!object || ![object isKindOfClass:self.class]) {
return NO;
}
if ((self.card || object.card) && (![self.card isEqual:object.card])) {
return NO;
}
if ((self.bankAccount || object.bankAccount) && (![self.bankAccount isEqual:object.bankAccount])) {
return NO;
}
return self.livemode == object.livemode && [self.tokenId isEqualToString:object.tokenId] && [self.created isEqualToDate:object.created] &&
[self.card isEqual:object.card] && [self.tokenId isEqualToString:object.tokenId] && [self.created isEqualToDate:object.created];
}
#pragma mark STPSource
- (NSString *)stripeID {
return self.tokenId;
}
#pragma mark STPAPIResponseDecodable
+ (NSArray *)requiredFields {
return @[@"id", @"livemode", @"created"];
}
+ (instancetype)decodedObjectFromAPIResponse:(NSDictionary *)response {
NSDictionary *dict = [response stp_dictionaryByRemovingNullsValidatingRequiredFields:[self requiredFields]];
if (!dict) {
return nil;
}
STPToken *token = [self new];
token.tokenId = dict[@"id"];
token.livemode = [dict[@"livemode"] boolValue];
token.created = [NSDate dateWithTimeIntervalSince1970:[dict[@"created"] doubleValue]];
NSDictionary *cardDictionary = dict[@"card"];
if (cardDictionary) {
token.card = [STPCard decodedObjectFromAPIResponse:cardDictionary];
}
NSDictionary *bankAccountDictionary = dict[@"bank_account"];
if (bankAccountDictionary) {
token.bankAccount = [STPBankAccount decodedObjectFromAPIResponse:bankAccountDictionary];
}
token.allResponseFields = dict;
return token;
}
@end
+19
View File
@@ -0,0 +1,19 @@
//
// STPWeakStrongMacros.h
// Stripe
//
// Created by Brian Dorfman on 7/28/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
/*
* Based on @weakify() and @strongify() from
* https://github.com/jspahrsummers/libextc
*/
#define WEAK(var) __weak typeof(var) weak_##var = var;
#define STRONG(var) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
__strong typeof(var) var = weak_##var; \
_Pragma("clang diagnostic pop") \
+137
View File
@@ -0,0 +1,137 @@
//
// StripeError.m
// Stripe
//
// Created by Saikat Chakrabarti on 11/4/12.
//
//
#import "StripeError.h"
#import "STPFormEncoder.h"
NSString *const StripeDomain = @"com.stripe.lib";
NSString *const STPCardErrorCodeKey = @"com.stripe.lib:CardErrorCodeKey";
NSString *const STPErrorMessageKey = @"com.stripe.lib:ErrorMessageKey";
NSString *const STPErrorParameterKey = @"com.stripe.lib:ErrorParameterKey";
NSString *const STPInvalidNumber = @"com.stripe.lib:InvalidNumber";
NSString *const STPInvalidExpMonth = @"com.stripe.lib:InvalidExpiryMonth";
NSString *const STPInvalidExpYear = @"com.stripe.lib:InvalidExpiryYear";
NSString *const STPInvalidCVC = @"com.stripe.lib:InvalidCVC";
NSString *const STPIncorrectNumber = @"com.stripe.lib:IncorrectNumber";
NSString *const STPExpiredCard = @"com.stripe.lib:ExpiredCard";
NSString *const STPCardDeclined = @"com.stripe.lib:CardDeclined";
NSString *const STPProcessingError = @"com.stripe.lib:ProcessingError";
NSString *const STPIncorrectCVC = @"com.stripe.lib:IncorrectCVC";
@implementation NSError(Stripe)
+ (NSError *)stp_errorFromStripeResponse:(NSDictionary *)jsonDictionary {
NSDictionary *errorDictionary = jsonDictionary[@"error"];
if (!errorDictionary) {
return nil;
}
NSString *type = errorDictionary[@"type"];
NSString *devMessage = errorDictionary[@"message"];
NSString *parameter = errorDictionary[@"param"];
NSInteger code = 0;
// There should always be a message and type for the error
if (devMessage == nil || type == nil) {
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: [self stp_unexpectedErrorMessage],
STPErrorMessageKey: @"Could not interpret the error response that was returned from Stripe."
};
return [[self alloc] initWithDomain:StripeDomain code:STPAPIError userInfo:userInfo];
}
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[STPErrorMessageKey] = devMessage;
if (parameter) {
userInfo[STPErrorParameterKey] = [STPFormEncoder stringByReplacingSnakeCaseWithCamelCase:parameter];
}
if ([type isEqualToString:@"api_error"]) {
code = STPAPIError;
userInfo[NSLocalizedDescriptionKey] = [self stp_unexpectedErrorMessage];
} else if ([type isEqualToString:@"invalid_request_error"]) {
code = STPInvalidRequestError;
userInfo[NSLocalizedDescriptionKey] = devMessage;
} else if ([type isEqualToString:@"card_error"]) {
code = STPCardError;
NSDictionary *errorCodes = @{
@"incorrect_number": @{@"code": STPIncorrectNumber, @"message": [self stp_cardErrorInvalidNumberUserMessage]},
@"invalid_number": @{@"code": STPInvalidNumber, @"message": [self stp_cardErrorInvalidNumberUserMessage]},
@"invalid_expiry_month": @{@"code": STPInvalidExpMonth, @"message": [self stp_cardErrorInvalidExpMonthUserMessage]},
@"invalid_expiry_year": @{@"code": STPInvalidExpYear, @"message": [self stp_cardErrorInvalidExpYearUserMessage]},
@"invalid_cvc": @{@"code": STPInvalidCVC, @"message": [self stp_cardInvalidCVCUserMessage]},
@"expired_card": @{@"code": STPExpiredCard, @"message": [self stp_cardErrorExpiredCardUserMessage]},
@"incorrect_cvc": @{@"code": STPIncorrectCVC, @"message": [self stp_cardInvalidCVCUserMessage]},
@"card_declined": @{@"code": STPCardDeclined, @"message": [self stp_cardErrorDeclinedUserMessage]},
@"processing_error": @{@"code": STPProcessingError, @"message": [self stp_cardErrorProcessingErrorUserMessage]},
};
NSDictionary *codeMapEntry = errorCodes[errorDictionary[@"code"]];
if (codeMapEntry) {
userInfo[STPCardErrorCodeKey] = codeMapEntry[@"code"];
userInfo[NSLocalizedDescriptionKey] = codeMapEntry[@"message"];
} else {
userInfo[STPCardErrorCodeKey] = errorDictionary[@"code"];
userInfo[NSLocalizedDescriptionKey] = devMessage;
}
}
return [[self alloc] initWithDomain:StripeDomain code:code userInfo:userInfo];
}
+ (nonnull NSError *)stp_genericFailedToParseResponseError {
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: [self stp_unexpectedErrorMessage],
STPErrorMessageKey: @"The response from Stripe failed to get parsed into valid JSON."
};
return [[self alloc] initWithDomain:StripeDomain code:STPAPIError userInfo:userInfo];
}
- (BOOL)stp_isUnknownCheckoutError {
return self.code == STPCheckoutUnknownError;
}
- (BOOL)stp_isURLSessionCancellationError {
return [self.domain isEqualToString:NSURLErrorDomain] && self.code == NSURLErrorCancelled;
}
#pragma mark Strings
+ (nonnull NSString *)stp_cardErrorInvalidNumberUserMessage {
return @"Your_cards_number_is_invalid";
}
+ (nonnull NSString *)stp_cardInvalidCVCUserMessage {
return @"Your_cards_security_code_is_invalid";
}
+ (nonnull NSString *)stp_cardErrorInvalidExpMonthUserMessage {
return @"Your_cards_expiration_month_is_invalid";
}
+ (nonnull NSString *)stp_cardErrorInvalidExpYearUserMessage {
return @"Your_cards_expiration_year_is_invalid";
}
+ (nonnull NSString *)stp_cardErrorExpiredCardUserMessage {
return @"Your_card_has_expired";
}
+ (nonnull NSString *)stp_cardErrorDeclinedUserMessage {
return @"Your_card_was_declined";
}
+ (nonnull NSString *)stp_unexpectedErrorMessage {
return @"Error.Generic";
}
+ (nonnull NSString *)stp_cardErrorProcessingErrorUserMessage {
return @"Error.Generic";
}
@end