From 12a23a06319ef4fe078c88869984328a3ce12387 Mon Sep 17 00:00:00 2001
From: Dan Jackson <danj@stripe.com>
Date: Tue, 6 Nov 2018 15:37:58 -0800
Subject: [PATCH 01/16] Remove `PaymentIntent.returnUrl` property

Added TODOs to pull the replacement property in the right places.
---
 Stripe/PublicHeaders/STPPaymentIntent.h      | 9 ---------
 Stripe/STPPaymentIntent.m                    | 3 ---
 Stripe/STPRedirectContext.m                  | 5 +++--
 Tests/Tests/STPPaymentIntentFunctionalTest.m | 3 ++-
 Tests/Tests/STPPaymentIntentTest.m           | 5 +++--
 Tests/Tests/STPRedirectContextTest.m         | 5 +++--
 6 files changed, 11 insertions(+), 19 deletions(-)

diff --git a/Stripe/PublicHeaders/STPPaymentIntent.h b/Stripe/PublicHeaders/STPPaymentIntent.h
index 942d0ec0f21..af01d2f13c9 100644
--- a/Stripe/PublicHeaders/STPPaymentIntent.h
+++ b/Stripe/PublicHeaders/STPPaymentIntent.h
@@ -83,15 +83,6 @@ NS_ASSUME_NONNULL_BEGIN
  */
 @property (nonatomic, nullable, readonly) NSString *receiptEmail;
 
-/**
- The URL to redirect your customer back to after they authenticate or cancel their
- payment on the payment method’s app or site.
-
- This should be a URL that your app handles if the PaymentIntent is going to
- be confirmed in your app, and it has a redirect authorization flow.
- */
-@property (nonatomic, nullable, readonly) NSURL *returnUrl;
-
 /**
  The Stripe ID of the Source used in this PaymentIntent.
  */
diff --git a/Stripe/STPPaymentIntent.m b/Stripe/STPPaymentIntent.m
index 591debc5eda..f780c4efec5 100644
--- a/Stripe/STPPaymentIntent.m
+++ b/Stripe/STPPaymentIntent.m
@@ -23,7 +23,6 @@ @interface STPPaymentIntent ()
 @property (nonatomic, copy, nullable, readwrite) NSString *stripeDescription;
 @property (nonatomic, assign, readwrite) BOOL livemode;
 @property (nonatomic, copy, nullable, readwrite) NSString *receiptEmail;
-@property (nonatomic, copy, nullable, readwrite) NSURL *returnUrl;
 @property (nonatomic, copy, nullable, readwrite) NSString *sourceId;
 @property (nonatomic, assign, readwrite) STPPaymentIntentStatus status;
 
