diff --git a/Example/Custom Integration.xcodeproj/project.pbxproj b/Example/Custom Integration.xcodeproj/project.pbxproj index f73b45d303d..0595bd94a50 100644 --- a/Example/Custom Integration.xcodeproj/project.pbxproj +++ b/Example/Custom Integration.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 36D4EA6422DFEF1300619BA8 /* CardSetupIntentBackendExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 36D4EA6322DFEF1300619BA8 /* CardSetupIntentBackendExampleViewController.m */; }; 8BBD79C6207FD2F900F85BED /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8BBD79C8207FD2F900F85BED /* Localizable.strings */; }; B3BDCADD20EF03010034F7F5 /* CardAutomaticConfirmationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B3BDCADB20EF03010034F7F5 /* CardAutomaticConfirmationViewController.m */; }; + B607FFBD2321DA99004203E0 /* MyAPIClient.m in Sources */ = {isa = PBXBuildFile; fileRef = B607FFBC2321DA99004203E0 /* MyAPIClient.m */; }; B65E8FCC22FA078A0057E64A /* WeChatPayExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B65E8FCB22FA078A0057E64A /* WeChatPayExampleViewController.m */; }; B66AC61E22CAAB8F0064C551 /* CardSetupIntentExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B66AC61D22CAAB8F0064C551 /* CardSetupIntentExampleViewController.m */; }; C12C50DD1E57B3C800EC6D58 /* BrowseExamplesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C12C50DC1E57B3C800EC6D58 /* BrowseExamplesViewController.m */; }; @@ -59,7 +60,7 @@ 04533F181A688A0A00C7E52E /* Constants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Constants.m; sourceTree = ""; }; 31A8934B230F6ABD007ABE37 /* FPXExampleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FPXExampleViewController.h; sourceTree = ""; }; 31A8934C230F6ABD007ABE37 /* FPXExampleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FPXExampleViewController.m; sourceTree = ""; }; - 366F93AF225FF2A2005CFBF6 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = "Standard Integration/README.md"; sourceTree = ""; }; + 366F93AF225FF2A2005CFBF6 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = "Custom Integration/README.md"; sourceTree = ""; }; 36D4EA6222DFEF1300619BA8 /* CardSetupIntentBackendExampleViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CardSetupIntentBackendExampleViewController.h; sourceTree = ""; }; 36D4EA6322DFEF1300619BA8 /* CardSetupIntentBackendExampleViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CardSetupIntentBackendExampleViewController.m; sourceTree = ""; }; 8BBD79C7207FD2F900F85BED /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; @@ -74,6 +75,8 @@ 8BBD79D1207FD35200F85BED /* es */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; B3BDCADB20EF03010034F7F5 /* CardAutomaticConfirmationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CardAutomaticConfirmationViewController.m; sourceTree = ""; }; B3BDCADC20EF03010034F7F5 /* CardAutomaticConfirmationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CardAutomaticConfirmationViewController.h; sourceTree = ""; }; + B607FFBB2321DA99004203E0 /* MyAPIClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MyAPIClient.h; sourceTree = ""; }; + B607FFBC2321DA99004203E0 /* MyAPIClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MyAPIClient.m; sourceTree = ""; }; B65E8FCA22FA078A0057E64A /* WeChatPayExampleViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WeChatPayExampleViewController.h; sourceTree = ""; }; B65E8FCB22FA078A0057E64A /* WeChatPayExampleViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WeChatPayExampleViewController.m; sourceTree = ""; }; B66AC61C22CAAB8F0064C551 /* CardSetupIntentExampleViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CardSetupIntentExampleViewController.h; sourceTree = ""; }; @@ -105,10 +108,10 @@ 04533E7E1A687F5D00C7E52E = { isa = PBXGroup; children = ( - 366F93AF225FF2A2005CFBF6 /* README.md */, 04533E891A687F5D00C7E52E /* Custom Integration */, 04533F0E1A68813100C7E52E /* Frameworks */, 04533E881A687F5D00C7E52E /* Products */, + 366F93AF225FF2A2005CFBF6 /* README.md */, ); sourceTree = ""; }; @@ -139,12 +142,14 @@ B66AC61D22CAAB8F0064C551 /* CardSetupIntentExampleViewController.m */, 04533F171A688A0A00C7E52E /* Constants.h */, 04533F181A688A0A00C7E52E /* Constants.m */, + 31A8934B230F6ABD007ABE37 /* FPXExampleViewController.h */, + 31A8934C230F6ABD007ABE37 /* FPXExampleViewController.m */, 04533E971A687F5D00C7E52E /* Images.xcassets */, 8BBD79C8207FD2F900F85BED /* Localizable.strings */, + B607FFBB2321DA99004203E0 /* MyAPIClient.h */, + B607FFBC2321DA99004203E0 /* MyAPIClient.m */, 04533EB01A68802E00C7E52E /* ShippingManager.h */, 04533EB11A68802E00C7E52E /* ShippingManager.m */, - 31A8934B230F6ABD007ABE37 /* FPXExampleViewController.h */, - 31A8934C230F6ABD007ABE37 /* FPXExampleViewController.m */, C1CACE921E5E3DF6002D0821 /* SofortExampleViewController.h */, C1CACE931E5E3DF6002D0821 /* SofortExampleViewController.m */, 04533E8A1A687F5D00C7E52E /* Supporting Files */, @@ -286,6 +291,7 @@ 04533E901A687F5D00C7E52E /* AppDelegate.m in Sources */, B65E8FCC22FA078A0057E64A /* WeChatPayExampleViewController.m in Sources */, C1CACE891E5DF7A9002D0821 /* ApplePayExampleViewController.m in Sources */, + B607FFBD2321DA99004203E0 /* MyAPIClient.m in Sources */, 04533E8D1A687F5D00C7E52E /* main.m in Sources */, 31A8934D230F6ABD007ABE37 /* FPXExampleViewController.m in Sources */, C12C50DD1E57B3C800EC6D58 /* BrowseExamplesViewController.m in Sources */, diff --git a/Example/Custom Integration/AppDelegate.m b/Example/Custom Integration/AppDelegate.m index a5999af6b6f..1ef1a88de8d 100644 --- a/Example/Custom Integration/AppDelegate.m +++ b/Example/Custom Integration/AppDelegate.m @@ -26,6 +26,11 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( return YES; } +/** + This method is implemented to route returnURLs back to the Stripe SDK. + + @see https://stripe.com/docs/mobile/ios/authentication#return-url + */ - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { BOOL stripeHandled = [Stripe handleStripeURLCallbackWithURL:url]; if (stripeHandled) { diff --git a/Example/Custom Integration/ApplePayExampleViewController.m b/Example/Custom Integration/ApplePayExampleViewController.m index a369d996fa0..f4e15bbceef 100644 --- a/Example/Custom Integration/ApplePayExampleViewController.m +++ b/Example/Custom Integration/ApplePayExampleViewController.m @@ -7,10 +7,12 @@ // #import + #import "ApplePayExampleViewController.h" #import "BrowseExamplesViewController.h" #import "Constants.h" #import "ShippingManager.h" +#import "MyAPIClient.h" /** This example demonstrates creating a payment using Apple Pay. First, we configure a PKPaymentRequest @@ -169,8 +171,8 @@ - (void)_createAndConfirmPaymentIntentWithPaymentMethod:(STPPaymentMethod *)paym }; // 1. Create a PaymentIntent on the backend. This is typically done at the beginning of the checkout flow. - [self.delegate createBackendPaymentIntentWithAmount:@(1000) completion:^(STPBackendResult status, NSString *clientSecret, NSError *error) { - if (status == STPBackendResultFailure || error) { + [[MyAPIClient sharedClient] createPaymentIntentWithCompletion:^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { + if (status == MyAPIClientResultFailure || error) { self.applePayError = error; finishWithStatus(PKPaymentAuthorizationStatusFailure); return; diff --git a/Example/Custom Integration/BrowseExamplesViewController.h b/Example/Custom Integration/BrowseExamplesViewController.h index bd1ceacdf70..e9c90b4a760 100644 --- a/Example/Custom Integration/BrowseExamplesViewController.h +++ b/Example/Custom Integration/BrowseExamplesViewController.h @@ -9,35 +9,11 @@ #import #import -typedef NS_ENUM(NSInteger, STPBackendResult) { - STPBackendResultSuccess, - STPBackendResultFailure, -}; - -typedef void (^STPPaymentIntentCreationHandler)(STPBackendResult status, NSString *clientSecret, NSError *error); -typedef void (^STPPaymentIntentCreateAndConfirmHandler)(STPBackendResult status, NSString *clientSecret, NSError *error); -typedef void (^STPConfirmPaymentIntentCompletionHandler)(STPBackendResult status, NSString *clientSecret, NSError *error); -typedef void (^STPCreateSetupIntentCompletionHandler)(STPBackendResult status, NSString *clientSecret, NSError *error); - - @protocol ExampleViewControllerDelegate - (void)exampleViewController:(UIViewController *)controller didFinishWithMessage:(NSString *)message; - (void)exampleViewController:(UIViewController *)controller didFinishWithError:(NSError *)error; -- (void)createBackendPaymentIntentWithAmount:(NSNumber *)amount completion:(STPPaymentIntentCreationHandler)completion; -- (void)createAndConfirmPaymentIntentWithAmount:(NSNumber *)amount - paymentMethod:(NSString *)paymentMethodID - returnURL:(NSString *)returnURL - completion:(STPPaymentIntentCreateAndConfirmHandler)completion; -- (void)confirmPaymentIntent:(STPPaymentIntent *)paymentIntent completion:(STPConfirmPaymentIntentCompletionHandler)completion; - - -// if paymentMethodID != nil, this will also confirm on the backend -- (void)createSetupIntentWithPaymentMethod:(NSString *)paymentMethodID - returnURL:(NSString *)returnURL - completion:(STPCreateSetupIntentCompletionHandler)completion; - @end @interface BrowseExamplesViewController : UITableViewController diff --git a/Example/Custom Integration/BrowseExamplesViewController.m b/Example/Custom Integration/BrowseExamplesViewController.m index 15b5fc05ae2..d10c1c0acc5 100644 --- a/Example/Custom Integration/BrowseExamplesViewController.m +++ b/Example/Custom Integration/BrowseExamplesViewController.m @@ -15,7 +15,6 @@ #import "CardManualConfirmationExampleViewController.h" #import "CardSetupIntentBackendExampleViewController.h" #import "CardSetupIntentExampleViewController.h" -#import "Constants.h" #import "SofortExampleViewController.h" #import "FPXExampleViewController.h" #import "WeChatPayExampleViewController.h" @@ -129,263 +128,6 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath [self.navigationController pushViewController:viewController animated:YES]; } -#pragma mark - STPBackendCharging - -- (void)_callOnMainThread:(void (^)(void))block { - if ([NSThread isMainThread]) { - block(); - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - block(); - }); - } -} - -/** - Ask the example backend to create a PaymentIntent with the specified amount. - - The implementation of this function is not interesting or relevant to using PaymentIntents. The - method signature is the most interesting part: you need some way to ask *your* backend to create - a PaymentIntent with the correct properties, and then it needs to pass the client secret back. - - @param amount Amount to charge the customer - @param completion completion block called with status of backend call & the client secret if successful. - */ -- (void)createBackendPaymentIntentWithAmount:(NSNumber *)amount completion:(STPPaymentIntentCreationHandler)completion { - if (!BackendBaseURL) { - NSError *error = [NSError errorWithDomain:StripeDomain - code:STPInvalidRequestError - userInfo:@{NSLocalizedDescriptionKey: @"You must set a backend base URL in Constants.m to create a payment intent."}]; - [self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, error); }]; - return; - } - - // This asks the backend to create a PaymentIntent for us, which can then be passed to the Stripe SDK to confirm - NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; - - NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"create_intent"]; - NSURL *url = [NSURL URLWithString:urlString]; - NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; - request.HTTPMethod = @"POST"; - NSString *postBody = [NSString stringWithFormat: - @"amount=%@&metadata[charge_request_id]=%@", - amount, - // example-ios-backend allows passing metadata through to Stripe - @"B3E611D1-5FA1-4410-9CEC-00958A5126CB" - ]; - NSData *data = [postBody dataUsingEncoding:NSUTF8StringEncoding]; - - NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request - fromData:data - completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; - if (!error && httpResponse.statusCode != 200) { - NSString *errorMessage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ?: @"There was an error connecting to your payment backend."; - error = [NSError errorWithDomain:StripeDomain - code:STPInvalidRequestError - userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; - } - if (error || data == nil) { - [self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, error); }]; - } - else { - NSError *jsonError = nil; - id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; - - if (json && - [json isKindOfClass:[NSDictionary class]] && - [json[@"secret"] isKindOfClass:[NSString class]]) { - [self _callOnMainThread:^{ completion(STPBackendResultSuccess, json[@"secret"], nil); }]; - } - else { - [self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, jsonError); }]; - } - } - }]; - - [uploadTask resume]; -} - -- (void)createAndConfirmPaymentIntentWithAmount:(NSNumber *)amount - paymentMethod:(NSString *)paymentMethodID - returnURL:(NSString *)returnURL - completion:(STPPaymentIntentCreateAndConfirmHandler)completion { - if (!BackendBaseURL) { - NSError *error = [NSError errorWithDomain:StripeDomain - code:STPInvalidRequestError - userInfo:@{NSLocalizedDescriptionKey: @"You must set a backend base URL in Constants.m to create a payment intent."}]; - [self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, error); }]; - return; - } - - // This passes the token off to our payment backend, which will then actually complete charging the card using your Stripe account's secret key - NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; - - NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"capture_payment"]; - NSURL *url = [NSURL URLWithString:urlString]; - NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; - request.HTTPMethod = @"POST"; - NSString *postBody = [NSString stringWithFormat: - @"payment_method=%@&amount=%@&return_url=%@", - paymentMethodID, - amount, - returnURL]; - NSData *data = [postBody dataUsingEncoding:NSUTF8StringEncoding]; - - NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request - fromData:data - completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; - if (!error && httpResponse.statusCode != 200) { - NSString *errorMessage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ?: @"There was an error connecting to your payment backend."; - error = [NSError errorWithDomain:StripeDomain - code:STPInvalidRequestError - userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; - } - if (error) { - [self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, error); }]; - } else { - NSError *jsonError = nil; - id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; - - if (json && [json isKindOfClass:[NSDictionary class]]) { - NSString *clientSecret = json[@"secret"]; - if (clientSecret != nil) { - [self _callOnMainThread:^{ completion(STPBackendResultSuccess, clientSecret, nil); }]; - } else { - [self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, [NSError errorWithDomain:StripeDomain - code:STPAPIError - userInfo:@{NSLocalizedDescriptionKey: @"There was an error parsing your backend response to a client secret."}]); }]; - } - } else { - [self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, jsonError); }]; - } - } - }]; - - [uploadTask resume]; -} - -- (void)confirmPaymentIntent:(STPPaymentIntent *)paymentIntent completion:(STPConfirmPaymentIntentCompletionHandler)completion { - if (!BackendBaseURL) { - NSError *error = [NSError errorWithDomain:StripeDomain - code:STPInvalidRequestError - userInfo:@{NSLocalizedDescriptionKey: @"You must set a backend base URL in Constants.m to confirm a payment intent."}]; - [self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, error); }]; - return; - } - - // This asks the backend to create a PaymentIntent for us, which can then be passed to the Stripe SDK to confirm - NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; - - NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"confirm_payment"]; - NSURL *url = [NSURL URLWithString:urlString]; - NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; - request.HTTPMethod = @"POST"; - NSString *postBody = [NSString stringWithFormat:@"payment_intent_id=%@", paymentIntent.stripeId]; - NSData *data = [postBody dataUsingEncoding:NSUTF8StringEncoding]; - - NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request - fromData:data - completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; - if (!error && httpResponse.statusCode != 200) { - NSString *errorMessage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ?: @"There was an error connecting to your payment backend."; - error = [NSError errorWithDomain:StripeDomain - code:STPInvalidRequestError - userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; - } - if (error || data == nil) { - [self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, error); }]; - } else { - NSError *jsonError = nil; - id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; - - if (json && [json isKindOfClass:[NSDictionary class]]) { - NSString *clientSecret = json[@"secret"]; - if (clientSecret != nil) { - [self _callOnMainThread:^{ completion(STPBackendResultSuccess, clientSecret, nil); }]; - } else { - [self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, [NSError errorWithDomain:StripeDomain - code:STPAPIError - userInfo:@{NSLocalizedDescriptionKey: @"There was an error parsing your backend response to a client secret."}]); }]; - } - } else { - [self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, jsonError); }]; - } - } - }]; - - [uploadTask resume]; -} - -- (void)createSetupIntentWithPaymentMethod:(NSString *)paymentMethodID - returnURL:(NSString *)returnURL - completion:(STPCreateSetupIntentCompletionHandler)completion { - if (!BackendBaseURL) { - NSError *error = [NSError errorWithDomain:StripeDomain - code:STPInvalidRequestError - userInfo:@{NSLocalizedDescriptionKey: @"You must set a backend base URL in Constants.m to confirm a payment intent."}]; - [self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, error); }]; - return; - } - - // This asks the backend to create a SetupIntent for us, which can then be passed to the Stripe SDK to confirm - NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; - - NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"create_setup_intent"]; - NSURL *url = [NSURL URLWithString:urlString]; - NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; - request.HTTPMethod = @"POST"; - NSString *postBody = @""; - - if (paymentMethodID != nil) { - postBody = [postBody stringByAppendingString:[NSString stringWithFormat:@"payment_method=%@", paymentMethodID]]; - } - if (returnURL != nil) { - if (postBody.length > 0) { - postBody = [postBody stringByAppendingString:@"&"]; - } - postBody = [postBody stringByAppendingString:[NSString stringWithFormat:@"return_url=%@", returnURL]]; - } - - NSData *data = postBody.length > 0 ? [postBody dataUsingEncoding:NSUTF8StringEncoding] : [NSData data]; - - NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request - fromData:data - completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; - if (!error && httpResponse.statusCode != 200) { - NSString *errorMessage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ?: @"There was an error connecting to your payment backend."; - error = [NSError errorWithDomain:StripeDomain - code:STPInvalidRequestError - userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; - } - if (error || data == nil) { - [self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, error); }]; - } - else { - NSError *jsonError = nil; - id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; - - if (json && - [json isKindOfClass:[NSDictionary class]] && - [json[@"secret"] isKindOfClass:[NSString class]]) { - [self _callOnMainThread:^{ completion(STPBackendResultSuccess, json[@"secret"], nil); }]; - } - else { - [self _callOnMainThread:^{ completion(STPBackendResultFailure, nil, jsonError); }]; - } - } - }]; - - [uploadTask resume]; -} - #pragma mark - ExampleViewControllerDelegate - (void)exampleViewController:(UIViewController *)controller didFinishWithMessage:(NSString *)message { diff --git a/Example/Custom Integration/CardAutomaticConfirmationViewController.m b/Example/Custom Integration/CardAutomaticConfirmationViewController.m index 2f130362852..30ebe9feac2 100644 --- a/Example/Custom Integration/CardAutomaticConfirmationViewController.m +++ b/Example/Custom Integration/CardAutomaticConfirmationViewController.m @@ -11,6 +11,8 @@ #import "CardAutomaticConfirmationViewController.h" #import "BrowseExamplesViewController.h" +#import "MyAPIClient.h" + /** This example demonstrates using PaymentIntents to accept card payments verified using 3D Secure. @@ -133,8 +135,8 @@ - (void)pay { // payment amount you wish to collect from your customer. For simplicity, this example does it once they've // pushed the Pay button. // https://stripe.com/docs/payments/dynamic-authentication#create-payment-intent - [self.delegate createBackendPaymentIntentWithAmount:@1099 completion:^(STPBackendResult status, NSString *clientSecret, NSError *error) { - if (status == STPBackendResultFailure || clientSecret == nil) { + [[MyAPIClient sharedClient] createPaymentIntentWithCompletion:^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { + if (status == MyAPIClientResultFailure || clientSecret == nil) { [self.delegate exampleViewController:self didFinishWithError:error]; return; } diff --git a/Example/Custom Integration/CardManualConfirmationExampleViewController.m b/Example/Custom Integration/CardManualConfirmationExampleViewController.m index 1328543d047..ea0260ffd79 100644 --- a/Example/Custom Integration/CardManualConfirmationExampleViewController.m +++ b/Example/Custom Integration/CardManualConfirmationExampleViewController.m @@ -7,8 +7,11 @@ // #import + #import "CardManualConfirmationExampleViewController.h" + #import "BrowseExamplesViewController.h" +#import "MyAPIClient.h" /** This example demonstrates creating a payment with a credit/debit card using Manual Integration. @@ -49,6 +52,10 @@ - (void)viewDidLoad { self.navigationItem.rightBarButtonItem = buyButton; STPPaymentCardTextField *paymentTextField = [[STPPaymentCardTextField alloc] init]; + STPPaymentMethodCardParams *cardParams = [STPPaymentMethodCardParams new]; + // Only successful 3D Secure transactions on this test card will succeed. + cardParams.number = @"4000000000003063"; + paymentTextField.cardParams = cardParams; paymentTextField.delegate = self; paymentTextField.cursorColor = [UIColor purpleColor]; #ifdef __IPHONE_13_0 @@ -56,7 +63,6 @@ - (void)viewDidLoad { paymentTextField.cursorColor = [UIColor systemPurpleColor]; } #endif - paymentTextField.postalCodeEntryEnabled = YES; self.paymentTextField = paymentTextField; [self.view addSubview:paymentTextField]; @@ -121,22 +127,12 @@ - (void)_createAndConfirmPaymentIntentWithPaymentMethod:(STPPaymentMethod *)paym case STPPaymentHandlerActionStatusSucceeded: if (paymentIntent.status == STPPaymentIntentStatusRequiresConfirmation) { // Manually confirm the PaymentIntent on the backend again to complete the payment. - [self.delegate confirmPaymentIntent:paymentIntent completion:^(STPBackendResult status, NSString *clientSecret, NSError *error) { - if (status == STPBackendResultFailure || error) { + [[MyAPIClient sharedClient] confirmPaymentIntent:paymentIntent.stripeId completion:^(MyAPIClientResult status, NSError *error) { + if (status == MyAPIClientResultFailure || error) { [self.delegate exampleViewController:self didFinishWithError:error]; return; } - [[STPAPIClient sharedClient] retrievePaymentIntentWithClientSecret:clientSecret completion:^(STPPaymentIntent *finalPaymentIntent, NSError *finalError) { - if (finalError) { - [self.delegate exampleViewController:self didFinishWithError:error]; - return; - } - if (finalPaymentIntent.status == STPPaymentIntentStatusSucceeded) { - [self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"]; - } else { - [self.delegate exampleViewController:self didFinishWithMessage:@"Payment failed"]; - } - }]; + [self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"]; }]; break; } else { @@ -144,20 +140,23 @@ - (void)_createAndConfirmPaymentIntentWithPaymentMethod:(STPPaymentMethod *)paym } } }; - STPPaymentIntentCreateAndConfirmHandler createAndConfirmCompletion = ^(STPBackendResult status, NSString *clientSecret, NSError *error) { - if (status == STPBackendResultFailure || error) { + STPPaymentIntentCreateAndConfirmHandler createAndConfirmCompletion = ^(MyAPIClientResult status, BOOL requiresAction, NSString *clientSecret, NSError *error) { + if (status == MyAPIClientResultFailure || error) { [self.delegate exampleViewController:self didFinishWithError:error]; return; } - [[STPPaymentHandler sharedHandler] handleNextActionForPayment:clientSecret - withAuthenticationContext:self.delegate - returnURL:@"payments-example://stripe-redirect" - completion:paymentHandlerCompletion]; + if (requiresAction) { + [[STPPaymentHandler sharedHandler] handleNextActionForPayment:clientSecret + withAuthenticationContext:self.delegate + returnURL:@"payments-example://stripe-redirect" + completion:paymentHandlerCompletion]; + } else { + [self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"]; + } }; - [self.delegate createAndConfirmPaymentIntentWithAmount:@(100) - paymentMethod:paymentMethod.stripeId - returnURL:@"payments-example://stripe-redirect" - completion:createAndConfirmCompletion]; + [[MyAPIClient sharedClient] createAndConfirmPaymentIntentWithPaymentMethod:paymentMethod.stripeId + returnURL:@"payments-example://stripe-redirect" + completion:createAndConfirmCompletion]; } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { diff --git a/Example/Custom Integration/CardSetupIntentBackendExampleViewController.m b/Example/Custom Integration/CardSetupIntentBackendExampleViewController.m index 4630426b303..89dd0b0c401 100644 --- a/Example/Custom Integration/CardSetupIntentBackendExampleViewController.m +++ b/Example/Custom Integration/CardSetupIntentBackendExampleViewController.m @@ -9,6 +9,8 @@ #import "CardSetupIntentBackendExampleViewController.h" #import "BrowseExamplesViewController.h" +#import "MyAPIClient.h" + /** This example demonstrates using SetupIntents to accept card payments verified using 3D Secure confirming with your backend. @@ -153,8 +155,8 @@ - (void)_createAndConfirmSetupIntentWithPaymentMethod:(STPPaymentMethod *)paymen } }; - STPCreateSetupIntentCompletionHandler createCompletion = ^(STPBackendResult status, NSString *clientSecret, NSError *error) { - if (status == STPBackendResultFailure || error) { + STPCreateSetupIntentCompletionHandler createCompletion = ^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { + if (status == MyAPIClientResultFailure || error) { [self.delegate exampleViewController:self didFinishWithError:error]; return; } @@ -164,9 +166,9 @@ - (void)_createAndConfirmSetupIntentWithPaymentMethod:(STPPaymentMethod *)paymen completion:paymentHandlerCompletion]; }; - [self.delegate createSetupIntentWithPaymentMethod:paymentMethod.stripeId - returnURL:@"payments-example://stripe-redirect" - completion:createCompletion]; + [[MyAPIClient sharedClient] createAndConfirmSetupIntentWithPaymentMethod:paymentMethod.stripeId + returnURL:@"payments-example://stripe-redirect" + completion:createCompletion]; } diff --git a/Example/Custom Integration/CardSetupIntentExampleViewController.m b/Example/Custom Integration/CardSetupIntentExampleViewController.m index 8f8056664fe..6ddd96394c7 100644 --- a/Example/Custom Integration/CardSetupIntentExampleViewController.m +++ b/Example/Custom Integration/CardSetupIntentExampleViewController.m @@ -9,7 +9,9 @@ @import Stripe; #import "CardSetupIntentExampleViewController.h" + #import "BrowseExamplesViewController.h" +#import "MyAPIClient.h" /** This example demonstrates using SetupIntents to accept card payments verified using 3D Secure. @@ -42,7 +44,7 @@ - (void)viewDidLoad { STPPaymentCardTextField *paymentTextField = [[STPPaymentCardTextField alloc] init]; STPPaymentMethodCardParams *cardParams = [STPPaymentMethodCardParams new]; // Only successful 3D Secure transactions on this test card will succeed. - cardParams.number = @"4000000000003063"; + cardParams.number = @"4000002500003155"; paymentTextField.cardParams = cardParams; paymentTextField.delegate = self; paymentTextField.cursorColor = [UIColor purpleColor]; @@ -122,35 +124,33 @@ - (void)pay { return; } [self updateUIForPaymentInProgress:YES]; - [self.delegate createSetupIntentWithPaymentMethod:nil - returnURL:nil - completion:^(STPBackendResult status, NSString *clientSecret, NSError *error) { - if (status == STPBackendResultFailure || clientSecret == nil) { - [self.delegate exampleViewController:self didFinishWithError:error]; - return; - } - STPSetupIntentConfirmParams *setupIntentConfirmParams = [[STPSetupIntentConfirmParams alloc] initWithClientSecret:clientSecret]; - setupIntentConfirmParams.paymentMethodParams = [STPPaymentMethodParams paramsWithCard:self.paymentTextField.cardParams - billingDetails:nil - metadata:nil]; - setupIntentConfirmParams.returnURL = @"payments-example://stripe-redirect"; - [[STPPaymentHandler sharedHandler] confirmSetupIntent:setupIntentConfirmParams - withAuthenticationContext:self.delegate - completion:^(STPPaymentHandlerActionStatus handlerStatus, STPSetupIntent * _Nullable handledIntent, NSError * _Nullable handlerError) { - switch (handlerStatus) { - case STPPaymentHandlerActionStatusSucceeded: - [self.delegate exampleViewController:self didFinishWithMessage:@"SetupIntent successfully created"]; - break; - case STPPaymentHandlerActionStatusCanceled: - [self.delegate exampleViewController:self didFinishWithMessage:@"Cancelled"]; - break; - case STPPaymentHandlerActionStatusFailed: - [self.delegate exampleViewController:self didFinishWithError:handlerError]; - break; - } - }]; - - }]; + [[MyAPIClient sharedClient] createSetupIntentWithCompletion:^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { + if (status == MyAPIClientResultFailure || clientSecret == nil) { + [self.delegate exampleViewController:self didFinishWithError:error]; + return; + } + STPSetupIntentConfirmParams *setupIntentConfirmParams = [[STPSetupIntentConfirmParams alloc] initWithClientSecret:clientSecret]; + setupIntentConfirmParams.paymentMethodParams = [STPPaymentMethodParams paramsWithCard:self.paymentTextField.cardParams + billingDetails:nil + metadata:nil]; + setupIntentConfirmParams.returnURL = @"payments-example://stripe-redirect"; + [[STPPaymentHandler sharedHandler] confirmSetupIntent:setupIntentConfirmParams + withAuthenticationContext:self.delegate + completion:^(STPPaymentHandlerActionStatus handlerStatus, STPSetupIntent * _Nullable handledIntent, NSError * _Nullable handlerError) { + switch (handlerStatus) { + case STPPaymentHandlerActionStatusSucceeded: + [self.delegate exampleViewController:self didFinishWithMessage:@"SetupIntent successfully created"]; + break; + case STPPaymentHandlerActionStatusCanceled: + [self.delegate exampleViewController:self didFinishWithMessage:@"Cancelled"]; + break; + case STPPaymentHandlerActionStatusFailed: + [self.delegate exampleViewController:self didFinishWithError:handlerError]; + break; + } + }]; + + }]; } @end diff --git a/Example/Custom Integration/Constants.m b/Example/Custom Integration/Constants.m index bf6fc444e23..627dab55f73 100644 --- a/Example/Custom Integration/Constants.m +++ b/Example/Custom Integration/Constants.m @@ -11,7 +11,7 @@ // This can be found at https://dashboard.stripe.com/account/apikeys NSString *const StripePublishableKey = nil; // TODO: replace nil with your own value -// To set this up, check out https://github.com/stripe/example-ios-backend/tree/v14.0.0 +// To set this up, check out https://github.com/stripe/example-ios-backend/tree/v17.0.0 // This should be in the format https://my-shiny-backend.herokuapp.com NSString *const BackendBaseURL = nil; // TODO: replace nil with your own value diff --git a/Example/Custom Integration/FPXExampleViewController.m b/Example/Custom Integration/FPXExampleViewController.m index 8bd6c350675..4462c843e7b 100644 --- a/Example/Custom Integration/FPXExampleViewController.m +++ b/Example/Custom Integration/FPXExampleViewController.m @@ -9,6 +9,7 @@ #import #import "FPXExampleViewController.h" #import "BrowseExamplesViewController.h" +#import "MyAPIClient.h" /** This example demonstrates using PaymentIntents to accept payments using FPX, a popular @@ -95,8 +96,8 @@ - (void)pay { } [self updateUIForPaymentInProgress:YES]; - [self.delegate createBackendPaymentIntentWithAmount:@234 completion:^(STPBackendResult status, NSString *clientSecret, NSError *error) { - if (status == STPBackendResultFailure || clientSecret == nil) { + [[MyAPIClient sharedClient] createPaymentIntentWithCompletion:^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { + if (status == MyAPIClientResultFailure || clientSecret == nil) { [self.delegate exampleViewController:self didFinishWithError:error]; return; } diff --git a/Example/Custom Integration/MyAPIClient.h b/Example/Custom Integration/MyAPIClient.h new file mode 100644 index 00000000000..ba06447f6e3 --- /dev/null +++ b/Example/Custom Integration/MyAPIClient.h @@ -0,0 +1,107 @@ +// +// ExampleAPIClient.h +// Custom Integration +// +// Created by Yuki Tokuhiro on 9/5/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, MyAPIClientResult) { + MyAPIClientResultSuccess, + MyAPIClientResultFailure, +}; + +typedef void (^STPPaymentIntentCreationHandler)(MyAPIClientResult status, NSString * _Nullable clientSecret, NSError * _Nullable error); +typedef void (^STPPaymentIntentCreateAndConfirmHandler)(MyAPIClientResult status, BOOL requiresAction, NSString * _Nullable clientSecret, NSError * _Nullable error); +typedef void (^STPConfirmPaymentIntentCompletionHandler)(MyAPIClientResult status, NSError * _Nullable error); +typedef void (^STPCreateSetupIntentCompletionHandler)(MyAPIClientResult status, NSString * _Nullable clientSecret, NSError * _Nullable error); + +@interface MyAPIClient : NSObject + ++ (instancetype)sharedClient; + +#pragma mark - PaymentIntents (automatic confirmation) + +/** + Asks our example backend to create and confirm a PaymentIntent using automatic confirmation. + + The implementation of this function is not interesting or relevant to using PaymentIntents. The + method signature is the most interesting part: you need some way to ask *your* backend to create + a PaymentIntent with the correct properties, and then it needs to pass the client secret back. + + @param completion completion block called with status of backend call & the client secret if successful. + @see https://stripe.com/docs/payments/payment-intents/ios + */ +- (void)createPaymentIntentWithCompletion:(STPPaymentIntentCreationHandler)completion; + +#pragma mark - PaymentIntents (manual confirmation) + +/** + Asks our example backend to create and confirm a PaymentIntent using manual confirmation. + + The implementation of this function is not interesting or relevant to using PaymentIntents. The + method signature is the most interesting part: you need some way to ask *your* backend to create + a PaymentIntent with the correct properties, and then it needs to pass the client secret back. + + @see https://stripe.com/docs/payments/payment-intents/ios-manual + @param paymentMethodID Stripe ID of the PaymentMethod representing the customer's payment method + @param returnURL A URL to the app, used to automatically redirect customers back to your app + after your they completes web-based authentication. See https://stripe.com/docs/mobile/ios/authentication#return-url + @param completion completion block called with status of backend call & the client secret if successful. + */ +- (void)createAndConfirmPaymentIntentWithPaymentMethod:(NSString *)paymentMethodID + returnURL:(NSString *)returnURL + completion:(STPPaymentIntentCreateAndConfirmHandler)completion; + +/** + Asks our example backend to confirm a PaymentIntent using manual confirmation. + + The implementation of this function is not interesting or relevant to using PaymentIntents. The + method signature is the most interesting part: you need some way to ask *your* backend to create + a PaymentIntent with the correct properties, and then it needs to pass the client secret back. + + @see https://stripe.com/docs/payments/payment-intents/ios-manual + @param paymentIntentId Stripe ID of the PaymentIntent to confirm. + @param completion completion block called with status of backend call. If the status is .success, the confirmation succeeded. + */ +- (void)confirmPaymentIntent:(NSString *)paymentIntentID completion:(STPConfirmPaymentIntentCompletionHandler)completion; + + +#pragma mark - SetupIntents + +/** + Asks our example backend to create a SetupIntent. + + The implementation of this function is not interesting or relevant to using SetupIntents. The + method signature is the most interesting part: you need some way to ask *your* backend to create + a SetupIntent with the correct properties, and then it needs to pass the client secret back. + + @see https://stripe.com/docs/payments/cards/saving-cards-without-payment + @param completion completion block called with status of backend call & the client secret if successful. + */ +- (void)createSetupIntentWithCompletion:(STPCreateSetupIntentCompletionHandler)completion; + +/** + Asks our example backend to create and confirm a SetupIntent. + + The implementation of this function is not interesting or relevant to using SetupIntents. The + method signature is the most interesting part: you need some way to ask *your* backend to create + a SetupIntent with the correct properties, and then it needs to pass the client secret back. + + @see https://stripe.com/docs/payments/cards/saving-cards-without-payment + @param returnURL A URL to the app, used to automatically redirect customers back to your app + after your they completes web-based authentication. See https://stripe.com/docs/mobile/ios/authentication#return-url. + @param paymentMethodID Stripe ID of the PaymentMethod to set up for future payments. + @param completion completion block called with status of backend call & the client secret if successful. + */ +- (void)createAndConfirmSetupIntentWithPaymentMethod:(NSString *)paymentMethodID + returnURL:(NSString *)returnURL + completion:(STPCreateSetupIntentCompletionHandler)completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Custom Integration/MyAPIClient.m b/Example/Custom Integration/MyAPIClient.m new file mode 100644 index 00000000000..63db1ba21a0 --- /dev/null +++ b/Example/Custom Integration/MyAPIClient.m @@ -0,0 +1,321 @@ +// +// ExampleAPIClient.m +// Custom Integration +// +// Created by Yuki Tokuhiro on 9/5/19. +// Copyright © 2019 Stripe. All rights reserved. +// +@import Stripe; + +#import "MyAPIClient.h" + +#import "Constants.h" + +@implementation MyAPIClient + ++ (instancetype)sharedClient { + static id sharedClient; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ sharedClient = [[self alloc] init]; }); + return sharedClient; +} + +- (void)_callOnMainThread:(void (^)(void))block { + if ([NSThread isMainThread]) { + block(); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + block(); + }); + } +} + +#pragma mark - PaymentIntents (automatic confirmation) + +/** + Ask the example backend to create a PaymentIntent with the specified amount. + + The implementation of this function is not interesting or relevant to using PaymentIntents. The + method signature is the most interesting part: you need some way to ask *your* backend to create + a PaymentIntent with the correct properties, and then it needs to pass the client secret back. + + @param amount Amount to charge the customer + @param completion completion block called with status of backend call & the client secret if successful. + */ +- (void)createPaymentIntentWithCompletion:(STPPaymentIntentCreationHandler)completion { + if (!BackendBaseURL) { + NSError *error = [NSError errorWithDomain:@"MyAPIClientErrorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey: @"You must set a backend base URL in Constants.m to create a payment intent."}]; + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, error); }]; + return; + } + + // This asks the backend to create a PaymentIntent for us, which can then be passed to the Stripe SDK to confirm + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; + + NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"create_payment_intent"]; + NSURL *url = [NSURL URLWithString:urlString]; + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; + request.HTTPMethod = @"POST"; + NSString *postBody = [NSString stringWithFormat: + @"metadata[charge_request_id]=%@", + // example-ios-backend allows passing metadata through to Stripe + @"B3E611D1-5FA1-4410-9CEC-00958A5126CB" + ]; + NSData *data = [postBody dataUsingEncoding:NSUTF8StringEncoding]; + + NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request + fromData:data + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + if (!error && httpResponse.statusCode != 200) { + NSString *errorMessage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ?: @"There was an error connecting to your payment backend."; + error = [NSError errorWithDomain:@"MyAPIClientErrorDomain" + code:STPInvalidRequestError + userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; + } + if (error || data == nil) { + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, error); }]; + } + else { + NSError *jsonError = nil; + id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + + if (json && + [json isKindOfClass:[NSDictionary class]] && + [json[@"secret"] isKindOfClass:[NSString class]]) { + [self _callOnMainThread:^{ completion(MyAPIClientResultSuccess, json[@"secret"], nil); }]; + } + else { + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, jsonError); }]; + } + } + }]; + + [uploadTask resume]; +} + +#pragma mark - PaymentIntents (manual confirmation) + +- (void)createAndConfirmPaymentIntentWithPaymentMethod:(NSString *)paymentMethodID + returnURL:(NSString *)returnURL + completion:(STPPaymentIntentCreateAndConfirmHandler)completion { + if (!BackendBaseURL) { + NSError *error = [NSError errorWithDomain:@"MyAPIClientErrorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey: @"You must set a backend base URL in Constants.m to create a payment intent."}]; + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, NO, nil, error); }]; + return; + } + + // This passes the token off to our payment backend, which will then actually complete charging the card using your Stripe account's secret key + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; + + NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"confirm_payment_intent"]; + NSURL *url = [NSURL URLWithString:urlString]; + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; + request.HTTPMethod = @"POST"; + NSString *postBody = [NSString stringWithFormat: + @"payment_method_id=%@&return_url=%@", + paymentMethodID, + returnURL]; + NSData *data = [postBody dataUsingEncoding:NSUTF8StringEncoding]; + + NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request + fromData:data + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + if (!error && httpResponse.statusCode != 200) { + NSString *errorMessage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ?: @"There was an error connecting to your payment backend."; + error = [NSError errorWithDomain:@"MyAPIClientErrorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; + } + if (error) { + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, NO, nil, error); }]; + } else { + NSError *jsonError = nil; + id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + + if (json && [json isKindOfClass:[NSDictionary class]]) { + if (json[@"success"]) { + [self _callOnMainThread:^{ completion(MyAPIClientResultSuccess, NO, nil, nil); }]; + return; + } else if (json[@"secret"]) { + NSString *clientSecret = json[@"secret"]; + [self _callOnMainThread:^{ completion(MyAPIClientResultSuccess, YES, clientSecret, nil); }]; + return; + } + } + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, NO, nil, [NSError errorWithDomain:@"MyAPIClientErrorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey: @"There was an error parsing your backend response to a client secret."}]); }]; + } + }]; + + [uploadTask resume]; +} + +- (void)confirmPaymentIntent:(NSString *)paymentIntentID completion:(STPConfirmPaymentIntentCompletionHandler)completion { + if (!BackendBaseURL) { + NSError *error = [NSError errorWithDomain:@"MyAPIClientErrorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey: @"You must set a backend base URL in Constants.m to confirm a payment intent."}]; + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, error); }]; + return; + } + + // This asks the backend to create a PaymentIntent for us, which can then be passed to the Stripe SDK to confirm + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; + + NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"confirm_payment_intent"]; + NSURL *url = [NSURL URLWithString:urlString]; + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; + request.HTTPMethod = @"POST"; + NSString *postBody = [NSString stringWithFormat:@"payment_intent_id=%@", paymentIntentID]; + NSData *data = [postBody dataUsingEncoding:NSUTF8StringEncoding]; + + NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request + fromData:data + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + if (!error && httpResponse.statusCode != 200) { + NSString *errorMessage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ?: @"There was an error connecting to your payment backend."; + error = [NSError errorWithDomain:@"MyAPIClientErrorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; + } + if (error || data == nil) { + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, error); }]; + } else { + NSError *jsonError = nil; + id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + + if (json && [json isKindOfClass:[NSDictionary class]]) { + NSNumber *success = json[@"success"]; + if ([success boolValue]) { + [self _callOnMainThread:^{ completion(MyAPIClientResultSuccess, nil); }]; + } else { + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, [NSError errorWithDomain:@"MyAPIClientErrorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey: @"There was an error parsing your backend response to a client secret."}]); }]; + } + } else { + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, jsonError); }]; + } + } + }]; + + [uploadTask resume]; +} + +#pragma mark - SetupIntents + +- (void)createSetupIntentWithCompletion:(STPCreateSetupIntentCompletionHandler)completion { + if (!BackendBaseURL) { + NSError *error = [NSError errorWithDomain:@"MyAPIClientErrorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey: @"You must set a backend base URL in Constants.m to confirm a payment intent."}]; + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, error); }]; + return; + } + + // This asks the backend to create a SetupIntent for us, which can then be passed to the Stripe SDK to confirm + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; + + NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"create_setup_intent"]; + NSURL *url = [NSURL URLWithString:urlString]; + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; + request.HTTPMethod = @"POST"; + + NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request + fromData:[NSData data] + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + if (!error && httpResponse.statusCode != 200) { + NSString *errorMessage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ?: @"There was an error connecting to your payment backend."; + error = [NSError errorWithDomain:@"MyAPIClientErrorDomain" + code:STPInvalidRequestError + userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; + } + if (error || data == nil) { + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, error); }]; + } + else { + NSError *jsonError = nil; + id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + + if (json && + [json isKindOfClass:[NSDictionary class]] && + [json[@"secret"] isKindOfClass:[NSString class]]) { + [self _callOnMainThread:^{ completion(MyAPIClientResultSuccess, json[@"secret"], nil); }]; + } + else { + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, jsonError); }]; + } + } + }]; + + [uploadTask resume]; +} + +- (void)createAndConfirmSetupIntentWithPaymentMethod:(NSString *)paymentMethodID + returnURL:(NSString *)returnURL + completion:(STPCreateSetupIntentCompletionHandler)completion { + if (!BackendBaseURL) { + NSError *error = [NSError errorWithDomain:@"MyAPIClientErrorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey: @"You must set a backend base URL in Constants.m to confirm a payment intent."}]; + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, error); }]; + return; + } + + // This asks the backend to create a SetupIntent for us, which can then be passed to the Stripe SDK to confirm + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; + + NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"create_setup_intent"]; + NSURL *url = [NSURL URLWithString:urlString]; + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; + request.HTTPMethod = @"POST"; + NSString *postBody = [NSString stringWithFormat:@"payment_method=%@&return_url=%@", paymentMethodID, returnURL]; + + NSData *data = postBody.length > 0 ? [postBody dataUsingEncoding:NSUTF8StringEncoding] : [NSData data]; + + NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request + fromData:data + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + if (!error && httpResponse.statusCode != 200) { + NSString *errorMessage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ?: @"There was an error connecting to your payment backend."; + error = [NSError errorWithDomain:@"MyAPIClientErrorDomain" + code:STPInvalidRequestError + userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; + } + if (error || data == nil) { + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, error); }]; + } + else { + NSError *jsonError = nil; + id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; + + if (json && + [json isKindOfClass:[NSDictionary class]] && + [json[@"secret"] isKindOfClass:[NSString class]]) { + [self _callOnMainThread:^{ completion(MyAPIClientResultSuccess, json[@"secret"], nil); }]; + } + else { + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, jsonError); }]; + } + } + }]; + + [uploadTask resume]; +} + +@end diff --git a/Example/Custom Integration/README.md b/Example/Custom Integration/README.md index f5bcfd99f1d..e5190560bbf 100644 --- a/Example/Custom Integration/README.md +++ b/Example/Custom Integration/README.md @@ -4,12 +4,10 @@ This example app demonstrates how to to use `STPAPIClient` to accept various pay For a detailed guide, see https://stripe.com/docs/mobile/ios/custom -For more details on using Sources, see https://stripe.com/docs/mobile/ios/sources - 1. If you haven't already, sign up for a [Stripe account](https://dashboard.stripe.com/register) (it takes seconds). Then go to https://dashboard.stripe.com/account/apikeys. 2. Open `./Stripe.xcworkspace` (not `./Stripe.xcodeproj`) with Xcode 3. Fill in the `stripePublishableKey` constant in `./Example/Custom Integration/Constants.m` with your test "Publishable key" from Stripe. This key should start with `pk_test`. -4. Head to [example-ios-backend](https://github.com/stripe/example-ios-backend/tree/v16.0.0) and click "Deploy to Heroku". Provide your Stripe test "Secret key" as the `STRIPE_TEST_SECRET_KEY` environment variable. This key should start with `sk_test`. +4. Head to [example-ios-backend](https://github.com/stripe/example-ios-backend/tree/v17.0.0) and click "Deploy to Heroku". Provide your Stripe test "Secret key" as the `STRIPE_TEST_SECRET_KEY` environment variable. This key should start with `sk_test`. 5. Fill in the `backendBaseURL` constant in `Constants.m` with the app URL Heroku provides (e.g. "https://my-example-app.herokuapp.com") After this is done, you can make test payments through the app and see them in your Stripe dashboard. diff --git a/Example/Standard Integration/CheckoutViewController.swift b/Example/Standard Integration/CheckoutViewController.swift index 99e59bf1184..88d7149f8a0 100644 --- a/Example/Standard Integration/CheckoutViewController.swift +++ b/Example/Standard Integration/CheckoutViewController.swift @@ -9,14 +9,14 @@ import UIKit import Stripe -class CheckoutViewController: UIViewController, STPPaymentContextDelegate { +class CheckoutViewController: UIViewController { // 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. var stripePublishableKey = "" // 2) Next, optionally, to have this demo save your user's payment details, head to - // https://github.com/stripe/example-ios-backend/tree/v16.0.0, click "Deploy to Heroku", and follow + // https://github.com/stripe/example-ios-backend/tree/v17.0.0, click "Deploy to Heroku", and follow // the instructions (don't worry, it's free). Replace nil on the line below with your // Heroku URL (it looks like https://blazing-sunrise-1234.herokuapp.com ). var backendBaseURL: String? = nil @@ -30,7 +30,7 @@ class CheckoutViewController: UIViewController, STPPaymentContextDelegate { let paymentCurrency = "usd" let paymentContext: STPPaymentContext - + let theme: STPTheme let tableView: UITableView let paymentRow: CheckoutRowView @@ -247,61 +247,50 @@ See https://stripe.com/docs/testing. self.paymentInProgress = true self.paymentContext.requestPayment() } +} - // MARK: STPPaymentContextDelegate +// MARK: STPPaymentContextDelegate +extension CheckoutViewController: STPPaymentContextDelegate { + enum CheckoutError: Error { + case unknown + var localizedDescription: String { + switch self { + case .unknown: + return "Unknown error" + } + } + } func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPPaymentStatusBlock) { - MyAPIClient.sharedClient.createAndConfirmPaymentIntent(paymentResult, - amount: self.paymentContext.paymentAmount, - returnURL: "payments-example://stripe-redirect", - shippingAddress: self.paymentContext.shippingAddress, - shippingMethod: self.paymentContext.selectedShippingMethod) { (clientSecret, error) in - guard let clientSecret = clientSecret else { - completion(.error, error ?? NSError(domain: StripeDomain, code: 123, userInfo: [NSLocalizedDescriptionKey: "Unable to parse clientSecret from response"])) - return - } - STPPaymentHandler.shared().handleNextAction(forPayment: clientSecret, authenticationContext: paymentContext, returnURL: "payments-example://stripe-redirect") { (status, handledPaymentIntent, actionError) in - switch (status) { - case .succeeded: - guard let handledPaymentIntent = handledPaymentIntent else { - completion(.error, actionError ?? NSError(domain: StripeDomain, code: 123, userInfo: [NSLocalizedDescriptionKey: "Unknown failure"])) - return - } - if (handledPaymentIntent.status == .requiresConfirmation) { - // Confirm again on the backend - MyAPIClient.sharedClient.confirmPaymentIntent(handledPaymentIntent) { clientSecret, error in - guard let clientSecret = clientSecret else { - completion(.error, error ?? NSError(domain: StripeDomain, code: 123, userInfo: [NSLocalizedDescriptionKey: "Unable to parse clientSecret from response"])) - return - } - - // Retrieve the Payment Intent and check the status for success - STPAPIClient.shared().retrievePaymentIntent(withClientSecret: clientSecret) { (paymentIntent, retrieveError) in - guard let paymentIntent = paymentIntent else { - completion(.error, retrieveError ?? NSError(domain: StripeDomain, code: 123, userInfo: [NSLocalizedDescriptionKey: "Unable to parse payment intent from response"])) - return - } - - if paymentIntent.status == .succeeded { - completion(.success, nil) - } - else { - completion(.error, NSError(domain: StripeDomain, code: 123, userInfo: [NSLocalizedDescriptionKey: "Authentication failed."])) - } - } - } - } else { - // Success - completion(.success, nil) - } - case .failed: - completion(.error, actionError) - case .canceled: - completion(.userCancellation, nil) - @unknown default: - completion(.error, nil) - } - } + // Create the PaymentIntent on the backend + // To speed this up, create the PaymentIntent earlier in the checkout flow and update it as necessary (e.g. when the cart subtotal updates or when shipping fees and taxes are calculated, instead of re-creating a PaymentIntent for every payment attempt. + MyAPIClient.sharedClient.createPaymentIntent(products: self.products, shippingMethod: paymentContext.selectedShippingMethod) { result in + switch result { + case .success(let clientSecret): + // Confirm the PaymentIntent + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret) + paymentIntentParams.paymentMethodId = paymentResult.paymentMethod.stripeId + paymentIntentParams.returnURL = "payments-example://stripe-redirect" + STPPaymentHandler.shared().confirmPayment(withParams: paymentIntentParams, authenticationContext: paymentContext) { status, paymentIntent, error in + switch status { + case .succeeded: + // Our example backend asynchronously fulfills the customer's order via webhook + // See https://stripe.com/docs/payments/payment-intents/ios#fulfillment + completion(.success, nil) + case .failed: + completion(.error, error) + case .canceled: + completion(.userCancellation, nil) + @unknown default: + completion(.error, nil) + } + } + case .failure(let error): + // A real app should retry this request if it was a network error. + print("Failed to create a Payment Intent: \(error)") + completion(.error, error) + break + } } } @@ -394,6 +383,7 @@ See https://stripe.com/docs/testing. } else { fedEx.amount = 20.99 + fedEx.identifier = "fedex_world" completion(.valid, nil, [upsWorldwide, fedEx], fedEx) } } diff --git a/Example/Standard Integration/MyAPIClient.swift b/Example/Standard Integration/MyAPIClient.swift index 216680e8ddd..5f835642e44 100644 --- a/Example/Standard Integration/MyAPIClient.swift +++ b/Example/Standard Integration/MyAPIClient.swift @@ -10,6 +10,16 @@ import Foundation import Stripe class MyAPIClient: NSObject, STPCustomerEphemeralKeyProvider { + enum APIError: Error { + case unknown + + var localizedDescription: String { + switch self { + case .unknown: + return "Unknown error" + } + } + } static let sharedClient = MyAPIClient() var baseURLString: String? = nil @@ -20,48 +30,21 @@ class MyAPIClient: NSObject, STPCustomerEphemeralKeyProvider { fatalError() } } - - func createAndConfirmPaymentIntent(_ result: STPPaymentResult, - amount: Int, - returnURL: String, - shippingAddress: STPAddress?, - shippingMethod: PKShippingMethod?, - completion: @escaping ((_ clientSecret: String?, _ error: Error?)->Void)) { - let url = self.baseURL.appendingPathComponent("capture_payment") + + func createPaymentIntent(products: [Product], shippingMethod: PKShippingMethod?, completion: @escaping ((Result) -> Void)) { + let url = self.baseURL.appendingPathComponent("create_payment_intent") var params: [String: Any] = [ - "payment_method": result.paymentMethod.stripeId, - "amount": amount, - "return_url": returnURL, "metadata": [ // example-ios-backend allows passing metadata through to Stripe "payment_request_id": "B3E611D1-5FA1-4410-9CEC-00958A5126CB", ], - ] - params["shipping"] = STPAddress.shippingInfoForCharge(with: shippingAddress, shippingMethod: shippingMethod) - - let jsonData = try? JSONSerialization.data(withJSONObject: params) - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = jsonData - let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in - guard let response = response as? HTTPURLResponse, - response.statusCode == 200, - let data = data, - let json = ((try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]) as [String : Any]??), - let secret = json?["secret"] as? String else { - completion(nil, error) - return - } - completion(secret, nil) + ] + params["products"] = products.map({ (p) -> String in + return p.emoji }) - task.resume() - } - - func confirmPaymentIntent(_ paymentIntent: STPPaymentIntent, completion: @escaping ((_ clientSecret: String?, _ error: Error?)->Void)) { - let url = self.baseURL.appendingPathComponent("confirm_payment") - let params: [String: Any] = ["payment_intent_id": paymentIntent.stripeId] - + if let shippingMethod = shippingMethod { + params["shipping"] = shippingMethod.identifier + } let jsonData = try? JSONSerialization.data(withJSONObject: params) var request = URLRequest(url: url) request.httpMethod = "POST" @@ -73,10 +56,10 @@ class MyAPIClient: NSObject, STPCustomerEphemeralKeyProvider { let data = data, let json = ((try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]) as [String : Any]??), let secret = json?["secret"] as? String else { - completion(nil, error) - return + completion(.failure(error ?? APIError.unknown)) + return } - completion(secret, nil) + completion(.success(secret)) }) task.resume() } diff --git a/Example/Standard Integration/README.md b/Example/Standard Integration/README.md index 9ef73e28b9c..0eca8236163 100644 --- a/Example/Standard Integration/README.md +++ b/Example/Standard Integration/README.md @@ -7,7 +7,7 @@ For a detailed guide, see https://stripe.com/docs/mobile/ios/standard 1. If you haven't already, sign up for a [Stripe account](https://dashboard.stripe.com/register) (it takes seconds). Then go to https://dashboard.stripe.com/account/apikeys. 2. Open `./Stripe.xcworkspace` (not `./Stripe.xcodeproj`) with Xcode 3. Fill in the `stripePublishableKey` constant in `./Example/Standard Integration/CheckoutViewController.swift` with your test "Publishable key" from Stripe. This key should start with `pk_test`. -4. Head to [example-ios-backend](https://github.com/stripe/example-ios-backend/tree/v16.0.0) and click "Deploy to Heroku". Provide your Stripe test "Secret key" as the `STRIPE_TEST_SECRET_KEY` environment variable. This key should start with `pk_test`. +4. Head to [example-ios-backend](https://github.com/stripe/example-ios-backend/tree/v17.0.0) and click "Deploy to Heroku". Provide your Stripe test "Secret key" as the `STRIPE_TEST_SECRET_KEY` environment variable. This key should start with `pk_test`. 5. Fill in the `backendBaseURL` constant in `./Example/Standard Integration/CheckoutViewController.swift` with the app URL Heroku provides (e.g. "https://my-example-app.herokuapp.com") After this is done, you can make test payments through the app and see them in your Stripe dashboard. diff --git a/Example/StandardIntegrationUITests/StandardIntegrationUITests.swift b/Example/StandardIntegrationUITests/StandardIntegrationUITests.swift index 400a0f1ef1d..c6944e82e69 100644 --- a/Example/StandardIntegrationUITests/StandardIntegrationUITests.swift +++ b/Example/StandardIntegrationUITests/StandardIntegrationUITests.swift @@ -17,7 +17,7 @@ class StandardIntegrationUITests: XCTestCase { // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. let app = XCUIApplication() let stripePublishableKey = "pk_test_6Q7qTzl8OkUj5K5ArgayVsFD00Sa5AHMj3" - let backendBaseURL = "https://stripe-mobile-test-backend.herokuapp.com/" + let backendBaseURL = "https://stripe-mobile-test-backend-17.herokuapp.com/" app.launchArguments.append(contentsOf: ["-StripePublishableKey", stripePublishableKey, "-StripeBackendBaseURL", backendBaseURL]) app.launch() } diff --git a/Stripe/PublicHeaders/STPAddCardViewController.h b/Stripe/PublicHeaders/STPAddCardViewController.h index 17fdf9d99b6..5b75d89dcba 100644 --- a/Stripe/PublicHeaders/STPAddCardViewController.h +++ b/Stripe/PublicHeaders/STPAddCardViewController.h @@ -11,7 +11,6 @@ #import "STPAPIClient.h" #import "STPAddress.h" #import "STPBlocks.h" -#import "STPCardParams.h" #import "STPCoreTableViewController.h" #import "STPPaymentConfiguration.h" #import "STPTheme.h" diff --git a/Stripe/PublicHeaders/STPEphemeralKeyProvider.h b/Stripe/PublicHeaders/STPEphemeralKeyProvider.h index c44cd10f70c..272045c9293 100644 --- a/Stripe/PublicHeaders/STPEphemeralKeyProvider.h +++ b/Stripe/PublicHeaders/STPEphemeralKeyProvider.h @@ -25,7 +25,7 @@ NS_ASSUME_NONNULL_BEGIN On your backend, you should create a new ephemeral key for the Stripe customer associated with your user, and return the raw JSON response from the Stripe API. For an example Ruby implementation of this API, refer to our example backend: - https://github.com/stripe/example-ios-backend/blob/v14.0.0/web.rb + https://github.com/stripe/example-ios-backend/blob/v17.0.0/web.rb Back in your iOS app, once you have a response from this API, call the provided completion block with the JSON response, or an error if one occurred. @@ -52,7 +52,7 @@ NS_ASSUME_NONNULL_BEGIN On your backend, you should create a new ephemeral key for your logged-in user's primary Issuing Card, and return the raw JSON response from the Stripe API. For an example Ruby implementation of this API, refer to our example backend: - https://github.com/stripe/example-ios-backend/blob/v14.0.0/web.rb + https://github.com/stripe/example-ios-backend/blob/v17.0.0/web.rb Back in your iOS app, once you have a response from this API, call the provided completion block with the JSON response, or an error if one occurred. diff --git a/Stripe/PublicHeaders/STPPaymentCardTextField.h b/Stripe/PublicHeaders/STPPaymentCardTextField.h index f41ce6139c6..80552c3263b 100644 --- a/Stripe/PublicHeaders/STPPaymentCardTextField.h +++ b/Stripe/PublicHeaders/STPPaymentCardTextField.h @@ -231,13 +231,13 @@ The curent brand image displayed in the receiver. @property (nonatomic, copy, nullable) NSString *countryCode; /** - Convenience property for creating an STPCardParams from the currently entered information + Convenience property for creating an `STPPaymentMethodCardParams` from the currently entered information or programmatically setting the field's contents. For example, if you're using another library - to scan your user's credit card with a camera, you can assemble that data into an STPCardParams + to scan your user's credit card with a camera, you can assemble that data into an `STPPaymentMethodCardParams` object and set this property to that object to prefill the fields you've collected. Accessing this property returns a *copied* `cardParams`. The only way to change properties in this - object is to make changes to a STPCardParams you own (retrieved from this text field if desired), + object is to make changes to a `STPPaymentMethodCardParams` you own (retrieved from this text field if desired), and then set this property to the new value. */ @property (nonatomic, copy, readwrite, nonnull) STPPaymentMethodCardParams *cardParams;