diff --git a/CHANGELOG.md b/CHANGELOG.md index c16713ead37..77a1e6e988a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 19.0.0 ?? +* Deprecates the `STPAPIClient` `initWithConfiguration:` method. Set the `configuration` property on the `STPAPIClient` instance instead. [#1474](https://github.com/stripe/stripe-ios/pull/1474) +* Deprecates `publishableKey` and `stripeAccount` properties of `STPPaymentConfiguration`. See [MIGRATING.md](https://github.com/stripe/stripe-ios/blob/master/MIGRATING.md) for more details. [#1474](https://github.com/stripe/stripe-ios/pull/1474) +* Adds explicit STPAPIClient properties on all SDK components that make API requests. These default to `[STPAPIClient sharedClient]`. This is a breaking change for some users of `stripeAccount`. See [MIGRATING.md](https://github.com/stripe/stripe-ios/blob/master/MIGRATING.md) for more details. [#1469](https://github.com/stripe/stripe-ios/pull/1469) + ## 18.4.0 2020-01-15 * Adds support for Klarna Pay on Sources API [#1444](https://github.com/stripe/stripe-ios/pull/1444) * Compresses images using `pngcrush` to reduce SDK size [#1471](https://github.com/stripe/stripe-ios/pull/1471) diff --git a/MIGRATING.md b/MIGRATING.md index ff1f37dcaaf..283438497e1 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -1,5 +1,48 @@ ## Migration Guides +### Migrating from versions < 19.0.0 + +* Deprecates `publishableKey` and `stripeAccount` properties of `STPPaymentConfiguration`. + * If you used `STPPaymentConfiguration.sharedConfiguration` to set `publishableKey` and/or `stripeAccount`, use `STPAPIClient.sharedClient` instead. + * If you passed a STPPaymentConfiguration instance to an SDK component, you should instead create an STPAPIClient, set publishableKey on it, and set the SDK component's APIClient property. +* The SDK now uses `STPAPIClient.sharedClient` to make API requests by default. + +This changes the behavior of the following classes, which previously used API client instances configured from `STPPaymentConfiguration.shared`: `STPCustomerContext`, `STPPaymentOptionsViewController`, `STPAddCardViewController`, `STPPaymentContext`, `STPPinManagementService`, `STPPushProvisioningContext`. + +You are affected by this change if: + +1. You use `stripeAccount` to work with your Connected accounts +2. You use one of the above affected classes +3. You set different `stripeAccount` values on `STPPaymentConfiguration` and `STPAPIClient`, i.e. `STPPaymentConfiguration.shared.stripeAccount != STPAPIClient.shared.stripeAccount` + +If all three of the above conditions are true, you must update your integration! The SDK used to send `STPPaymentConfiguration.shared.stripeAccount`, and will now send `STPAPIClient.shared.stripeAccount`. + +For example, if you are a Connect user who stores Payment Methods on your platform, but clones PaymentMethods to a connected account and creates direct charges on that connected account i.e. if: + +1. You never set `STPPaymentConfiguration.shared.stripeAccount` +2. You set `STPAPIClient.shared.stripeAccount` + +We recommend you do the following: + +``` + // By default, you don't want the SDK to pass stripeAccount + STPAPIClient.shared().publishableKey = "pk_platform" + STPAPIClient.shared().stripeAccount = nil + + // You do want the SDK to pass stripeAccount when it makes payments directly on your connected account, so + // you create a separate APIClient instance... + let connectedAccountAPIClient = STPAPIClient(publishableKey: "pk_platform") + + // ...set stripeAccount on it... + connectedAccountAPIClient.stripeAccount = "your connected account's id" + + // ...and either set the relevant SDK components' apiClient property to your custom APIClient instance: + STPPaymentHandler.shared().apiClient = connectedAccountAPIClient // e.g. if you are using PaymentIntents + + // ...or use it directly to make API requests with `stripeAccount` set: + connectedAccountAPIClient.createToken(withCard:...) // e.g. if you are using Tokens + Charges +``` + ### Migrating from versions < 18.0.0 * Some error messages from the Payment Intents API are now localized to the user's display language. If your application's logic depends on specific `message` strings from the Stripe API, please use the error [`code`](https://stripe.com/docs/error-codes) instead. * `STPPaymentResult` may contain a `paymentMethodParams` instead of a `paymentMethod` when using single-use payment methods such as FPX. Because of this, `STPPaymentResult.paymentMethod` is now nullable. Instead of setting the `paymentMethodId` manually on your `paymentIntentParams`, you may now call `paymentIntentParams.configure(with result: STPPaymentResult)`: diff --git a/Stripe/PublicHeaders/STPAPIClient.h b/Stripe/PublicHeaders/STPAPIClient.h index 831e0ae4796..058ceca96d9 100644 --- a/Stripe/PublicHeaders/STPAPIClient.h +++ b/Stripe/PublicHeaders/STPAPIClient.h @@ -32,8 +32,8 @@ static NSString *const STPSDKVersion = @"18.4.0"; /** 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 + + @param publishableKey Your publishable key, obtained from https://dashboard.stripe.com/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; @@ -51,20 +51,13 @@ static NSString *const STPSDKVersion = @"18.4.0"; @interface STPAPIClient : NSObject /** - A shared singleton API client. Its API key will be initially equal to [Stripe defaultPublishableKey]. + A shared singleton API client. + + By default, the SDK uses this instance to make API requests + eg in STPPaymentHandler, STPPaymentContext, STPCustomerContext, etc. */ + (instancetype)sharedClient; - -/** - Initializes an API client with the given configuration. Its API key will be - set to the configuration's publishable key. - - @param configuration The configuration to use. - @return An instance of STPAPIClient. - */ -- (instancetype)initWithConfiguration:(STPPaymentConfiguration *)configuration NS_DESIGNATED_INITIALIZER; - /** Initializes an API client with the given publishable key. @@ -75,11 +68,15 @@ static NSString *const STPSDKVersion = @"18.4.0"; /** The client's publishable key. + + The default value is [Stripe defaultPublishableKey]. */ @property (nonatomic, copy, nullable) NSString *publishableKey; /** The client's configuration. + + Defaults to [STPPaymentConfiguration sharedConfiguration]. */ @property (nonatomic, copy) STPPaymentConfiguration *configuration; @@ -481,4 +478,21 @@ Converts the last 4 SSN digits into a Stripe token using the Stripe API. @end +#pragma mark - Deprecated + +/** + Deprecated STPAPIClient methods + */ +@interface STPAPIClient (Deprecated) + +/** + Initializes an API client with the given configuration. + + @param configuration The configuration to use. + @return An instance of STPAPIClient. + */ +- (instancetype)initWithConfiguration:(STPPaymentConfiguration *)configuration DEPRECATED_MSG_ATTRIBUTE("This initializer previously configured publishableKey and stripeAccount via the STPPaymentConfiguration instance. This behavior is deprecated; set the STPAPIClient configuration, publishableKey, and stripeAccount properties directly on the STPAPIClient instead."); + +@end + NS_ASSUME_NONNULL_END diff --git a/Stripe/PublicHeaders/STPPaymentConfiguration.h b/Stripe/PublicHeaders/STPPaymentConfiguration.h index bb58ac46902..a483dd5e52a 100644 --- a/Stripe/PublicHeaders/STPPaymentConfiguration.h +++ b/Stripe/PublicHeaders/STPPaymentConfiguration.h @@ -30,13 +30,6 @@ NS_ASSUME_NONNULL_BEGIN */ + (instancetype)sharedConfiguration; -/** - Your Stripe publishable key - - @see https://dashboard.stripe.com/account/apikeys - */ -@property (nonatomic, copy, readwrite) NSString *publishableKey; - /** An enum value representing which payment options you will accept from your user in addition to credit cards. @@ -119,14 +112,27 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign, readwrite) BOOL canDeletePaymentOptions; +#pragma mark - Deprecated + /** + If you used [STPPaymentConfiguration sharedConfiguration].publishableKey, use [STPAPIClient sharedClient].publishableKey instead. The SDK uses [STPAPIClient sharedClient] to make API requests by default. + + Your Stripe publishable key + + @see https://dashboard.stripe.com/account/apikeys + */ +@property (nonatomic, copy, readwrite) NSString *publishableKey DEPRECATED_MSG_ATTRIBUTE("If you used [STPPaymentConfiguration sharedConfiguration].publishableKey, use [STPAPIClient sharedClient].publishableKey instead. If you passed a STPPaymentConfiguration instance to an SDK component, create an STPAPIClient, set publishableKey on it, and set the SDK component's APIClient property."); + +/** + If you used [STPPaymentConfiguration sharedConfiguration].stripeAccount, use [STPAPIClient sharedClient].stripeAccount instead. The SDK uses [STPAPIClient sharedClient] to make API requests by default. + In order to perform API requests on behalf of a connected account, e.g. to create charges for a connected account, set this property to the ID of the account for which this request is being made. @see https://stripe.com/docs/payments/payment-intents/use-cases#connected-accounts */ -@property (nonatomic, copy, nullable) NSString *stripeAccount; +@property (nonatomic, copy, nullable) NSString *stripeAccount DEPRECATED_MSG_ATTRIBUTE("If you used [STPPaymentConfiguration sharedConfiguration].stripeAccount, use [STPAPIClient sharedClient].stripeAccount instead. If you passed a STPPaymentConfiguration instance to an SDK component, create an STPAPIClient, set stripeAccount on it, and set the SDK component's APIClient property.");; @end diff --git a/Stripe/STPAPIClient+ApplePay.m b/Stripe/STPAPIClient+ApplePay.m index 0ab180c6590..dc892c8c1e6 100644 --- a/Stripe/STPAPIClient+ApplePay.m +++ b/Stripe/STPAPIClient+ApplePay.m @@ -126,7 +126,7 @@ + (NSDictionary *)parametersForPayment:(PKPayment *)payment { payload[@"pk_token"] = paymentString; payload[@"card"] = [self addressParamsFromPKContact:payment.billingContact]; - NSCAssert(!(paymentString.length == 0 && [[Stripe defaultPublishableKey] hasPrefix:@"pk_live"]), @"The pk_token is empty. Using Apple Pay with an iOS Simulator while not in Stripe Test Mode will always fail."); + NSCAssert(!(paymentString.length == 0 && [[STPAPIClient sharedClient].publishableKey hasPrefix:@"pk_live"]), @"The pk_token is empty. Using Apple Pay with an iOS Simulator while not in Stripe Test Mode will always fail."); NSString *paymentInstrumentName = payment.token.paymentMethod.displayName; if (paymentInstrumentName) { diff --git a/Stripe/STPAPIClient.m b/Stripe/STPAPIClient.m index ed239ac3ca6..ae8ca697651 100644 --- a/Stripe/STPAPIClient.m +++ b/Stripe/STPAPIClient.m @@ -71,14 +71,14 @@ @implementation Stripe static NSArray *_additionalEnabledApplePayNetworks; +static NSString *_defaultPublishableKey; + (void)setDefaultPublishableKey:(NSString *)publishableKey { - [STPAPIClient validateKey:publishableKey]; - [STPPaymentConfiguration sharedConfiguration].publishableKey = publishableKey; + _defaultPublishableKey = [publishableKey copy]; } + (NSString *)defaultPublishableKey { - return [STPPaymentConfiguration sharedConfiguration].publishableKey; + return _defaultPublishableKey; } @end @@ -89,7 +89,6 @@ @interface STPAPIClient() @property (nonatomic, strong, readwrite) NSMutableDictionary *sourcePollers; @property (nonatomic, strong, readwrite) dispatch_queue_t sourcePollersQueue; -@property (nonatomic, strong, readwrite) NSString *apiKey; // See STPAPIClient+Private.h @@ -117,33 +116,35 @@ + (instancetype)sharedClient { } - (instancetype)init { - return [self initWithConfiguration:[STPPaymentConfiguration sharedConfiguration]]; -} - -- (instancetype)initWithPublishableKey:(NSString *)publishableKey { - STPPaymentConfiguration *config = [[STPPaymentConfiguration alloc] init]; - config.publishableKey = [publishableKey copy]; - return [self initWithConfiguration:config]; -} - -- (instancetype)initWithConfiguration:(STPPaymentConfiguration *)configuration { - NSString *publishableKey = [configuration.publishableKey copy]; - if (publishableKey) { - [self.class validateKey:publishableKey]; - } self = [super init]; if (self) { - _apiKey = publishableKey; _apiURL = [NSURL URLWithString:APIBaseURL]; - _configuration = configuration; - _stripeAccount = configuration.stripeAccount; + _configuration = [STPPaymentConfiguration sharedConfiguration]; _sourcePollers = [NSMutableDictionary dictionary]; _sourcePollersQueue = dispatch_queue_create("com.stripe.sourcepollers", DISPATCH_QUEUE_SERIAL); _urlSession = [NSURLSession sessionWithConfiguration:[self.class sharedUrlSessionConfiguration]]; + _publishableKey = [Stripe defaultPublishableKey]; } return self; } +- (instancetype)initWithPublishableKey:(NSString *)publishableKey { + STPAPIClient *apiClient = [self init]; + apiClient.publishableKey = publishableKey; + return apiClient; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" +- (instancetype)initWithConfiguration:(STPPaymentConfiguration *)configuration { + // For legacy reasons, we'll support this initializer and use the deprecated configuration.{publishableKey, stripeAccount} properties + STPAPIClient *apiClient = [self init]; + apiClient.publishableKey = configuration.publishableKey; + apiClient.stripeAccount = configuration.stripeAccount; + return apiClient; +} +#pragma clang diagnostic pop + + (NSURLSessionConfiguration *)sharedUrlSessionConfiguration { static NSURLSessionConfiguration *STPSharedURLSessionConfiguration; static dispatch_once_t configToken; @@ -175,12 +176,7 @@ - (NSMutableURLRequest *)configuredRequestForURL:(NSURL *)url additionalHeaders: - (void)setPublishableKey:(NSString *)publishableKey { [self.class validateKey:publishableKey]; - self.configuration.publishableKey = [publishableKey copy]; - self.apiKey = [publishableKey copy]; -} - -- (NSString *)publishableKey { - return self.configuration.publishableKey; + _publishableKey = [publishableKey copy]; } - (void)createTokenWithParameters:(NSDictionary *)parameters @@ -259,7 +255,7 @@ + (NSString *)stripeUserAgentDetailsWithAppInfo:(nullable STPAppInfo *)appInfo { } - (NSDictionary *)authorizationHeaderUsingEphemeralKey:(STPEphemeralKey *)ephemeralKey { - NSString *authorizationBearer = self.apiKey ?: @""; + NSString *authorizationBearer = self.publishableKey ?: @""; if (ephemeralKey != nil) { authorizationBearer = ephemeralKey.secret; } diff --git a/Stripe/STPAnalyticsClient.m b/Stripe/STPAnalyticsClient.m index e9db686f265..0129e1fec4a 100644 --- a/Stripe/STPAnalyticsClient.m +++ b/Stripe/STPAnalyticsClient.m @@ -410,7 +410,7 @@ + (NSMutableDictionary *)commonPayload { + (NSDictionary *)serializeConfiguration:(STPPaymentConfiguration *)configuration { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - dictionary[@"publishable_key"] = configuration.publishableKey ?: @"unknown"; + dictionary[@"publishable_key"] = [STPAPIClient sharedClient].publishableKey ?: @"unknown"; if (configuration.additionalPaymentOptions == STPPaymentOptionTypeDefault) { dictionary[@"additional_payment_methods"] = @"default"; diff --git a/Stripe/STPPaymentConfiguration.m b/Stripe/STPPaymentConfiguration.m index 93c01658fe4..66815eac468 100644 --- a/Stripe/STPPaymentConfiguration.m +++ b/Stripe/STPPaymentConfiguration.m @@ -22,6 +22,9 @@ @interface STPPaymentConfiguration () @implementation STPPaymentConfiguration +@synthesize publishableKey = _publishableKey; +@synthesize stripeAccount = _stripeAccount; + + (void)initialize { [STPAnalyticsClient initializeIfNeeded]; [STPTelemetryClient sharedInstance]; @@ -126,7 +129,6 @@ - (NSString *)description { [NSString stringWithFormat:@"%@: %p", NSStringFromClass([self class]), self], // Basic configuration - [NSString stringWithFormat:@"publishableKey = %@", (self.publishableKey) ? @"" : nil], [NSString stringWithFormat:@"additionalPaymentOptions = %@", additionalPaymentOptionsDescription], // Billing and shipping @@ -149,7 +151,6 @@ - (NSString *)description { - (id)copyWithZone:(__unused NSZone *)zone { STPPaymentConfiguration *copy = [self.class new]; - copy.publishableKey = self.publishableKey; copy.additionalPaymentOptions = self.additionalPaymentOptions; copy.requiredBillingAddressFields = self.requiredBillingAddressFields; copy.requiredShippingAddressFields = self.requiredShippingAddressFields; @@ -159,7 +160,47 @@ - (id)copyWithZone:(__unused NSZone *)zone { copy.appleMerchantIdentifier = self.appleMerchantIdentifier; copy.canDeletePaymentOptions = self.canDeletePaymentOptions; copy.availableCountries = _availableCountries; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" + copy.publishableKey = self.publishableKey; + copy.stripeAccount = self.stripeAccount; +#pragma clang diagnostic pop + return copy; } +#pragma mark - Deprecated + +// For legacy reasons, we'll try to keep the same behavior as before if setting these properties on the singleton. + +- (void)setPublishableKey:(NSString *)publishableKey { + if (self == [STPPaymentConfiguration sharedConfiguration]) { + [STPAPIClient sharedClient].publishableKey = publishableKey; + } else { + _publishableKey = [publishableKey copy]; + } +} + +- (NSString *)publishableKey { + if (self == [STPPaymentConfiguration sharedConfiguration]) { + return [STPAPIClient sharedClient].publishableKey; + } + return _publishableKey; +} + +- (void)setStripeAccount:(NSString *)stripeAccount { + if (self == [STPPaymentConfiguration sharedConfiguration]) { + [STPAPIClient sharedClient].stripeAccount = stripeAccount; + } else { + _stripeAccount = [stripeAccount copy]; + } +} + +- (NSString *)stripeAccount { + if (self == [STPPaymentConfiguration sharedConfiguration]) { + return [STPAPIClient sharedClient].stripeAccount; + } + return _stripeAccount; +} + @end diff --git a/Tests/Tests/STPAPIClientTest.m b/Tests/Tests/STPAPIClientTest.m index c95a8a530fd..0b044be4198 100644 --- a/Tests/Tests/STPAPIClientTest.m +++ b/Tests/Tests/STPAPIClientTest.m @@ -31,8 +31,17 @@ - (void)testSharedClient { - (void)testSetDefaultPublishableKey { [Stripe setDefaultPublishableKey:@"test"]; - STPAPIClient *client = [STPAPIClient sharedClient]; - XCTAssertEqualObjects(client.publishableKey, @"test"); + STPAPIClient *clientInitializedAfter = [STPAPIClient new]; + STPAPIClient *sharedClient = [STPAPIClient sharedClient]; + XCTAssertEqualObjects(sharedClient.publishableKey, @"test"); + XCTAssertEqualObjects(clientInitializedAfter.publishableKey, @"test"); + + // Setting the STPAPIClient instance overrides Stripe.defaultPublishableKey... + sharedClient.publishableKey = @"test2"; + XCTAssertEqualObjects(sharedClient.publishableKey, @"test2"); + + // ...while Stripe.defaultPublishableKey remains the same + XCTAssertEqualObjects(Stripe.defaultPublishableKey, @"test"); } - (void)testInitWithPublishableKey { @@ -69,11 +78,16 @@ - (void)testSetStripeAccount { - (void)testInitWithConfiguration { STPPaymentConfiguration *config = [STPFixtures paymentConfiguration]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" + config.publishableKey = @"pk_123"; config.stripeAccount = @"acct_123"; STPAPIClient *sut = [[STPAPIClient alloc] initWithConfiguration:config]; XCTAssertEqualObjects(sut.publishableKey, config.publishableKey); XCTAssertEqualObjects(sut.stripeAccount, config.stripeAccount); +#pragma clang diagnostic pop + NSString *accountHeader = [sut configuredRequestForURL:[NSURL URLWithString:@"https://www.stripe.com"] additionalHeaders:nil].allHTTPHeaderFields[@"Stripe-Account"]; XCTAssertEqualObjects(accountHeader, @"acct_123"); } diff --git a/Tests/Tests/STPFixtures.m b/Tests/Tests/STPFixtures.m index 087c557db19..22892c643c6 100644 --- a/Tests/Tests/STPFixtures.m +++ b/Tests/Tests/STPFixtures.m @@ -226,7 +226,6 @@ + (STPSetupIntent *)setupIntent { + (STPPaymentConfiguration *)paymentConfiguration { STPPaymentConfiguration *config = [STPPaymentConfiguration new]; - config.publishableKey = @"pk_fake_publishable_key"; return config; } diff --git a/Tests/Tests/STPPaymentConfigurationTest.m b/Tests/Tests/STPPaymentConfigurationTest.m index f5276573161..0358125b692 100644 --- a/Tests/Tests/STPPaymentConfigurationTest.m +++ b/Tests/Tests/STPPaymentConfigurationTest.m @@ -31,7 +31,6 @@ - (void)testInit { STPPaymentConfiguration *paymentConfiguration = [[STPPaymentConfiguration alloc] init]; - XCTAssertNil(paymentConfiguration.publishableKey); XCTAssertEqual(paymentConfiguration.additionalPaymentOptions, STPPaymentOptionTypeDefault); XCTAssertEqual(paymentConfiguration.requiredBillingAddressFields, STPBillingAddressFieldsNone); XCTAssertNil(paymentConfiguration.requiredShippingAddressFields); @@ -102,7 +101,11 @@ - (void)testCopyWithZone { STPContactFieldName]]; STPPaymentConfiguration *paymentConfigurationA = [[STPPaymentConfiguration alloc] init]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" paymentConfigurationA.publishableKey = @"publishableKey"; + paymentConfigurationA.stripeAccount = @"stripeAccount"; +#pragma clang diagnostic pop paymentConfigurationA.additionalPaymentOptions = STPPaymentOptionTypeApplePay; paymentConfigurationA.requiredBillingAddressFields = STPBillingAddressFieldsFull; paymentConfigurationA.requiredShippingAddressFields = allFields; @@ -115,8 +118,12 @@ - (void)testCopyWithZone { STPPaymentConfiguration *paymentConfigurationB = [paymentConfigurationA copy]; XCTAssertNotEqual(paymentConfigurationA, paymentConfigurationB); - + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" XCTAssertEqualObjects(paymentConfigurationB.publishableKey, @"publishableKey"); + XCTAssertEqualObjects(paymentConfigurationB.stripeAccount, @"stripeAccount"); +#pragma clang diagnostic pop XCTAssertEqual(paymentConfigurationB.additionalPaymentOptions, STPPaymentOptionTypeApplePay); XCTAssertEqual(paymentConfigurationB.requiredBillingAddressFields, STPBillingAddressFieldsFull); XCTAssertEqualObjects(paymentConfigurationB.requiredShippingAddressFields, allFields); diff --git a/Tests/Tests/UINavigationBar+StripeTest.m b/Tests/Tests/UINavigationBar+StripeTest.m index 8fba5b94f3b..bd4d454e647 100644 --- a/Tests/Tests/UINavigationBar+StripeTest.m +++ b/Tests/Tests/UINavigationBar+StripeTest.m @@ -21,7 +21,6 @@ @implementation UINavigationBar_StripeTest - (STPPaymentOptionsViewController *)buildPaymentOptionsViewController { id customerContext = [STPMocks staticCustomerContext]; STPPaymentConfiguration *config = [STPFixtures paymentConfiguration]; - config.publishableKey = @"pk_test"; STPTheme *theme = [STPTheme defaultTheme]; id delegate = OCMProtocolMock(@protocol(STPPaymentOptionsViewControllerDelegate)); STPPaymentOptionsViewController *paymentOptionsVC = [[STPPaymentOptionsViewController alloc] initWithConfiguration:config