Skip to content

Commit

Permalink
Merge pull request #1131 from stripe/jack/issuing-ephemeral-keys
Browse files Browse the repository at this point in the history
New type of ephemeral keys
  • Loading branch information
acavailhez-stripe authored Feb 22, 2019
2 parents 512ce28 + 69d2bbb commit 3fe5307
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 94 deletions.
4 changes: 2 additions & 2 deletions Stripe/PublicHeaders/STPCustomerContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

NS_ASSUME_NONNULL_BEGIN

@protocol STPEphemeralKeyProvider;
@protocol STPCustomerEphemeralKeyProvider;
@class STPEphemeralKey, STPEphemeralKeyManager;

/**
Expand All @@ -35,7 +35,7 @@ NS_ASSUME_NONNULL_BEGIN
@param keyProvider The key provider the customer context will use.
@return the newly-instantiated customer context.
*/
- (instancetype)initWithKeyProvider:(id<STPEphemeralKeyProvider>)keyProvider;
- (instancetype)initWithKeyProvider:(id<STPCustomerEphemeralKeyProvider>)keyProvider;

/**
`STPCustomerContext` will cache its customer object for up to 60 seconds.
Expand Down
48 changes: 42 additions & 6 deletions Stripe/PublicHeaders/STPEphemeralKeyProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,20 @@ NS_ASSUME_NONNULL_BEGIN

/**
You should make your application's API client conform to this interface.
It provides a way for `STPCustomerContext` to request a new ephemeral key from
your backend, which it will use to retrieve and update a Stripe customer.
It provides a way for Stripe utility classes to request a new ephemeral key from
your backend, which it will use to retrieve and update Stripe API objects.
*/
@protocol STPEphemeralKeyProvider <NSObject>

@protocol STPCustomerEphemeralKeyProvider <NSObject>
/**
Creates a new ephemeral key for retrieving and updating a Stripe customer.
On your backend, you should create a new ephemeral key for the Stripe customer
associated with your user, and return the raw JSON response from the Stripe API.
For an example Ruby implementation of this API, refer to our example backend:
https://github.com/stripe/example-ios-backend/blob/v14.0.0/web.rb
Back in your iOS app, once you have a response from this API, call the provided
completion block with the JSON response, or an error if one occurred.
@param apiVersion The Stripe API version to use when creating a key.
You should pass this parameter to your backend, and use it to set the API version
in your key creation request. Passing this version parameter ensures that the
Expand All @@ -40,7 +39,44 @@ NS_ASSUME_NONNULL_BEGIN
or `completion(nil, error)` if an error is returned.
*/
- (void)createCustomerKeyWithAPIVersion:(NSString *)apiVersion completion:(STPJSONResponseCompletionBlock)completion;
@end

/**
You should make your application's API client conform to this interface.
It provides a way for Stripe utility classes to request a new ephemeral key from
your backend, which it will use to retrieve and update Stripe API objects.
*/
@protocol STPIssuingCardEphemeralKeyProvider <NSObject>
/**
Creates a new ephemeral key for retrieving and updating a Stripe Issuing Card.
On your backend, you should create a new ephemeral key for your logged-in user's
primary Issuing Card, and return the raw JSON response from the Stripe API.
For an example Ruby implementation of this API, refer to our example backend:
https://github.com/stripe/example-ios-backend/blob/v14.0.0/web.rb
Back in your iOS app, once you have a response from this API, call the provided
completion block with the JSON response, or an error if one occurred.
@param apiVersion The Stripe API version to use when creating a key.
You should pass this parameter to your backend, and use it to set the API version
in your key creation request. Passing this version parameter ensures that the
Stripe SDK can always parse the ephemeral key response from your server.
@param completion Call this callback when you're done fetching a new ephemeral
key from your backend. For example, `completion(json, nil)` (if your call succeeds)
or `completion(nil, error)` if an error is returned.
*/
- (void)createIssuingCardKeyWithAPIVersion:(NSString *)apiVersion completion:(STPJSONResponseCompletionBlock)completion;
@end

/**
You should make your application's API client conform to this interface.
It provides a way for Stripe utility classes to request a new ephemeral key from
your backend, which it will use to retrieve and update Stripe API objects.
@deprecated use `STPCustomerEphemeralKeyProvider` or `STPIssuingCardEphemeralKeyProvider`
depending on the type of key that will be fetched.
*/
__attribute__ ((deprecated("STPEphemeralKeyProvider has been renamed to STPCustomerEphemeralKeyProvider", "STPCustomerEphemeralKeyProvider")))
@protocol STPEphemeralKeyProvider <STPCustomerEphemeralKeyProvider>
@end