@@ -52,7 +51,6 @@ - (NSString *)description {
                        [NSString stringWithFormat:@"livemode = %@", self.livemode ? @"YES" : @"NO"],
                        [NSString stringWithFormat:@"nextSourceAction = %@", self.allResponseFields[@"next_source_action"]],
                        [NSString stringWithFormat:@"receiptEmail = %@", self.receiptEmail],
-                       [NSString stringWithFormat:@"returnUrl = %@", self.returnUrl],
                        [NSString stringWithFormat:@"shipping = %@", self.allResponseFields[@"shipping"]],
                        [NSString stringWithFormat:@"sourceId = %@", self.sourceId],
                        [NSString stringWithFormat:@"status = %@", [self.allResponseFields stp_stringForKey:@"status"]],
@@ -150,7 +148,6 @@ + (nullable instancetype)decodedObjectFromAPIResponse:(nullable NSDictionary *)r
     // next_source_action is not being parsed. Today type=`authorize_with_url` is the only one
     // and STPRedirectContext reaches directly into it. Not yet sure how I want to model
     // this polymorphic object, so keeping it out of the public API.
-    paymentIntent.returnUrl = [dict stp_urlForKey:@"return_url"];
     paymentIntent.receiptEmail = [dict stp_stringForKey:@"receipt_email"];
     // FIXME: add support for `shipping`
     paymentIntent.sourceId = [dict stp_stringForKey:@"source"];
diff --git a/Stripe/STPRedirectContext.m b/Stripe/STPRedirectContext.m
index f0b3b8077b8..c1064ecc35d 100644
--- a/Stripe/STPRedirectContext.m
+++ b/Stripe/STPRedirectContext.m
@@ -56,7 +56,8 @@ - (nullable instancetype)initWithSource:(STPSource *)source
 
 - (nullable instancetype)initWithPaymentIntent:(STPPaymentIntent *)paymentIntent
                                     completion:(STPRedirectContextPaymentIntentCompletionBlock)completion {
-    if (!(paymentIntent.returnUrl != nil
+    NSURL *returnUrl = nil; // FIXME
+    if (!(returnUrl != nil
           && paymentIntent.status == STPPaymentIntentStatusRequiresSourceAction
           && [paymentIntent.allResponseFields[@"next_source_action"] isKindOfClass: [NSDictionary class]])) {
         return nil;
@@ -72,7 +73,7 @@ - (nullable instancetype)initWithPaymentIntent:(STPPaymentIntent *)paymentIntent
     NSString *redirectURL = nextSourceAction[@"value"][@"url"];
     return [self initWithNativeRedirectURL:nil
                                redirectURL:[NSURL URLWithString:redirectURL]
-                                 returnURL:paymentIntent.returnUrl
+                                 returnURL:returnUrl
                                 completion:^(NSError * _Nullable error) {
                                     completion(paymentIntent.clientSecret, error);
                                 }];
diff --git a/Tests/Tests/STPPaymentIntentFunctionalTest.m b/Tests/Tests/STPPaymentIntentFunctionalTest.m
index db1275f9d1b..7e11ca25eca 100644
--- a/Tests/Tests/STPPaymentIntentFunctionalTest.m
+++ b/Tests/Tests/STPPaymentIntentFunctionalTest.m
@@ -32,7 +32,8 @@ - (void)testRetrievePreviousCreatedPaymentIntent {
                                            XCTAssertFalse(paymentIntent.livemode);
                                            XCTAssertNil(paymentIntent.sourceId);
                                            XCTAssertEqual(paymentIntent.status, STPPaymentIntentStatusCanceled);
-                                           XCTAssertEqualObjects(paymentIntent.returnUrl, [NSURL URLWithString:@"payments-example://stripe-redirect"]);
+                                           NSURL *returnUrl = nil; // FIXME
+                                           XCTAssertEqualObjects(returnUrl, [NSURL URLWithString:@"payments-example://stripe-redirect"]);
 
                                            [expectation fulfill];
                                        }];
diff --git a/Tests/Tests/STPPaymentIntentTest.m b/Tests/Tests/STPPaymentIntentTest.m
index bcc557aff56..5ada1a42dcd 100644
--- a/Tests/Tests/STPPaymentIntentTest.m
+++ b/Tests/Tests/STPPaymentIntentTest.m
@@ -159,8 +159,9 @@ - (void)testDecodedObjectFromAPIResponseMapping {
     XCTAssertEqualObjects(paymentIntent.stripeDescription, @"My Sample PaymentIntent");
     XCTAssertFalse(paymentIntent.livemode);
     XCTAssertEqualObjects(paymentIntent.receiptEmail, @"danj@example.com");
-    XCTAssertNotNil(paymentIntent.returnUrl);
-    XCTAssertEqualObjects(paymentIntent.returnUrl, [NSURL URLWithString:@"payments-example://stripe-redirect"]);
+    NSURL *returnUrl = nil; // FIXME
+    XCTAssertNotNil(returnUrl);
+    XCTAssertEqualObjects(returnUrl, [NSURL URLWithString:@"payments-example://stripe-redirect"]);
     XCTAssertEqualObjects(paymentIntent.sourceId, @"src_1Cl1AdIl4IdHmuTbseiDWq6m");
     XCTAssertEqual(paymentIntent.status, STPPaymentIntentStatusRequiresSourceAction);
 
diff --git a/Tests/Tests/STPRedirectContextTest.m b/Tests/Tests/STPRedirectContextTest.m
index 457de211b4f..2b37b2baf5c 100644
--- a/Tests/Tests/STPRedirectContextTest.m
+++ b/Tests/Tests/STPRedirectContextTest.m
@@ -139,10 +139,11 @@ - (void)testInitWithPaymentIntent {
     XCTAssertNil(sut.nativeRedirectURL);
     XCTAssertEqualObjects(sut.redirectURL.absoluteString,
                           @"https://hooks.stripe.com/redirect/authenticate/src_1Cl1AeIl4IdHmuTb1L7x083A?client_secret=src_client_secret_DBNwUe9qHteqJ8qQBwNWiigk");
-    XCTAssertEqualObjects(sut.returnURL, paymentIntent.returnUrl);
+    NSURL *returnUrl = nil; // FIXME
+    XCTAssertEqualObjects(sut.returnURL, returnUrl);
 
     // and make sure the completion calls the completion block above
-    sut.completion(fakeError);
+    if (sut.completion) sut.completion(fakeError); // FIXME: HACK to avoid EXC_BAD_ACCESS when NULL
     XCTAssertTrue(completionCalled);
 }
 

From 0e7501cf37d20893440bdc4f93523d884ee5883a Mon Sep 17 00:00:00 2001
From: Dan Jackson <danj@stripe.com>
Date: Wed, 7 Nov 2018 10:35:04 -0800
Subject: [PATCH 02/16] Add (empty) classes for next_source_action values

---
 Stripe.xcodeproj/project.pbxproj              | 24 +++++++++++++++++++
 .../STPPaymentIntentSourceAction.h            | 15 ++++++++++++
 ...aymentIntentSourceActionAuthorizeWithURL.h | 14 +++++++++++
 Stripe/PublicHeaders/Stripe.h                 |  2 ++
 Stripe/STPPaymentIntentSourceAction.m         | 21 ++++++++++++++++
 ...aymentIntentSourceActionAuthorizeWithURL.m | 21 ++++++++++++++++
 6 files changed, 97 insertions(+)
 create mode 100644 Stripe/PublicHeaders/STPPaymentIntentSourceAction.h
 create mode 100644 Stripe/PublicHeaders/STPPaymentIntentSourceActionAuthorizeWithURL.h
 create mode 100644 Stripe/STPPaymentIntentSourceAction.m
 create mode 100644 Stripe/STPPaymentIntentSourceActionAuthorizeWithURL.m

diff --git a/Stripe.xcodeproj/project.pbxproj b/Stripe.xcodeproj/project.pbxproj
index 888bb4a768f..ee0283eacda 100644
--- a/Stripe.xcodeproj/project.pbxproj
+++ b/Stripe.xcodeproj/project.pbxproj
@@ -396,6 +396,14 @@
 		B3302F4C200700AB005DDBE9 /* STPLegalEntityParamsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B3302F4B200700AB005DDBE9 /* STPLegalEntityParamsTest.m */; };
 		B347DD481FE35423006B3BAC /* STPValidatedTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = B347DD461FE35423006B3BAC /* STPValidatedTextField.h */; };
 		B347DD491FE35423006B3BAC /* STPValidatedTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = B347DD471FE35423006B3BAC /* STPValidatedTextField.m */; };
+		B36C6D6D2193671400D17575 /* STPPaymentIntentSourceAction.h in Headers */ = {isa = PBXBuildFile; fileRef = B36C6D6B2193671400D17575 /* STPPaymentIntentSourceAction.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		B36C6D6E2193671400D17575 /* STPPaymentIntentSourceAction.h in Headers */ = {isa = PBXBuildFile; fileRef = B36C6D6B2193671400D17575 /* STPPaymentIntentSourceAction.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		B36C6D6F2193671400D17575 /* STPPaymentIntentSourceAction.m in Sources */ = {isa = PBXBuildFile; fileRef = B36C6D6C2193671400D17575 /* STPPaymentIntentSourceAction.m */; };
+		B36C6D702193671400D17575 /* STPPaymentIntentSourceAction.m in Sources */ = {isa = PBXBuildFile; fileRef = B36C6D6C2193671400D17575 /* STPPaymentIntentSourceAction.m */; };
+		B36C6D732193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.h in Headers */ = {isa = PBXBuildFile; fileRef = B36C6D712193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		B36C6D742193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.h in Headers */ = {isa = PBXBuildFile; fileRef = B36C6D712193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		B36C6D752193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.m in Sources */ = {isa = PBXBuildFile; fileRef = B36C6D722193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.m */; };
+		B36C6D762193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.m in Sources */ = {isa = PBXBuildFile; fileRef = B36C6D722193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.m */; };
 		B382D6611FE8BEA0009B56AB /* STPValidatedTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = B347DD471FE35423006B3BAC /* STPValidatedTextField.m */; };
 		B3A241391FFEB57400A2F00D /* STPConnectAccountParams.h in Headers */ = {isa = PBXBuildFile; fileRef = B3A241371FFEB57400A2F00D /* STPConnectAccountParams.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		B3A2413A1FFEB57400A2F00D /* STPConnectAccountParams.h in Headers */ = {isa = PBXBuildFile; fileRef = B3A241371FFEB57400A2F00D /* STPConnectAccountParams.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -1101,6 +1109,10 @@
 		B3302F4B200700AB005DDBE9 /* STPLegalEntityParamsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPLegalEntityParamsTest.m; sourceTree = "<group>"; };
 		B347DD461FE35423006B3BAC /* STPValidatedTextField.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STPValidatedTextField.h; sourceTree = "<group>"; };
 		B347DD471FE35423006B3BAC /* STPValidatedTextField.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPValidatedTextField.m; sourceTree = "<group>"; };
+		B36C6D6B2193671400D17575 /* STPPaymentIntentSourceAction.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = STPPaymentIntentSourceAction.h; path = PublicHeaders/STPPaymentIntentSourceAction.h; sourceTree = "<group>"; };
+		B36C6D6C2193671400D17575 /* STPPaymentIntentSourceAction.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPPaymentIntentSourceAction.m; sourceTree = "<group>"; };
+		B36C6D712193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = STPPaymentIntentSourceActionAuthorizeWithURL.h; path = PublicHeaders/STPPaymentIntentSourceActionAuthorizeWithURL.h; sourceTree = "<group>"; };
+		B36C6D722193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPPaymentIntentSourceActionAuthorizeWithURL.m; sourceTree = "<group>"; };
 		B3A241371FFEB57400A2F00D /* STPConnectAccountParams.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = STPConnectAccountParams.h; path = PublicHeaders/STPConnectAccountParams.h; sourceTree = "<group>"; };
 		B3A241381FFEB57400A2F00D /* STPConnectAccountParams.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPConnectAccountParams.m; sourceTree = "<group>"; };
 		B3A99BC11FEAF2CA003F6ED3 /* STPLegalEntityParams.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = STPLegalEntityParams.h; path = PublicHeaders/STPLegalEntityParams.h; sourceTree = "<group>"; };
@@ -2041,6 +2053,10 @@
 				B3BDCAC720EEF22D0034F7F5 /* STPPaymentIntentEnums.h */,
 				B3BDCAD520EEF5EC0034F7F5 /* STPPaymentIntentParams.h */,
 				B3BDCAD220EEF5E00034F7F5 /* STPPaymentIntentParams.m */,
+				B36C6D6B2193671400D17575 /* STPPaymentIntentSourceAction.h */,
+				B36C6D6C2193671400D17575 /* STPPaymentIntentSourceAction.m */,
+				B36C6D712193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.h */,
+				B36C6D722193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.m */,
 				C1D7B51E1E36C32F002181F5 /* STPSource.h */,
 				C1D7B51F1E36C32F002181F5 /* STPSource.m */,
 				F19491DD1E5F6B8C001E1FC2 /* STPSourceCardDetails.h */,
@@ -2198,6 +2214,7 @@
 				04F94DAB1D229F3F004FC826 /* UIBarButtonItem+Stripe.h in Headers */,
 				04F94DC91D22A20A004FC826 /* STPSwitchTableViewCell.h in Headers */,
 				0433EB4B1BD06313003912B4 /* NSDictionary+Stripe.h in Headers */,
+				B36C6D6E2193671400D17575 /* STPPaymentIntentSourceAction.h in Headers */,
 				04F94DCB1D22A229004FC826 /* UIView+Stripe_FirstResponder.h in Headers */,
 				04F94DD11D22A239004FC826 /* STPPromise.h in Headers */,
 				C1BD9B351E3940C400CEE925 /* STPSourceVerification.h in Headers */,
@@ -2231,6 +2248,7 @@
 				C124A17D1CCAA0C2007D42EE /* NSMutableURLRequest+Stripe.h in Headers */,
 				F1FA6F991E25970F00EB444D /* STPCoreTableViewController+Private.h in Headers */,
 				C1BD9B2F1E3940A200CEE925 /* STPSourceRedirect.h in Headers */,
+				B36C6D742193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.h in Headers */,
 				8B429AD91EF9D4B500F95F34 /* STPBankAccountParams+Private.h in Headers */,
 				F1BEB2FE1F3508BB0043F48C /* NSError+Stripe.h in Headers */,
 				04E32AA01B7A9490009C9E35 /* STPPaymentCardTextField.h in Headers */,
@@ -2317,6 +2335,7 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				B36C6D732193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.h in Headers */,
 				C15993361D8808680047950D /* STPShippingMethodsViewController.h in Headers */,
 				F1A2F92C1EEB6A70006B0456 /* NSCharacterSet+Stripe.h in Headers */,
 				F1D3A24E1EB012010095BFA9 /* STPMultipartFormDataPart.h in Headers */,
@@ -2399,6 +2418,7 @@
 				0426B9721CEAE3EB006AC8DD /* UITableViewCell+Stripe_Borders.h in Headers */,
 				04CDE5C91BC20B1D00548833 /* STPBankAccountParams.h in Headers */,
 				049A3F891CC73C7100F57DE7 /* STPPaymentContext.h in Headers */,
+				B36C6D6D2193671400D17575 /* STPPaymentIntentSourceAction.h in Headers */,
 				04E39F541CECF7A100AF3B96 /* STPPaymentMethodTuple.h in Headers */,
 				F15232241EA9303800D65C67 /* STPURLCallbackHandler.h in Headers */,
 				C18410761EC2529400178149 /* STPEphemeralKeyManager.h in Headers */,
@@ -2944,6 +2964,7 @@
 				0438EF451B74170D00D506CC /* STPCardValidator.m in Sources */,
 				04F94DBB1D229F8D004FC826 /* PKPaymentAuthorizationViewController+Stripe_Blocks.m in Sources */,
 				C1271A3E1E3FA4E800F25DFE /* STPSectionHeaderView.m in Sources */,
+				B36C6D702193671400D17575 /* STPPaymentIntentSourceAction.m in Sources */,
 				F12829DD1D7747E4008B10D6 /* STPBundleLocator.m in Sources */,
 				04F94DA91D229F32004FC826 /* STPPaymentMethodTuple.m in Sources */,
 				C15608E01FE08F2E0032AE66 /* UIView+Stripe_SafeAreaBounds.m in Sources */,
@@ -2994,6 +3015,7 @@
 				C159933A1D8808880047950D /* STPShippingAddressViewController.m in Sources */,
 				04F94DA41D229F1C004FC826 /* STPAddressViewModel.m in Sources */,
 				049880FF1CED5A2300EA4FFD /* STPPaymentConfiguration.m in Sources */,
+				B36C6D762193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.m in Sources */,
 				B3A99BC61FEAF2CA003F6ED3 /* STPLegalEntityParams.m in Sources */,
 				C180211D1E3A58710089D712 /* STPSourcePoller.m in Sources */,
 				04F94DB91D229F86004FC826 /* STPApplePayPaymentMethod.m in Sources */,
@@ -3075,6 +3097,7 @@
 				04CDB5101A5F30A700B854EE /* STPCard.m in Sources */,
 				04CDB5001A5F30A700B854EE /* STPAPIClient.m in Sources */,
 				C18410781EC2529400178149 /* STPEphemeralKeyManager.m in Sources */,
+				B36C6D6F2193671400D17575 /* STPPaymentIntentSourceAction.m in Sources */,
 				04CDB50C1A5F30A700B854EE /* STPBankAccount.m in Sources */,
 				049A3F7F1CC1920A00F57DE7 /* UIViewController+Stripe_KeyboardAvoiding.m in Sources */,
 				04F416271CA3639500486FB5 /* STPAddCardViewController.m in Sources */,
@@ -3147,6 +3170,7 @@
 				049A3F961CC75B2E00F57DE7 /* STPPromise.m in Sources */,
 				B3BDCAD320EEF5E10034F7F5 /* STPPaymentIntentParams.m in Sources */,
 				04695AD41C77F9DB00E08063 /* NSString+Stripe.m in Sources */,
+				B36C6D752193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.m in Sources */,
 				F15232261EA9303800D65C67 /* STPURLCallbackHandler.m in Sources */,
 				F1BEB2FF1F3508BB0043F48C /* NSError+Stripe.m in Sources */,
 				049952D01BCF13510088C703 /* STPAPIRequest.m in Sources */,
diff --git a/Stripe/PublicHeaders/STPPaymentIntentSourceAction.h b/Stripe/PublicHeaders/STPPaymentIntentSourceAction.h
new file mode 100644
index 00000000000..941a50b0f56
--- /dev/null
+++ b/Stripe/PublicHeaders/STPPaymentIntentSourceAction.h
@@ -0,0 +1,15 @@
+//
+//  STPPaymentIntentSourceAction.h
+//  Stripe
+//
+//  Created by Daniel Jackson on 11/7/18.
+//  Copyright © 2018 Stripe, Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "STPAPIResponseDecodable.h"
+
+@interface STPPaymentIntentSourceAction: NSObject<STPAPIResponseDecodable>
+
+@end
diff --git a/Stripe/PublicHeaders/STPPaymentIntentSourceActionAuthorizeWithURL.h b/Stripe/PublicHeaders/STPPaymentIntentSourceActionAuthorizeWithURL.h
new file mode 100644
index 00000000000..4fbcde7aa25
--- /dev/null
+++ b/Stripe/PublicHeaders/STPPaymentIntentSourceActionAuthorizeWithURL.h
@@ -0,0 +1,14 @@
+//
+//  STPPaymentIntentSourceActionAuthorizeWithURL.h
+//  Stripe
+//
+//  Created by Daniel Jackson on 11/7/18.
+//  Copyright © 2018 Stripe, Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "STPAPIResponseDecodable.h"
+
+@interface STPPaymentIntentSourceActionAuthorizeWithURL: NSObject<STPAPIResponseDecodable>
+
+@end
diff --git a/Stripe/PublicHeaders/Stripe.h b/Stripe/PublicHeaders/Stripe.h
index 1f965905140..6e8cefea3d6 100644
--- a/Stripe/PublicHeaders/Stripe.h
+++ b/Stripe/PublicHeaders/Stripe.h
@@ -40,6 +40,8 @@
 #import "STPPaymentIntent.h"
 #import "STPPaymentIntentEnums.h"
 #import "STPPaymentIntentParams.h"
+#import "STPPaymentIntentSourceAction.h"
+#import "STPPaymentIntentSourceActionAuthorizeWithURL.h"
 #import "STPPaymentMethod.h"
 #import "STPPaymentMethodsViewController.h"
 #import "STPPaymentResult.h"
diff --git a/Stripe/STPPaymentIntentSourceAction.m b/Stripe/STPPaymentIntentSourceAction.m
new file mode 100644
index 00000000000..6d9e935ca99
--- /dev/null
+++ b/Stripe/STPPaymentIntentSourceAction.m
@@ -0,0 +1,21 @@
+//
+//  STPPaymentIntentSourceAction.m
+//  Stripe
+//
+//  Created by Daniel Jackson on 11/7/18.
+//  Copyright © 2018 Stripe, Inc. All rights reserved.
+//
+
+#import "STPPaymentIntentSourceAction.h"
+
+@implementation STPPaymentIntentSourceAction
+
+@synthesize allResponseFields;
+
++ (nullable instancetype)decodedObjectFromAPIResponse:(nullable NSDictionary *)response {
+    // TODO
+    NSLog(@"%@", response);
+    return nil;
+}
+
+@end
diff --git a/Stripe/STPPaymentIntentSourceActionAuthorizeWithURL.m b/Stripe/STPPaymentIntentSourceActionAuthorizeWithURL.m
new file mode 100644
index 00000000000..bd538f19f9e
--- /dev/null
+++ b/Stripe/STPPaymentIntentSourceActionAuthorizeWithURL.m
@@ -0,0 +1,21 @@
+//
+//  STPPaymentIntentSourceActionAuthorizeWithURL.m
+//  Stripe
+//
+//  Created by Daniel Jackson on 11/7/18.
+//  Copyright © 2018 Stripe, Inc. All rights reserved.
+//
+
+#import "STPPaymentIntentSourceActionAuthorizeWithURL.h"
+
+@implementation STPPaymentIntentSourceActionAuthorizeWithURL
+
+@synthesize allResponseFields;
+
++ (nullable instancetype)decodedObjectFromAPIResponse:(nullable NSDictionary *)response {
+    // TODO
+    NSLog(@"%@", response);
+    return nil;
+}
+
+@end

From 98a68e7b919a46a9cc4f26029c61cbbb1c5f4863 Mon Sep 17 00:00:00 2001
From: Dan Jackson <danj@stripe.com>
Date: Wed, 7 Nov 2018 11:07:35 -0800
Subject: [PATCH 03/16] Fill out implementation of
 STPPaymentIntentSourceActionAuthorizeWithURL

---
 ...aymentIntentSourceActionAuthorizeWithURL.h | 26 ++++++++++++
 ...aymentIntentSourceActionAuthorizeWithURL.m | 42 +++++++++++++++++--
 2 files changed, 65 insertions(+), 3 deletions(-)

diff --git a/Stripe/PublicHeaders/STPPaymentIntentSourceActionAuthorizeWithURL.h b/Stripe/PublicHeaders/STPPaymentIntentSourceActionAuthorizeWithURL.h
index 4fbcde7aa25..536398c032a 100644
--- a/Stripe/PublicHeaders/STPPaymentIntentSourceActionAuthorizeWithURL.h
+++ b/Stripe/PublicHeaders/STPPaymentIntentSourceActionAuthorizeWithURL.h
@@ -7,8 +7,34 @@
 //
 
 #import <Foundation/Foundation.h>
+
 #import "STPAPIResponseDecodable.h"
 
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ The `STPPaymentIntentSourceAction` details when type is `authorize_with_url`.
+
+ These are created & owned by the containing `STPPaymentIntent`.
+ */
 @interface STPPaymentIntentSourceActionAuthorizeWithURL: NSObject<STPAPIResponseDecodable>
 
+/**
+ You cannot directly instantiate an `STPPaymentIntentSourceActionAuthorizeWithURL`.
+ */
+- (instancetype)init __attribute__((unavailable("You cannot directly instantiate an STPPaymentIntentSourceActionAuthorizeWithURL.")));
+
+/**
+ The URL where the user will authorize this charge.
+ */
+@property (nonatomic, readonly) NSURL *url;
+
+/**
+ The return URL that'll be redirected back to when the user is done
+ authorizing the charge.
+ */
+@property (nonatomic, nullable, readonly) NSURL *returnURL;
+
 @end
+
+NS_ASSUME_NONNULL_END
diff --git a/Stripe/STPPaymentIntentSourceActionAuthorizeWithURL.m b/Stripe/STPPaymentIntentSourceActionAuthorizeWithURL.m
index bd538f19f9e..82fe6cab006 100644
--- a/Stripe/STPPaymentIntentSourceActionAuthorizeWithURL.m
+++ b/Stripe/STPPaymentIntentSourceActionAuthorizeWithURL.m
@@ -8,14 +8,50 @@
 
 #import "STPPaymentIntentSourceActionAuthorizeWithURL.h"
 
+#import "NSDictionary+Stripe.h"
+
+@interface STPPaymentIntentSourceActionAuthorizeWithURL()
+@property (nonatomic, strong, nonnull, readwrite) NSURL *url;
+@property (nonatomic, strong, nullable, readwrite) NSURL *returnURL;
+@property (nonatomic, readwrite, nonnull, copy) NSDictionary *allResponseFields;
+@end
+
 @implementation STPPaymentIntentSourceActionAuthorizeWithURL
 
 @synthesize allResponseFields;
 
+- (NSString *)description {
+    NSArray *props = @[
+                       // Object
+                       [NSString stringWithFormat:@"%@: %p", NSStringFromClass([self class]), self],
+
+                       // AuthorizeWithURL details (alphabetical)
+                       [NSString stringWithFormat:@"returnURL = %@", self.returnURL],
+                       [NSString stringWithFormat:@"url = %@", self.url],
+                       ];
+
+    return [NSString stringWithFormat:@"<%@>", [props componentsJoinedByString:@"; "]];
+}
+
 + (nullable instancetype)decodedObjectFromAPIResponse:(nullable NSDictionary *)response {
-    // TODO
-    NSLog(@"%@", response);
-    return nil;
+    NSDictionary *dict = [response stp_dictionaryByRemovingNulls];
+    if (!dict) {
+        return nil;
+    }
+
+    // required fields
+    NSURL *url = [response stp_urlForKey:@"url"];
+    if (!url) {
+        return nil;
+    }
+
+    STPPaymentIntentSourceActionAuthorizeWithURL *authorize = [self new];
+
+    authorize.url = url;
+    authorize.returnURL = [dict stp_urlForKey:@"return_url"];
+    authorize.allResponseFields = dict;
+
+    return authorize;
 }
 
 @end

From d8cfcab588992eaa688894c5a08cb038957c2722 Mon Sep 17 00:00:00 2001
From: Dan Jackson <danj@stripe.com>
Date: Wed, 7 Nov 2018 12:01:52 -0800
Subject: [PATCH 04/16] Add enum for nextSourceAction.type

---
 Stripe/PublicHeaders/STPPaymentIntentEnums.h | 20 ++++++++++++++++++++
 Stripe/STPPaymentIntent+Private.h            |  8 ++++++++
 Stripe/STPPaymentIntent.m                    | 10 ++++++++++
 3 files changed, 38 insertions(+)

diff --git a/Stripe/PublicHeaders/STPPaymentIntentEnums.h b/Stripe/PublicHeaders/STPPaymentIntentEnums.h
index b8431d16570..0e5cb3e3a91 100644
--- a/Stripe/PublicHeaders/STPPaymentIntentEnums.h
+++ b/Stripe/PublicHeaders/STPPaymentIntentEnums.h
@@ -92,3 +92,23 @@ typedef NS_ENUM(NSInteger, STPPaymentIntentConfirmationMethod) {
      */
     STPPaymentIntentConfirmationMethodSecret,
 };
+
+/**
+ Types of Source Actions from a `STPPaymentIntent`, when the payment intent
+ status is `STPPaymentIntentStatusRequiresSourceAction`.
+ */
+typedef NS_ENUM(NSUInteger, STPPaymentIntentSourceActionType) {
+    /**
+     This is an unknown source action, that's been added since the SDK
+     was last updated.
+     Update your SDK, or use the `nextSourceAction.allResponseFields`
+     for custom handling.
+     */
+    STPPaymentIntentSourceActionTypeUnknown,
+
+    /**
+     The payment intent needs to be authorized by the user. We provide
+     `STPRedirectContext` to handle the url redirections necessary.
+     */
+    STPPaymentIntentSourceActionTypeAuthorizeWithURL,
+};
diff --git a/Stripe/STPPaymentIntent+Private.h b/Stripe/STPPaymentIntent+Private.h
index 1dba1665b59..571980f6c75 100644
--- a/Stripe/STPPaymentIntent+Private.h
+++ b/Stripe/STPPaymentIntent+Private.h
@@ -44,6 +44,14 @@ NS_ASSUME_NONNULL_BEGIN
  */
 + (STPPaymentIntentConfirmationMethod)confirmationMethodFromString:(NSString *)string;
 
+/**
+ Parse the string and return the correct `STPPaymentIntentSourceActionType`,
+ or `STPPaymentIntentSourceActionTypeUnknown` if it's unrecognized by this version of the SDK.
+
+ @param string the NSString with the `next_source_action.type`
+ */
++ (STPPaymentIntentSourceActionType)sourceActionTypeFromString:(NSString *)string;
+
 @end
 
 NS_ASSUME_NONNULL_END
diff --git a/Stripe/STPPaymentIntent.m b/Stripe/STPPaymentIntent.m
index f780c4efec5..6fe73391625 100644
--- a/Stripe/STPPaymentIntent.m
+++ b/Stripe/STPPaymentIntent.m
@@ -112,6 +112,16 @@ + (STPPaymentIntentConfirmationMethod)confirmationMethodFromString:(NSString *)s
     return statusNumber.integerValue;
 }
 
++ (STPPaymentIntentSourceActionType)sourceActionTypeFromString:(NSString *)string {
+    NSDictionary<NSString *, NSNumber *> *map = @{
+                                                  @"authorize_with_url": @(STPPaymentIntentSourceActionTypeAuthorizeWithURL),
+                                                  };
+
+    NSString *key = string.lowercaseString;
+    NSNumber *statusNumber = map[key] ?: @(STPPaymentIntentSourceActionTypeUnknown);
+    return statusNumber.integerValue;
+}
+
 
 #pragma mark - STPAPIResponseDecodable
 

From a842822fcbd129a253b6b07ffc882fc575eb0589 Mon Sep 17 00:00:00 2001
From: Dan Jackson <danj@stripe.com>
Date: Wed, 7 Nov 2018 12:02:21 -0800
Subject: [PATCH 05/16] Fill out implementation of STPPaymentIntentSourceAction

---
 .../STPPaymentIntentSourceAction.h            | 29 +++++++++
 Stripe/STPPaymentIntentSourceAction.m         | 62 ++++++++++++++++++-
 2 files changed, 88 insertions(+), 3 deletions(-)

diff --git a/Stripe/PublicHeaders/STPPaymentIntentSourceAction.h b/Stripe/PublicHeaders/STPPaymentIntentSourceAction.h
index 941a50b0f56..afccab72d92 100644
--- a/Stripe/PublicHeaders/STPPaymentIntentSourceAction.h
+++ b/Stripe/PublicHeaders/STPPaymentIntentSourceAction.h
@@ -9,7 +9,36 @@
 #import <Foundation/Foundation.h>
 
 #import "STPAPIResponseDecodable.h"
+#import "STPPaymentIntentEnums.h"
 
+NS_ASSUME_NONNULL_BEGIN
+
+@class STPPaymentIntentSourceActionAuthorizeWithURL;
+
+/**
+ Source Action details for an STPPaymentIntent. This is a container for
+ the various types that are available. Check the `type` to see which one
+ it is, and then use the related property for the details necessary to handle
+ it.
+ */
 @interface STPPaymentIntentSourceAction: NSObject<STPAPIResponseDecodable>
 
+/**
+ You cannot directly instantiate an `STPPaymentIntentSourceAction`.
+ */
+- (instancetype)init __attribute__((unavailable("You cannot directly instantiate an STPPaymentIntentSourceAction.")));
+
+/**
+ The type of source action needed. The value of this field determines which
+ property of this object contains further details about the action.
+ */
+@property (nonatomic, readonly) STPPaymentIntentSourceActionType type;
+
+/**
+ The details for authorizing via URL, when `type == STPPaymentIntentSourceActionTypeAuthorizeWithURL`
+ */
+@property (nonatomic, nullable, readonly) STPPaymentIntentSourceActionAuthorizeWithURL* authorizeWithURL;
+
 @end
+
+NS_ASSUME_NONNULL_END
diff --git a/Stripe/STPPaymentIntentSourceAction.m b/Stripe/STPPaymentIntentSourceAction.m
index 6d9e935ca99..211fca3891c 100644
--- a/Stripe/STPPaymentIntentSourceAction.m
+++ b/Stripe/STPPaymentIntentSourceAction.m
@@ -8,14 +8,70 @@
 
 #import "STPPaymentIntentSourceAction.h"
 
+#import "STPPaymentIntent+Private.h"
+#import "STPPaymentIntentSourceActionAuthorizeWithURL.h"
+
+#import "NSDictionary+Stripe.h"
+
+@interface STPPaymentIntentSourceAction()
+@property (nonatomic, readwrite) STPPaymentIntentSourceActionType type;
+@property (nonatomic, strong, nullable, readwrite) STPPaymentIntentSourceActionAuthorizeWithURL* authorizeWithURL;
+@property (nonatomic, readwrite, nonnull, copy) NSDictionary *allResponseFields;
+@end
+
 @implementation STPPaymentIntentSourceAction
 
 @synthesize allResponseFields;
 
+- (NSString *)description {
+    NSMutableArray *props = [@[
+                               // Object
+                               [NSString stringWithFormat:@"%@: %p", NSStringFromClass([self class]), self],
+
+                               // Type
+                               [NSString stringWithFormat:@"type = %@", [allResponseFields stp_stringForKey:@"type"]],
+                               ] mutableCopy];
+
+    // omit properties that don't apply to this type
+    switch (self.type) {
+        case STPPaymentIntentSourceActionTypeAuthorizeWithURL:
+            [props addObject:[NSString stringWithFormat:@"authorizeWithURL = %@", self.authorizeWithURL]];
+            break;
+        default:
+            // unrecognized type, just show the original dictionary for debugging help
+            [props addObject:[NSString stringWithFormat:@"allResponseFields = %@", self.allResponseFields]];
+    }
+
+    return [NSString stringWithFormat:@"<%@>", [props componentsJoinedByString:@"; "]];
+}
+
 + (nullable instancetype)decodedObjectFromAPIResponse:(nullable NSDictionary *)response {
-    // TODO
-    NSLog(@"%@", response);
-    return nil;
+    NSDictionary *dict = [response stp_dictionaryByRemovingNulls];
+    NSString *rawType = [dict stp_stringForKey:@"type"];
+    if (!dict || !rawType) {
+        return nil;
+    }
+
+    STPPaymentIntentSourceActionType type = [STPPaymentIntent sourceActionTypeFromString:rawType];
+    NSDictionary *authorizeDict = [dict stp_dictionaryForKey:@"authorize_with_url"];
+    STPPaymentIntentSourceActionAuthorizeWithURL *authorize = [STPPaymentIntentSourceActionAuthorizeWithURL decodedObjectFromAPIResponse:authorizeDict];
+
+    STPPaymentIntentSourceAction *sourceAction = [self new];
+
+    // Only set the type to a recognized value if we *also* have the expected sub-details.
+    // ex: If the server said it was `.authorizeWithURL`, but decoding the
+    // STPPaymentIntentSourceActionAuthorizeWithURL object fails, map type to `.unknown`
+    if (type == STPPaymentIntentSourceActionTypeAuthorizeWithURL && authorize) {
+        sourceAction.type = type;
+        sourceAction.authorizeWithURL = authorize;
+    }
+    else {
+        sourceAction.type = STPPaymentIntentSourceActionTypeUnknown;
+    }
+
+    sourceAction.allResponseFields = dict;
+
+    return sourceAction;
 }
 
 @end

From 975f56fa51cf4ba1ffac5161e41eeabdb6caa927 Mon Sep 17 00:00:00 2001
From: Dan Jackson <danj@stripe.com>
Date: Wed, 7 Nov 2018 12:03:18 -0800
Subject: [PATCH 06/16] Add STPPaymentIntentSourceAction to STPPaymentIntent

---
 Stripe/PublicHeaders/STPPaymentIntent.h | 8 ++++++++
 Stripe/STPPaymentIntent.m               | 9 +++++----
 2 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/Stripe/PublicHeaders/STPPaymentIntent.h b/Stripe/PublicHeaders/STPPaymentIntent.h
index af01d2f13c9..1ebea57cd93 100644
--- a/Stripe/PublicHeaders/STPPaymentIntent.h
+++ b/Stripe/PublicHeaders/STPPaymentIntent.h
@@ -13,6 +13,8 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
+@class STPPaymentIntentSourceAction;
+
 /**
  A PaymentIntent tracks the process of collecting a payment from your customer.
 
@@ -78,6 +80,12 @@ NS_ASSUME_NONNULL_BEGIN
  */
 @property (nonatomic, readonly) BOOL livemode;
 
+/**
+ If `status == STPPaymentIntentStatusRequiresSourceAction`, this
+ property contains the next action to take for this PaymentIntent.
+ */
+@property (nonatomic, nullable, readonly) STPPaymentIntentSourceAction* nextSourceAction;
+
 /**
  Email address that the receipt for the resulting payment will be sent to.
  */
diff --git a/Stripe/STPPaymentIntent.m b/Stripe/STPPaymentIntent.m
index 6fe73391625..e68f77603a8 100644
--- a/Stripe/STPPaymentIntent.m
+++ b/Stripe/STPPaymentIntent.m
@@ -8,6 +8,7 @@
 
 #import "STPPaymentIntent.h"
 #import "STPPaymentIntent+Private.h"
+#import "STPPaymentIntentSourceAction.h"
 
 #import "NSDictionary+Stripe.h"
 
@@ -22,6 +23,7 @@ @interface STPPaymentIntent ()
 @property (nonatomic, copy, readwrite) NSString *currency;
 @property (nonatomic, copy, nullable, readwrite) NSString *stripeDescription;
 @property (nonatomic, assign, readwrite) BOOL livemode;
+@property (nonatomic, strong, nullable, readwrite) STPPaymentIntentSourceAction* nextSourceAction;
 @property (nonatomic, copy, nullable, readwrite) NSString *receiptEmail;
 @property (nonatomic, copy, nullable, readwrite) NSString *sourceId;
 @property (nonatomic, assign, readwrite) STPPaymentIntentStatus status;
@@ -49,7 +51,7 @@ - (NSString *)description {
                        [NSString stringWithFormat:@"currency = %@", self.currency],
                        [NSString stringWithFormat:@"description = %@", self.stripeDescription],
                        [NSString stringWithFormat:@"livemode = %@", self.livemode ? @"YES" : @"NO"],
-                       [NSString stringWithFormat:@"nextSourceAction = %@", self.allResponseFields[@"next_source_action"]],
+                       [NSString stringWithFormat:@"nextSourceAction = %@", self.nextSourceAction],
                        [NSString stringWithFormat:@"receiptEmail = %@", self.receiptEmail],
                        [NSString stringWithFormat:@"shipping = %@", self.allResponseFields[@"shipping"]],
                        [NSString stringWithFormat:@"sourceId = %@", self.sourceId],
@@ -155,9 +157,8 @@ + (nullable instancetype)decodedObjectFromAPIResponse:(nullable NSDictionary *)r
     paymentIntent.currency = currency;
     paymentIntent.stripeDescription = [dict stp_stringForKey:@"description"];
     paymentIntent.livemode = [dict stp_boolForKey:@"livemode" or:YES];
-    // next_source_action is not being parsed. Today type=`authorize_with_url` is the only one
-    // and STPRedirectContext reaches directly into it. Not yet sure how I want to model
-    // this polymorphic object, so keeping it out of the public API.
+    NSDictionary *nextSourceActionDict = [dict stp_dictionaryForKey:@"next_source_action"];
+    paymentIntent.nextSourceAction = [STPPaymentIntentSourceAction decodedObjectFromAPIResponse:nextSourceActionDict];
     paymentIntent.receiptEmail = [dict stp_stringForKey:@"receipt_email"];
     // FIXME: add support for `shipping`
     paymentIntent.sourceId = [dict stp_stringForKey:@"source"];

From a211cd6752cecaa7c6ddf4d20d01e3c489b22230 Mon Sep 17 00:00:00 2001
From: Dan Jackson <danj@stripe.com>
Date: Wed, 7 Nov 2018 13:16:02 -0800
Subject: [PATCH 07/16] Update STPPaymentIntent tests, and JSON for fixture

---
 Tests/Tests/PaymentIntent.json               |  4 ++--
 Tests/Tests/STPPaymentIntentFunctionalTest.m |  3 +--
 Tests/Tests/STPPaymentIntentTest.m           | 13 ++++++++++---
 3 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/Tests/Tests/PaymentIntent.json b/Tests/Tests/PaymentIntent.json
index 97bb95c05c8..2f6df0eea20 100644
--- a/Tests/Tests/PaymentIntent.json
+++ b/Tests/Tests/PaymentIntent.json
@@ -15,12 +15,12 @@
   "livemode": false,
   "next_source_action": {
     "type": "authorize_with_url",
-    "value": {
+    "authorize_with_url": {
+      "return_url": "payments-example://stripe-redirect",
       "url": "https://hooks.stripe.com/redirect/authenticate/src_1Cl1AeIl4IdHmuTb1L7x083A?client_secret=src_client_secret_DBNwUe9qHteqJ8qQBwNWiigk"
     }
   },
   "receipt_email": "danj@example.com",
-  "return_url": "payments-example://stripe-redirect",
   "shipping": {
     "address": {
       "city": "San Francisco",
diff --git a/Tests/Tests/STPPaymentIntentFunctionalTest.m b/Tests/Tests/STPPaymentIntentFunctionalTest.m
index 7e11ca25eca..5fbbc75f66b 100644
--- a/Tests/Tests/STPPaymentIntentFunctionalTest.m
+++ b/Tests/Tests/STPPaymentIntentFunctionalTest.m
@@ -32,8 +32,7 @@ - (void)testRetrievePreviousCreatedPaymentIntent {
                                            XCTAssertFalse(paymentIntent.livemode);
                                            XCTAssertNil(paymentIntent.sourceId);
                                            XCTAssertEqual(paymentIntent.status, STPPaymentIntentStatusCanceled);
-                                           NSURL *returnUrl = nil; // FIXME
-                                           XCTAssertEqualObjects(returnUrl, [NSURL URLWithString:@"payments-example://stripe-redirect"]);
+                                           XCTAssertNil(paymentIntent.nextSourceAction);
 
                                            [expectation fulfill];
                                        }];
diff --git a/Tests/Tests/STPPaymentIntentTest.m b/Tests/Tests/STPPaymentIntentTest.m
index 5ada1a42dcd..a853e59dc11 100644
--- a/Tests/Tests/STPPaymentIntentTest.m
+++ b/Tests/Tests/STPPaymentIntentTest.m
@@ -159,9 +159,16 @@ - (void)testDecodedObjectFromAPIResponseMapping {
     XCTAssertEqualObjects(paymentIntent.stripeDescription, @"My Sample PaymentIntent");
     XCTAssertFalse(paymentIntent.livemode);
     XCTAssertEqualObjects(paymentIntent.receiptEmail, @"danj@example.com");
-    NSURL *returnUrl = nil; // FIXME
-    XCTAssertNotNil(returnUrl);
-    XCTAssertEqualObjects(returnUrl, [NSURL URLWithString:@"payments-example://stripe-redirect"]);
+    XCTAssertNotNil(paymentIntent.nextSourceAction);
+    XCTAssertEqual(paymentIntent.nextSourceAction.type, STPPaymentIntentSourceActionTypeAuthorizeWithURL);
+    XCTAssertNotNil(paymentIntent.nextSourceAction.authorizeWithURL);
+    XCTAssertNotNil(paymentIntent.nextSourceAction.authorizeWithURL.url);
+    NSURL *returnURL = paymentIntent.nextSourceAction.authorizeWithURL.returnURL;
+    XCTAssertNotNil(returnURL);
+    XCTAssertEqualObjects(returnURL, [NSURL URLWithString:@"payments-example://stripe-redirect"]);
+    NSURL *url = paymentIntent.nextSourceAction.authorizeWithURL.url;
+    XCTAssertNotNil(url);
+    XCTAssertEqualObjects(url, [NSURL URLWithString:@"https://hooks.stripe.com/redirect/authenticate/src_1Cl1AeIl4IdHmuTb1L7x083A?client_secret=src_client_secret_DBNwUe9qHteqJ8qQBwNWiigk"]);
     XCTAssertEqualObjects(paymentIntent.sourceId, @"src_1Cl1AdIl4IdHmuTbseiDWq6m");
     XCTAssertEqual(paymentIntent.status, STPPaymentIntentStatusRequiresSourceAction);
 

From 8f8de9684b9384b9ca4e69deda592c9bd608a0e5 Mon Sep 17 00:00:00 2001
From: Dan Jackson <danj@stripe.com>
Date: Wed, 7 Nov 2018 13:20:29 -0800
Subject: [PATCH 08/16] Adding test for new enum value from string

---
 Tests/Tests/STPPaymentIntentTest.m | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/Tests/Tests/STPPaymentIntentTest.m b/Tests/Tests/STPPaymentIntentTest.m
index a853e59dc11..778fd3b71fd 100644
--- a/Tests/Tests/STPPaymentIntentTest.m
+++ b/Tests/Tests/STPPaymentIntentTest.m
@@ -107,6 +107,18 @@ - (void)testConfirmationMethodFromString {
                    STPPaymentIntentConfirmationMethodUnknown);
 }
 
+- (void)testSourceActionFromString {
+    XCTAssertEqual([STPPaymentIntent sourceActionTypeFromString:@"authorize_with_url"],
+                   STPPaymentIntentSourceActionTypeAuthorizeWithURL);
+    XCTAssertEqual([STPPaymentIntent confirmationMethodFromString:@"AUTHORIZE_WITH_URL"],
+                   STPPaymentIntentSourceActionTypeAuthorizeWithURL);
+
+    XCTAssertEqual([STPPaymentIntent confirmationMethodFromString:@"garbage"],
+                   STPPaymentIntentSourceActionTypeUnknown);
+    XCTAssertEqual([STPPaymentIntent confirmationMethodFromString:@"GARBAGE"],
+                   STPPaymentIntentSourceActionTypeUnknown);
+}
+
 #pragma mark - Description Tests
 
 - (void)testDescription {

From 40ac1f4dbfe3bd6b45704656c53803a053ee888b Mon Sep 17 00:00:00 2001
From: Dan Jackson <danj@stripe.com>
Date: Wed, 7 Nov 2018 14:57:25 -0800
Subject: [PATCH 09/16] Add test for decoding `STPPaymentIntentSourceAction`

---
 Stripe.xcodeproj/project.pbxproj              |  4 +
 .../Tests/STPPaymentIntentSourceActionTest.m  | 94 +++++++++++++++++++
 2 files changed, 98 insertions(+)
 create mode 100644 Tests/Tests/STPPaymentIntentSourceActionTest.m

diff --git a/Stripe.xcodeproj/project.pbxproj b/Stripe.xcodeproj/project.pbxproj
index ee0283eacda..f5ea26b7060 100644
--- a/Stripe.xcodeproj/project.pbxproj
+++ b/Stripe.xcodeproj/project.pbxproj
@@ -404,6 +404,7 @@
 		B36C6D742193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.h in Headers */ = {isa = PBXBuildFile; fileRef = B36C6D712193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		B36C6D752193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.m in Sources */ = {isa = PBXBuildFile; fileRef = B36C6D722193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.m */; };
 		B36C6D762193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.m in Sources */ = {isa = PBXBuildFile; fileRef = B36C6D722193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.m */; };
+		B36C6D782193A16F00D17575 /* STPPaymentIntentSourceActionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B36C6D772193A16F00D17575 /* STPPaymentIntentSourceActionTest.m */; };
 		B382D6611FE8BEA0009B56AB /* STPValidatedTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = B347DD471FE35423006B3BAC /* STPValidatedTextField.m */; };
 		B3A241391FFEB57400A2F00D /* STPConnectAccountParams.h in Headers */ = {isa = PBXBuildFile; fileRef = B3A241371FFEB57400A2F00D /* STPConnectAccountParams.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		B3A2413A1FFEB57400A2F00D /* STPConnectAccountParams.h in Headers */ = {isa = PBXBuildFile; fileRef = B3A241371FFEB57400A2F00D /* STPConnectAccountParams.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -1113,6 +1114,7 @@
 		B36C6D6C2193671400D17575 /* STPPaymentIntentSourceAction.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPPaymentIntentSourceAction.m; sourceTree = "<group>"; };
 		B36C6D712193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = STPPaymentIntentSourceActionAuthorizeWithURL.h; path = PublicHeaders/STPPaymentIntentSourceActionAuthorizeWithURL.h; sourceTree = "<group>"; };
 		B36C6D722193676600D17575 /* STPPaymentIntentSourceActionAuthorizeWithURL.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPPaymentIntentSourceActionAuthorizeWithURL.m; sourceTree = "<group>"; };
+		B36C6D772193A16F00D17575 /* STPPaymentIntentSourceActionTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPPaymentIntentSourceActionTest.m; sourceTree = "<group>"; };
 		B3A241371FFEB57400A2F00D /* STPConnectAccountParams.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = STPConnectAccountParams.h; path = PublicHeaders/STPConnectAccountParams.h; sourceTree = "<group>"; };
 		B3A241381FFEB57400A2F00D /* STPConnectAccountParams.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPConnectAccountParams.m; sourceTree = "<group>"; };
 		B3A99BC11FEAF2CA003F6ED3 /* STPLegalEntityParams.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = STPLegalEntityParams.h; path = PublicHeaders/STPLegalEntityParams.h; sourceTree = "<group>"; };
@@ -1707,6 +1709,7 @@
 				8B013C881F1E784A00DD831B /* STPPaymentConfigurationTest.m */,
 				F14C872E1D4FCDBA00C7CC6A /* STPPaymentContextApplePayTest.m */,
 				B3BDCAD020EEF5B90034F7F5 /* STPPaymentIntentParamsTest.m */,
+				B36C6D772193A16F00D17575 /* STPPaymentIntentSourceActionTest.m */,
 				B3BDCACC20EEF4540034F7F5 /* STPPaymentIntentTest.m */,
 				F1DE87FF1F8D410D00602F4C /* STPPaymentMethodsViewControllerTest.m */,
 				C1EEDCC91CA2186300A54582 /* STPPhoneNumberValidatorTest.m */,
@@ -2948,6 +2951,7 @@
 				C1EF044D1DD2397500FBF452 /* STPShippingAddressViewControllerLocalizationTests.m in Sources */,
 				C1080F4C1CBED48A007B2D89 /* STPAddressTests.m in Sources */,
 				C1C02CCE1ECCE92900DF5643 /* STPEphemeralKeyTest.m in Sources */,
+				B36C6D782193A16F00D17575 /* STPPaymentIntentSourceActionTest.m in Sources */,
 				C14C4DB11EC3B34500C2FDF6 /* STPAPIRequestTest.m in Sources */,
 				F1D96F9B1DC7DCDE00477E64 /* STPLocalizationUtils+STPTestAdditions.m in Sources */,
 				04415C6F1A6605B5001225ED /* STPCertTest.m in Sources */,
diff --git a/Tests/Tests/STPPaymentIntentSourceActionTest.m b/Tests/Tests/STPPaymentIntentSourceActionTest.m
new file mode 100644
index 00000000000..5a9853303ce
--- /dev/null
+++ b/Tests/Tests/STPPaymentIntentSourceActionTest.m
@@ -0,0 +1,94 @@
+//
+//  STPPaymentIntentSourceActionTest.m
+//  StripeiOS Tests
+//
+//  Created by Daniel Jackson on 11/7/18.
+//  Copyright © 2018 Stripe, Inc. All rights reserved.
+//
+
+#import <XCTest/XCTest.h>
+
+#import "STPPaymentIntentSourceAction.h"
+#import "STPPaymentIntentSourceActionAuthorizeWithURL.h"
+
+@interface STPPaymentIntentSourceActionTest : XCTestCase
+
+@end
+
+@implementation STPPaymentIntentSourceActionTest
+
+- (void)testDecodedObjectFromAPIResponseAuthorizeWithURL {
+    STPPaymentIntentSourceAction *(^decode)(NSDictionary *) = ^STPPaymentIntentSourceAction *(NSDictionary * dict) {
+        return [STPPaymentIntentSourceAction decodedObjectFromAPIResponse:dict];
+    };
+
+    XCTAssertNil(decode(nil));
+    XCTAssertNil(decode(@{}));
+    XCTAssertNil(decode(@{ @"authorize_with_url": @{@"url": @"http://stripe.com"} }),
+                 @"fails without type");
+
+    STPPaymentIntentSourceAction *missingDetails = decode(@{
+                                                            @"type": @"authorize_with_url"
+                                                            });
+    XCTAssertNotNil(missingDetails);
+    XCTAssertEqual(missingDetails.type, STPPaymentIntentSourceActionTypeUnknown,
+                   @"Type becomes unknown if the authorize_with_url details are missing");
+
+    STPPaymentIntentSourceAction *badURL = decode(@{
+                                                    @"type": @"authorize_with_url",
+                                                    @"authorize_with_url": @{
+                                                            @"url": @"not a url"
+                                                            }
+                                                    });
+    XCTAssertNotNil(badURL);
+    XCTAssertEqual(badURL.type, STPPaymentIntentSourceActionTypeUnknown,
+                   @"Type becomes unknown if the authorize_with_url details don't have a valid URL");
+
+    STPPaymentIntentSourceAction *missingReturnURL = decode(@{
+                                                              @"type": @"authorize_with_url",
+                                                              @"authorize_with_url": @{
+                                                                      @"url": @"https://stripe.com/"
+                                                                      }
+                                                              });
+    XCTAssertNotNil(missingReturnURL);
+    XCTAssertEqual(missingReturnURL.type, STPPaymentIntentSourceActionTypeAuthorizeWithURL,
+                   @"Missing return_url won't prevent it from decoding");
+    XCTAssertNotNil(missingReturnURL.authorizeWithURL.url);
+    XCTAssertEqualObjects(missingReturnURL.authorizeWithURL.url,
+                          [NSURL URLWithString:@"https://stripe.com/"]);
+    XCTAssertNil(missingReturnURL.authorizeWithURL.returnURL);
+
+    STPPaymentIntentSourceAction *badReturnURL = decode(@{
+                                                          @"type": @"authorize_with_url",
+                                                          @"authorize_with_url": @{
+                                                                  @"url": @"https://stripe.com/",
+                                                                  @"return_url": @"not a url"
+                                                                  }
+                                                          });
+    XCTAssertNotNil(badReturnURL);
+    XCTAssertEqual(badReturnURL.type, STPPaymentIntentSourceActionTypeAuthorizeWithURL,
+                   @"invalid return_url won't prevent it from decoding");
+    XCTAssertNotNil(badReturnURL.authorizeWithURL.url);
+    XCTAssertEqualObjects(badReturnURL.authorizeWithURL.url,
+                          [NSURL URLWithString:@"https://stripe.com/"]);
+    XCTAssertNil(badReturnURL.authorizeWithURL.returnURL);
+
+
+    STPPaymentIntentSourceAction *complete = decode(@{
+                                                              @"type": @"authorize_with_url",
+                                                              @"authorize_with_url": @{
+                                                                      @"url": @"https://stripe.com/",
+                                                                      @"return_url": @"my-app://payment-complete"
+                                                                      }
+                                                              });
+    XCTAssertNotNil(complete);
+    XCTAssertEqual(complete.type, STPPaymentIntentSourceActionTypeAuthorizeWithURL);
+    XCTAssertNotNil(complete.authorizeWithURL.url);
+    XCTAssertEqualObjects(complete.authorizeWithURL.url,
+                          [NSURL URLWithString:@"https://stripe.com/"]);
+    XCTAssertNotNil(complete.authorizeWithURL.returnURL);
+    XCTAssertEqualObjects(complete.authorizeWithURL.returnURL,
+                          [NSURL URLWithString:@"my-app://payment-complete"]);
+}
+
+@end

From 52f660618e90aa9e815ead7213b15eeb5f27a8e8 Mon Sep 17 00:00:00 2001
From: Dan Jackson <danj@stripe.com>
Date: Wed, 7 Nov 2018 15:06:29 -0800
Subject: [PATCH 10/16] Convert enum value to a string, instead of using `type`
 from the server

It's confusing to print `type = authorize_with_url` if decoding the source action details
as a `STPPaymentIntentSourceActionAuthorizeWithURL` object fails. Instead, log the value
that the enum currently has.
---
 Stripe/STPPaymentIntent+Private.h     |  8 ++++++++
 Stripe/STPPaymentIntent.m             | 12 ++++++++++++
 Stripe/STPPaymentIntentSourceAction.m |  2 +-
 3 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/Stripe/STPPaymentIntent+Private.h b/Stripe/STPPaymentIntent+Private.h
index 571980f6c75..3f0df95d26e 100644
--- a/Stripe/STPPaymentIntent+Private.h
+++ b/Stripe/STPPaymentIntent+Private.h
@@ -52,6 +52,14 @@ NS_ASSUME_NONNULL_BEGIN
  */
 + (STPPaymentIntentSourceActionType)sourceActionTypeFromString:(NSString *)string;
 
+/**
+ Return the string representing the provided `STPPaymentIntentSourceActionType`.
+
+ @param sourceActionType the enum value to convert to a string
+ @return the string, or @"unknown" if this was an unrecognized type
+ */
++ (NSString *)stringFromSourceActionType:(STPPaymentIntentSourceActionType)sourceActionType;
+
 @end
 
 NS_ASSUME_NONNULL_END
diff --git a/Stripe/STPPaymentIntent.m b/Stripe/STPPaymentIntent.m
index e68f77603a8..c2752ab18de 100644
--- a/Stripe/STPPaymentIntent.m
+++ b/Stripe/STPPaymentIntent.m
@@ -124,6 +124,18 @@ + (STPPaymentIntentSourceActionType)sourceActionTypeFromString:(NSString *)strin
     return statusNumber.integerValue;
 }
 
++ (NSString *)stringFromSourceActionType:(STPPaymentIntentSourceActionType)sourceActionType {
+    switch (sourceActionType) {
+        case STPPaymentIntentSourceActionTypeAuthorizeWithURL:
+            return @"authorize_with_url";
+        case STPPaymentIntentSourceActionTypeUnknown:
+            break;
+    }
+
+    // catch any unknown values here
+    return @"unknown";
+}
+
 
 #pragma mark - STPAPIResponseDecodable
 
diff --git a/Stripe/STPPaymentIntentSourceAction.m b/Stripe/STPPaymentIntentSourceAction.m
index 211fca3891c..0c9277ade8b 100644
--- a/Stripe/STPPaymentIntentSourceAction.m
+++ b/Stripe/STPPaymentIntentSourceAction.m
@@ -29,7 +29,7 @@ - (NSString *)description {
                                [NSString stringWithFormat:@"%@: %p", NSStringFromClass([self class]), self],
 
                                // Type
-                               [NSString stringWithFormat:@"type = %@", [allResponseFields stp_stringForKey:@"type"]],
+                               [NSString stringWithFormat:@"type = %@", [STPPaymentIntent stringFromSourceActionType:self.type]],
                                ] mutableCopy];
 
     // omit properties that don't apply to this type

From 4adcc957921c46422473ef045c95be0bd693e33c Mon Sep 17 00:00:00 2001
From: Dan Jackson <danj@stripe.com>
Date: Wed, 7 Nov 2018 15:48:12 -0800
Subject: [PATCH 11/16] Update disabled functional test for confirming to make
 sure the returnURL exists as desired

---
 Tests/Tests/STPPaymentIntentFunctionalTest.m | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/Tests/Tests/STPPaymentIntentFunctionalTest.m b/Tests/Tests/STPPaymentIntentFunctionalTest.m
index 5fbbc75f66b..b1d81d943e0 100644
--- a/Tests/Tests/STPPaymentIntentFunctionalTest.m
+++ b/Tests/Tests/STPPaymentIntentFunctionalTest.m
@@ -124,6 +124,8 @@ - (void)disabled_testConfirmPaymentIntentSucceeds {
 
     STPPaymentIntentParams *params = [[STPPaymentIntentParams alloc] initWithClientSecret:clientSecret];
     params.sourceParams = [self cardSourceParams];
+    // returnURL must be passed in while confirming (not creation time)
+    params.returnURL = @"example-app-scheme://authorized";
     [client confirmPaymentIntentWithParams:params
                                 completion:^(STPPaymentIntent * _Nullable paymentIntent, NSError * _Nullable error) {
                                     XCTAssertNil(error, @"With valid key + secret, should be able to confirm the intent");
@@ -135,6 +137,11 @@ - (void)disabled_testConfirmPaymentIntentSucceeds {
                                     // sourceParams is the 3DS-required test card
                                     XCTAssertEqual(paymentIntent.status, STPPaymentIntentStatusRequiresSourceAction);
 
+                                    // STPRedirectContext is relying on receiving returnURL
+                                    XCTAssertNotNil(paymentIntent.nextSourceAction.authorizeWithURL.returnURL);
+                                    XCTAssertEqualObjects(paymentIntent.nextSourceAction.authorizeWithURL.returnURL,
+                                                          [NSURL URLWithString:@"example-app-scheme://authorized"]);
+
                                     // Going to log all the fields, so that you, the developer manually running this test can inspect them
                                     NSLog(@"Confirmed PaymentIntent: %@", paymentIntent.allResponseFields);
 

From b4ccf91681a01229d9d8c3f4920e3095bbda2aa8 Mon Sep 17 00:00:00 2001
From: Dan Jackson <danj@stripe.com>
Date: Wed, 7 Nov 2018 17:56:19 -0800
Subject: [PATCH 12/16] Use
 `STPPaymentIntentSourceActionAuthorizeWithURL.returnURL` in
 `STPRedirectContext`

Also, since we're using our normal object parsing code, could simplify the whitebox test
that tried unexpected types during deserialization - covered elsewhere.
---
 Stripe/STPRedirectContext.m          | 23 +++++++--------
 Tests/Tests/STPRedirectContextTest.m | 44 ++++++++++------------------
 2 files changed, 26 insertions(+), 41 deletions(-)

diff --git a/Stripe/STPRedirectContext.m b/Stripe/STPRedirectContext.m
index c1064ecc35d..5107721448b 100644
--- a/Stripe/STPRedirectContext.m
+++ b/Stripe/STPRedirectContext.m
@@ -12,6 +12,8 @@
 #import "STPBlocks.h"
 #import "STPDispatchFunctions.h"
 #import "STPPaymentIntent.h"
+#import "STPPaymentIntentSourceAction.h"
+#import "STPPaymentIntentSourceActionAuthorizeWithURL.h"
 #import "STPSource.h"
 #import "STPURLCallbackHandler.h"
 #import "STPWeakStrongMacros.h"
@@ -56,24 +58,19 @@ - (nullable instancetype)initWithSource:(STPSource *)source
 
 - (nullable instancetype)initWithPaymentIntent:(STPPaymentIntent *)paymentIntent
                                     completion:(STPRedirectContextPaymentIntentCompletionBlock)completion {
-    NSURL *returnUrl = nil; // FIXME
-    if (!(returnUrl != nil
-          && paymentIntent.status == STPPaymentIntentStatusRequiresSourceAction
-          && [paymentIntent.allResponseFields[@"next_source_action"] isKindOfClass: [NSDictionary class]])) {
-        return nil;
-    }
+    NSURL *redirectURL = paymentIntent.nextSourceAction.authorizeWithURL.url;
+    NSURL *returnURL = paymentIntent.nextSourceAction.authorizeWithURL.returnURL;
 
-    NSDictionary *nextSourceAction = paymentIntent.allResponseFields[@"next_source_action"];
-    if (!([nextSourceAction[@"type"] isEqual:@"authorize_with_url"]
-          && [nextSourceAction[@"value"] isKindOfClass:[NSDictionary class]]
-          && [nextSourceAction[@"value"][@"url"] isKindOfClass:[NSString class]])) {
+    if (paymentIntent.status != STPPaymentIntentStatusRequiresSourceAction
+        || paymentIntent.nextSourceAction.type != STPPaymentIntentSourceActionTypeAuthorizeWithURL
+        || !redirectURL
+        || !returnURL) {
         return nil;
     }
 
-    NSString *redirectURL = nextSourceAction[@"value"][@"url"];
     return [self initWithNativeRedirectURL:nil
-                               redirectURL:[NSURL URLWithString:redirectURL]
-                                 returnURL:returnUrl
+                               redirectURL:redirectURL
+                                 returnURL:returnURL
                                 completion:^(NSError * _Nullable error) {
                                     completion(paymentIntent.clientSecret, error);
                                 }];
diff --git a/Tests/Tests/STPRedirectContextTest.m b/Tests/Tests/STPRedirectContextTest.m
index 2b37b2baf5c..98a2961922b 100644
--- a/Tests/Tests/STPRedirectContextTest.m
+++ b/Tests/Tests/STPRedirectContextTest.m
@@ -139,18 +139,18 @@ - (void)testInitWithPaymentIntent {
     XCTAssertNil(sut.nativeRedirectURL);
     XCTAssertEqualObjects(sut.redirectURL.absoluteString,
                           @"https://hooks.stripe.com/redirect/authenticate/src_1Cl1AeIl4IdHmuTb1L7x083A?client_secret=src_client_secret_DBNwUe9qHteqJ8qQBwNWiigk");
-    NSURL *returnUrl = nil; // FIXME
-    XCTAssertEqualObjects(sut.returnURL, returnUrl);
+    XCTAssertNotNil(paymentIntent.nextSourceAction.authorizeWithURL.returnURL);
+    XCTAssertEqualObjects(sut.returnURL, paymentIntent.nextSourceAction.authorizeWithURL.returnURL);
 
     // and make sure the completion calls the completion block above
-    if (sut.completion) sut.completion(fakeError); // FIXME: HACK to avoid EXC_BAD_ACCESS when NULL
+    sut.completion(fakeError);
     XCTAssertTrue(completionCalled);
 }
 
 - (void)testInitWithPaymentIntentFailures {
     NSMutableDictionary *json = [[STPTestUtils jsonNamed:STPTestJSONPaymentIntent] mutableCopy];
     json[@"next_source_action"] = [json[@"next_source_action"] mutableCopy];
-    json[@"next_source_action"][@"value"] = [json[@"next_source_action"][@"value"] mutableCopy];
+    json[@"next_source_action"][@"authorize_with_url"] = [json[@"next_source_action"][@"authorize_with_url"] mutableCopy];
 
     void (^unusedCompletion)(NSString *, NSError *) = ^(__unused NSString * _Nonnull clientSecret, __unused NSError * _Nullable error) {
         XCTFail(@"should not be constructed, definitely not completed");
@@ -164,37 +164,25 @@ - (void)testInitWithPaymentIntentFailures {
 
     XCTAssertNotNil(create(), @"before mutation of json, creation should succeed");
 
-    // `next_source_action` is not (currently) represented in the public API, and so there aren't
-    // any tests on it's decoding *other* than these right here. This is a white-box test for each condition
-    // that might result in a nil `STPRedirectContext`, because `STPRedirectContext` is the only place that
-    // understands `next_source_action` right now.
-
-    json[@"next_source_action"][@"value"][@"url"] = @"not a valid URL";
-    XCTAssertNil(create(), @"not created with an invalid URL in next_source_action.value.url");
-
-    json[@"next_source_action"][@"value"][@"url"] = @[@"an array", @"not a string"];
-    XCTAssertNil(create(), @"not created with a non-string next_source_action.value.url");
-
-    json[@"next_source_action"][@"value"] = @"not a dictionary";
-    XCTAssertNil(create(), @"not created with a non-dictionary next_source_action.value");
+    json[@"status"] = @"processing";
+    XCTAssertNil(create(), @"not created with wrong status");
+    json[@"status"] = @"requires_source_action";
 
-    json[@"next_source_action"][@"value"] = @{ @"url": @"http://example.com/" };
     json[@"next_source_action"][@"type"] = @"not_authorize_with_url";
     XCTAssertNil(create(), @"not created with wrong next_source_action.type");
-
     json[@"next_source_action"][@"type"] = @"authorize_with_url";
-    NSString *correctStatus = json[@"status"];
-    json[@"status"] = @"processing";
-    XCTAssertNil(create(), @"not created with wrong status");
 
-    json[@"status"] = correctStatus;
-    NSDictionary *nextSourceAction = json[@"next_source_action"];
-    json[@"next_source_action"] = @"not a dictionary";
-    XCTAssertNil(create(), @"not created with a non-dictionary next_source_action");
+    NSString *correctURL = json[@"next_source_action"][@"authorize_with_url"][@"url"];
+    json[@"next_source_action"][@"authorize_with_url"][@"url"] = @"not a valid URL";
+    XCTAssertNil(create(), @"not created with an invalid URL in next_source_action.authorize_with_url.url");
+    json[@"next_source_action"][@"authorize_with_url"][@"url"] = correctURL;
 
-    json[@"next_source_action"] = nextSourceAction;
-    json[@"return_url"] = @"not a url";
+    NSString *correctReturnURL = json[@"next_source_action"][@"authorize_with_url"][@"return_url"];
+    json[@"next_source_action"][@"authorize_with_url"][@"return_url"] = @"not a url";
     XCTAssertNil(create(), @"not created with invalid returnUrl");
+    json[@"next_source_action"][@"authorize_with_url"][@"return_url"] = correctReturnURL;
+
+    XCTAssertNotNil(create(), @"works again when everything is back to normal");
 }
 
 /**

From eba2751d7d94eb243cfe4543f71d3989a6e56137 Mon Sep 17 00:00:00 2001
From: Dan Jackson <danj@stripe.com>
Date: Wed, 7 Nov 2018 18:07:40 -0800
Subject: [PATCH 13/16] Update MIGRATING for removal of
 `STPPaymentIntent.returnUrl`

---
 MIGRATING.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/MIGRATING.md b/MIGRATING.md
index 73ce864f328..549fd08b409 100644
--- a/MIGRATING.md
+++ b/MIGRATING.md
@@ -6,6 +6,7 @@
   * Changes to the object returned by `STPPaymentCardTextField.cardParams` no longer mutate the object held by the `STPPaymentCardTextField`
   * This is a breaking change for code like: `paymentCardTextField.cardParams.name = @"Jane Doe";`
 * `STPPaymentIntentParams.returnUrl` has been renamed to `STPPaymentIntentParams.returnURL`. Xcode should offer a deprecation warning & fix-it to help you migrate.
+* `STPPaymentIntent.returnUrl` has been removed, because it's no longer a property of the PaymentIntent. When the PaymentIntent status is `.requiresSourceAction`, and the `nextSourceAction.type` is `.authorizeWithURL`, you can find the return URL at `nextSourceAction.authorizeWithURL.returnURL`.
 
 ### Migrating from versions < 13.1.0
  * The SDK now supports PaymentIntents with `STPPaymentIntent`, which use `STPRedirectContext` in the same way that `STPSource` does

From 04d4d10ab5ebb92244f07cf43ff9e89fcd548a44 Mon Sep 17 00:00:00 2001
From: Dan Jackson <danj@stripe.com>
Date: Thu, 8 Nov 2018 14:25:23 -0800
Subject: [PATCH 14/16] Use SDK's enum value instead of server's string
 constant in documentation

---
 .../STPPaymentIntentSourceActionAuthorizeWithURL.h              | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Stripe/PublicHeaders/STPPaymentIntentSourceActionAuthorizeWithURL.h b/Stripe/PublicHeaders/STPPaymentIntentSourceActionAuthorizeWithURL.h
index 536398c032a..81b89d10fe0 100644
--- a/Stripe/PublicHeaders/STPPaymentIntentSourceActionAuthorizeWithURL.h
+++ b/Stripe/PublicHeaders/STPPaymentIntentSourceActionAuthorizeWithURL.h
@@ -13,7 +13,7 @@
 NS_ASSUME_NONNULL_BEGIN
 
 /**
- The `STPPaymentIntentSourceAction` details when type is `authorize_with_url`.
+ The `STPPaymentIntentSourceAction` details when type is `STPPaymentIntentSourceActionTypeAuthorizeWithURL`.
 
  These are created & owned by the containing `STPPaymentIntent`.
  */

From 40e9acf7c3ccfb0fb0aeb1a9b89abd11d332763c Mon Sep 17 00:00:00 2001
From: Dan Jackson <danj@stripe.com>
Date: Thu, 8 Nov 2018 14:25:39 -0800
Subject: [PATCH 15/16] Use dict, not response, while decoding.

---
 Stripe/STPPaymentIntentSourceActionAuthorizeWithURL.m | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Stripe/STPPaymentIntentSourceActionAuthorizeWithURL.m b/Stripe/STPPaymentIntentSourceActionAuthorizeWithURL.m
index 82fe6cab006..3c24de7e799 100644
--- a/Stripe/STPPaymentIntentSourceActionAuthorizeWithURL.m
+++ b/Stripe/STPPaymentIntentSourceActionAuthorizeWithURL.m
@@ -40,7 +40,7 @@ + (nullable instancetype)decodedObjectFromAPIResponse:(nullable NSDictionary *)r
     }
 
     // required fields
-    NSURL *url = [response stp_urlForKey:@"url"];
+    NSURL *url = [dict stp_urlForKey:@"url"];
     if (!url) {
         return nil;
     }

From 09614b1359039e69476afaeb0f7e9e63f5479d8f Mon Sep 17 00:00:00 2001
From: Dan Jackson <danj@stripe.com>
Date: Tue, 13 Nov 2018 15:43:43 -0800
Subject: [PATCH 16/16] Fix copy/paste error in failing test

---
 Tests/Tests/STPPaymentIntentTest.m | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Tests/Tests/STPPaymentIntentTest.m b/Tests/Tests/STPPaymentIntentTest.m
index 778fd3b71fd..61f8b340b88 100644
--- a/Tests/Tests/STPPaymentIntentTest.m
+++ b/Tests/Tests/STPPaymentIntentTest.m
@@ -110,7 +110,7 @@ - (void)testConfirmationMethodFromString {
 - (void)testSourceActionFromString {
     XCTAssertEqual([STPPaymentIntent sourceActionTypeFromString:@"authorize_with_url"],
                    STPPaymentIntentSourceActionTypeAuthorizeWithURL);
-    XCTAssertEqual([STPPaymentIntent confirmationMethodFromString:@"AUTHORIZE_WITH_URL"],
+    XCTAssertEqual([STPPaymentIntent sourceActionTypeFromString:@"AUTHORIZE_WITH_URL"],
                    STPPaymentIntentSourceActionTypeAuthorizeWithURL);
 
     XCTAssertEqual([STPPaymentIntent confirmationMethodFromString:@"garbage"],