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

add includeApplePaySources to CustomerContext #864

Merged
merged 5 commits into from
Dec 19, 2017

Conversation

bg-stripe
Copy link
Contributor

@bg-stripe bg-stripe commented Dec 18, 2017

r? @danj-stripe

Addresses #842 by adding a new includeApplePaySources property to CustomerContext

Copy link
Contributor

@danj-stripe danj-stripe left a 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
Copy link
Contributor

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)

Copy link
Contributor Author

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.

expectedCount:2];
id mockKeyManager = [self mockKeyManagerWithKey:customerKey];
STPCustomerContext *sut = [[STPCustomerContext alloc] initWithKeyManager:mockKeyManager];
[sut clearCachedCustomer];
Copy link
Contributor

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?

Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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

id mockKeyManager = [self mockKeyManagerWithKey:customerKey];
STPCustomerContext *sut = [[STPCustomerContext alloc] initWithKeyManager:mockKeyManager];
sut.includeApplePaySources = YES;
[sut clearCachedCustomer];
Copy link
Contributor

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?

Copy link
Contributor

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?

Copy link
Contributor Author

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.

@@ -48,6 +49,10 @@ - (void)clearCachedCustomer {
}

- (void)setCustomer:(STPCustomer *)customer {
if (self.includeApplePaySources) {
[customer updateSourcesWithResponse:customer.allResponseFields
Copy link
Contributor

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 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.

Copy link
Contributor Author

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.

NSMutableArray *sources = [NSMutableArray new];
for (id contents in data) {
if ([contents isKindOfClass:[NSDictionary class]]) {
if ([contents[@"object"] isEqualToString:@"card"]) {
Copy link
Contributor

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"

Copy link
Contributor

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 :)

if (![data isKindOfClass:[NSArray class]]) {
return;
}
NSString *defaultSourceId;
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 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah yep, good catch!

@interface STPCustomer ()

/**
Updates the customer's sources and defaultSource with a Customer API response.
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

@stripe-ci stripe-ci assigned bg-stripe and unassigned danj-stripe Dec 18, 2017
@bg-stripe bg-stripe assigned danj-stripe and unassigned bg-stripe Dec 18, 2017
@bg-stripe
Copy link
Contributor Author

updated, re-r? @danj-stripe

Copy link
Contributor

@danj-stripe danj-stripe left a 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;
Copy link
Contributor

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.

Copy link
Contributor Author

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 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated in ffb5ff6

@danj-stripe danj-stripe assigned bg-stripe and unassigned danj-stripe Dec 18, 2017
@bg-stripe bg-stripe force-pushed the bg-applepay-filtering branch from ef387e5 to ffb5ff6 Compare December 18, 2017 23:33
@bg-stripe bg-stripe assigned danj-stripe and unassigned bg-stripe Dec 18, 2017
Copy link
Contributor

@danj-stripe danj-stripe left a comment

Choose a reason for hiding this comment

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

Sweet!

@stripe-ci stripe-ci assigned bg-stripe and unassigned danj-stripe Dec 18, 2017
@bg-stripe bg-stripe merged commit 7046189 into master Dec 19, 2017
@bg-stripe bg-stripe deleted the bg-applepay-filtering branch December 19, 2017 17:07
danj-stripe added a commit that referenced this pull request Sep 5, 2018
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.
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.

3 participants