NS_ASSUME_NONNULL_END
4 changes: 4 additions & 0 deletions Stripe/STPAPIClient+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ NS_ASSUME_NONNULL_BEGIN

@end

@interface STPAPIClient (EphemeralKeys)
+ (instancetype)apiClientWithEphemeralKey:(STPEphemeralKey *)key;
@end

@interface STPAPIClient (Customers)

/**
Expand Down
15 changes: 8 additions & 7 deletions Stripe/STPCustomerContext.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ @interface STPCustomerContext ()

@implementation STPCustomerContext

- (instancetype)initWithKeyProvider:(nonnull id<STPEphemeralKeyProvider>)keyProvider {
- (instancetype)initWithKeyProvider:(nonnull id<STPCustomerEphemeralKeyProvider>)keyProvider {
STPEphemeralKeyManager *keyManager = [[STPEphemeralKeyManager alloc] initWithKeyProvider:keyProvider
apiVersion:[STPAPIClient apiVersion]];

apiVersion:[STPAPIClient apiVersion] performsEagerFetching:YES];
return [self initWithKeyManager:keyManager];
}

Expand Down Expand Up @@ -75,7 +76,7 @@ - (void)retrieveCustomer:(STPCustomerCompletionBlock)completion {
}
return;
}
[self.keyManager getCustomerKey:^(STPEphemeralKey *ephemeralKey, NSError *retrieveKeyError) {
[self.keyManager getOrCreateKey:^(STPEphemeralKey *ephemeralKey, NSError *retrieveKeyError) {
if (retrieveKeyError) {
if (completion) {
stpDispatchToMainThreadIfNecessary(^{
Expand All @@ -99,7 +100,7 @@ - (void)retrieveCustomer:(STPCustomerCompletionBlock)completion {
}

- (void)attachSourceToCustomer:(id<STPSourceProtocol>)source completion:(STPErrorBlock)completion {
[self.keyManager getCustomerKey:^(STPEphemeralKey *ephemeralKey, NSError *retrieveKeyError) {
[self.keyManager getOrCreateKey:^(STPEphemeralKey *ephemeralKey, NSError *retrieveKeyError) {
if (retrieveKeyError) {
if (completion) {
stpDispatchToMainThreadIfNecessary(^{
Expand All @@ -123,7 +124,7 @@ - (void)attachSourceToCustomer:(id<STPSourceProtocol>)source completion:(STPErro
}

- (void)selectDefaultCustomerSource:(id<STPSourceProtocol>)source completion:(STPErrorBlock)completion {
[self.keyManager getCustomerKey:^(STPEphemeralKey *ephemeralKey, NSError *retrieveKeyError) {
[self.keyManager getOrCreateKey:^(STPEphemeralKey *ephemeralKey, NSError *retrieveKeyError) {
if (retrieveKeyError) {
if (completion) {
stpDispatchToMainThreadIfNecessary(^{
Expand All @@ -149,7 +150,7 @@ - (void)selectDefaultCustomerSource:(id<STPSourceProtocol>)source completion:(ST
}

- (void)updateCustomerWithShippingAddress:(STPAddress *)shipping completion:(STPErrorBlock)completion {
[self.keyManager getCustomerKey:^(STPEphemeralKey *ephemeralKey, NSError *retrieveKeyError) {
[self.keyManager getOrCreateKey:^(STPEphemeralKey *ephemeralKey, NSError *retrieveKeyError) {
if (retrieveKeyError) {
if (completion) {
stpDispatchToMainThreadIfNecessary(^{
Expand Down Expand Up @@ -178,7 +179,7 @@ - (void)updateCustomerWithShippingAddress:(STPAddress *)shipping completion:(STP
}

- (void)detachSourceFromCustomer:(id<STPSourceProtocol>)source completion:(STPErrorBlock)completion {
[self.keyManager getCustomerKey:^(STPEphemeralKey *ephemeralKey, NSError *retrieveKeyError) {
[self.keyManager getOrCreateKey:^(STPEphemeralKey *ephemeralKey, NSError *retrieveKeyError) {
if (retrieveKeyError) {
if (completion) {
stpDispatchToMainThreadIfNecessary(^{
Expand Down
3 changes: 2 additions & 1 deletion Stripe/STPEphemeralKey.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) BOOL livemode;
@property (nonatomic, readonly) NSString *secret;
@property (nonatomic, readonly) NSDate *expires;
@property (nonatomic, readonly) NSString *customerID;
@property (nonatomic, readonly, nullable) NSString *customerID;
@property (nonatomic, readonly, nullable) NSString *issuingCardID;

/**
You cannot directly instantiate an `STPEphemeralKey`. You should instead use
Expand Down
19 changes: 11 additions & 8 deletions Stripe/STPEphemeralKey.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ @interface STPEphemeralKey ()
@property (nonatomic, readwrite) BOOL livemode;
@property (nonatomic, readwrite) NSString *secret;
@property (nonatomic, readwrite) NSDate *expires;
@property (nonatomic, readwrite) NSString *customerID;
@property (nonatomic, readwrite, nullable) NSString *customerID;
@property (nonatomic, readwrite, nullable) NSString *issuingCardID;
@property (nonatomic, readwrite, nonnull, copy) NSDictionary *allResponseFields;

@end
Expand All @@ -40,19 +41,24 @@ + (instancetype)decodedObjectFromAPIResponse:(NSDictionary *)response {
}

NSString *customerID;
NSString *issuingCardID;
for (id obj in associatedObjects) {
if ([obj isKindOfClass:[NSDictionary class]]) {
NSString *type = [obj stp_stringForKey:@"type"];
if ([type isEqualToString:@"customer"]) {
customerID = [obj stp_stringForKey:@"id"];
}
if ([type isEqualToString:@"issuing.card"]) {
issuingCardID = [obj stp_stringForKey:@"id"];
}
}
}
if (!customerID) {
if (!customerID && !issuingCardID) {
return nil;
}
STPEphemeralKey *key = [self new];
key.customerID = customerID;
key.issuingCardID = issuingCardID;
key.stripeID = stripeId;
key.livemode = [dict stp_boolForKey:@"livemode" or:YES];
key.created = created;
Expand All @@ -73,14 +79,11 @@ - (BOOL)isEqual:(id)object {
if (!object || ![object isKindOfClass:self.class]) {
return NO;
}
return [self isEqualToResourceKey:object];
return [self isEqualToEphemeralKey:object];
}

- (BOOL)isEqualToResourceKey:(STPEphemeralKey *)other {
return [self.stripeID isEqualToString:other.stripeID]
&& [self.secret isEqualToString:other.secret]
&& [self.expires isEqual:other.expires]
&& [self.customerID isEqual:other.customerID];
- (BOOL)isEqualToEphemeralKey:(STPEphemeralKey *)other {
return [self.stripeID isEqualToString:other.stripeID];
}

@end
20 changes: 14 additions & 6 deletions Stripe/STPEphemeralKeyManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,37 @@ typedef void (^STPEphemeralKeyCompletionBlock)(STPEphemeralKey * __nullable ephe

/**
If the current ephemeral key expires in less than this time interval, a call
to `getCustomerKey` will request a new key from the manager's key provider.
to `getOrCreateKey` will request a new key from the manager's key provider.
The maximum allowed value is one hour – higher values will be clamped.
*/
@property (nonatomic, assign) NSTimeInterval expirationInterval;

