Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New type of ephemeral keys #1131

Merged
merged 12 commits into from
Feb 22, 2019
Merged

Conversation

acavailhez-stripe
Copy link
Contributor

Summary

Allowing for ephemeral keys that are not customer

Motivation

Stripe Issuing will need those

Testing

Existing ephemeral keys test apply

Copy link

@fred-stripe fred-stripe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall a nice refactor to add the new type of ephemeral keys!. I'll defer the rest of review to someone who understands the SDK internals better than I do.

@@ -64,18 +68,44 @@ - (void)handleWillForegroundNotification {
// eager refreshses to once per hour.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor typo:
// eager refreshes to once per hour.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// the ephemeral key could not be decoded
else {
[self.createKeyPromise fail:[NSError stp_ephemeralKeyDecodingError]];
NSAssert(NO, @"Could not parse the ephemeral key response. Make sure your backend is sending the unmodified JSON of the ephemeral key to your app. For more info, see https://stripe.com/docs/mobile/ios/standard#prepare-your-api");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be handy to log the type of key here (Issuing vs Customer), not sure if that's available in this context. My thought is the user will contact us when they see that error, and it'd be nice to know which key provider is causing the issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -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;
Copy link

@fred-stripe fred-stripe Feb 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming this class means:

  1. We're also going to have to update the Examples, the stripe.com docs, and the generated docs
  2. We'll need coordinate a release between all three.
  3. Users will have to update their existing code when they upgrade, even if they're not using Issuing.

I don't think any of this is a blocker, just thinking through the user-facing consequences here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jack-stripe Just to make sure: is it in line with what you expected?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe I safely deprecated STPEphemeralKeyProvider by changing it to subclass STPCustomerEphemeralKeyProvider - this would trigger a deprecation warning in existing integrations but not a compiler error. Yes, this will mean a docs update though.

}

- (BOOL)isEqualToResourceKey:(STPEphemeralKey *)other {
return [self.stripeID isEqualToString:other.stripeID]
&& [self.secret isEqualToString:other.secret]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why were these removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed it to isEqualToEphemeralKey ("Resource Key" was an early and terrible name for ephemeral keys)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah sorry, I mean why was it changed so it only compares the stripeIDs?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ping on this @jack-stripe ? (not going to block patch because id equivalence makes sense as sufficient to me but would be useful to know the reasoning

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry, missed your comment @csabol-stripe!
I honestly don't remember why I changed that. I don't see a world in which stripeID is the same but secret is different. I'm happy with either implementation.

self.customerKey = key;
}
[self getOrCreateKey:^(__unused STPEphemeralKey * _Nullable ephemeralKey, __unused NSError * _Nullable error) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like self.ephemeralKey gets set in the actual to getOrCreateKey. Could we add some comments here explaining why this is called and why we do nothing with the response here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

[self.createKeyPromise fail:error];
}
// the ephemeral key could not be decoded
else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style-nit: I prefer to have the else on the same line as the closing brace, } else {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

id<STPCustomerEphemeralKeyProvider> provider = self.keyProvider;
[provider createCustomerKeyWithAPIVersion:self.apiVersion completion:jsonCompletion];
}
else if ([self.keyProvider conformsToProtocol:@protocol(STPIssuingCardEphemeralKeyProvider)]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style-nit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}];
}
}

