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 STPPaymentIntent, and method to retrieve from the API given a clientSecret #985

Merged
merged 8 commits into from
Jul 10, 2018

Conversation

danj-stripe
Copy link
Contributor

Summary

Adding support for /v1/payment_intent/:id

Motivation

IOS-792

Testing

Added some tests. This is the first in a series of PRs that'll add full PaymentIntent
support, and this was tested in the sample app too.

@danj-stripe
Copy link
Contributor Author

Not ready to merge yet, still needs implementation of STPPaymentIntent description, and better tests for decoding from JSON response (see FIXMEs). However, it is ready for early reviewing.

…entSecret

Still needs implementation of `STPPaymentIntent description`, and better tests for
decoding from JSON response
@danj-stripe danj-stripe force-pushed the danj/feature/retrieve-paymentintent branch from dc9c943 to 1ed8e33 Compare July 6, 2018 02:03
I'm omitting our normal enum to string helper methods, and instead reaching directly
into the `allResponseFields` dictionary. I think this is slightly preferable, because
`Unknown` enum values will be represented as the actual string value instead, which may
help with debugging.

Also logging the `shipping` and `nextSourceAction` fields, even though those aren't
exposed by the class yet.
Adding a canceled_at time to the JSON, otherwise the test that the allResponseFields
matches the json object fails - because we strip null values out.
@danj-stripe
Copy link
Contributor Author

This is ready for review now. The PaymentIntent.shipping field support has been deferred from the initial release, see IOS-811.

@interface STPAPIClient (PaymentIntents)

/**
Retrieves the PaymentIntent object using the given secret. @see https://stripe.com/docs/api#retrieve_payment_intent
Copy link
Contributor

Choose a reason for hiding this comment

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

This link isn't working for me. Is it going to be soon?

Copy link
Contributor

Choose a reason for hiding this comment

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

found it :)

@param secret The client secret of the payment intent to be retrieved. Cannot be nil.
@param completion The callback to run with the returned PaymentIntent object, or an error.
*/
- (void)retrievePaymentIntentWithClientSecret:(NSString *)secret
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 wondering if there's less ambiguous vocabulary we can use so it's not confused with Stripe client secret? Or is it?

Copy link
Contributor

Choose a reason for hiding this comment

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

looks like the rest of our documentation uses this ambiguous vocabulary so nevermind :)

Copy link
Contributor

@joeydong-stripe joeydong-stripe Jul 9, 2018

Choose a reason for hiding this comment

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

if we're confident it's not going to change, maybe we can mention it starts with pi_* and assert for that fact before putting it on the network

Copy link
Contributor Author

Choose a reason for hiding this comment

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

client_secret is a secret for working with a specific PaymentIntent or Source, and is different from the publishable key (or the secret key that should never be part of the SDK). (I think you figured this out, but commenting for posterity)

I'm still not sure what the right level of client-side protection is, vs encoding specifics about the current state of things.