/**
If this value is YES, the manager will eagerly refresh its key on app foregrounding.
*/
@property (nonatomic, readonly, assign) BOOL performsEagerFetching;

/**
Initializes a new `STPEphemeralKeyManager` with the specified key provider.
@param keyProvider The key provider the manager will use.
@param apiVersion The Stripe API version the manager will use.
@param keyProvider The key provider the manager will use.
@param apiVersion The Stripe API version the manager will use.
@param performsEagerFetching If the manager should eagerly refresh its key on app foregrounding.
@return the newly-initiated `STPEphemeralKeyManager`.
*/
- (instancetype)initWithKeyProvider:(id<STPEphemeralKeyProvider>)keyProvider apiVersion:(NSString *)apiVersion;
- (instancetype)initWithKeyProvider:(id)keyProvider
apiVersion:(NSString *)apiVersion
performsEagerFetching:(BOOL)performsEagerFetching;

/**
If the retriever's stored customer ephemeral key has not expired, it will be
If the retriever's stored ephemeral key has not expired, it will be
returned immediately to the given callback. If the stored key is expiring, a
new key will be requested from the key provider, and returned to the callback.
If the retriever is unable to provide an unexpired key, an error will be returned.
@param completion The callback to be run with the returned key, or an error.
*/
- (void)getCustomerKey:(STPEphemeralKeyCompletionBlock)completion;
- (void)getOrCreateKey:(STPEphemeralKeyCompletionBlock)completion;

@end

Expand Down
Loading

0 comments on commit 3fe5307

Please sign in to comment.