-
Notifications
You must be signed in to change notification settings - Fork 999
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
add includeApplePaySources to CustomerContext #864
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you'll want to make some minor changes based on my feedback, but not necessarily everything.
@@ -46,6 +46,15 @@ NS_ASSUME_NONNULL_BEGIN | |||
*/ | |||
- (void)clearCachedCustomer; | |||
|
|||
/** | |||
By default, `STPCustomerContext` will filter Apple Pay sources when it retrieves | |||
a Customer object. If you are using `STPCustomerContext` to back your own UI and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth mentioning why we filter? Something like "because the Apple Pay sources should not be re-used and shouldn't be offered to users as a new payment source"? (I think)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yep, I'll add a note.
Tests/Tests/STPCustomerContextTest.m
Outdated
expectedCount:2]; | ||
id mockKeyManager = [self mockKeyManagerWithKey:customerKey]; | ||
STPCustomerContext *sut = [[STPCustomerContext alloc] initWithKeyManager:mockKeyManager]; | ||
[sut clearCachedCustomer]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was confused, and had to figure out why expectedCount = 2
. Could you change it to 1
and just delete this call to clearCachedCustomer
, and test the same things? Or is there something that's being added by this that I've missed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this is for parity with the test below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah yeah, I'll add a comment explaining the 2
. CustomerContext fetches a customer on initialization, but there's no callback we can use for assertions, so I just used another retrieveCustomer
call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh actually, after changing the behavior of includeApplePaySources
per below, we can just use the cached customer and change this to 1
Tests/Tests/STPCustomerContextTest.m
Outdated
id mockKeyManager = [self mockKeyManagerWithKey:customerKey]; | ||
STPCustomerContext *sut = [[STPCustomerContext alloc] initWithKeyManager:mockKeyManager]; | ||
sut.includeApplePaySources = YES; | ||
[sut clearCachedCustomer]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if changing the includeApplePaySources
property should clear the cached customer automatically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or maybe it just calls the updateSourcesWithResponse:filteringApplePay:
method, instead of hitting the server again?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah interesting, it does seem reasonable for includeApplePaySources
to update the current customer too. I'll update.
Stripe/STPCustomerContext.m
Outdated
@@ -48,6 +49,10 @@ - (void)clearCachedCustomer { | |||
} | |||
|
|||
- (void)setCustomer:(STPCustomer *)customer { | |||
if (self.includeApplePaySources) { | |||
[customer updateSourcesWithResponse:customer.allResponseFields |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is kind of funky/subtle/non-obvious.
- This mutates the
STPCustomer
object passed into the setter, which the caller still has a reference to. - I didn't understand why you changed
customer
->self.customer
on L92 below.- I'm guessing because
setCustomer:
has side effects, but because they're just references to the same object, I don't think that change actually changes the behavior?
- I'm guessing because
I see that this is a private property, and I think there're only three places that call the setter and none of them use the customer afterwords, so it's fine as-is (although maybe a well-placed & well-worded comment could help?).
I think it might be easier to understand & maintain if this was (duplicated and) moved up a level in the call stack to those places that receive STPCustomer
objects from STPAPI
. In each of them it's more clear that the STPCustomerContext
completely owns the object & mutating it is fine, as well as not having (potentially) surprising behavior "hidden" in the setter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah that's good feedback, putting it in the setter was kinda lazy. I'll move it up in the stack.
Stripe/STPCustomer.m
Outdated
NSMutableArray *sources = [NSMutableArray new]; | ||
for (id contents in data) { | ||
if ([contents isKindOfClass:[NSDictionary class]]) { | ||
if ([contents[@"object"] isEqualToString:@"card"]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recognize this is just the old code, but this line makes an assumption about the type of contents[@"object]"
by using an NSString
-only method (isEqualToString:
), where the parsing code so far has been diligently checking types.
Maybe -> isEqual:@"card"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
side note: reading this method makes me want as?
, flatMap
, filter
& find
:)
Stripe/STPCustomer.m
Outdated
if (![data isKindOfClass:[NSArray class]]) { | ||
return; | ||
} | ||
NSString *defaultSourceId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should probably reset self.defaultSource = nil
.
ex: calling with filterApplePay = NO
and then with YES
, for a customer whose default source is ApplePay, will still have the apple pay defaultSource
on the customer object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah yep, good catch!
Stripe/STPCustomer+Private.h
Outdated
@interface STPCustomer () | ||
|
||
/** | ||
Updates the customer's sources and defaultSource with a Customer API response. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it's worth changing this from "Updates" to "Replaces", since you're getting all new objects?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
updated, re-r? @danj-stripe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those changes look great!
I also like the updates to property attributes, but I think they've highlighted an unhandled nullability corner case.
data = response[@"sources"][@"data"]; | ||
} | ||
if (![data isKindOfClass:[NSArray class]]) { | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When called from decodedObjectFromAPIResponse:
, this will return a customer with sources = nil
(if sources.data
is not an NSArray
), but the property declaration for sources
is nonnull
I wasn't (and still am not) sure how we want to handle a malformed response
. I like that we're not crashing! :)
I also thought I liked the idea that you're not resetting sources
and defaultSources
if this is not the initialization pass. However, this is private, and it looks like you're always just using the cached response, so maybe it'd be easier to just reset to empty array & nil?
Another option would be to set sources to the empty array in decodedObjectFromAPIResponse:
before calling this method. I think I like this option slightly better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah good catch, I agree that the second option is better – I'll update. Working on some related guards against malformed responses in my next PR :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated in ffb5ff6
ef387e5
to
ffb5ff6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sweet!
Looks like a copy/paste error from f3b2d72 in #864. `STPCustomerContext` holds onto a `STPCustomer`. There are three places where it retrieves a new `STPCustomer` - and when it does it needs to update the `sources` of that customer based on the `STPCustomerContext.includeApplePaySources` boolean. The bug is that we were using the *old* `STPCustomer.allResponseFields` (via `self.customer`) instead of the `allResponseFields` property on the newly retrieved `STPCustomer` object. In isolation, it's a pretty obvious bug (once you know to look for it). There's no reason we'd want to retrieve a Customer, and then use the response data for the previous object to update the `sources`/`defaultSource` properties of the new one. There is a fourth place that updates the sources, and that's inside `setIncludeApplePaySources:`. In that case, we're updating the existing customer, and using the existing customer's `allResponseFields`, so `self.customer.allResponseFields` is correct there.
r? @danj-stripe
Addresses #842 by adding a new
includeApplePaySources
property toCustomerContext