From f4b37e6d506a63967d40554125725614380a597b Mon Sep 17 00:00:00 2001 From: Yuki Tokuhiro Date: Wed, 4 Sep 2019 12:49:14 -0700 Subject: [PATCH 01/12] Remove some outdated STPCardParams references --- Stripe/PublicHeaders/STPAddCardViewController.h | 1 - Stripe/PublicHeaders/STPPaymentCardTextField.h | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) 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/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; From f7c9b9e92c841a0612e5fde0f60e082bd0276fc7 Mon Sep 17 00:00:00 2001 From: Yuki Tokuhiro Date: Wed, 4 Sep 2019 14:39:39 -0700 Subject: [PATCH 02/12] Migrate STPPaymentContext to automatic confirmation, the default recommendation. Dont pass amount from the client - security antipattern --- .../CheckoutViewController.swift | 95 ++++++++----------- .../Standard Integration/MyAPIClient.swift | 58 ++++------- 2 files changed, 59 insertions(+), 94 deletions(-) diff --git a/Example/Standard Integration/CheckoutViewController.swift b/Example/Standard Integration/CheckoutViewController.swift index 99e59bf1184..5ab1627ec2b 100644 --- a/Example/Standard Integration/CheckoutViewController.swift +++ b/Example/Standard Integration/CheckoutViewController.swift @@ -9,7 +9,7 @@ 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. @@ -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,48 @@ 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 + // A real app should do this at the beginning of the checkout flow, instead of re-creating a PaymentIntent for every payment attempt. + MyAPIClient.sharedClient.createPaymentIntent() { result in + switch result { + case .success(let clientSecret): + // Confirm the PaymentIntent + let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret) + paymentIntentParams.paymentMethodId = paymentResult.paymentMethod.stripeId + 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 ?? CheckoutError.unknown) + 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)") + break + } } } diff --git a/Example/Standard Integration/MyAPIClient.swift b/Example/Standard Integration/MyAPIClient.swift index 216680e8ddd..77266e2639d 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,16 @@ 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") - var params: [String: Any] = [ - "payment_method": result.paymentMethod.stripeId, - "amount": amount, - "return_url": returnURL, + + func createPaymentIntent(completion: @escaping ((Result) -> Void)) { + let url = self.baseURL.appendingPathComponent("create_payment_intent") + let params: [String: Any] = [ "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) - }) - 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] - let jsonData = try? JSONSerialization.data(withJSONObject: params) var request = URLRequest(url: url) request.httpMethod = "POST" @@ -73,10 +51,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() } From 7789c54cd443ee1397f09a769b508979640f68b5 Mon Sep 17 00:00:00 2001 From: Yuki Tokuhiro Date: Fri, 6 Sep 2019 10:34:41 -0700 Subject: [PATCH 03/12] Make things a bit more like example code while I'm here... --- .../project.pbxproj | 8 +- .../ApplePayExampleViewController.m | 6 +- .../BrowseExamplesViewController.h | 24 -- .../BrowseExamplesViewController.m | 258 ---------------- .../CardAutomaticConfirmationViewController.m | 6 +- ...dManualConfirmationExampleViewController.m | 23 +- ...dSetupIntentBackendExampleViewController.m | 12 +- .../CardSetupIntentExampleViewController.m | 60 ++-- Example/Custom Integration/ExampleAPIClient.h | 88 ++++++ Example/Custom Integration/ExampleAPIClient.m | 276 ++++++++++++++++++ 10 files changed, 431 insertions(+), 330 deletions(-) create mode 100644 Example/Custom Integration/ExampleAPIClient.h create mode 100644 Example/Custom Integration/ExampleAPIClient.m diff --git a/Example/Custom Integration.xcodeproj/project.pbxproj b/Example/Custom Integration.xcodeproj/project.pbxproj index 1c92d321f51..7bf5213b3bb 100644 --- a/Example/Custom Integration.xcodeproj/project.pbxproj +++ b/Example/Custom Integration.xcodeproj/project.pbxproj @@ -20,6 +20,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 /* ExampleAPIClient.m in Sources */ = {isa = PBXBuildFile; fileRef = B607FFBC2321DA99004203E0 /* ExampleAPIClient.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 */; }; @@ -71,6 +72,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 /* ExampleAPIClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExampleAPIClient.h; sourceTree = ""; }; + B607FFBC2321DA99004203E0 /* ExampleAPIClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExampleAPIClient.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 = ""; }; @@ -102,10 +105,10 @@ 04533E7E1A687F5D00C7E52E = { isa = PBXGroup; children = ( - 366F93AF225FF2A2005CFBF6 /* README.md */, 04533E891A687F5D00C7E52E /* Custom Integration */, 04533F0E1A68813100C7E52E /* Frameworks */, 04533E881A687F5D00C7E52E /* Products */, + 366F93AF225FF2A2005CFBF6 /* README.md */, ); sourceTree = ""; }; @@ -136,6 +139,8 @@ B66AC61D22CAAB8F0064C551 /* CardSetupIntentExampleViewController.m */, 04533F171A688A0A00C7E52E /* Constants.h */, 04533F181A688A0A00C7E52E /* Constants.m */, + B607FFBB2321DA99004203E0 /* ExampleAPIClient.h */, + B607FFBC2321DA99004203E0 /* ExampleAPIClient.m */, 04533E971A687F5D00C7E52E /* Images.xcassets */, 8BBD79C8207FD2F900F85BED /* Localizable.strings */, 04533EB01A68802E00C7E52E /* ShippingManager.h */, @@ -281,6 +286,7 @@ 04533E901A687F5D00C7E52E /* AppDelegate.m in Sources */, B65E8FCC22FA078A0057E64A /* WeChatPayExampleViewController.m in Sources */, C1CACE891E5DF7A9002D0821 /* ApplePayExampleViewController.m in Sources */, + B607FFBD2321DA99004203E0 /* ExampleAPIClient.m in Sources */, 04533E8D1A687F5D00C7E52E /* main.m in Sources */, C12C50DD1E57B3C800EC6D58 /* BrowseExamplesViewController.m in Sources */, ); diff --git a/Example/Custom Integration/ApplePayExampleViewController.m b/Example/Custom Integration/ApplePayExampleViewController.m index a369d996fa0..bad47269dc6 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 "ExampleAPIClient.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) { + [[ExampleAPIClient 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 6a79baf0866..0b99842a192 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 "WeChatPayExampleViewController.h" @@ -118,263 +117,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..d9406c0a616 100644 --- a/Example/Custom Integration/CardAutomaticConfirmationViewController.m +++ b/Example/Custom Integration/CardAutomaticConfirmationViewController.m @@ -11,6 +11,8 @@ #import "CardAutomaticConfirmationViewController.h" #import "BrowseExamplesViewController.h" +#import "ExampleAPIClient.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) { + [[ExampleAPIClient 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..ca6778ea8ea 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 "ExampleAPIClient.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,8 +127,8 @@ - (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) { + [[ExampleAPIClient sharedClient] confirmPaymentIntent:paymentIntent.stripeId completion:^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { + if (status == MyAPIClientResultFailure || error) { [self.delegate exampleViewController:self didFinishWithError:error]; return; } @@ -144,8 +150,8 @@ - (void)_createAndConfirmPaymentIntentWithPaymentMethod:(STPPaymentMethod *)paym } } }; - STPPaymentIntentCreateAndConfirmHandler createAndConfirmCompletion = ^(STPBackendResult status, NSString *clientSecret, NSError *error) { - if (status == STPBackendResultFailure || error) { + STPPaymentIntentCreateAndConfirmHandler createAndConfirmCompletion = ^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { + if (status == MyAPIClientResultFailure || error) { [self.delegate exampleViewController:self didFinishWithError:error]; return; } @@ -154,10 +160,9 @@ - (void)_createAndConfirmPaymentIntentWithPaymentMethod:(STPPaymentMethod *)paym returnURL:@"payments-example://stripe-redirect" completion:paymentHandlerCompletion]; }; - [self.delegate createAndConfirmPaymentIntentWithAmount:@(100) - paymentMethod:paymentMethod.stripeId - returnURL:@"payments-example://stripe-redirect" - completion:createAndConfirmCompletion]; + [[ExampleAPIClient 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..b82ccbe4511 100644 --- a/Example/Custom Integration/CardSetupIntentBackendExampleViewController.m +++ b/Example/Custom Integration/CardSetupIntentBackendExampleViewController.m @@ -9,6 +9,8 @@ #import "CardSetupIntentBackendExampleViewController.h" #import "BrowseExamplesViewController.h" +#import "ExampleAPIClient.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]; + [[ExampleAPIClient sharedClient] createSetupIntentWithPaymentMethod: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..38360b2c415 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 "ExampleAPIClient.h" /** This example demonstrates using SetupIntents to accept card payments verified using 3D Secure. @@ -122,35 +124,35 @@ - (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; - } - }]; - - }]; + [[ExampleAPIClient sharedClient] createSetupIntentWithPaymentMethod:nil + returnURL:@"payment-example://stripe-redirect" + completion:^(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/ExampleAPIClient.h b/Example/Custom Integration/ExampleAPIClient.h new file mode 100644 index 00000000000..aa5916ca89e --- /dev/null +++ b/Example/Custom Integration/ExampleAPIClient.h @@ -0,0 +1,88 @@ +// +// 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, NSString * _Nullable clientSecret, NSError * _Nullable error); +typedef void (^STPConfirmPaymentIntentCompletionHandler)(MyAPIClientResult status, NSString * _Nullable clientSecret, NSError * _Nullable error); +typedef void (^STPCreateSetupIntentCompletionHandler)(MyAPIClientResult status, NSString * _Nullable clientSecret, NSError * _Nullable error); + +@interface ExampleAPIClient : NSObject + ++ (instancetype)sharedClient; + +/** + 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; + +/** + 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 & the client secret if successful. + */ +- (void)confirmPaymentIntent:(NSString *)paymentIntentID completion:(STPConfirmPaymentIntentCompletionHandler)completion; + + +/** + Asks our example backend to confirm a SetupIntent. + + 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 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 If non-nil, this will also confirm the SetupIntent on the backend + + */ +- (void)createSetupIntentWithPaymentMethod:(nullable NSString *)paymentMethodID + returnURL:(NSString *)returnURL + completion:(STPCreateSetupIntentCompletionHandler)completion; +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Custom Integration/ExampleAPIClient.m b/Example/Custom Integration/ExampleAPIClient.m new file mode 100644 index 00000000000..88eef5bf6ee --- /dev/null +++ b/Example/Custom Integration/ExampleAPIClient.m @@ -0,0 +1,276 @@ +// +// ExampleAPIClient.m +// Custom Integration +// +// Created by Yuki Tokuhiro on 9/5/19. +// Copyright © 2019 Stripe. All rights reserved. +// +@import Stripe; + +#import "ExampleAPIClient.h" + +#import "Constants.h" + +@implementation ExampleAPIClient + ++ (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(); + }); + } +} + +/** + 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_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]; +} + +- (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, 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=%@&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, 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(MyAPIClientResultSuccess, clientSecret, nil); }]; + } else { + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, [NSError errorWithDomain:@"MyAPIClientErrorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey: @"There was an error parsing your backend response to a client secret."}]); }]; + } + } else { + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, jsonError); }]; + } + } + }]; + + [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, 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=%@", 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, 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(MyAPIClientResultSuccess, clientSecret, nil); }]; + } else { + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, [NSError errorWithDomain:@"MyAPIClientErrorDomain" + code:0 + userInfo:@{NSLocalizedDescriptionKey: @"There was an error parsing your backend response to a client secret."}]); }]; + } + } else { + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, jsonError); }]; + } + } + }]; + + [uploadTask resume]; +} + +- (void)createSetupIntentWithPaymentMethod:(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 = @""; + + 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:@"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 From 9ff28f806d143755968170eb6331854dda129d48 Mon Sep 17 00:00:00 2001 From: Yuki Tokuhiro Date: Fri, 6 Sep 2019 15:51:40 -0700 Subject: [PATCH 04/12] Update manual confirmation --- Example/Custom Integration/AppDelegate.m | 5 ++ ...dManualConfirmationExampleViewController.m | 32 +++++------- Example/Custom Integration/ExampleAPIClient.h | 10 +++- Example/Custom Integration/ExampleAPIClient.m | 49 +++++++++++-------- 4 files changed, 54 insertions(+), 42 deletions(-) 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/CardManualConfirmationExampleViewController.m b/Example/Custom Integration/CardManualConfirmationExampleViewController.m index ca6778ea8ea..3e791b5bee0 100644 --- a/Example/Custom Integration/CardManualConfirmationExampleViewController.m +++ b/Example/Custom Integration/CardManualConfirmationExampleViewController.m @@ -127,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. - [[ExampleAPIClient sharedClient] confirmPaymentIntent:paymentIntent.stripeId completion:^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { + [[ExampleAPIClient 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 { @@ -150,19 +140,23 @@ - (void)_createAndConfirmPaymentIntentWithPaymentMethod:(STPPaymentMethod *)paym } } }; - STPPaymentIntentCreateAndConfirmHandler createAndConfirmCompletion = ^(MyAPIClientResult status, NSString *clientSecret, NSError *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"]; + } }; [[ExampleAPIClient sharedClient] createAndConfirmPaymentIntentWithPaymentMethod:paymentMethod.stripeId - returnURL:@"payments-example://stripe-redirect" - completion:createAndConfirmCompletion]; + returnURL:@"payments-example://stripe-redirect" + completion:createAndConfirmCompletion]; } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { diff --git a/Example/Custom Integration/ExampleAPIClient.h b/Example/Custom Integration/ExampleAPIClient.h index aa5916ca89e..ea210de4a88 100644 --- a/Example/Custom Integration/ExampleAPIClient.h +++ b/Example/Custom Integration/ExampleAPIClient.h @@ -16,14 +16,16 @@ typedef NS_ENUM(NSInteger, MyAPIClientResult) { }; typedef void (^STPPaymentIntentCreationHandler)(MyAPIClientResult status, NSString * _Nullable clientSecret, NSError * _Nullable error); -typedef void (^STPPaymentIntentCreateAndConfirmHandler)(MyAPIClientResult status, NSString * _Nullable clientSecret, NSError * _Nullable error); -typedef void (^STPConfirmPaymentIntentCompletionHandler)(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 ExampleAPIClient : NSObject + (instancetype)sharedClient; +#pragma mark - PaymentIntents (automatic confirmation) + /** Asks our example backend to create and confirm a PaymentIntent using automatic confirmation. @@ -36,6 +38,8 @@ typedef void (^STPCreateSetupIntentCompletionHandler)(MyAPIClientResult status, */ - (void)createPaymentIntentWithCompletion:(STPPaymentIntentCreationHandler)completion; +#pragma mark - PaymentIntents (manual confirmation) + /** Asks our example backend to create and confirm a PaymentIntent using manual confirmation. @@ -67,6 +71,8 @@ typedef void (^STPCreateSetupIntentCompletionHandler)(MyAPIClientResult status, - (void)confirmPaymentIntent:(NSString *)paymentIntentID completion:(STPConfirmPaymentIntentCompletionHandler)completion; +#pragma mark - SetupIntents + /** Asks our example backend to confirm a SetupIntent. diff --git a/Example/Custom Integration/ExampleAPIClient.m b/Example/Custom Integration/ExampleAPIClient.m index 88eef5bf6ee..9ad9160fa51 100644 --- a/Example/Custom Integration/ExampleAPIClient.m +++ b/Example/Custom Integration/ExampleAPIClient.m @@ -30,6 +30,8 @@ - (void)_callOnMainThread:(void (^)(void))block { } } +#pragma mark - PaymentIntents (automatic confirmation) + /** Ask the example backend to create a PaymentIntent with the specified amount. @@ -53,7 +55,7 @@ - (void)createPaymentIntentWithCompletion:(STPPaymentIntentCreationHandler)compl NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; - NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"create_intent"]; + NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"create_payment_intent"]; NSURL *url = [NSURL URLWithString:urlString]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; request.HTTPMethod = @"POST"; @@ -95,6 +97,8 @@ - (void)createPaymentIntentWithCompletion:(STPPaymentIntentCreationHandler)compl [uploadTask resume]; } +#pragma mark - PaymentIntents (manual confirmation) + - (void)createAndConfirmPaymentIntentWithPaymentMethod:(NSString *)paymentMethodID returnURL:(NSString *)returnURL completion:(STPPaymentIntentCreateAndConfirmHandler)completion { @@ -102,7 +106,7 @@ - (void)createAndConfirmPaymentIntentWithPaymentMethod:(NSString *)paymentMethod 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); }]; + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, NO, nil, error); }]; return; } @@ -115,7 +119,7 @@ - (void)createAndConfirmPaymentIntentWithPaymentMethod:(NSString *)paymentMethod NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; request.HTTPMethod = @"POST"; NSString *postBody = [NSString stringWithFormat: - @"payment_method=%@&return_url=%@", + @"payment_method_id=%@&return_url=%@", paymentMethodID, returnURL]; NSData *data = [postBody dataUsingEncoding:NSUTF8StringEncoding]; @@ -131,23 +135,24 @@ - (void)createAndConfirmPaymentIntentWithPaymentMethod:(NSString *)paymentMethod userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; } if (error) { - [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, 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]]) { - NSString *clientSecret = json[@"secret"]; - if (clientSecret != nil) { - [self _callOnMainThread:^{ completion(MyAPIClientResultSuccess, clientSecret, nil); }]; - } else { - [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, [NSError errorWithDomain:@"MyAPIClientErrorDomain" - code:0 - userInfo:@{NSLocalizedDescriptionKey: @"There was an error parsing your backend response to a client secret."}]); }]; + 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; } - } else { - [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, jsonError); }]; } + [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."}]); }]; } }]; @@ -159,7 +164,7 @@ - (void)confirmPaymentIntent:(NSString *)paymentIntentID completion:(STPConfirmP 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); }]; + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, error); }]; return; } @@ -167,7 +172,7 @@ - (void)confirmPaymentIntent:(NSString *)paymentIntentID completion:(STPConfirmP NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; - NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"confirm_payment"]; + NSString *urlString = [BackendBaseURL stringByAppendingPathComponent:@"confirm_payment_intent"]; NSURL *url = [NSURL URLWithString:urlString]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; request.HTTPMethod = @"POST"; @@ -185,22 +190,22 @@ - (void)confirmPaymentIntent:(NSString *)paymentIntentID completion:(STPConfirmP userInfo:@{NSLocalizedDescriptionKey: errorMessage}]; } if (error || data == nil) { - [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, error); }]; + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, 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(MyAPIClientResultSuccess, clientSecret, nil); }]; + NSNumber *success = json[@"success"]; + if ([success boolValue]) { + [self _callOnMainThread:^{ completion(MyAPIClientResultSuccess, nil); }]; } else { - [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, nil, [NSError errorWithDomain:@"MyAPIClientErrorDomain" + [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, nil, jsonError); }]; + [self _callOnMainThread:^{ completion(MyAPIClientResultFailure, jsonError); }]; } } }]; @@ -208,6 +213,8 @@ - (void)confirmPaymentIntent:(NSString *)paymentIntentID completion:(STPConfirmP [uploadTask resume]; } +#pragma mark - SetupIntents + - (void)createSetupIntentWithPaymentMethod:(NSString *)paymentMethodID returnURL:(NSString *)returnURL completion:(STPCreateSetupIntentCompletionHandler)completion { From d576fb03e0e480c7a85d7cb6c434a3277293dfcf Mon Sep 17 00:00:00 2001 From: Yuki Tokuhiro Date: Fri, 6 Sep 2019 16:54:38 -0700 Subject: [PATCH 05/12] Update setupintent --- ...dSetupIntentBackendExampleViewController.m | 6 +- .../CardSetupIntentExampleViewController.m | 58 ++++++++--------- Example/Custom Integration/ExampleAPIClient.h | 33 +++++++--- Example/Custom Integration/ExampleAPIClient.m | 64 +++++++++++++++---- 4 files changed, 105 insertions(+), 56 deletions(-) diff --git a/Example/Custom Integration/CardSetupIntentBackendExampleViewController.m b/Example/Custom Integration/CardSetupIntentBackendExampleViewController.m index b82ccbe4511..f5b206c09b6 100644 --- a/Example/Custom Integration/CardSetupIntentBackendExampleViewController.m +++ b/Example/Custom Integration/CardSetupIntentBackendExampleViewController.m @@ -166,9 +166,9 @@ - (void)_createAndConfirmSetupIntentWithPaymentMethod:(STPPaymentMethod *)paymen completion:paymentHandlerCompletion]; }; - [[ExampleAPIClient sharedClient] createSetupIntentWithPaymentMethod:paymentMethod.stripeId - returnURL:@"payments-example://stripe-redirect" - completion:createCompletion]; + [[ExampleAPIClient 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 38360b2c415..eb9982d5487 100644 --- a/Example/Custom Integration/CardSetupIntentExampleViewController.m +++ b/Example/Custom Integration/CardSetupIntentExampleViewController.m @@ -44,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]; @@ -124,35 +124,33 @@ - (void)pay { return; } [self updateUIForPaymentInProgress:YES]; - [[ExampleAPIClient sharedClient] createSetupIntentWithPaymentMethod:nil - returnURL:@"payment-example://stripe-redirect" - completion:^(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; - } - }]; - - }]; + [[ExampleAPIClient 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/ExampleAPIClient.h b/Example/Custom Integration/ExampleAPIClient.h index ea210de4a88..abb5139b757 100644 --- a/Example/Custom Integration/ExampleAPIClient.h +++ b/Example/Custom Integration/ExampleAPIClient.h @@ -59,14 +59,14 @@ typedef void (^STPCreateSetupIntentCompletionHandler)(MyAPIClientResult status, /** 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 & the client secret if successful. + @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; @@ -74,21 +74,34 @@ typedef void (^STPCreateSetupIntentCompletionHandler)(MyAPIClientResult status, #pragma mark - SetupIntents /** - Asks our example backend to confirm a SetupIntent. + Asks our example backend to create a SetupIntent. - The implementation of this function is not interesting or relevant to using PaymentIntents. The + 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 If non-nil, this will also confirm the SetupIntent on the backend + @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)createSetupIntentWithPaymentMethod:(nullable NSString *)paymentMethodID - returnURL:(NSString *)returnURL - completion:(STPCreateSetupIntentCompletionHandler)completion; +- (void)createAndConfirmSetupIntentWithPaymentMethod:(NSString *)paymentMethodID + returnURL:(NSString *)returnURL + completion:(STPCreateSetupIntentCompletionHandler)completion; + @end NS_ASSUME_NONNULL_END diff --git a/Example/Custom Integration/ExampleAPIClient.m b/Example/Custom Integration/ExampleAPIClient.m index 9ad9160fa51..add2205bd33 100644 --- a/Example/Custom Integration/ExampleAPIClient.m +++ b/Example/Custom Integration/ExampleAPIClient.m @@ -215,9 +215,7 @@ - (void)confirmPaymentIntent:(NSString *)paymentIntentID completion:(STPConfirmP #pragma mark - SetupIntents -- (void)createSetupIntentWithPaymentMethod:(NSString *)paymentMethodID - returnURL:(NSString *)returnURL - completion:(STPCreateSetupIntentCompletionHandler)completion { +- (void)createSetupIntentWithCompletion:(STPCreateSetupIntentCompletionHandler)completion { if (!BackendBaseURL) { NSError *error = [NSError errorWithDomain:@"MyAPIClientErrorDomain" code:0 @@ -234,18 +232,59 @@ - (void)createSetupIntentWithPaymentMethod:(NSString *)paymentMethodID NSURL *url = [NSURL URLWithString:urlString]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; request.HTTPMethod = @"POST"; - NSString *postBody = @""; + + 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); }]; + } + } + }]; - 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]]; + [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 @@ -279,5 +318,4 @@ - (void)createSetupIntentWithPaymentMethod:(NSString *)paymentMethodID [uploadTask resume]; } - @end From 4de5ea942da1183dfdf625a7cc5b25a4726bce4f Mon Sep 17 00:00:00 2001 From: Yuki Tokuhiro Date: Tue, 10 Sep 2019 14:52:23 -0700 Subject: [PATCH 06/12] Bump example backend version --- .../StandardIntegrationUITests/StandardIntegrationUITests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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() } From 5c386e689b561c7bdd0c77a187fa469ef86a0013 Mon Sep 17 00:00:00 2001 From: davidme-stripe <52758633+davidme-stripe@users.noreply.github.com> Date: Tue, 10 Sep 2019 15:00:34 -0700 Subject: [PATCH 07/12] Send selected products to backend (#1370) --- .../Standard Integration/CheckoutViewController.swift | 3 ++- Example/Standard Integration/MyAPIClient.swift | 11 ++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Example/Standard Integration/CheckoutViewController.swift b/Example/Standard Integration/CheckoutViewController.swift index 5ab1627ec2b..a238f930d2b 100644 --- a/Example/Standard Integration/CheckoutViewController.swift +++ b/Example/Standard Integration/CheckoutViewController.swift @@ -264,7 +264,7 @@ extension CheckoutViewController: STPPaymentContextDelegate { func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPPaymentStatusBlock) { // Create the PaymentIntent on the backend // A real app should do this at the beginning of the checkout flow, instead of re-creating a PaymentIntent for every payment attempt. - MyAPIClient.sharedClient.createPaymentIntent() { result in + MyAPIClient.sharedClient.createPaymentIntent(products: self.products, shippingMethod: paymentContext.selectedShippingMethod) { result in switch result { case .success(let clientSecret): // Confirm the PaymentIntent @@ -381,6 +381,7 @@ extension CheckoutViewController: STPPaymentContextDelegate { } 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 77266e2639d..5f835642e44 100644 --- a/Example/Standard Integration/MyAPIClient.swift +++ b/Example/Standard Integration/MyAPIClient.swift @@ -31,15 +31,20 @@ class MyAPIClient: NSObject, STPCustomerEphemeralKeyProvider { } } - func createPaymentIntent(completion: @escaping ((Result) -> Void)) { + func createPaymentIntent(products: [Product], shippingMethod: PKShippingMethod?, completion: @escaping ((Result) -> Void)) { let url = self.baseURL.appendingPathComponent("create_payment_intent") - let params: [String: Any] = [ + var params: [String: Any] = [ "metadata": [ // example-ios-backend allows passing metadata through to Stripe "payment_request_id": "B3E611D1-5FA1-4410-9CEC-00958A5126CB", ], ] - + params["products"] = products.map({ (p) -> String in + return p.emoji + }) + if let shippingMethod = shippingMethod { + params["shipping"] = shippingMethod.identifier + } let jsonData = try? JSONSerialization.data(withJSONObject: params) var request = URLRequest(url: url) request.httpMethod = "POST" From 41487b698da04b2c3c2fcebc1a1222045dc09288 Mon Sep 17 00:00:00 2001 From: Yuki Tokuhiro Date: Wed, 11 Sep 2019 17:14:18 -0700 Subject: [PATCH 08/12] Fix msising returnURL in CheckoutViewController.swift --- Example/Standard Integration/CheckoutViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Example/Standard Integration/CheckoutViewController.swift b/Example/Standard Integration/CheckoutViewController.swift index a238f930d2b..f72cd04593c 100644 --- a/Example/Standard Integration/CheckoutViewController.swift +++ b/Example/Standard Integration/CheckoutViewController.swift @@ -270,6 +270,7 @@ extension CheckoutViewController: STPPaymentContextDelegate { // 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: From 61da1b4f6b4f8d398e22e885dca5b8eb4a217c95 Mon Sep 17 00:00:00 2001 From: Yuki Tokuhiro Date: Wed, 11 Sep 2019 17:18:50 -0700 Subject: [PATCH 09/12] Update FPXExampleVC --- Example/Custom Integration/FPXExampleViewController.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Example/Custom Integration/FPXExampleViewController.m b/Example/Custom Integration/FPXExampleViewController.m index 8bd6c350675..feda9207140 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 "ExampleAPIClient.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) { + [[ExampleAPIClient sharedClient] createPaymentIntentWithCompletion:^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { + if (status == MyAPIClientResultFailure || clientSecret == nil) { [self.delegate exampleViewController:self didFinishWithError:error]; return; } From 739fc43a1a13129b592a4547e55aaaf1d0d3c24a Mon Sep 17 00:00:00 2001 From: Yuki Tokuhiro Date: Wed, 11 Sep 2019 17:19:40 -0700 Subject: [PATCH 10/12] Rename ExampleAPIClient -> MyAPIClient --- .../Custom Integration.xcodeproj/project.pbxproj | 16 ++++++++-------- .../ApplePayExampleViewController.m | 4 ++-- .../CardAutomaticConfirmationViewController.m | 4 ++-- ...CardManualConfirmationExampleViewController.m | 6 +++--- ...CardSetupIntentBackendExampleViewController.m | 4 ++-- .../CardSetupIntentExampleViewController.m | 4 ++-- .../FPXExampleViewController.m | 4 ++-- .../{ExampleAPIClient.h => MyAPIClient.h} | 2 +- .../{ExampleAPIClient.m => MyAPIClient.m} | 4 ++-- 9 files changed, 24 insertions(+), 24 deletions(-) rename Example/Custom Integration/{ExampleAPIClient.h => MyAPIClient.h} (99%) rename Example/Custom Integration/{ExampleAPIClient.m => MyAPIClient.m} (99%) diff --git a/Example/Custom Integration.xcodeproj/project.pbxproj b/Example/Custom Integration.xcodeproj/project.pbxproj index d1a87d6b61f..19cc98fcd67 100644 --- a/Example/Custom Integration.xcodeproj/project.pbxproj +++ b/Example/Custom Integration.xcodeproj/project.pbxproj @@ -21,7 +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 /* ExampleAPIClient.m in Sources */ = {isa = PBXBuildFile; fileRef = B607FFBC2321DA99004203E0 /* ExampleAPIClient.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 */; }; @@ -75,8 +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 /* ExampleAPIClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExampleAPIClient.h; sourceTree = ""; }; - B607FFBC2321DA99004203E0 /* ExampleAPIClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExampleAPIClient.m; 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 = ""; }; @@ -142,14 +142,14 @@ B66AC61D22CAAB8F0064C551 /* CardSetupIntentExampleViewController.m */, 04533F171A688A0A00C7E52E /* Constants.h */, 04533F181A688A0A00C7E52E /* Constants.m */, - B607FFBB2321DA99004203E0 /* ExampleAPIClient.h */, - B607FFBC2321DA99004203E0 /* ExampleAPIClient.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 */, @@ -291,7 +291,7 @@ 04533E901A687F5D00C7E52E /* AppDelegate.m in Sources */, B65E8FCC22FA078A0057E64A /* WeChatPayExampleViewController.m in Sources */, C1CACE891E5DF7A9002D0821 /* ApplePayExampleViewController.m in Sources */, - B607FFBD2321DA99004203E0 /* ExampleAPIClient.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/ApplePayExampleViewController.m b/Example/Custom Integration/ApplePayExampleViewController.m index bad47269dc6..f4e15bbceef 100644 --- a/Example/Custom Integration/ApplePayExampleViewController.m +++ b/Example/Custom Integration/ApplePayExampleViewController.m @@ -12,7 +12,7 @@ #import "BrowseExamplesViewController.h" #import "Constants.h" #import "ShippingManager.h" -#import "ExampleAPIClient.h" +#import "MyAPIClient.h" /** This example demonstrates creating a payment using Apple Pay. First, we configure a PKPaymentRequest @@ -171,7 +171,7 @@ - (void)_createAndConfirmPaymentIntentWithPaymentMethod:(STPPaymentMethod *)paym }; // 1. Create a PaymentIntent on the backend. This is typically done at the beginning of the checkout flow. - [[ExampleAPIClient sharedClient] createPaymentIntentWithCompletion:^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { + [[MyAPIClient sharedClient] createPaymentIntentWithCompletion:^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { if (status == MyAPIClientResultFailure || error) { self.applePayError = error; finishWithStatus(PKPaymentAuthorizationStatusFailure); diff --git a/Example/Custom Integration/CardAutomaticConfirmationViewController.m b/Example/Custom Integration/CardAutomaticConfirmationViewController.m index d9406c0a616..30ebe9feac2 100644 --- a/Example/Custom Integration/CardAutomaticConfirmationViewController.m +++ b/Example/Custom Integration/CardAutomaticConfirmationViewController.m @@ -11,7 +11,7 @@ #import "CardAutomaticConfirmationViewController.h" #import "BrowseExamplesViewController.h" -#import "ExampleAPIClient.h" +#import "MyAPIClient.h" /** This example demonstrates using PaymentIntents to accept card payments verified using 3D Secure. @@ -135,7 +135,7 @@ - (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 - [[ExampleAPIClient sharedClient] createPaymentIntentWithCompletion:^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { + [[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 3e791b5bee0..ea0260ffd79 100644 --- a/Example/Custom Integration/CardManualConfirmationExampleViewController.m +++ b/Example/Custom Integration/CardManualConfirmationExampleViewController.m @@ -11,7 +11,7 @@ #import "CardManualConfirmationExampleViewController.h" #import "BrowseExamplesViewController.h" -#import "ExampleAPIClient.h" +#import "MyAPIClient.h" /** This example demonstrates creating a payment with a credit/debit card using Manual Integration. @@ -127,7 +127,7 @@ - (void)_createAndConfirmPaymentIntentWithPaymentMethod:(STPPaymentMethod *)paym case STPPaymentHandlerActionStatusSucceeded: if (paymentIntent.status == STPPaymentIntentStatusRequiresConfirmation) { // Manually confirm the PaymentIntent on the backend again to complete the payment. - [[ExampleAPIClient sharedClient] confirmPaymentIntent:paymentIntent.stripeId completion:^(MyAPIClientResult status, NSError *error) { + [[MyAPIClient sharedClient] confirmPaymentIntent:paymentIntent.stripeId completion:^(MyAPIClientResult status, NSError *error) { if (status == MyAPIClientResultFailure || error) { [self.delegate exampleViewController:self didFinishWithError:error]; return; @@ -154,7 +154,7 @@ - (void)_createAndConfirmPaymentIntentWithPaymentMethod:(STPPaymentMethod *)paym [self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"]; } }; - [[ExampleAPIClient sharedClient] createAndConfirmPaymentIntentWithPaymentMethod:paymentMethod.stripeId + [[MyAPIClient sharedClient] createAndConfirmPaymentIntentWithPaymentMethod:paymentMethod.stripeId returnURL:@"payments-example://stripe-redirect" completion:createAndConfirmCompletion]; } diff --git a/Example/Custom Integration/CardSetupIntentBackendExampleViewController.m b/Example/Custom Integration/CardSetupIntentBackendExampleViewController.m index f5b206c09b6..89dd0b0c401 100644 --- a/Example/Custom Integration/CardSetupIntentBackendExampleViewController.m +++ b/Example/Custom Integration/CardSetupIntentBackendExampleViewController.m @@ -9,7 +9,7 @@ #import "CardSetupIntentBackendExampleViewController.h" #import "BrowseExamplesViewController.h" -#import "ExampleAPIClient.h" +#import "MyAPIClient.h" /** This example demonstrates using SetupIntents to accept card payments verified using 3D Secure confirming with your backend. @@ -166,7 +166,7 @@ - (void)_createAndConfirmSetupIntentWithPaymentMethod:(STPPaymentMethod *)paymen completion:paymentHandlerCompletion]; }; - [[ExampleAPIClient sharedClient] createAndConfirmSetupIntentWithPaymentMethod:paymentMethod.stripeId + [[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 eb9982d5487..6ddd96394c7 100644 --- a/Example/Custom Integration/CardSetupIntentExampleViewController.m +++ b/Example/Custom Integration/CardSetupIntentExampleViewController.m @@ -11,7 +11,7 @@ #import "CardSetupIntentExampleViewController.h" #import "BrowseExamplesViewController.h" -#import "ExampleAPIClient.h" +#import "MyAPIClient.h" /** This example demonstrates using SetupIntents to accept card payments verified using 3D Secure. @@ -124,7 +124,7 @@ - (void)pay { return; } [self updateUIForPaymentInProgress:YES]; - [[ExampleAPIClient sharedClient] createSetupIntentWithCompletion:^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { + [[MyAPIClient sharedClient] createSetupIntentWithCompletion:^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { if (status == MyAPIClientResultFailure || clientSecret == nil) { [self.delegate exampleViewController:self didFinishWithError:error]; return; diff --git a/Example/Custom Integration/FPXExampleViewController.m b/Example/Custom Integration/FPXExampleViewController.m index feda9207140..4462c843e7b 100644 --- a/Example/Custom Integration/FPXExampleViewController.m +++ b/Example/Custom Integration/FPXExampleViewController.m @@ -9,7 +9,7 @@ #import #import "FPXExampleViewController.h" #import "BrowseExamplesViewController.h" -#import "ExampleAPIClient.h" +#import "MyAPIClient.h" /** This example demonstrates using PaymentIntents to accept payments using FPX, a popular @@ -96,7 +96,7 @@ - (void)pay { } [self updateUIForPaymentInProgress:YES]; - [[ExampleAPIClient sharedClient] createPaymentIntentWithCompletion:^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { + [[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/ExampleAPIClient.h b/Example/Custom Integration/MyAPIClient.h similarity index 99% rename from Example/Custom Integration/ExampleAPIClient.h rename to Example/Custom Integration/MyAPIClient.h index abb5139b757..ba06447f6e3 100644 --- a/Example/Custom Integration/ExampleAPIClient.h +++ b/Example/Custom Integration/MyAPIClient.h @@ -20,7 +20,7 @@ typedef void (^STPPaymentIntentCreateAndConfirmHandler)(MyAPIClientResult status typedef void (^STPConfirmPaymentIntentCompletionHandler)(MyAPIClientResult status, NSError * _Nullable error); typedef void (^STPCreateSetupIntentCompletionHandler)(MyAPIClientResult status, NSString * _Nullable clientSecret, NSError * _Nullable error); -@interface ExampleAPIClient : NSObject +@interface MyAPIClient : NSObject + (instancetype)sharedClient; diff --git a/Example/Custom Integration/ExampleAPIClient.m b/Example/Custom Integration/MyAPIClient.m similarity index 99% rename from Example/Custom Integration/ExampleAPIClient.m rename to Example/Custom Integration/MyAPIClient.m index add2205bd33..63db1ba21a0 100644 --- a/Example/Custom Integration/ExampleAPIClient.m +++ b/Example/Custom Integration/MyAPIClient.m @@ -7,11 +7,11 @@ // @import Stripe; -#import "ExampleAPIClient.h" +#import "MyAPIClient.h" #import "Constants.h" -@implementation ExampleAPIClient +@implementation MyAPIClient + (instancetype)sharedClient { static id sharedClient; From 1a62ff02f6608112fcf517b50f33affb57442d08 Mon Sep 17 00:00:00 2001 From: Yuki Tokuhiro Date: Thu, 12 Sep 2019 11:21:43 -0700 Subject: [PATCH 11/12] Minor tweaks to CheckoutViewController --- Example/Standard Integration/CheckoutViewController.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Example/Standard Integration/CheckoutViewController.swift b/Example/Standard Integration/CheckoutViewController.swift index f72cd04593c..20fd453204a 100644 --- a/Example/Standard Integration/CheckoutViewController.swift +++ b/Example/Standard Integration/CheckoutViewController.swift @@ -263,7 +263,7 @@ extension CheckoutViewController: STPPaymentContextDelegate { } func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPPaymentStatusBlock) { // Create the PaymentIntent on the backend - // A real app should do this at the beginning of the checkout flow, instead of re-creating a PaymentIntent for every payment attempt. + // 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): @@ -278,7 +278,7 @@ extension CheckoutViewController: STPPaymentContextDelegate { // See https://stripe.com/docs/payments/payment-intents/ios#fulfillment completion(.success, nil) case .failed: - completion(.error, error ?? CheckoutError.unknown) + completion(.error, error) case .canceled: completion(.userCancellation, nil) @unknown default: @@ -288,6 +288,7 @@ extension CheckoutViewController: STPPaymentContextDelegate { 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 } } From 287b1265803f7a7731071cd6ea54ae3b2c2442db Mon Sep 17 00:00:00 2001 From: Yuki Tokuhiro Date: Fri, 13 Sep 2019 13:20:40 -0700 Subject: [PATCH 12/12] Bump example backend version from 16.0.0 -> 17.0.0 --- Example/Custom Integration.xcodeproj/project.pbxproj | 2 +- Example/Custom Integration/Constants.m | 2 +- Example/Custom Integration/README.md | 4 +--- Example/Standard Integration/CheckoutViewController.swift | 2 +- Example/Standard Integration/README.md | 2 +- Stripe/PublicHeaders/STPEphemeralKeyProvider.h | 4 ++-- 6 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Example/Custom Integration.xcodeproj/project.pbxproj b/Example/Custom Integration.xcodeproj/project.pbxproj index 19cc98fcd67..0595bd94a50 100644 --- a/Example/Custom Integration.xcodeproj/project.pbxproj +++ b/Example/Custom Integration.xcodeproj/project.pbxproj @@ -60,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 = ""; }; 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/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 20fd453204a..88d7149f8a0 100644 --- a/Example/Standard Integration/CheckoutViewController.swift +++ b/Example/Standard Integration/CheckoutViewController.swift @@ -16,7 +16,7 @@ class CheckoutViewController: UIViewController { 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 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/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.