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

Workaround for SafariVC reporting failure while redirecting to iDEAL #937

Merged
merged 4 commits into from
May 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 13.0.1 2018-05-17
* Fixes bug in `STPRedirectContext` that'd close the `SFSafariViewController` during the initial redirects, but only in livemode. [#937](https://github.com/stripe/stripe-ios/pull/937)

## 13.0.0 2018-04-26
* Removes Bitcoin source support. See MIGRATING.md. [#931](https://github.com/stripe/stripe-ios/pull/931)
* Adds Masterpass support to `STPSourceParams` [#928](https://github.com/stripe/stripe-ios/pull/928)
Expand Down
52 changes: 28 additions & 24 deletions Example/Custom Integration (ObjC)/SofortExampleViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -100,31 +100,35 @@ - (void)pay {
// your app delegate to forward URLs to the Stripe SDK.
// See `[Stripe handleStripeURLCallback:]`
self.redirectContext = [[STPRedirectContext alloc] initWithSource:source completion:^(NSString *sourceID, NSString *clientSecret, NSError *error) {
[[STPAPIClient sharedClient] startPollingSourceWithId:sourceID
clientSecret:clientSecret
timeout:10
completion:^(STPSource *source, NSError *error) {
[self updateUIForPaymentInProgress:NO];
if (error) {
[self.delegate exampleViewController:self didFinishWithError:error];
} else {
switch (source.status) {
case STPSourceStatusChargeable:
case STPSourceStatusConsumed:
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"];
break;
case STPSourceStatusCanceled:
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment failed"];
break;
case STPSourceStatusPending:
case STPSourceStatusFailed:
case STPSourceStatusUnknown:
[self.delegate exampleViewController:self didFinishWithMessage:@"Order received"];
break;
if (error) {
[self.delegate exampleViewController:self didFinishWithError:error];
} else {
[[STPAPIClient sharedClient] startPollingSourceWithId:sourceID
clientSecret:clientSecret
timeout:10
completion:^(STPSource *source, NSError *error) {
[self updateUIForPaymentInProgress:NO];
if (error) {
[self.delegate exampleViewController:self didFinishWithError:error];
} else {
switch (source.status) {
case STPSourceStatusChargeable:
case STPSourceStatusConsumed:
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"];
break;
case STPSourceStatusCanceled:
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment failed"];
break;
case STPSourceStatusPending:
case STPSourceStatusFailed:
case STPSourceStatusUnknown:
[self.delegate exampleViewController:self didFinishWithMessage:@"Order received"];
break;
}
}
}
self.redirectContext = nil;
}];
self.redirectContext = nil;
}];
}
}];
[self.redirectContext startRedirectFlowFromViewController:self];
}
Expand Down
52 changes: 28 additions & 24 deletions Example/Custom Integration (ObjC)/ThreeDSExampleViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -129,31 +129,35 @@ - (void)pay {
// your app delegate to forwards URLs to the Stripe SDK.
// See `[Stripe handleStripeURLCallback:]`
self.redirectContext = [[STPRedirectContext alloc] initWithSource:source completion:^(NSString *sourceID, NSString *clientSecret, NSError *error) {
[[STPAPIClient sharedClient] startPollingSourceWithId:sourceID
clientSecret:clientSecret
timeout:10
completion:^(STPSource *source, NSError *error) {
[self updateUIForPaymentInProgress:NO];
if (error) {
[self.delegate exampleViewController:self didFinishWithError:error];
} else {
switch (source.status) {
case STPSourceStatusChargeable:
case STPSourceStatusConsumed:
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"];
break;
case STPSourceStatusCanceled:
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment failed"];
break;
case STPSourceStatusPending:
case STPSourceStatusFailed:
case STPSourceStatusUnknown:
[self.delegate exampleViewController:self didFinishWithMessage:@"Order received"];
break;
if (error) {
[self.delegate exampleViewController:self didFinishWithError:error];
} else {
[[STPAPIClient sharedClient] startPollingSourceWithId:sourceID
clientSecret:clientSecret
timeout:10
completion:^(STPSource *source, NSError *error) {
[self updateUIForPaymentInProgress:NO];
if (error) {
[self.delegate exampleViewController:self didFinishWithError:error];
} else {
switch (source.status) {
case STPSourceStatusChargeable:
case STPSourceStatusConsumed:
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"];
break;
case STPSourceStatusCanceled:
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment failed"];
break;
case STPSourceStatusPending:
case STPSourceStatusFailed:
case STPSourceStatusUnknown:
[self.delegate exampleViewController:self didFinishWithMessage:@"Order received"];
break;
}
}
}
self.redirectContext = nil;
}];
self.redirectContext = nil;
}];
}
}];
[self.redirectContext startRedirectFlowFromViewController:self];
}
Expand Down
38 changes: 33 additions & 5 deletions Stripe/STPRedirectContext.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ @interface STPRedirectContext () <SFSafariViewControllerDelegate, STPURLCallback
@property (nonatomic, strong) STPSource *source;
@property (nonatomic, strong, nullable) SFSafariViewController *safariVC;
@property (nonatomic, assign, readwrite) STPRedirectContextState state;
/// If we're on iOS 11+ and in the SafariVC flow, this tracks the latest URL loaded/redirected to during the initial load
@property (nonatomic, strong, readwrite, nullable) NSURL *lastKnownSafariVCUrl;

@property (nonatomic, assign) BOOL subscribedToURLNotifications;
@property (nonatomic, assign) BOOL subscribedToForegroundNotifications;
Expand Down Expand Up @@ -121,7 +123,8 @@ - (void)startSafariViewControllerRedirectFlowFromViewController:(UIViewControlle
if (self.state == STPRedirectContextStateNotStarted) {
_state = STPRedirectContextStateInProgress;
[self subscribeToUrlNotifications];
self.safariVC = [[SFSafariViewController alloc] initWithURL:self.source.redirect.url];
self.lastKnownSafariVCUrl = self.source.redirect.url;
self.safariVC = [[SFSafariViewController alloc] initWithURL:self.lastKnownSafariVCUrl];
self.safariVC.delegate = self;
[presentingViewController presentViewController:self.safariVC
animated:YES
Expand Down Expand Up @@ -154,14 +157,39 @@ - (void)safariViewControllerDidFinish:(__unused SFSafariViewController *)control
}

- (void)safariViewController:(__unused SFSafariViewController *)controller didCompleteInitialLoad:(BOOL)didLoadSuccessfully {
/*
SafariVC is, imo, over-eager to report errors. The way that (for example) girogate.de redirects
can cause SafariVC to report that the initial load failed, even though it completes successfully.

So, only report failures to complete the initial load if the host was a Stripe domain.
Stripe uses 302 redirects, and this should catch local connection problems as well as
server-side failures from Stripe.
*/
if (didLoadSuccessfully == NO) {
stpDispatchToMainThreadIfNecessary(^{
[self handleRedirectCompletionWithError:[NSError stp_genericConnectionError]
shouldDismissViewController:YES];
});
if (@available(iOS 11, *)) {
stpDispatchToMainThreadIfNecessary(^{
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't do anything here on iOS < 11?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If there was some way to distinguish between real failures and spurious ones, I'd love to. I wasn't able to find one though.

Copy link
Contributor

Choose a reason for hiding this comment

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

I had a similar initial reaction mostly by the way it's written. Maybe it needs an else clause with a doc:

stpDispatchToMainThreadIfNecessary(^{
  if (didLoadSuccessfully == NO) {
    if (@available(iOS 11, *) && [self.latestSafariVCUrl.host containsString:@"stripe.com"]) {
    // Report load errors that exist on `stripe.com` website
    ...
    } else {
      // Do nothing because `latestSafariVCUrl` is not accurate `< iOS 11`
    }
  }
}

In any case, this looks functional and don't have strong opinion on this one.

if ([self.lastKnownSafariVCUrl.host containsString:@"stripe.com"]) {
[self handleRedirectCompletionWithError:[NSError stp_genericConnectionError]
shouldDismissViewController:YES];
}
});
} else {
/*
We can only track the latest URL loaded on iOS 11, because `safariViewController:initialLoadDidRedirectToURL:`
didn't exist prior to that. This might be a spurious error, so we need to ignore it.
*/
}
}
}

- (void)safariViewController:(__unused SFSafariViewController *)controller initialLoadDidRedirectToURL:(NSURL *)URL {
stpDispatchToMainThreadIfNecessary(^{
// This is only kept up to date during the "initial load", but we only need the value in
// `safariViewController:didCompleteInitialLoad:`, so that's fine.
self.lastKnownSafariVCUrl = URL;
});
}

#pragma mark - Private methods -

- (void)handleWillForegroundNotification {
Expand Down