Skip to content

Commit

Permalink
ApplePay support in STPPaymentHandler, STPPaymentContext (#1264)
Browse files Browse the repository at this point in the history
* Add defaultPaymentMethod docstring

* Improve STPPaymentMethodCardParams docstring

* Add authenticationWillPresent: to STPAuthenticationContext to support Apple Pay

* Conform STPPaymentContext to STPAuthenticationContext, implement authenticationWillPresent to dismiss apple pay, handle the dismissal case not calling paymentAuthorizationViewControllerDidFinish:

* Update Standard Integration example app

* Update ApplePayExampleViewController

* Make sure hostViewController is non-nil before attempting Apple Pay in STPPaymentContext

* Fix fauxpas, jazzy issue

* PR feedback

* PR feedback
  • Loading branch information
yuki-stripe authored Jul 31, 2019
1 parent b244fab commit 43f1510
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 99 deletions.
160 changes: 109 additions & 51 deletions Example/Custom Integration/ApplePayExampleViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@
that address. After the user submits their information, we create a token using the authorized PKPayment,
and then send it to our backend to create the charge request.
*/
@interface ApplePayExampleViewController () <PKPaymentAuthorizationViewControllerDelegate>
@interface ApplePayExampleViewController () <PKPaymentAuthorizationViewControllerDelegate, STPAuthenticationContext>
@property (nonatomic) ShippingManager *shippingManager;
@property (nonatomic, weak) UIButton *payButton;
@property (nonatomic) BOOL applePaySucceeded;
@property (nonatomic) NSError *applePayError;
@property (nonatomic) PKPaymentAuthorizationViewController *applePayVC;
@end

@implementation ApplePayExampleViewController
Expand Down Expand Up @@ -79,10 +80,11 @@ - (void)pay {

PKPaymentRequest *paymentRequest = [self buildPaymentRequest];
if ([Stripe canSubmitPaymentRequest:paymentRequest]) {
PKPaymentAuthorizationViewController *auth = [[PKPaymentAuthorizationViewController alloc] initWithPaymentRequest:paymentRequest];
auth.delegate = self;
if (auth) {
[self presentViewController:auth animated:YES completion:nil];
self.applePayVC = [[PKPaymentAuthorizationViewController alloc] initWithPaymentRequest:paymentRequest];
self.applePayVC.delegate = self;

if (self.applePayVC) {
[self presentViewController:self.applePayVC animated:YES completion:nil];
} else {
NSLog(@"Apple Pay returned a nil PKPaymentAuthorizationViewController - make sure you've configured Apple Pay correctly, as outlined at https://stripe.com/docs/mobile/apple-pay");
}
Expand Down Expand Up @@ -118,66 +120,122 @@ - (void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController
- (void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller
didAuthorizePayment:(PKPayment *)payment
completion:(void (^)(PKPaymentAuthorizationStatus))completion {
[[STPAPIClient sharedClient] createTokenWithPayment:payment
completion:^(STPToken *token, NSError *error) {
if (error) {
self.applePayError = error;
completion(PKPaymentAuthorizationStatusFailure);
} else {
// We could also send the token.stripeID to our backend to create
// a payment method and subsequent payment intent
[self _createPaymentMethodForApplePayToken:token completion:completion];
}
}];
}

- (void)_createPaymentMethodForApplePayToken:(STPToken *)token completion:(void (^)(PKPaymentAuthorizationStatus))completion {
STPPaymentMethodCardParams *applePayParams = [[STPPaymentMethodCardParams alloc] init];
applePayParams.token = token.stripeID;
STPPaymentMethodParams *paymentMethodParams = [STPPaymentMethodParams paramsWithCard:applePayParams
billingDetails:nil
metadata:nil];

[[STPAPIClient sharedClient] createPaymentMethodWithParams:paymentMethodParams
completion:^(STPPaymentMethod * _Nullable paymentMethod, NSError * _Nullable error) {
if (error) {
self.applePayError = error;
completion(PKPaymentAuthorizationStatusFailure);
} else {
[self _createAndConfirmPaymentIntentWithPaymentMethod:paymentMethod
completion:completion];
}
}];
[[STPAPIClient sharedClient] createPaymentMethodWithPayment:payment completion:^(STPPaymentMethod *paymentMethod, NSError *error) {
if (error) {
self.applePayError = error;
completion(PKPaymentAuthorizationStatusFailure);
} else {
// We could also send the token.stripeID to our backend to create
// a payment method and subsequent payment intent
[self _createAndConfirmPaymentIntentWithPaymentMethod:paymentMethod
completion:completion];
}
}];
}

- (void)_createAndConfirmPaymentIntentWithPaymentMethod:(STPPaymentMethod *)paymentMethod completion:(void (^)(PKPaymentAuthorizationStatus))completion {
void (^finishWithStatus)(PKPaymentAuthorizationStatus) = ^(PKPaymentAuthorizationStatus status) {
if (self.applePayVC) {
completion(status);
} else {
[self _finish];
}
};
void (^reconfirmPaymentIntent)(STPPaymentIntent *) = ^(STPPaymentIntent *paymentIntent) {
[self.delegate confirmPaymentIntent:paymentIntent completion:^(STPBackendResult status, NSString *clientSecret, NSError *error) {
if (status == STPBackendResultFailure || error) {
self.applePayError = error;
finishWithStatus(PKPaymentAuthorizationStatusFailure);
return;
}
[[STPAPIClient sharedClient] retrievePaymentIntentWithClientSecret:clientSecret completion:^(STPPaymentIntent *finalPaymentIntent, NSError *finalError) {
if (finalError) {
self.applePayError = finalError;
finishWithStatus(PKPaymentAuthorizationStatusFailure);
return;
}
if (finalPaymentIntent.status == STPPaymentIntentStatusSucceeded || finalPaymentIntent.status == STPPaymentIntentStatusRequiresCapture) {
self.applePaySucceeded = YES;
finishWithStatus(PKPaymentAuthorizationStatusSuccess);
} else {
finishWithStatus(PKPaymentAuthorizationStatusFailure);
}
}];
}];
};
STPPaymentHandlerActionPaymentIntentCompletionBlock paymentHandlerCompletion = ^(STPPaymentHandlerActionStatus handlerStatus, STPPaymentIntent * _Nullable paymentIntent, NSError * _Nullable handlerError) {
switch (handlerStatus) {
case STPPaymentHandlerActionStatusFailed:
self.applePayError = handlerError;
finishWithStatus(PKPaymentAuthorizationStatusFailure);
break;
case STPPaymentHandlerActionStatusCanceled:
self.applePayError = [NSError errorWithDomain:StripeDomain code:123 userInfo:@{NSLocalizedDescriptionKey: @"User cancelled"}];
finishWithStatus(PKPaymentAuthorizationStatusFailure);
break;
case STPPaymentHandlerActionStatusSucceeded:
if (paymentIntent.status == STPPaymentIntentStatusRequiresConfirmation) {
// Manually confirm the PaymentIntent on the backend again to complete the payment.
reconfirmPaymentIntent(paymentIntent);
break;
} else {
finishWithStatus(PKPaymentAuthorizationStatusSuccess);
}
}
};
STPPaymentIntentCreateAndConfirmHandler createAndConfirmCompletion = ^(STPBackendResult status, NSString *clientSecret, NSError *error) {
if (status == STPBackendResultFailure || error) {
self.applePayError = error;
completion(PKPaymentAuthorizationStatusFailure);
return;
}
[[STPPaymentHandler sharedHandler] handleNextActionForPayment:clientSecret
withAuthenticationContext:self
returnURL:@"payments-example://stripe-redirect"
completion:paymentHandlerCompletion];
};

[self.delegate createAndConfirmPaymentIntentWithAmount:@(1000)
paymentMethod:paymentMethod.stripeId
returnURL:@"payments-example://stripe-redirect"
completion:^(STPBackendResult status, NSString *clientSecret, NSError *error) {
if (error) {
self.applePayError = error;
completion(PKPaymentAuthorizationStatusFailure);
} else {
self.applePaySucceeded = YES;
completion(PKPaymentAuthorizationStatusSuccess);
}
}];
completion:createAndConfirmCompletion];
}

- (void)paymentAuthorizationViewControllerDidFinish:(PKPaymentAuthorizationViewController *)controller {
dispatch_async(dispatch_get_main_queue(), ^{
// This only gets called if you call the PKPaymentAuthorizationStatus completion block before dismissing PKPaymentAuthorizationViewController
[self dismissViewControllerAnimated:YES completion:^{
if (self.applePaySucceeded) {
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"];
} else if (self.applePayError) {
[self.delegate exampleViewController:self didFinishWithError:self.applePayError];
}
self.applePaySucceeded = NO;
self.applePayError = nil;
[self _finish];
}];
});
}

- (void)_finish {
if (self.applePaySucceeded) {
[self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"];
} else if (self.applePayError) {
[self.delegate exampleViewController:self didFinishWithError:self.applePayError];
}
self.applePaySucceeded = NO;
self.applePayError = nil;
self.applePayVC = nil;
}

#pragma mark - STPAuthenticationContext

- (UIViewController *)authenticationPresentingViewController {
return self;
}

- (void)prepareAuthenticationContextForPresentation:(STPVoidBlock)completion {
if (self.applePayVC.presentingViewController != nil) {
[self dismissViewControllerAnimated:YES completion:^{
self.applePayVC = nil;
completion();
}];
} else {
completion();
}
}

@end
9 changes: 2 additions & 7 deletions Example/Standard Integration/CheckoutViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import UIKit
import Stripe

class CheckoutViewController: UIViewController, STPPaymentContextDelegate, STPAuthenticationContext {
class CheckoutViewController: UIViewController, STPPaymentContextDelegate {

// 1) To get started with this demo, first head to https://dashboard.stripe.com/account/apikeys
// and copy your "Test Publishable Key" (it looks like pk_test_abcdef) into the line below.
Expand Down Expand Up @@ -231,11 +231,6 @@ See https://stripe.com/docs/testing.
self.paymentContext.requestPayment()
}

// MARK: STPAuthenticationContext
func authenticationPresentingViewController() -> UIViewController {
return self
}

// MARK: STPPaymentContextDelegate

func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPErrorBlock) {
Expand All @@ -248,7 +243,7 @@ See https://stripe.com/docs/testing.
completion(error ?? NSError(domain: StripeDomain, code: 123, userInfo: [NSLocalizedDescriptionKey: "Unable to parse clientSecret from response"]))
return
}
STPPaymentHandler.shared().handleNextAction(forPayment: clientSecret, with: self, returnURL: "payments-example://stripe-redirect") { (status, handledPaymentIntent, actionError) in
STPPaymentHandler.shared().handleNextAction(forPayment: clientSecret, with: paymentContext, returnURL: "payments-example://stripe-redirect") { (status, handledPaymentIntent, actionError) in
switch (status) {
case .succeeded:
guard let handledPaymentIntent = handledPaymentIntent else {
Expand Down
14 changes: 13 additions & 1 deletion Stripe/PKPaymentAuthorizationViewController+Stripe_Blocks.m
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ @interface STPBlockBasedApplePayDelegate : NSObject <PKPaymentAuthorizationViewC

@implementation STPBlockBasedApplePayDelegate

- (void)paymentAuthorizationViewController:(__unused PKPaymentAuthorizationViewController *)controller
- (void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller
didAuthorizePayment:(PKPayment *)payment completion:(STPPaymentAuthorizationStatusCallback)completion {
self.onPaymentAuthorization(payment);

Expand All @@ -48,10 +48,18 @@ - (void)paymentAuthorizationViewController:(__unused PKPaymentAuthorizationViewC
if (error) {
self.lastError = error;
completion(PKPaymentAuthorizationStatusFailure);
if (controller.presentingViewController == nil) {
// If we call completion() after dismissing, didFinishWithStatus is NOT called.
[self _finish];
}
return;
}
self.didSucceed = YES;
completion(PKPaymentAuthorizationStatusSuccess);
if (controller.presentingViewController == nil) {
// If we call completion() after dismissing, didFinishWithStatus is NOT called.
[self _finish];
}
});
};
[self.apiClient createPaymentMethodWithPayment:payment completion:paymentMethodCreateCompletion];
Expand Down Expand Up @@ -80,6 +88,10 @@ - (void)paymentAuthorizationViewController:(__unused PKPaymentAuthorizationViewC
}

- (void)paymentAuthorizationViewControllerDidFinish:(__unused PKPaymentAuthorizationViewController *)controller {
[self _finish];
}

- (void)_finish {
if (self.didSucceed) {
self.onFinish(STPPaymentStatusSuccess, nil);
}
Expand Down
Loading

0 comments on commit 43f1510

Please sign in to comment.