NSNumber *amount = [dict stp_numberForKey:@"amount"];
NSString *currency = [dict stp_stringForKey:@"currency"];
NSString *rawStatus = [dict stp_stringForKey:@"status"];
if (!stripeId || !clientSecret || amount == nil || !currency || !rawStatus || !dict[@"livemode"]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

can you do !amount here too to be consistent? now I'm questioning the behavior for NSNumber

Copy link
Contributor

Choose a reason for hiding this comment

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

also do we need to fail if rawStatus or livemode are missing since we have STPPaymentIntentStatusUnknown or the default livemode = YES?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

!amount triggers a Faux Pas warning, protecting us against accidentally checking nil/not-nil instead of YES/NO.

I believe we do want to enforce these being required.

STPPaymentIntentStatusSucceeded,

/**
Indicates the payment must be captured, for STPPaymentIntentCaptureMethodManual
Copy link
Contributor

Choose a reason for hiding this comment

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

"Indicates the payment must be captured. The captureMethod is also set to STPPaymentIntentCaptureMethodManual."?

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess what you have is fine, I'm just wondering what's the intention behind having a separate captureMethod field and if we can communicate that reason.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

captureMethod might be interesting to the app's business logic even when the status is not .requiresCapture, but yeah, I'm not sure how this is most likely to be used. Primarily just exposing what exists in the API.

completion:(STPPaymentIntentCompletionBlock)completion {
NSCAssert(secret != nil, @"'secret' is required to retrieve a PaymentIntent");
NSCAssert(completion != nil, @"'completion' is required to use the PaymentIntent that is retrieved");
NSString *identifier = [STPPaymentIntent idFromClientSecret:secret];
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to check that identifier is not nil 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.

I'm not sure.

I didn't want to NSCAssert, because the most likely way this'd be nil is a change on the Stripe server to the client_secret format, and that'd be a really bad experience if someone shipped an app with asserts enabled.

Similarly, we don't have a pattern (that I found) for early exit, instead we seem to pass (potentially) bad data to the server and allow it to generate an appropriate error. So I think consistency is my preferred choice.

The other alternative that comes to mind is constructing a STPInvalidRequestError error here, and populating the user info with useful values.

/**
A callback to be run with a PaymentIntent response from the Stripe API.

@param paymentIntent The Stripe PaymentIntent from the response. Will be nil if an error occurs. @see STPPaymentIntent
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need to specifically say STPPaymentIntent.h here like you did for StripeError.h? I'm just imagining this from a very unaware perspective.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IDK? I was following the pattern of every other callback here.

I think this is reasonable, because it's saying "see STPPaymentIntent" (the class), vs "see the values in StripeError.h" (the header with a bunch of values).

#pragma mark Payment Intents

/**
Stripe extensions for working with PaymentIntent objects.
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like the docs for the other extension interface definitions in this file say "STPAPIClient extensions..."

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's actually a mix of extensions on STPAPIClient and on Stripe, which I didn't realize at first. Great catch, thanks!

A callback to be run with a PaymentIntent response from the Stripe API.

@param paymentIntent The Stripe PaymentIntent from the response. Will be nil if an error occurs. @see STPPaymentIntent
@param error The error returned from the response, or nil in one occurs. @see StripeError.h for possible values.
Copy link
Contributor

@joeydong-stripe joeydong-stripe Jul 9, 2018

Choose a reason for hiding this comment

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

if* none* occurs

looks like similar typos elsewhere in this file

@property (nonatomic, readonly) NSString *clientSecret;

/**
The amount associated with the PaymentIntent
Copy link
Contributor

Choose a reason for hiding this comment

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

Our doc has a better sounding description to me: "Amount intended to be collected by this PaymentIntent."

@property (nonatomic, readonly) NSNumber *amount;

/**
If status is canceled, when the PaymentIntent was canceled.
Copy link
Contributor

@joeydong-stripe joeydong-stripe Jul 9, 2018

Choose a reason for hiding this comment

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

"If status is STPPaymentIntentStatusCanceled, when the PaymentIntent was canceled."?

@property (nonatomic, nullable, readonly) NSDate *created;

/**
The currency associated with the PaymentIntent.
Copy link
Contributor

Choose a reason for hiding this comment

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

Our docs nicely mention it's the ISO currency code, I wonder if we mention that elsewhere we reference currency:

"Three-letter ISO currency code, in lowercase. Must be a supported currency."

https://www.iso.org/iso-4217-currency-codes.html
https://stripe.com/docs/currencies

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It looks mixed. Most headers that have a currency don't provide more details, but a couple do. Similarly, we don't explain amount being the smallest currency unit in most places that have an amount.

I think it's reasonable to elide this

XCTAssertNotNil(paymentIntent);
NSString *desc = paymentIntent.description;
XCTAssertTrue([desc containsString:NSStringFromClass([paymentIntent class])]);
XCTAssertGreaterThan(desc.length, 500UL, @"Custom description should be long");
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

@joeydong-stripe joeydong-stripe left a comment

Choose a reason for hiding this comment

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

👍

@danj-stripe danj-stripe merged commit 73a6f75 into master Jul 10, 2018
@danj-stripe danj-stripe deleted the danj/feature/retrieve-paymentintent branch August 3, 2018 20:34
csabol-stripe pushed a commit that referenced this pull request Apr 26, 2022
* Delete invalid cookies

* Add test
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.

4 participants