- (void)getCustomerKey:(STPEphemeralKeyCompletionBlock)completion {
- (void)createKey {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style-nit: (this is a new one I'm trying to enforce so a lot of existing code doesn't follow it). Internal methods should start with an underscore to differentiate from public methods - (void)_createKey

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -32,12 +31,16 @@ - (void)setUp {
self.apiVersion = @"2015-03-03";
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are we using here that's deprecated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My mistake, I added this accidentally in my initial testing, feel free to remove these ignores

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

- (id)mockKeyProviderWithKeyResponse:(NSDictionary *)keyResponse {
XCTestExpectation *exp = [self expectationWithDescription:@"createCustomerKey"];
id mockKeyProvider = OCMProtocolMock(@protocol(STPEphemeralKeyProvider));
OCMStub([mockKeyProvider createCustomerKeyWithAPIVersion:[OCMArg isEqual:self.apiVersion]
completion:[OCMArg any]])
.andDo(^(NSInvocation *invocation) {
[invocation retainArguments];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a bug using OCMock that this avoids: erikdoe/ocmock#147

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 a comment referencing this would be helpfule

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me add this

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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure these need to be two separate protocols. The documentation says that the API client should conform to both of these, which means checking for keyProvider protocol conformance will always succeed for both.

It's not totally clear to me how or why we intend to differentiate between creating customer and card ephemeral keys? Is the intention for the integrator to create two instances of STPEphemeralKeyManager and call the right one as needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the way I imagined it, yes. You might imagine one API that calls POST https://myapi.com/users/:id/ephemeral_keys and one that calls POST https://myapi.com/cards/:id/ephemeral_keys. Then you might have one API client client-side that calls the first, and another that calls the second.

The other alternative, as you suggest, is to make a single API - POST https://myapi.com/ephemeral_keys (with user or card parameters) and a single API client to talk to both. The issue is that now the method signature needs to change - we need to tell the app what they should be making an ephemeral key for exactly. So the protocol method would have to be something generic like createKey:forAssociatedStripeObject: which just feels like turning a thing to figure out at compile-time into a thing to figure out at runtime.

You raise a good point, though, that this breaks if someone's API client conforms to both protocols. Sadly, I think we'll need to add a property to STPEphemeralKeyManager to indicate what kind of key it is intended to make (not the worst thing, as it's a private class). Thoughts @csabol-stripe ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, what about adding a new method to STPEphemeralKeyManager so we have getCustomerKey: and getCardKey: (or similar)? That way we only have one key manager but the requests are still "typed" at compile time.

Similarly, what are your thoughts about keeping STPEphemeralKeyProvider and just adding createIssuingCardKeyWithAPIVersion:completion: to the existing protocol (and marking as optional if we think that would be useful?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd prefer to keep a property on STPEphemeralKeyManager, because I think it'd be a programmer error to call getCustomerKey: and then later getCardKey: on the same instance. Nice thing is, assuming that property is set in the initializer, we could assert that the given keyProvider conforms to the necessary protocol.

I don't feel as strongly about whether or not to split STPEphemeralKeyProvider into 2 protocols, but realistically users are going to be implementing only one of createCustomerKeyWithAPIVersion:completion or createIssuingCardKeyWithAPIVersion:completion`, so both would have to be optional. In that case, I think it's a little nicer to just have two protocols so that you can enforce at compile-time (instead of runtime) that the necessary methods are implemented.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts @csabol-stripe? Can also discuss in slack on monday : )

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

closing the loop: @jack-stripe and I met to discuss this today and we are happy moving forward with the patch as is :)

@acavailhez-stripe
Copy link
Contributor Author

r? @csabol-stripe

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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

closing the loop: @jack-stripe and I met to discuss this today and we are happy moving forward with the patch as is :)

}

- (BOOL)isEqualToResourceKey:(STPEphemeralKey *)other {
return [self.stripeID isEqualToString:other.stripeID]
&& [self.secret isEqualToString:other.secret]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ping on this @jack-stripe ? (not going to block patch because id equivalence makes sense as sufficient to me but would be useful to know the reasoning

@acavailhez-stripe
Copy link
Contributor Author

Thanks everyone

@acavailhez-stripe acavailhez-stripe merged commit 3fe5307 into master Feb 22, 2019
ramont-stripe added a commit that referenced this pull request May 23, 2022
* Extract UI logic into a view model

* Implement name collection

* Add test for `String.isBlank`

* Cleanup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants