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
+31
View File
@@ -0,0 +1,31 @@
objc_library(
name = "Stripe",
enable_modules = True,
module_name = "Stripe",
srcs = glob([
"Sources/**/*.m",
"Sources/**/*.h",
]),
hdrs = glob([
"PublicHeaders/**/*.h",
]),
includes = [
"PublicHeaders",
],
copts = [
"-I{}/PublicHeaders/Stripe".format(package_name()),
"-Werror",
],
sdk_frameworks = [
"Foundation",
"UIKit",
"AddressBook",
],
weak_sdk_frameworks = [
"PassKit",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,32 @@
//
// STPAPIClient+ApplePay.h
// Stripe
//
// Created by Jack Flintermann on 12/19/14.
//
#import <Foundation/Foundation.h>
#import <PassKit/PassKit.h>
#import <Stripe/STPAPIClient.h>
#define FAUXPAS_IGNORED_IN_FILE(...)
FAUXPAS_IGNORED_IN_FILE(APIAvailability)
/**
* STPAPIClient extensions to create Stripe tokens from Apple Pay PKPayment objects.
*/
@interface STPAPIClient (ApplePay)
/**
* Converts a PKPayment object into a Stripe token using the Stripe API.
*
* @param payment The user's encrypted payment information as returned from a PKPaymentAuthorizationViewController. Cannot be nil.
* @param completion The callback to run with the returned Stripe token (and any errors that may have occurred).
*/
- (void)createTokenWithPayment:(nonnull PKPayment *)payment
completion:(nonnull STPTokenCompletionBlock)completion NS_AVAILABLE_IOS(8_0);
@end
void linkSTPAPIClientApplePayCategory(void);
+204
View File
@@ -0,0 +1,204 @@
//
// STPAPIClient.h
// StripeExample
//
// Created by Jack Flintermann on 12/18/14.
// Copyright (c) 2014 Stripe. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <PassKit/PassKit.h>
#import <Stripe/STPBlocks.h>
#import <Stripe/STPCardBrand.h>
NS_ASSUME_NONNULL_BEGIN
#define FAUXPAS_IGNORED_ON_LINE(...)
#define FAUXPAS_IGNORED_IN_FILE(...)
FAUXPAS_IGNORED_IN_FILE(APIAvailability)
static NSString *const STPSDKVersion = @"9.1.0";
@class STPBankAccount, STPBankAccountParams, STPCard, STPCardParams, STPToken, STPPaymentConfiguration;
/**
A top-level class that imports the rest of the Stripe SDK. This class used to contain several methods to create Stripe tokens, but those are now deprecated in
favor of STPAPIClient.
*/
@interface Stripe : NSObject FAUXPAS_IGNORED_ON_LINE(UnprefixedClass);
/**
* Set your Stripe API key with this method. New instances of STPAPIClient will be initialized with this value. You should call this method as early as
* possible in your application's lifecycle, preferably in your AppDelegate.
*
* @param publishableKey Your publishable key, obtained from https://stripe.com/account/apikeys
* @warning Make sure not to ship your test API keys to the App Store! This will log a warning if you use your test key in a release build.
*/
+ (void)setDefaultPublishableKey:(NSString *)publishableKey;
/// The current default publishable key.
+ (nullable NSString *)defaultPublishableKey;
/**
* By default, Stripe collects some basic information about SDK usage.
* You can call this method to turn off analytics collection.
*/
+ (void)disableAnalytics;
@end
/// A client for making connections to the Stripe API.
@interface STPAPIClient : NSObject
+ (NSString *)stringWithCardBrand:(STPCardBrand)brand;
/**
* A shared singleton API client. Its API key will be initially equal to [Stripe defaultPublishableKey].
*/
+ (instancetype)sharedClient;
- (instancetype)initWithConfiguration:(STPPaymentConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithPublishableKey:(NSString *)publishableKey;
/**
* @see [Stripe setDefaultPublishableKey:]
*/
@property (nonatomic, copy, nullable) NSString *publishableKey;
/**
* @see -initWithConfiguration
*/
@property (nonatomic, copy) STPPaymentConfiguration *configuration;
- (void)createTokenWithCard:(STPCardParams *)card completion:(nullable STPTokenCompletionBlock)completion;
@end
#pragma mark Bank Accounts
/**
* STPAPIClient extensions to create Stripe tokens from bank accounts.
*/
@interface STPAPIClient (BankAccounts)
/**
* Converts an STPBankAccount object into a Stripe token using the Stripe API.
*
* @param bankAccount The user's bank account details. Cannot be nil. @see https://stripe.com/docs/api#create_bank_account_token
* @param completion The callback to run with the returned Stripe token (and any errors that may have occurred).
*/
- (void)createTokenWithBankAccount:(STPBankAccountParams *)bankAccount completion:(__nullable STPTokenCompletionBlock)completion;
@end
#pragma mark Credit Cards
/**
* STPAPIClient extensions to create Stripe tokens from credit or debit cards.
*/
@interface STPAPIClient (CreditCards)
/**
* Converts an STPCardParams object into a Stripe token using the Stripe API.
*
* @param card The user's card details. Cannot be nil. @see https://stripe.com/docs/api#create_card_token
* @param completion The callback to run with the returned Stripe token (and any errors that may have occurred).
*/
@end
/**
* Convenience methods for working with Apple Pay.
*/
@interface Stripe(ApplePay)
/**
* Whether or not this device is capable of using Apple Pay. This checks both whether the user is running an iPhone 6/6+ or later, iPad Air 2 or later, or iPad
*mini 3 or later, as well as whether or not they have stored any cards in Apple Pay on their device.
*
* @param paymentRequest The return value of this method depends on the `supportedNetworks` property of this payment request, which by default should be
*`@[PKPaymentNetworkAmex, PKPaymentNetworkMasterCard, PKPaymentNetworkVisa, PKPaymentNetworkDiscover]`.
*
* @return whether or not the user is currently able to pay with Apple Pay.
*/
+ (BOOL)canSubmitPaymentRequest:(PKPaymentRequest *)paymentRequest NS_AVAILABLE_IOS(8_0);
/**
* A convenience method to return a `PKPaymentRequest` with sane default values. You will still need to configure the `paymentSummaryItems` property to indicate
*what the user is purchasing, as well as the optional `requiredShippingAddressFields`, `requiredBillingAddressFields`, and `shippingMethods` properties to indicate
*what contact information your application requires.
*
* @param merchantIdentifier Your Apple Merchant ID, as obtained at https://developer.apple.com/account/ios/identifiers/merchant/merchantCreate.action
*
* @return a `PKPaymentRequest` with proper default values. Returns nil if running on < iOS8.
*/
+ (PKPaymentRequest *)paymentRequestWithMerchantIdentifier:(NSString *)merchantIdentifier NS_AVAILABLE_IOS(8_0);
+ (void)createTokenWithPayment:(PKPayment *)payment
completion:(STPTokenCompletionBlock)handler __attribute__((deprecated("Use STPAPIClient instead.")));
@end
#pragma mark - Deprecated Methods
/**
* A callback to be run with a token response from the Stripe API.
*
* @param token The Stripe token from the response. Will be nil if an error occurs. @see STPToken
* @param error The error returned from the response, or nil in one occurs. @see StripeError.h for possible values.
* @deprecated This has been renamed to STPTokenCompletionBlock.
*/
typedef void (^STPCompletionBlock)(STPToken * __nullable token, NSError * __nullable error) __attribute__((deprecated("STPCompletionBlock has been renamed to STPTokenCompletionBlock.")));
// These methods are deprecated. You should instead use STPAPIClient to create tokens.
// Example: [Stripe createTokenWithCard:card completion:completion];
// becomes [[STPAPIClient sharedClient] createTokenWithCard:card completion:completion];
@interface Stripe (Deprecated)
/**
* Securely convert your user's credit card details into a Stripe token, which you can then safely store on your server and use to charge the user. The URL
*connection will run on the main queue. Uses the value of [Stripe defaultPublishableKey] for authentication.
*
* @param card The user's card details. @see STPCard
* @param handler Code to run when the user's card has been turned into a Stripe token.
* @deprecated Use STPAPIClient instead.
*/
+ (void)createTokenWithCard:(STPCard *)card completion:(nullable STPCompletionBlock)handler __attribute__((deprecated));
/**
* Securely convert your user's credit card details into a Stripe token, which you can then safely store on your server and use to charge the user. The URL
*connection will run on the main queue.
*
* @param card The user's card details. @see STPCard
* @param publishableKey The API key to use to authenticate with Stripe. Get this at https://stripe.com/account/apikeys .
* @param handler Code to run when the user's card has been turned into a Stripe token.
* @deprecated Use STPAPIClient instead.
*/
+ (void)createTokenWithCard:(STPCard *)card publishableKey:(NSString *)publishableKey completion:(nullable STPCompletionBlock)handler __attribute__((deprecated));
/**
* Securely convert your user's credit card details into a Stripe token, which you can then safely store on your server and use to charge the user. The URL
*connection will run on the main queue. Uses the value of [Stripe defaultPublishableKey] for authentication.
*
* @param bankAccount The user's bank account details. @see STPBankAccount
* @param handler Code to run when the user's card has been turned into a Stripe token.
* @deprecated Use STPAPIClient instead.
*/
+ (void)createTokenWithBankAccount:(STPBankAccount *)bankAccount completion:(nullable STPCompletionBlock)handler __attribute__((deprecated));
/**
* Securely convert your user's credit card details into a Stripe token, which you can then safely store on your server and use to charge the user. The URL
*connection will run on the main queue. Uses the value of [Stripe defaultPublishableKey] for authentication.
*
* @param bankAccount The user's bank account details. @see STPBankAccount
* @param publishableKey The API key to use to authenticate with Stripe. Get this at https://stripe.com/account/apikeys .
* @param handler Code to run when the user's card has been turned into a Stripe token.
* @deprecated Use STPAPIClient instead.
*/
+ (void)createTokenWithBankAccount:(STPBankAccount *)bankAccount
publishableKey:(NSString *)publishableKey
completion:(nullable STPCompletionBlock)handler __attribute__((deprecated));
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,28 @@
//
// STPAPIResponseDecodable.h
// Stripe
//
// Created by Jack Flintermann on 10/14/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@protocol STPAPIResponseDecodable <NSObject>
/**
* These fields are required to be present in the API response. If any of them are nil, `decodedObjectFromAPIResponse` should also return nil.
*/
+ (nonnull NSArray *)requiredFields;
/**
* Parses an response from the Stripe API (in JSON format; represented as an `NSDictionary`) into an instance of the class. Returns nil if the object could not be decoded (i.e. if one of its `requiredFields` is nil).
*/
+ (nullable instancetype)decodedObjectFromAPIResponse:(nullable NSDictionary *)response;
/**
* The raw JSON response used to create the object. This can be useful for using beta features that haven't yet been made into properties in the SDK.
*/
@property(nonatomic, readonly, nonnull, copy)NSDictionary *allResponseFields;
@end
+99
View File
@@ -0,0 +1,99 @@
//
// STPAddress.h
// Stripe
//
// Created by Ben Guo on 4/13/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
#import <AddressBook/AddressBook.h>
#pragma clang diagnostic pop
#define FAUXPAS_IGNORED_IN_METHOD(...)
#define FAUXPAS_IGNORED_ON_LINE(...)
#import <Foundation/Foundation.h>
#import <PassKit/PassKit.h>
/**
* What set of billing address information you need to collect from your user.
*
* @note If the user is from a country that does not use zip/postal codes,
* the user may not be asked for one regardless of this setting.
*/
typedef NS_ENUM(NSUInteger, STPBillingAddressFields) {
/**
* No billing address information
*/
STPBillingAddressFieldsNone,
/**
* Just request the user's billing ZIP code
*/
STPBillingAddressFieldsZip,
/**
* Request the user's full billing address
*/
STPBillingAddressFieldsFull,
};
/**
* STPAddress Contains an address as represented by the Stripe API.
*/
@interface STPAddress : NSObject
/**
* The user's full name (e.g. "Jane Doe")
*/
@property (nonatomic, copy) NSString *name;
/**
* The first line of the user's street address (e.g. "123 Fake St")
*/
@property (nonatomic, copy) NSString *line1;
/**
* The apartment, floor number, etc of the user's street address (e.g. "Apartment 1A")
*/
@property (nonatomic, copy) NSString *line2;
/**
* The city in which the user resides (e.g. "San Francisco")
*/
@property (nonatomic, copy) NSString *city;
/**
* The state in which the user resides (e.g. "CA")
*/
@property (nonatomic, copy) NSString *state;
/**
* The postal code in which the user resides (e.g. "90210")
*/
@property (nonatomic, copy) NSString *postalCode;
/**
* The ISO country code of the address (e.g. "US")
*/
@property (nonatomic, copy) NSString *country;
/**
* The phone number of the address (e.g. "8885551212")
*/
@property (nonatomic, copy) NSString *phone;
/**
* The email of the address (e.g. "jane@doe.com")
*/
@property (nonatomic, copy) NSString *email;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
- (instancetype)initWithABRecord:(ABRecordRef)record;
#pragma clang diagnostic pop
- (BOOL)containsRequiredFields:(STPBillingAddressFields)requiredFields;
//+ (PKAddressField)applePayAddressFieldsFromBillingAddressFields:(STPBillingAddressFields)billingAddressFields; FAUXPAS_IGNORED_ON_LINE(APIAvailability);
@end
+26
View File
@@ -0,0 +1,26 @@
//
// STPBINRange.h
// Stripe
//
// Created by Jack Flintermann on 5/24/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <Stripe/STPCardBrand.h>
NS_ASSUME_NONNULL_BEGIN
@interface STPBINRange : NSObject
@property(nonatomic, readonly)NSUInteger length;
@property(nonatomic, readonly)STPCardBrand brand;
+ (NSArray<STPBINRange *> *)allRanges;
+ (NSArray<STPBINRange *> *)binRangesForNumber:(NSString *)number;
+ (NSArray<STPBINRange *> *)binRangesForBrand:(STPCardBrand)brand;
+ (instancetype)mostSpecificBINRangeForNumber:(NSString *)number;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,65 @@
//
// STPBackendAPIAdapter.h
// Stripe
//
// Created by Jack Flintermann on 1/12/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <Stripe/STPAddress.h>
#import <Stripe/STPBlocks.h>
#import <Stripe/STPSource.h>
#import <Stripe/STPCustomer.h>
NS_ASSUME_NONNULL_BEGIN
@class STPCard, STPToken;
/**
* Call this block after you're done fetching a customer on your server. You can use the `STPCustomerDeserializer` class to convert a JSON response into an `STPCustomer` object.
*
* @param customer a deserialized `STPCustomer` object obtained from your backend API, or nil if an error occurred.
* @param error any error that occurred while communicating with your server, or nil if your call succeeded
*/
typedef void (^STPCustomerCompletionBlock)(STPCustomer * __nullable customer, NSError * __nullable error);
/**
* You should make your application's API client conform to this interface in order to use it with an `STPPaymentContext`. It provides a "bridge" from the prebuilt UI we expose (such as `STPPaymentMethodsViewController`) to your backend to fetch the information it needs to power those views. To read about how to implement this protocol, see https://stripe.com/docs/mobile/ios#prepare-your-api . To see examples of implementing these APIs, see MyAPIClient.swift in our example project and https://github.com/stripe/example-ios-backend .
*/
@protocol STPBackendAPIAdapter<NSObject>
/**
* Retrieve the cards to be displayed inside a payment context. On your backend, retrieve the Stripe customer associated with your currently logged-in user (see https://stripe.com/docs/api#retrieve_customer ), and return the raw JSON response from the Stripe API. (For an example Ruby implementation of this API, see https://github.com/stripe/example-ios-backend/blob/master/web.rb#L40 ). Back in your iOS app, after you've called this API, deserialize your API response into an `STPCustomer` object (you can use the `STPCustomerDeserializer` class to do this). See MyAPIClient.swift in our example project to see this in action.
*
* @see STPCard
* @param completion call this callback when you're done fetching and parsing the above information from your backend. For example, `completion(customer, nil)` (if your call succeeds) or `completion(nil, error)` if an error is returned.
*
* @note If you are on Swift 3, you must declare the completion block as `@escaping` or Xcode will give you a protocol conformance error. https://bugs.swift.org/browse/SR-2597
*/
- (void)retrieveCustomer:(STPCustomerCompletionBlock)completion;
/**
* Adds a payment source to a customer. On your backend, retrieve the Stripe customer associated with your logged-in user. Then, call the Update Customer method on that customer as described at https://stripe.com/docs/api#update_customer (for an example Ruby implementation of this API, see https://github.com/stripe/example-ios-backend/blob/master/web.rb#L60 ). If this API call succeeds, call `completion(nil)`. Otherwise, call `completion(error)` with the error that occurred.
*
* @param source a valid payment source, such as a card token.
* @param completion call this callback when you're done adding the token to the customer on your backend. For example, `completion(nil)` (if your call succeeds) or `completion(error)` if an error is returned.
*
* @note If you are on Swift 3, you must declare the completion block as `@escaping` or Xcode will give you a protocol conformance error. https://bugs.swift.org/browse/SR-2597
*/
- (void)attachSourceToCustomer:(id<STPSource>)source completion:(STPErrorBlock)completion;
/**
* Change a customer's `default_source` to be the provided card. On your backend, retrieve the Stripe customer associated with your logged-in user. Then, call the Customer Update method as described at https://stripe.com/docs/api#update_customer , specifying default_source to be the value of source.stripeID (for an example Ruby implementation of this API, see https://github.com/stripe/example-ios-backend/blob/master/web.rb#L82 ). If this API call succeeds, call `completion(nil)`. Otherwise, call `completion(error)` with the error that occurred.
*
* @param source The newly-selected default source for the user.
* @param completion call this callback when you're done selecting the new default source for the customer on your backend. For example, `completion(nil)` (if your call succeeds) or `completion(error)` if an error is returned.
*
* @note If you are on Swift 3, you must declare the completion block as `@escaping` or Xcode will give you a protocol conformance error. https://bugs.swift.org/browse/SR-2597
*/
- (void)selectDefaultCustomerSource:(id<STPSource>)source completion:(STPErrorBlock)completion;
@end
NS_ASSUME_NONNULL_END
+98
View File
@@ -0,0 +1,98 @@
//
// STPBankAccount.h
// Stripe
//
// Created by Charles Scalesse on 10/1/14.
//
//
#import <Foundation/Foundation.h>
#import <Stripe/STPBankAccountParams.h>
#import <Stripe/STPAPIResponseDecodable.h>
typedef NS_ENUM(NSInteger, STPBankAccountStatus) {
STPBankAccountStatusNew,
STPBankAccountStatusValidated,
STPBankAccountStatusVerified,
STPBankAccountStatusErrored,
};
/**
* Representation of a user's bank account details that have been tokenized with the Stripe API. @see https://stripe.com/docs/api#cards
*/
@interface STPBankAccount : STPBankAccountParams<STPAPIResponseDecodable>
/**
* The last 4 digits of the bank account's account number.
*/
- (nonnull NSString *)last4;
/**
* The routing number for the bank account. This should be the ACH routing number, not the wire routing number.
*/
@property (nonatomic, copy, nonnull) NSString *routingNumber;
/**
* Two-letter ISO code representing the country the bank account is located in.
*/
@property (nonatomic, copy, nullable) NSString *country;
/**
* The default currency for the bank account.
*/
@property (nonatomic, copy, nullable) NSString *currency;
/**
* The Stripe ID for the bank account.
*/
@property (nonatomic, readonly, nonnull) NSString *bankAccountId;
/**
* The last 4 digits of the account number.
*/
@property (nonatomic, readonly, nullable) NSString *last4;
/**
* The name of the bank that owns the account.
*/
@property (nonatomic, readonly, nullable) NSString *bankName;
/**
* The name of the person or business that owns the bank account.
*/
@property(nonatomic, copy, nullable) NSString *accountHolderName;
/**
* The type of entity that holds the account.
*/
@property(nonatomic) STPBankAccountHolderType accountHolderType;
/**
* A proxy for the account number, this uniquely identifies the account and can be used to compare equality of different bank accounts.
*/
@property (nonatomic, readonly, nullable) NSString *fingerprint;
/**
* The validation status of the bank account. @see STPBankAccountStatus
*/
@property (nonatomic, readonly) STPBankAccountStatus status;
/**
* Whether or not the bank account has been validated via microdeposits or other means.
* @deprecated Use status == STPBankAccountStatusValidated instead.
*/
@property (nonatomic, readonly) BOOL validated __attribute__((deprecated("Use status == STPBankAccountStatusValidated instead.")));
/**
* Whether or not the bank account is currently disabled.
* @deprecated Use status == STPBankAccountStatusErrored instead.
*/
@property (nonatomic, readonly) BOOL disabled __attribute__((deprecated("Use status == STPBankAccountStatusErrored instead.")));
#pragma mark - deprecated setters for STPBankAccountParams properties
#define DEPRECATED_IN_FAVOR_OF_STPBANKACCOUNTPARAMS __attribute__((deprecated("For collecting your users' bank account details, you should use an STPBankAccountParams object instead of an STPBankAccount.")))
- (void)setAccountNumber:(nullable NSString *)accountNumber DEPRECATED_IN_FAVOR_OF_STPBANKACCOUNTPARAMS;
@end
@@ -0,0 +1,58 @@
//
// STPBankAccountParams.h
// Stripe
//
// Created by Jack Flintermann on 10/4/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <Stripe/STPFormEncodable.h>
typedef NS_ENUM(NSInteger, STPBankAccountHolderType) {
STPBankAccountHolderTypeIndividual,
STPBankAccountHolderTypeCompany,
};
/**
* Representation of a user's bank account details. You can assemble these with information that your user enters and
* then create Stripe tokens with them using an STPAPIClient. @see https://stripe.com/docs/api#create_bank_account_token
*/
@interface STPBankAccountParams : NSObject<STPFormEncodable>
/**
* The account number for the bank account. Currently must be a checking account.
*/
@property (nonatomic, copy, nullable) NSString *accountNumber;
/**
* The last 4 digits of the bank account's account number, if it's been set, otherwise nil.
*/
- (nullable NSString *)last4;
/**
* The routing number for the bank account. This should be the ACH routing number, not the wire routing number.
*/
@property (nonatomic, copy, nullable) NSString *routingNumber;
/**
* Two-letter ISO code representing the country the bank account is located in.
*/
@property (nonatomic, copy, nullable) NSString *country;
/**
* The default currency for the bank account.
*/
@property (nonatomic, copy, nullable) NSString *currency;
/**
* The name of the person or business that owns the bank account.
*/
@property(nonatomic, copy, nullable) NSString *accountHolderName;
/**
* The type of entity that holds the account. Defaults to STPBankAccountHolderTypeIndividual.
*/
@property(nonatomic) STPBankAccountHolderType accountHolderType;
@end
+49
View File
@@ -0,0 +1,49 @@
//
// STPBlocks.h
// Stripe
//
// Created by Jack Flintermann on 3/23/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
@class STPToken;
/**
* An enum representing the status of a payment requested from the user.
*/
typedef NS_ENUM(NSUInteger, STPPaymentStatus) {
/**
* The payment succeeded.
*/
STPPaymentStatusSuccess,
/**
* The payment failed due to an unforeseen error, such as the user's Internet connection being offline.
*/
STPPaymentStatusError,
/**
* The user cancelled the payment (for example, by hitting "cancel" in the Apple Pay dialog).
*/
STPPaymentStatusUserCancellation,
};
/**
* An empty block, called with no arguments, returning nothing.
*/
typedef void (^STPVoidBlock)();
/**
* A block that may optionally be called with an error.
*
* @param error The error that occurred, if any.
*/
typedef void (^STPErrorBlock)(NSError * __nullable error);
/**
* A callback to be run with a token response from the Stripe API.
*
* @param token The Stripe token from the response. Will be nil if an error occurs. @see STPToken
* @param error The error returned from the response, or nil in one occurs. @see StripeError.h for possible values.
*/
typedef void (^STPTokenCompletionBlock)(STPToken * __nullable token, NSError * __nullable error);
+168
View File
@@ -0,0 +1,168 @@
//
// STPCard.h
// Stripe
//
// Created by Saikat Chakrabarti on 11/2/12.
//
//
#import <Foundation/Foundation.h>
#import <Stripe/STPCardBrand.h>
#import <Stripe/STPCardParams.h>
#import <Stripe/STPAPIResponseDecodable.h>
#import <Stripe/STPPaymentMethod.h>
#import <Stripe/STPSource.h>
NS_ASSUME_NONNULL_BEGIN
/**
* The various funding sources for a payment card.
*/
typedef NS_ENUM(NSInteger, STPCardFundingType) {
STPCardFundingTypeDebit,
STPCardFundingTypeCredit,
STPCardFundingTypePrepaid,
STPCardFundingTypeOther,
};
/**
* Representation of a user's credit card details that have been tokenized with the Stripe API. @see https://stripe.com/docs/api#cards
*/
@interface STPCard : STPCardParams<STPAPIResponseDecodable, STPPaymentMethod, STPSource>
/**
* Create an STPCard from a Stripe API response.
*
* @param cardID The Stripe ID of the card, e.g. `card_185iQx4JYtv6MPZKfcuXwkOx`
* @param brand The brand of the card (e.g. "Visa". To obtain this enum value from a string, use `[STPCardBrand brandFromString:string]`;
* @param last4 The last 4 digits of the card, e.g. 4242
* @param expMonth The card's expiration month, 1-indexed (i.e. 1 = January)
* @param expYear The card's expiration year
* @param funding The card's funding type (credit, debit, or prepaid). To obtain this enum value from a string, use `[STPCardBrand fundingFromString:string]`.
*
* @return an STPCard instance populated with the provided values.
*/
- (instancetype)initWithID:(NSString *)cardID
brand:(STPCardBrand)brand
last4:(NSString *)last4
expMonth:(NSUInteger)expMonth
expYear:(NSUInteger)expYear
funding:(STPCardFundingType)funding;
/**
* This parses a string representing a card's brand into the appropriate STPCardBrand enum value, i.e. `[STPCard brandFromString:@"American Express"] == STPCardBrandAmex`
*
* @param string a string representing the card's brand as returned from the Stripe API
*
* @return an enum value mapped to that string. If the string is unrecognized, returns STPCardBrandUnknown.
*/
+ (STPCardBrand)brandFromString:(NSString *)string;
/**
* This parses a string representing a card's funding type into the appropriate `STPCardFundingType` enum value, i.e. `[STPCard fundingFromString:@"prepaid"] == STPCardFundingTypePrepaid`.
*
* @param string a string representing the card's funding type as returned from the Stripe API
*
* @return an enum value mapped to that string. If the string is unrecognized, returns `STPCardFundingTypeOther`.
*/
+ (STPCardFundingType)fundingFromString:(NSString *)string;
/**
* The last 4 digits of the card.
*/
@property (nonatomic, readonly) NSString *last4;
/**
* For cards made with Apple Pay, this refers to the last 4 digits of the "Device Account Number" for the tokenized card. For regular cards, it will be nil.
*/
@property (nonatomic, readonly, nullable) NSString *dynamicLast4;
/**
* Whether or not the card originated from Apple Pay.
*/
@property (nonatomic, readonly) BOOL isApplePayCard;
/**
* The card's expiration month. 1-indexed (i.e. 1 == January)
*/
@property (nonatomic) NSUInteger expMonth;
/**
* The card's expiration year.
*/
@property (nonatomic) NSUInteger expYear;
/**
* The cardholder's name.
*/
@property (nonatomic, copy, nullable) NSString *name;
/**
* The cardholder's address.
*/
@property (nonatomic, copy, nullable) NSString *addressLine1;
@property (nonatomic, copy, nullable) NSString *addressLine2;
@property (nonatomic, copy, nullable) NSString *addressCity;
@property (nonatomic, copy, nullable) NSString *addressState;
@property (nonatomic, copy, nullable) NSString *addressZip;
@property (nonatomic, copy, nullable) NSString *addressCountry;
/**
* The Stripe ID for the card.
*/
@property (nonatomic, readonly, nullable) NSString *cardId;
/**
* The issuer of the card.
*/
@property (nonatomic, readonly) STPCardBrand brand;
/**
* The issuer of the card.
* Can be one of "Visa", "American Express", "MasterCard", "Discover", "JCB", "Diners Club", or "Unknown"
* @deprecated use `brand` instead.
*/
@property (nonatomic, readonly) NSString *type __attribute__((deprecated));
/**
* The funding source for the card (credit, debit, prepaid, or other)
*/
@property (nonatomic, readonly) STPCardFundingType funding;
/**
* A proxy for the card's number, this uniquely identifies the credit card and can be used to compare different cards.
* @deprecated This field will no longer be present in responses when using your publishable key. If you want to access the value of this field, you can look it up on your backend using your secret key.
*/
@property (nonatomic, readonly, nullable) NSString *fingerprint __attribute__((deprecated("This field will no longer be present in responses when using your publishable key. If you want to access the value of this field, you can look it up on your backend using your secret key.")));
/**
* Two-letter ISO code representing the issuing country of the card.
*/
@property (nonatomic, readonly, nullable) NSString *country;
/**
* This is only applicable when tokenizing debit cards to issue payouts to managed accounts. You should not set it otherwise. The card can then be used as a transfer destination for funds in this currency.
*/
@property (nonatomic, copy, nullable) NSString *currency;
#pragma mark - deprecated properties
#define DEPRECATED_IN_FAVOR_OF_STPCARDPARAMS __attribute__((deprecated("For collecting your users' credit card details, you should use an STPCardParams object instead of an STPCard.")))
@property (nonatomic, copy, nullable) NSString *number DEPRECATED_IN_FAVOR_OF_STPCARDPARAMS;
@property (nonatomic, copy, nullable) NSString *cvc DEPRECATED_IN_FAVOR_OF_STPCARDPARAMS;
- (void)setExpMonth:(NSUInteger)expMonth DEPRECATED_IN_FAVOR_OF_STPCARDPARAMS;
- (void)setExpYear:(NSUInteger)expYear DEPRECATED_IN_FAVOR_OF_STPCARDPARAMS;
- (void)setName:(nullable NSString *)name DEPRECATED_IN_FAVOR_OF_STPCARDPARAMS;
- (void)setAddressLine1:(nullable NSString *)addressLine1 DEPRECATED_IN_FAVOR_OF_STPCARDPARAMS;
- (void)setAddressLine2:(nullable NSString *)addressLine2 DEPRECATED_IN_FAVOR_OF_STPCARDPARAMS;
- (void)setAddressCity:(nullable NSString *)addressCity DEPRECATED_IN_FAVOR_OF_STPCARDPARAMS;
- (void)setAddressState:(nullable NSString *)addressState DEPRECATED_IN_FAVOR_OF_STPCARDPARAMS;
- (void)setAddressZip:(nullable NSString *)addressZip DEPRECATED_IN_FAVOR_OF_STPCARDPARAMS;
- (void)setAddressCountry:(nullable NSString *)addressCountry DEPRECATED_IN_FAVOR_OF_STPCARDPARAMS;
@end
NS_ASSUME_NONNULL_END
+23
View File
@@ -0,0 +1,23 @@
//
// STPCardBrand.h
// Stripe
//
// Created by Jack Flintermann on 7/24/15.
// Copyright (c) 2015 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* The various card brands to which a payment card can belong.
*/
typedef NS_ENUM(NSInteger, STPCardBrand) {
STPCardBrandVisa,
STPCardBrandAmex,
STPCardBrandMasterCard,
STPCardBrandDiscover,
STPCardBrandJCB,
STPCardBrandDinersClub,
STPCardBrandOther,
STPCardBrandUnknown,
};
+98
View File
@@ -0,0 +1,98 @@
//
// STPCardParams.h
// Stripe
//
// Created by Jack Flintermann on 10/4/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <Stripe/STPFormEncodable.h>
#if TARGET_OS_IPHONE
#import <Stripe/STPAddress.h>
#endif
/**
* Representation of a user's credit card details. You can assemble these with information that your user enters and
* then create Stripe tokens with them using an STPAPIClient. @see https://stripe.com/docs/api#cards
*/
@interface STPCardParams : NSObject<STPFormEncodable>
/**
* The card's number.
*/
@property (nonatomic, copy, nullable) NSString *number;
/**
* The last 4 digits of the card's number, if it's been set, otherwise nil.
*/
- (nullable NSString *)last4;
/**
* The card's expiration month.
*/
@property (nonatomic) NSUInteger expMonth;
/**
* The card's expiration year.
*/
@property (nonatomic) NSUInteger expYear;
/**
* The card's security code, found on the back.
*/
@property (nonatomic, copy, nullable) NSString *cvc;
/**
* The cardholder's name.
*/
@property (nonatomic, copy, nullable) NSString *name;
/**
* The cardholder's address.
*/
#if TARGET_OS_IPHONE
@property(nonatomic, copy, nonnull) STPAddress *address;
#endif
@property (nonatomic, copy, nullable) NSString *addressLine1;
@property (nonatomic, copy, nullable) NSString *addressLine2;
@property (nonatomic, copy, nullable) NSString *addressCity;
@property (nonatomic, copy, nullable) NSString *addressState;
@property (nonatomic, copy, nullable) NSString *addressZip;
@property (nonatomic, copy, nullable) NSString *addressCountry;
/**
* Three-letter ISO currency code representing the currency paid out to the bank account. This is only applicable when tokenizing debit cards to issue payouts to managed accounts. You should not set it otherwise. The card can then be used as a transfer destination for funds in this currency.
*/
@property (nonatomic, copy, nullable) NSString *currency;
/**
* Validate each field of the card.
* @return whether or not that field is valid.
* @deprecated use STPCardValidator instead.
*/
- (BOOL)validateNumber:(__nullable id * __nullable )ioValue
error:(NSError * __nullable * __nullable )outError __attribute__((deprecated("Use STPCardValidator instead.")));
- (BOOL)validateCvc:(__nullable id * __nullable )ioValue
error:(NSError * __nullable * __nullable )outError __attribute__((deprecated("Use STPCardValidator instead.")));
- (BOOL)validateExpMonth:(__nullable id * __nullable )ioValue
error:(NSError * __nullable * __nullable )outError __attribute__((deprecated("Use STPCardValidator instead.")));
- (BOOL)validateExpYear:(__nullable id * __nullable)ioValue
error:(NSError * __nullable * __nullable )outError __attribute__((deprecated("Use STPCardValidator instead.")));
/**
* This validates a fully populated card to check for all errors, including ones that come about
* from the interaction of more than one property. It will also do all the validations on individual
* properties, so if you only want to call one method on your card to validate it after setting all the
* properties, call this one
*
* @param outError a pointer to an NSError that, after calling this method, will be populated with an error if the card is not valid. See StripeError.h for
possible values
*
* @return whether or not the card is valid.
* @deprecated use STPCardValidator instead.
*/
- (BOOL)validateCardReturningError:(NSError * __nullable * __nullable)outError __attribute__((deprecated("Use STPCardValidator instead.")));
@end
@@ -0,0 +1,18 @@
//
// STPCardValidationState.h
// Stripe
//
// Created by Jack Flintermann on 8/7/15.
// Copyright (c) 2015 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* These fields indicate whether a card field represents a valid value, invalid value, or incomplete value.
*/
typedef NS_ENUM(NSInteger, STPCardValidationState) {
STPCardValidationStateValid, // The field's contents are valid. For example, a valid, 16-digit card number.
STPCardValidationStateInvalid, // The field's contents are invalid. For example, an expiration date of "13/42".
STPCardValidationStateIncomplete, // The field's contents are not yet valid, but could be by typing additional characters. For example, a CVC of "1".
};
+119
View File
@@ -0,0 +1,119 @@
//
// STPCardValidator.h
// Stripe
//
// Created by Jack Flintermann on 7/15/15.
// Copyright (c) 2015 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <Stripe/STPCardParams.h>
#import <Stripe/STPCardBrand.h>
#import <Stripe/STPCardValidationState.h>
NS_ASSUME_NONNULL_BEGIN
/**
* This class contains static methods to validate card numbers, expiration dates, and CVCs. For a list of test card numbers to use with this code, see https://stripe.com/docs/testing
*/
@interface STPCardValidator : NSObject
/**
* Returns a copy of the passed string with all non-numeric characters removed.
*/
+ (NSString *)sanitizedNumericStringForString:(NSString *)string;
/**
* Whether or not the target string contains only numeric characters.
*/
+ (BOOL)stringIsNumeric:(NSString *)string;
/**
* Validates a card number, passed as a string. This will return STPCardValidationStateInvalid for numbers that are too short or long, contain invalid characters, do not pass Luhn validation, or (optionally) do not match a number format issued by a major card brand.
*
* @param cardNumber The card number to validate. Ex. @"4242424242424242"
* @param validatingCardBrand Whether or not to enforce that the number appears to be issued by a major card brand (or could be). For example, no issuing card network currently issues card numbers beginning with the digit 9; if an otherwise correct-length and luhn-valid card number beginning with 9 (example: 9999999999999995) were passed to this method, it would return STPCardValidationStateInvalid if this parameter were YES and STPCardValidationStateValid if this parameter were NO. If unsure, you should use YES for this value.
*
* @return STPCardValidationStateValid if the number is valid, STPCardValidationStateInvalid if the number is invalid, or STPCardValidationStateIncomplete if the number is a substring of a valid card (e.g. @"4242").
*/
+ (STPCardValidationState)validationStateForNumber:(NSString *)cardNumber
validatingCardBrand:(BOOL)validatingCardBrand;
/**
* The card brand for a card number or substring thereof.
*
* @param cardNumber A card number, or partial card number. For example, @"4242", @"5555555555554444", or @"123".
*
* @return The brand for that card number. The example parameters would return STPCardBrandVisa, STPCardBrandMasterCard, and STPCardBrandUnknown, respectively.
*/
+ (STPCardBrand)brandForNumber:(NSString *)cardNumber;
/**
* The possible number lengths for cards associated with a card brand. For example, Discover card numbers contain 16 characters, while American Express cards contain 15 characters.
*/
+ (NSSet<NSNumber *>*)lengthsForCardBrand:(STPCardBrand)brand;
+ (NSInteger)maxLengthForCardBrand:(STPCardBrand)brand;
+ (NSInteger)lengthForCardBrand:(STPCardBrand)brand __attribute__((deprecated("Card brands may have multiple lengths - use lengthsForCardBrand or maxLengthForCardBrand instead.")));
/**
* The length of the final grouping of digits to use when formatting a card number for display. For example, Visa cards display their final 4 numbers, e.g. "4242", while American Express cards display their final 5 digits, e.g. "10005".
*/
+ (NSInteger)fragmentLengthForCardBrand:(STPCardBrand)brand;
/**
* Validates an expiration month, passed as an (optionally 0-padded) string. Example valid values are "3", "12", and "08". Example invalid values are "99", "a", and "00". Incomplete values include "0" and "1".
*
* @param expirationMonth A string representing a 2-digit expiration month for a payment card.
*
* @return STPCardValidationStateValid if the month is valid, STPCardValidationStateInvalid if the month is invalid, or STPCardValidationStateIncomplete if the month is a substring of a valid month (e.g. @"0" or @"1").
*/
+ (STPCardValidationState)validationStateForExpirationMonth:(NSString *)expirationMonth;
/**
* Validates an expiration year, passed as a string representing the final 2 digits of the year. This considers the period between the current year until 2099 as valid times. An example valid value would be "16" (assuming the current year, as determined by [NSDate date], is 2015). Will return STPCardValidationStateInvalid for a month/year combination that is earlier than the current date (i.e. @"15" and @"04" in October 2015. Example invalid values are "00", "a", and "13". Any 1-digit string will return STPCardValidationStateIncomplete.
*
* @param expirationYear A string representing a 2-digit expiration year for a payment card.
* @param expirationMonth A string representing a 2-digit expiration month for a payment card. See -validationStateForExpirationMonth for the desired formatting of this string.
*
* @return STPCardValidationStateValid if the year is valid, STPCardValidationStateInvalid if the year is invalid, or STPCardValidationStateIncomplete if the year is a substring of a valid year (e.g. @"1" or @"2").
*/
+ (STPCardValidationState)validationStateForExpirationYear:(NSString *)expirationYear
inMonth:(NSString *)expirationMonth cardBrand:(STPCardBrand)cardBrand;
/**
* The max CVC length for a card brand (for context, American Express CVCs are 4 digits, while all others are 3).
*/
+ (NSUInteger)maxCVCLengthForCardBrand:(STPCardBrand)brand;
/**
* Validates a card's CVC, passed as a numeric string, for the given card brand.
*
* @param cvc the CVC to validate
* @param brand the card brand (can be determined from the card's number using +brandForNumber)
*
* @return Whether the CVC represents a valid CVC for that card brand. For example, would return STPCardValidationStateValid for @"123" and STPCardBrandVisa, STPCardValidationStateValid for @"1234" and STPCardBrandAmericanExpress, STPCardValidationStateIncomplete for @"12" and STPCardBrandVisa, and STPCardValidationStateInvalid for @"12345" and any brand.
*/
+ (STPCardValidationState)validationStateForCVC:(NSString *)cvc cardBrand:(STPCardBrand)brand;
/**
* Validates the given card details.
*
* @param card the card details to validate.
*
* @return STPCardValidationStateValid if all fields are valid, STPCardValidationStateInvalid if any field is invalid, or STPCardValidationStateIncomplete if all fields are either incomplete or valid.
*/
+ (STPCardValidationState)validationStateForCard:(STPCardParams *)card;
// Exposed for testing only.
+ (STPCardValidationState)validationStateForExpirationYear:(NSString *)expirationYear
inMonth:(NSString *)expirationMonth
inCurrentYear:(NSInteger)currentYear
currentMonth:(NSInteger)currentMonth cardBrand:(STPCardBrand)cardBrand;
+ (STPCardValidationState)validationStateForCard:(STPCardParams *)card
inCurrentYear:(NSInteger)currentYear
currentMonth:(NSInteger)currentMonth;
@end
NS_ASSUME_NONNULL_END
+86
View File
@@ -0,0 +1,86 @@
//
// STPCustomer.h
// Stripe
//
// Created by Jack Flintermann on 6/9/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <Stripe/STPSource.h>
NS_ASSUME_NONNULL_BEGIN
/**
* An `STPCustomer` represents a deserialized Customer object from the Stripe API. You can use `STPCustomerDeserializer` to convert a JSON response from the Stripe API into an `STPCustomer`.
*/
@interface STPCustomer : NSObject
/**
* Initialize a customer object with the provided values.
*
* @param stripeID The ID of the customer, e.g. `cus_abc`
* @param defaultSource The default source of the customer, such as an `STPCard` object. Can be nil.
* @param sources All of the customer's payment sources. This might be an empty array.
*
* @return an instance of STPCustomer
*/
+ (instancetype)customerWithStripeID:(NSString *)stripeID
defaultSource:(nullable id<STPSource>)defaultSource
sources:(NSArray<id<STPSource>> *)sources;
/**
* The Stripe ID of the customer, e.g. `cus_1234`
*/
@property(nonatomic, readonly, copy)NSString *stripeID;
/**
* The default source used to charge the customer.
*/
@property(nonatomic, readonly, nullable) id<STPSource> defaultSource;
/**
* The available payment sources the customer has (this may be an empty array).
*/
@property(nonatomic, readonly) NSArray<id<STPSource>> *sources;
@end
/**
Use `STPCustomerDeserializer` to convert a response from the Stripe API into an `STPCustomer` object. `STPCustomerDeserializer` expects the JSON response to be in the exact same format as the Stripe API.
*/
@interface STPCustomerDeserializer : NSObject
/**
* Initialize a customer deserializer. The `data`, `urlResponse`, and `error` parameters are intended to be passed from an `NSURLSessionDataTask` callback. After it has been initialized, you can inspect the `error` and `customer` properties to see if the deserialization was successful. If `error` is nil, `customer` will be non-nil (and vice versa).
*
* @param data An `NSData` object representing encoded JSON for a Customer object
* @param urlResponse The URL response obtained from the `NSURLSessionTask`
* @param error Any error that occurred from the URL session task (if this is non-nil, the `error` property will be set to this value after initialization).
*
*/
- (instancetype)initWithData:(nullable NSData *)data
urlResponse:(nullable NSURLResponse *)urlResponse
error:(nullable NSError *)error;
/**
* Initializes a customer deserializer with a JSON dictionary. This JSON should be in the exact same format as what the Stripe API returns. If it's successfully parsed, the `customer` parameter will be present after initialization; otherwise `error` will be present.
*
* @param json a JSON dictionary.
*
*/
- (instancetype)initWithJSONResponse:(id)json;
/**
* If a customer was successfully parsed from the response, it will be set here. Otherwise, this value wil be nil (and the `error` property will explain what went wrong).
*/
@property(nonatomic, readonly, nullable)STPCustomer *customer;
/**
* If the deserializer failed to parse a customer, this property will explain why (and the `customer` property will be nil).
*/
@property(nonatomic, readonly, nullable)NSError *error;
@end
NS_ASSUME_NONNULL_END
+35
View File
@@ -0,0 +1,35 @@
//
// STPFormEncodable.h
// Stripe
//
// Created by Jack Flintermann on 10/14/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* Objects conforming to STPFormEncodable can be automatically converted to a form-encoded string, which can then be used when making requests to the Stripe API.
*/
@protocol STPFormEncodable <NSObject>
/**
* The root object name to be used when converting this object to a form-encoded string. For example, if this returns @"card", then the form-encoded output will resemble @"card[foo]=bar" (where 'foo' and 'bar' are specified by `propertyNamesToFormFieldNamesMapping` below.
*/
+ (nonnull NSString *)rootObjectName;
/**
* This maps properties on an object that is being form-encoded into parameter names in the Stripe API. For example, STPCardParams has a field called `expMonth`, but the Stripe API expects a field called `exp_month`. This dictionary represents a mapping from the former to the latter (in other words, [STPCardParams propertyNamesToFormFieldNamesMapping][@"expMonth"] == @"exp_month".)
*/
+ (nonnull NSDictionary *)propertyNamesToFormFieldNamesMapping;
/**
* You can use this property to add additional fields to an API request that are not explicitly defined by the object's interface. This can be useful when using beta features that haven't been added to the Stripe SDK yet. For example, if the /v1/tokens API began to accept a beta field called "test_field", you might do the following:
STPCardParams *cardParams = [STPCardParams new];
// add card values
cardParams.additionalAPIParameters = @{@"test_field": @"example_value"};
[[STPAPIClient sharedClient] createTokenWithCard:cardParams completion:...];
*/
@property(nonatomic, readwrite, nonnull, copy)NSDictionary *additionalAPIParameters;
@end
+42
View File
@@ -0,0 +1,42 @@
//
// STPFormTextField.h
// Stripe
//
// Created by Jack Flintermann on 7/16/15.
// Copyright (c) 2015 Stripe, Inc. All rights reserved.
//
#import <UIKit/UIKit.h>
@class STPFormTextField;
typedef NS_ENUM(NSInteger, STPFormTextFieldAutoFormattingBehavior) {
STPFormTextFieldAutoFormattingBehaviorNone,
STPFormTextFieldAutoFormattingBehaviorPhoneNumbers,
STPFormTextFieldAutoFormattingBehaviorCardNumbers,
STPFormTextFieldAutoFormattingBehaviorExpiration,
};
@protocol STPFormTextFieldDelegate <UITextFieldDelegate>
@optional
- (void)formTextFieldDidBackspaceOnEmpty:(nonnull STPFormTextField *)formTextField;
- (nonnull NSAttributedString *)formTextField:(nonnull STPFormTextField *)formTextField
modifyIncomingTextChange:(nonnull NSAttributedString *)input;
- (void)formTextFieldTextDidChange:(nonnull STPFormTextField *)textField;
@end
@interface STPFormTextField : UITextField
@property(nonatomic, readwrite, nullable) UIColor *defaultColor;
@property(nonatomic, readwrite, nullable) UIColor *errorColor;
@property(nonatomic, readwrite, nullable) UIColor *placeholderColor;
@property(nonatomic, readwrite, assign)BOOL selectionEnabled; // defaults to NO
@property(nonatomic, readwrite, assign)BOOL preservesContentsOnPaste; // defaults to NO
@property(nonatomic, readwrite, assign)STPFormTextFieldAutoFormattingBehavior autoFormattingBehavior;
@property(nonatomic, readwrite, assign)BOOL validText;
@property(nonatomic, readwrite, weak, nullable)id<STPFormTextFieldDelegate>formDelegate;
- (CGSize)measureTextSize;
@end
@@ -0,0 +1,298 @@
//
// STPPaymentCardTextField.h
// Stripe
//
// Created by Jack Flintermann on 7/16/15.
// Copyright (c) 2015 Stripe, Inc. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <Stripe/STPCard.h>
@class STPPaymentCardTextField;
/**
* This protocol allows a delegate to be notified when a payment text field's contents change, which can in turn be used to take further actions depending on the validity of its contents.
*/
@protocol STPPaymentCardTextFieldDelegate <NSObject>
@optional
/**
* Called when either the card number, expiration, or CVC changes. At this point, one can call -isValid on the text field to determine, for example, whether or not to enable a button to submit the form. Example:
- (void)paymentCardTextFieldDidChange:(STPPaymentCardTextField *)textField {
self.paymentButton.enabled = textField.isValid;
}
*
* @param textField the text field that has changed
*/
- (void)paymentCardTextFieldDidChange:(nonnull STPPaymentCardTextField *)textField;
/**
* Called when editing begins in the payment card field's number field.
*/
- (void)paymentCardTextFieldDidBeginEditingNumber:(nonnull STPPaymentCardTextField *)textField;
/**
* Called when editing ends in the payment card field's number field.
*/
- (void)paymentCardTextFieldDidEndEditingNumber:(nonnull STPPaymentCardTextField *)textField;
/**
* Called when editing begins in the payment card field's CVC field.
*/
- (void)paymentCardTextFieldDidBeginEditingCVC:(nonnull STPPaymentCardTextField *)textField;
/**
* Called when editing ends in the payment card field's CVC field.
*/
- (void)paymentCardTextFieldDidEndEditingCVC:(nonnull STPPaymentCardTextField *)textField;
/**
* Called when editing begins in the payment card field's expiration field.
*/
- (void)paymentCardTextFieldDidBeginEditingExpiration:(nonnull STPPaymentCardTextField *)textField;
/**
* Called when editing ends in the payment card field's expiration field.
*/
- (void)paymentCardTextFieldDidEndEditingExpiration:(nonnull STPPaymentCardTextField *)textField;
@end
/**
* STPPaymentCardTextField is a text field with similar properties to UITextField, but specialized for collecting credit/debit card information. It manages multiple UITextFields under the hood to collect this information. It's designed to fit on a single line, and from a design perspective can be used anywhere a UITextField would be appropriate.
*/
@interface STPPaymentCardTextField : UIControl <UIKeyInput>
/**
* @see STPPaymentCardTextFieldDelegate
*/
@property(nonatomic, weak, nullable) IBOutlet id<STPPaymentCardTextFieldDelegate> delegate;
/**
* The font used in each child field. Default is [UIFont systemFontOfSize:18]. Set this property to nil to reset to the default.
*/
@property(nonatomic, copy, null_resettable) UIFont *font UI_APPEARANCE_SELECTOR;
/**
* The text color to be used when entering valid text. Default is [UIColor blackColor]. Set this property to nil to reset to the default.
*/
@property(nonatomic, copy, null_resettable) UIColor *textColor UI_APPEARANCE_SELECTOR;
/**
* The text color to be used when the user has entered invalid information, such as an invalid card number. Default is [UIColor redColor]. Set this property to nil to reset to the default.
*/
@property(nonatomic, copy, null_resettable) UIColor *textErrorColor UI_APPEARANCE_SELECTOR;
/**
* The text placeholder color used in each child field. Default is [UIColor lightGreyColor]. Set this property to nil to reset to the default. On iOS 7 and above, this will also set the color of the card placeholder icon.
*/
@property(nonatomic, copy, null_resettable) UIColor *placeholderColor UI_APPEARANCE_SELECTOR;
/**
* The placeholder for the card number field. Default is @"1234567812345678". If this is set to something that resembles a card number, it will automatically format it as such (in other words, you don't need to add spaces to this string).
*/
@property(nonatomic, copy, nullable) NSString *numberPlaceholder;
/**
* The placeholder for the expiration field. Defaults to @"MM/YY".
*/
@property(nonatomic, copy, nullable) NSString *expirationPlaceholder;
/**
* The placeholder for the cvc field. Defaults to @"CVC".
*/
@property(nonatomic, copy, nullable) NSString *cvcPlaceholder;
/**
* The cursor color for the field. This is a proxy for the view's tintColor property, exposed for clarity only (in other words, calling setCursorColor is identical to calling setTintColor).
*/
@property(nonatomic, copy, null_resettable) UIColor *cursorColor UI_APPEARANCE_SELECTOR;
/**
* The border color for the field. Default is [UIColor lightGreyColor]. Can be nil (in which case no border will be drawn).
*/
@property(nonatomic, copy, nullable) UIColor *borderColor UI_APPEARANCE_SELECTOR;
/**
* The width of the field's border. Default is 1.0.
*/
@property(nonatomic, assign) CGFloat borderWidth UI_APPEARANCE_SELECTOR;
/**
* The corner radius for the field's border. Default is 5.0.
*/
@property(nonatomic, assign) CGFloat cornerRadius UI_APPEARANCE_SELECTOR;
/**
* The keyboard appearance for the field. Default is UIKeyboardAppearanceDefault.
*/
@property(nonatomic, assign) UIKeyboardAppearance keyboardAppearance UI_APPEARANCE_SELECTOR;
/**
* This behaves identically to setting the inputAccessoryView for each child text field.
*/
@property(nonatomic, strong, nullable) UIView *inputAccessoryView;
/**
* The curent brand image displayed in the receiver.
*/
@property (nonatomic, readonly, nullable) UIImage *brandImage;
/**
* Causes the text field to begin editing. Presents the keyboard.
*
* @return Whether or not the text field successfully began editing.
* @see UIResponder
*/
- (BOOL)becomeFirstResponder;
/**
* Causes the text field to stop editing. Dismisses the keyboard.
*
* @return Whether or not the field successfully stopped editing.
* @see UIResponder
*/
- (BOOL)resignFirstResponder;
/**
* Resets all of the contents of all of the fields. If the field is currently being edited, the number field will become selected.
*/
- (void)clear;
/**
* Returns the cvc image used for a card brand.
* Override this method in a subclass if you would like to provide custom images.
* @param cardBrand The brand of card entered.
* @return The cvc image used for a card brand.
*/
+ (nullable UIImage *)cvcImageForCardBrand:(STPCardBrand)cardBrand;
/**
* Returns the brand image used for a card brand.
* Override this method in a subclass if you would like to provide custom images.
* @param cardBrand The brand of card entered.
* @return The brand image used for a card brand.
*/
+ (nullable UIImage *)brandImageForCardBrand:(STPCardBrand)cardBrand;
/**
* Returns the rectangle in which the receiver draws its brand image.
* @param bounds The bounding rectangle of the receiver.
* @return the rectangle in which the receiver draws its brand image.
*/
- (CGRect)brandImageRectForBounds:(CGRect)bounds;
/**
* Returns the rectangle in which the receiver draws the text fields.
* @param bounds The bounding rectangle of the receiver.
* @return The rectangle in which the receiver draws the text fields.
*/
- (CGRect)fieldsRectForBounds:(CGRect)bounds;
/**
* Returns the rectangle in which the receiver draws its number field.
* @param bounds The bounding rectangle of the receiver.
* @return the rectangle in which the receiver draws its number field.
*/
- (CGRect)numberFieldRectForBounds:(CGRect)bounds;
/**
* Returns the rectangle in which the receiver draws its cvc field.
* @param bounds The bounding rectangle of the receiver.
* @return the rectangle in which the receiver draws its cvc field.
*/
- (CGRect)cvcFieldRectForBounds:(CGRect)bounds;
/**
* Returns the rectangle in which the receiver draws its expiration field.
* @param bounds The bounding rectangle of the receiver.
* @return the rectangle in which the receiver draws its expiration field.
*/
- (CGRect)expirationFieldRectForBounds:(CGRect)bounds;
/**
* Whether or not the form currently contains a valid card number, expiration date, and CVC.
* @see STPCardValidator
*/
@property(nonatomic, readonly)BOOL isValid;
@property(nonatomic, readonly)BOOL valid; // for backwards-compatibility
/**
* Enable/disable selecting or editing the field. Useful when submitting card details to Stripe.
*/
@property(nonatomic, getter=isEnabled) BOOL enabled;
/**
* The current card number displayed by the field. May or may not be valid, unless isValid is true, in which case it is guaranteed to be valid.
*/
@property(nonatomic, readonly, nullable) NSString *cardNumber;
/**
* The current expiration month displayed by the field (1 = January, etc). May or may not be valid, unless isValid is true, in which case it is guaranteed to be valid.
*/
@property(nonatomic, readonly) NSUInteger expirationMonth;
/**
* The current expiration month displayed by the field, as a string. This may or may not be a valid entry (i.e. "0", and may be 0-prefixed (i.e. "01" for January). You can use [STPCardValidator validationStateForExpirationMonth] to validate this value.
*/
@property(nonatomic, readonly, nullable) NSString *formattedExpirationMonth;
/**
* The current expiration year displayed by the field, modulo 100 (e.g. the year 2015 will be represented as 15). May or may not be valid, unless isValid is true, in which case it is guaranteed to be valid.
*/
@property(nonatomic, readonly) NSUInteger expirationYear;
/**
* The current expiration year displayed by the field, as a string. This is a 2-digit year (i.e. "15"), and may or may not be a valid entry. You can use [STPCardValidator validationStateForExpirationYear:inMonth] to validate this value.
*/
@property(nonatomic, readonly, nullable) NSString *formattedExpirationYear;
/**
* The current card CVC displayed by the field. May or may not be valid, unless isValid is true, in which case it is guaranteed to be valid.
*/
@property(nonatomic, readonly, nullable) NSString *cvc;
/**
* Convenience property for creating an STPCardParams from the currently entered information
* or programmatically setting the field's contents. For example, if you're using another library
* to scan your user's credit card with a camera, you can assemble that data into an STPCardParams
* object and set this property to that object to prefill the fields you've collected.
*/
@property(nonatomic, strong, readwrite, nonnull) STPCardParams *cardParams;
@property(nonatomic, strong, readwrite, nullable) STPCardParams *card __attribute__((deprecated("This has been renamed to cardParams; use that instead.")));
- (void)commonInit;
@end
#pragma mark - PaymentKit compatibility
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
__attribute__((deprecated("This class is provided only for backwards-compatibility with PaymentKit. You shouldn't use it - use STPCard instead.")))
@interface PTKCard : STPCard
@end
@class PTKView;
__attribute__((deprecated("This protocol is provided only for backwards-compatibility with PaymentKit. You shouldn't use it - use STPPaymentCardTextFieldDelegate instead.")))
@protocol PTKViewDelegate <STPPaymentCardTextFieldDelegate>
@optional
- (void)paymentView:(nonnull PTKView *)paymentView withCard:(nonnull PTKCard *)card isValid:(BOOL)valid;
@end
__attribute__((deprecated("This class is provided only for backwards-compatibility with PaymentKit. You shouldn't use it - use STPPaymentCardTextField instead.")))
@interface PTKView : STPPaymentCardTextField
@property(nonatomic, weak, nullable)id<PTKViewDelegate>delegate;
@property(nonatomic, strong, readwrite, nonnull) PTKCard *card;
@end
#pragma clang diagnostic pop
@@ -0,0 +1,37 @@
//
// STPPaymentCardTextFieldViewModel.h
// Stripe
//
// Created by Jack Flintermann on 7/21/15.
// Copyright (c) 2015 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "STPCard.h"
#import "STPCardValidator.h"
typedef NS_ENUM(NSInteger, STPCardFieldType) {
STPCardFieldTypeNumber,
STPCardFieldTypeExpiration,
STPCardFieldTypeCVC,
};
@interface STPPaymentCardTextFieldViewModel : NSObject
@property(nonatomic, readwrite, copy, nullable)NSString *cardNumber;
@property(nonatomic, readwrite, copy, nullable)NSString *rawExpiration;
@property(nonatomic, readonly, nullable)NSString *expirationMonth;
@property(nonatomic, readonly, nullable)NSString *expirationYear;
@property(nonatomic, readwrite, copy, nullable)NSString *cvc;
@property(nonatomic, readonly) STPCardBrand brand;
- (nonnull NSString *)defaultPlaceholder;
- (nullable NSString *)numberWithoutLastDigits;
- (BOOL)isValid;
- (STPCardValidationState)validationStateForField:(STPCardFieldType)fieldType;
@end
@@ -0,0 +1,65 @@
//
// STPPaymentConfiguration.h
// Stripe
//
// Created by Jack Flintermann on 5/18/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <Stripe/STPBackendAPIAdapter.h>
#import <Stripe/STPPaymentMethod.h>
NS_ASSUME_NONNULL_BEGIN
/**
An `STPPaymentConfiguration` represents all the options you can set or change
around a payment.
You provide an `STPPaymentConfiguration` object to your `STPPaymentContext`
when making a charge. The configuration generally has settings that
will not change from payment to payment and thus is reusable, while the context
is specific to a single particular payment instance.
*/
@interface STPPaymentConfiguration : NSObject<NSCopying>
/**
This is a convenience singleton configuration that uses the default values
for every property
*/
+ (instancetype)sharedConfiguration;
/**
* Your Stripe publishable key. You can get this from https://dashboard.stripe.com/account/apikeys .
*/
@property(nonatomic, copy)NSString *publishableKey;
/**
* An enum value representing which payment methods you will accept from your user in addition to credit cards. Unless you have a very specific reason not to, you should leave this at the default, `STPPaymentMethodTypeAll`.
*/
@property(nonatomic)STPPaymentMethodType additionalPaymentMethods;
/**
* The billing address fields the user must fill out when prompted for their payment details. These fields will all be present on the returned token from Stripe. See https://stripe.com/docs/api#create_card_token for more information.
*/
@property(nonatomic)STPBillingAddressFields requiredBillingAddressFields;
/**
* The name of your company, for displaying to the user during payment flows. For example, when using Apple Pay, the payment sheet's final line item will read "PAY {companyName}". This defaults to the name of your iOS application.
*/
@property(nonatomic, copy)NSString *companyName;
/**
* The Apple Merchant Identifier to use during Apple Pay transactions. To create one of these, see our guide at https://stripe.com/docs/mobile/apple-pay . You must set this to a valid identifier in order to automatically enable Apple Pay.
*/
@property(nonatomic, nullable, copy)NSString *appleMerchantIdentifier;
/**
* When entering their payment information, users who have a saved card with Stripe will be prompted to autofill it by entering an SMS code. Set this property to `YES` to disable this feature. The user won't receive an SMS code even if they have their payment information stored with Stripe, and won't be prompted to save it if they don't.
*/
@property(nonatomic)BOOL smsAutofillDisabled;
@end
NS_ASSUME_NONNULL_END
+51
View File
@@ -0,0 +1,51 @@
//
// STPPaymentMethod.h
// Stripe
//
// Created by Ben Guo on 4/19/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import <UIKit/UIKit.h>
/**
* This represents all of the payment methods available to your user (in addition to card payments, which are always enabled) when configuring an `STPPaymentContext`.
*/
typedef NS_OPTIONS(NSUInteger, STPPaymentMethodType) {
/**
* Don't use any payment methods except for cards.
*/
STPPaymentMethodTypeNone = 0,
/**
* The user is allowed to pay with Apple Pay (if it's configured and available on their device).
*/
STPPaymentMethodTypeApplePay = 1 << 0,
/**
* The user can use any available payment method to pay.
*/
STPPaymentMethodTypeAll = STPPaymentMethodTypeApplePay
};
/**
* This protocol represents a payment method that a user can select and use to pay. Currently the only classes that conform to it are `STPCard` (which represents that the user wants to pay with a specific card) and `STPApplePayPaymentMethod` (which represents that the user wants to pay with Apple Pay).
*/
@protocol STPPaymentMethod <NSObject>
/**
* A small (32 x 20 points) logo image representing the payment method. For example, the Visa logo for a Visa card, or the Apple Pay logo.
*/
@property (nonatomic, readonly) UIImage *image;
/**
* A small (32 x 20 points) logo image representing the payment method that can be used as template for tinted icons.
*/
@property (nonatomic, readonly) UIImage *templateImage;
/**
* A string describing the payment method, such as "Apple Pay" or "Visa 4242".
*/
@property (nonatomic, readonly) NSString *label;
@end
@@ -0,0 +1,31 @@
//
// STPPhoneNumberValidator.h
// Stripe
//
// Created by Jack Flintermann on 10/16/15.
// Copyright © 2015 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface STPPhoneNumberValidator : NSObject
+ (BOOL)stringIsValidPartialPhoneNumber:(NSString *)string;
+ (BOOL)stringIsValidPhoneNumber:(NSString *)string;
+ (BOOL)stringIsValidPartialPhoneNumber:(NSString *)string
forCountryCode:(nullable NSString *)countryCode;
+ (BOOL)stringIsValidPhoneNumber:(NSString *)string
forCountryCode:(nullable NSString *)countryCode;
+ (NSString *)formattedSanitizedPhoneNumberForString:(NSString *)string;
+ (NSString *)formattedSanitizedPhoneNumberForString:(NSString *)string
forCountryCode:(nullable NSString *)countryCode;
+ (NSString *)formattedRedactedPhoneNumberForString:(NSString *)string;
+ (NSString *)formattedRedactedPhoneNumberForString:(NSString *)string
forCountryCode:(nullable NSString *)countryCode;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,26 @@
//
// STPPostalCodeValidator.h
// Stripe
//
// Created by Ben Guo on 4/14/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, STPPostalCodeType) {
STPCountryPostalCodeTypeNumericOnly,
STPCountryPostalCodeTypeAlphanumeric,
STPCountryPostalCodeTypeNotRequired,
};
@interface STPPostalCodeValidator : NSObject
+ (BOOL)stringIsValidPostalCode:(nullable NSString *)string
type:(STPPostalCodeType)postalCodeType;
+ (BOOL)stringIsValidPostalCode:(nullable NSString *)string
countryCode:(nullable NSString *)countryCode;
+ (STPPostalCodeType)postalCodeTypeForCountryCode:(nullable NSString *)countryCode;
@end
+22
View File
@@ -0,0 +1,22 @@
//
// STPSource.h
// Stripe
//
// Created by Jack Flintermann on 1/15/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
/**
* A source represents a source of funds for your user that you can charge - for example, a card on file. Currently, only `STPCard` implements this interface, although future payment methods will use it as well. When implementing your server backend, you should pass the `stripeID` property to the Create Charge method as the `source` parameter.
*/
@protocol STPSource <NSObject>
/**
* The stripe ID of the source. When implementing your server backend, you should pass this property to the Create Charge method as the `source` parameter.
*/
@property(nonatomic, readonly, copy, nonnull)NSString *stripeID;
@end
+53
View File
@@ -0,0 +1,53 @@
//
// STPToken.h
// Stripe
//
// Created by Saikat Chakrabarti on 11/5/12.
//
//
#import <Foundation/Foundation.h>
#import <Stripe/STPAPIResponseDecodable.h>
#import <Stripe/STPSource.h>
@class STPCard;
@class STPBankAccount;
/**
* A token returned from submitting payment details to the Stripe API. You should not have to instantiate one of these directly.
*/
@interface STPToken : NSObject<STPAPIResponseDecodable, STPSource>
/**
* You cannot directly instantiate an `STPToken`. You should only use one that has been returned from an `STPAPIClient` callback.
*/
- (nonnull instancetype) init;
/**
* The value of the token. You can store this value on your server and use it to make charges and customers. @see
* https://stripe.com/docs/mobile/ios#sending-tokens
*/
@property (nonatomic, readonly, nonnull) NSString *tokenId;
/**
* Whether or not this token was created in livemode. Will be YES if you used your Live Publishable Key, and NO if you used your Test Publishable Key.
*/
@property (nonatomic, readonly) BOOL livemode;
/**
* The credit card details that were used to create the token. Will only be set if the token was created via a credit card or Apple Pay, otherwise it will be
* nil.
*/
@property (nonatomic, readonly, nullable) STPCard *card;
/**
* The bank account details that were used to create the token. Will only be set if the token was created with a bank account, otherwise it will be nil.
*/
@property (nonatomic, readonly, nullable) STPBankAccount *bankAccount;
/**
* When the token was created.
*/
@property (nonatomic, readonly, nullable) NSDate *created;
@end
@@ -0,0 +1,25 @@
#import <UIKit/UIKit.h>
#import <Stripe/STPAddress.h>
#import <Stripe/STPPaymentCardTextField.h>
#import <Stripe/STPFormTextField.h>
#import <Stripe/STPAPIClient.h>
#import <Stripe/STPAPIClient+ApplePay.h>
#import <Stripe/STPAPIResponseDecodable.h>
#import <Stripe/STPPaymentConfiguration.h>
#import <Stripe/STPCard.h>
#import <Stripe/STPCardBrand.h>
#import <Stripe/STPCardParams.h>
#import <Stripe/STPToken.h>
#import <Stripe/STPBankAccount.h>
#import <Stripe/STPBankAccountParams.h>
#import <Stripe/STPBINRange.h>
#import <Stripe/STPCardValidator.h>
#import <Stripe/STPCustomer.h>
#import <Stripe/STPFormEncodable.h>
#import <Stripe/STPPaymentMethod.h>
#import <Stripe/STPPhoneNumberValidator.h>
#import <Stripe/STPPostalCodeValidator.h>
#import <Stripe/STPSource.h>
#import <Stripe/STPBlocks.h>
#import <Stripe/StripeError.h>
+74
View File
@@ -0,0 +1,74 @@
//
// StripeError.h
// Stripe
//
// Created by Saikat Chakrabarti on 11/4/12.
//
//
#import <Foundation/Foundation.h>
/**
* All Stripe iOS errors will be under this domain.
*/
FOUNDATION_EXPORT NSString * __nonnull const StripeDomain;
typedef NS_ENUM(NSInteger, STPErrorCode) {
STPConnectionError = 40, // Trouble connecting to Stripe.
STPInvalidRequestError = 50, // Your request had invalid parameters.
STPAPIError = 60, // General-purpose API error (should be rare).
STPCardError = 70, // Something was wrong with the given card (most common).
STPCancellationError = 80, // The operation was cancelled.
STPCheckoutUnknownError = 5000, // Checkout failed
STPCheckoutTooManyAttemptsError = 5001, // Too many incorrect code attempts
};
#pragma mark userInfo keys
// A developer-friendly error message that explains what went wrong. You probably
// shouldn't show this to your users, but might want to use it yourself.
FOUNDATION_EXPORT NSString * __nonnull const STPErrorMessageKey;
// What went wrong with your STPCard (e.g., STPInvalidCVC. See below for full list).
FOUNDATION_EXPORT NSString * __nonnull const STPCardErrorCodeKey;
// Which parameter on the STPCard had an error (e.g., "cvc"). Useful for marking up the
// right UI element.
FOUNDATION_EXPORT NSString * __nonnull const STPErrorParameterKey;
#pragma mark STPCardErrorCodeKeys
// (Usually determined locally:)
FOUNDATION_EXPORT NSString * __nonnull const STPInvalidNumber;
FOUNDATION_EXPORT NSString * __nonnull const STPInvalidExpMonth;
FOUNDATION_EXPORT NSString * __nonnull const STPInvalidExpYear;
FOUNDATION_EXPORT NSString * __nonnull const STPInvalidCVC;
// (Usually sent from the server:)
FOUNDATION_EXPORT NSString * __nonnull const STPIncorrectNumber;
FOUNDATION_EXPORT NSString * __nonnull const STPExpiredCard;
FOUNDATION_EXPORT NSString * __nonnull const STPCardDeclined;
FOUNDATION_EXPORT NSString * __nonnull const STPProcessingError;
FOUNDATION_EXPORT NSString * __nonnull const STPIncorrectCVC;
@interface NSError(Stripe)
+ (nullable NSError *)stp_errorFromStripeResponse:(nullable NSDictionary *)jsonDictionary;
+ (nonnull NSError *)stp_genericFailedToParseResponseError;
- (BOOL)stp_isUnknownCheckoutError;
- (BOOL)stp_isURLSessionCancellationError;
#pragma mark Strings
+ (nonnull NSString *)stp_cardErrorInvalidNumberUserMessage;
+ (nonnull NSString *)stp_cardInvalidCVCUserMessage;
+ (nonnull NSString *)stp_cardErrorInvalidExpMonthUserMessage;
+ (nonnull NSString *)stp_cardErrorInvalidExpYearUserMessage;
+ (nonnull NSString *)stp_cardErrorExpiredCardUserMessage;
+ (nonnull NSString *)stp_cardErrorDeclinedUserMessage;
+ (nonnull NSString *)stp_cardErrorProcessingErrorUserMessage;
+ (nonnull NSString *)stp_unexpectedErrorMessage;
@end
+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