diff --git a/Stripe.xcodeproj/project.pbxproj b/Stripe.xcodeproj/project.pbxproj index e057e0b882d..0152239e62b 100644 --- a/Stripe.xcodeproj/project.pbxproj +++ b/Stripe.xcodeproj/project.pbxproj @@ -386,6 +386,17 @@ B3A99BC41FEAF2CA003F6ED3 /* STPLegalEntityParams.h in Headers */ = {isa = PBXBuildFile; fileRef = B3A99BC11FEAF2CA003F6ED3 /* STPLegalEntityParams.h */; settings = {ATTRIBUTES = (Public, ); }; }; B3A99BC51FEAF2CA003F6ED3 /* STPLegalEntityParams.m in Sources */ = {isa = PBXBuildFile; fileRef = B3A99BC21FEAF2CA003F6ED3 /* STPLegalEntityParams.m */; }; B3A99BC61FEAF2CA003F6ED3 /* STPLegalEntityParams.m in Sources */ = {isa = PBXBuildFile; fileRef = B3A99BC21FEAF2CA003F6ED3 /* STPLegalEntityParams.m */; }; + B3BDCAC220EEF2150034F7F5 /* STPPaymentIntent.m in Sources */ = {isa = PBXBuildFile; fileRef = B3BDCAC020EEF2150034F7F5 /* STPPaymentIntent.m */; }; + B3BDCAC320EEF2150034F7F5 /* STPPaymentIntent.m in Sources */ = {isa = PBXBuildFile; fileRef = B3BDCAC020EEF2150034F7F5 /* STPPaymentIntent.m */; }; + B3BDCAC420EEF2150034F7F5 /* STPPaymentIntent+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = B3BDCAC120EEF2150034F7F5 /* STPPaymentIntent+Private.h */; }; + B3BDCAC520EEF2150034F7F5 /* STPPaymentIntent+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = B3BDCAC120EEF2150034F7F5 /* STPPaymentIntent+Private.h */; }; + B3BDCAC820EEF22D0034F7F5 /* STPPaymentIntent.h in Headers */ = {isa = PBXBuildFile; fileRef = B3BDCAC620EEF22D0034F7F5 /* STPPaymentIntent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B3BDCAC920EEF22D0034F7F5 /* STPPaymentIntent.h in Headers */ = {isa = PBXBuildFile; fileRef = B3BDCAC620EEF22D0034F7F5 /* STPPaymentIntent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B3BDCACA20EEF22D0034F7F5 /* STPPaymentIntentEnums.h in Headers */ = {isa = PBXBuildFile; fileRef = B3BDCAC720EEF22D0034F7F5 /* STPPaymentIntentEnums.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B3BDCACB20EEF22D0034F7F5 /* STPPaymentIntentEnums.h in Headers */ = {isa = PBXBuildFile; fileRef = B3BDCAC720EEF22D0034F7F5 /* STPPaymentIntentEnums.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B3BDCACD20EEF4540034F7F5 /* STPPaymentIntentTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B3BDCACC20EEF4540034F7F5 /* STPPaymentIntentTest.m */; }; + B3BDCACF20EEF4640034F7F5 /* STPPaymentIntentFunctionalTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B3BDCACE20EEF4640034F7F5 /* STPPaymentIntentFunctionalTest.m */; }; + B3BDCADF20F0142C0034F7F5 /* PaymentIntent.json in Resources */ = {isa = PBXBuildFile; fileRef = B3BDCADE20F0142C0034F7F5 /* PaymentIntent.json */; }; B3C9CF2D2004595A005502ED /* STPConnectAccountFunctionalTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B3C9CF2C2004595A005502ED /* STPConnectAccountFunctionalTest.m */; }; C1054F911FE197AE0033C87E /* STPPaymentContextSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C1054F901FE197AE0033C87E /* STPPaymentContextSnapshotTests.m */; }; C1080F491CBECF7B007B2D89 /* STPAddress.h in Headers */ = {isa = PBXBuildFile; fileRef = C1080F471CBECF7B007B2D89 /* STPAddress.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1056,6 +1067,13 @@ B3A241381FFEB57400A2F00D /* STPConnectAccountParams.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPConnectAccountParams.m; sourceTree = ""; }; B3A99BC11FEAF2CA003F6ED3 /* STPLegalEntityParams.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = STPLegalEntityParams.h; path = PublicHeaders/STPLegalEntityParams.h; sourceTree = ""; }; B3A99BC21FEAF2CA003F6ED3 /* STPLegalEntityParams.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPLegalEntityParams.m; sourceTree = ""; }; + B3BDCAC020EEF2150034F7F5 /* STPPaymentIntent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPPaymentIntent.m; sourceTree = ""; }; + B3BDCAC120EEF2150034F7F5 /* STPPaymentIntent+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "STPPaymentIntent+Private.h"; sourceTree = ""; }; + B3BDCAC620EEF22D0034F7F5 /* STPPaymentIntent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = STPPaymentIntent.h; path = PublicHeaders/STPPaymentIntent.h; sourceTree = ""; }; + B3BDCAC720EEF22D0034F7F5 /* STPPaymentIntentEnums.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = STPPaymentIntentEnums.h; path = PublicHeaders/STPPaymentIntentEnums.h; sourceTree = ""; }; + B3BDCACC20EEF4540034F7F5 /* STPPaymentIntentTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPPaymentIntentTest.m; sourceTree = ""; }; + B3BDCACE20EEF4640034F7F5 /* STPPaymentIntentFunctionalTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPPaymentIntentFunctionalTest.m; sourceTree = ""; }; + B3BDCADE20F0142C0034F7F5 /* PaymentIntent.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = PaymentIntent.json; sourceTree = ""; }; B3C9CF2C2004595A005502ED /* STPConnectAccountFunctionalTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPConnectAccountFunctionalTest.m; sourceTree = ""; }; C1054F901FE197AE0033C87E /* STPPaymentContextSnapshotTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPPaymentContextSnapshotTests.m; sourceTree = ""; }; C1080F471CBECF7B007B2D89 /* STPAddress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = STPAddress.h; path = PublicHeaders/STPAddress.h; sourceTree = ""; }; @@ -1551,6 +1569,7 @@ 8BD2133B1F0458F5007F6FD1 /* BitcoinSource.json */, F1BA241C1E57BE5700E4A1CF /* CardSource.json */, F152322E1EA9344000D65C67 /* iDEALSource.json */, + B3BDCADE20F0142C0034F7F5 /* PaymentIntent.json */, 8BD2133D1F045D31007F6FD1 /* SEPADebitSource.json */, F16AA26D1F5A05A100207FFF /* AlipaySource.json */, ); @@ -1577,6 +1596,7 @@ 04CDB5241A5F3A9300B854EE /* STPCardFunctionalTest.m */, B3C9CF2C2004595A005502ED /* STPConnectAccountFunctionalTest.m */, C1CFCB6F1ED5E11500BE45DF /* STPFileFunctionalTest.m */, + B3BDCACE20EEF4640034F7F5 /* STPPaymentIntentFunctionalTest.m */, C1CFCB711ED5E11500BE45DF /* STPPIIFunctionalTest.m */, C1D7B5241E36C70D002181F5 /* STPSourceFunctionalTest.m */, ); @@ -1625,6 +1645,7 @@ 0438EF4B1B741B0100D506CC /* STPPaymentCardTextFieldViewModelTest.m */, 8B013C881F1E784A00DD831B /* STPPaymentConfigurationTest.m */, F14C872E1D4FCDBA00C7CC6A /* STPPaymentContextApplePayTest.m */, + B3BDCACC20EEF4540034F7F5 /* STPPaymentIntentTest.m */, F1DE87FF1F8D410D00602F4C /* STPPaymentMethodsViewControllerTest.m */, C1EEDCC91CA2186300A54582 /* STPPhoneNumberValidatorTest.m */, C1FEE5981CBFF24000A7632B /* STPPostalCodeValidatorTest.m */, @@ -1958,6 +1979,10 @@ 04F213301BCEAB61001D6F22 /* STPFormEncodable.h */, B3A99BC11FEAF2CA003F6ED3 /* STPLegalEntityParams.h */, B3A99BC21FEAF2CA003F6ED3 /* STPLegalEntityParams.m */, + B3BDCAC620EEF22D0034F7F5 /* STPPaymentIntent.h */, + B3BDCAC020EEF2150034F7F5 /* STPPaymentIntent.m */, + B3BDCAC120EEF2150034F7F5 /* STPPaymentIntent+Private.h */, + B3BDCAC720EEF22D0034F7F5 /* STPPaymentIntentEnums.h */, C1D7B51E1E36C32F002181F5 /* STPSource.h */, C1D7B51F1E36C32F002181F5 /* STPSource.m */, F19491DD1E5F6B8C001E1FC2 /* STPSourceCardDetails.h */, @@ -2191,6 +2216,7 @@ 04B31DDB1D09A4DC00EF1631 /* STPPaymentConfiguration+Private.h in Headers */, 045D710F1CEEE30500F6CD65 /* STPAspects.h in Headers */, 04F94DAF1D229F59004FC826 /* STPPaymentMethodsViewController+Private.h in Headers */, + B3BDCAC520EEF2150034F7F5 /* STPPaymentIntent+Private.h in Headers */, 04633AFD1CD129AF009D4FB5 /* STPPhoneNumberValidator.h in Headers */, 04633AFE1CD129B4009D4FB5 /* STPDelegateProxy.h in Headers */, F1FA6F961E25960500EB444D /* STPCoreScrollViewController+Private.h in Headers */, @@ -2220,6 +2246,8 @@ C113D21A1EBB9A36006FACC2 /* STPEphemeralKey.h in Headers */, 04A4C38E1C4F25F900B3B290 /* UIViewController+Stripe_ParentViewController.h in Headers */, C18410771EC2529400178149 /* STPEphemeralKeyManager.h in Headers */, + B3BDCAC920EEF22D0034F7F5 /* STPPaymentIntent.h in Headers */, + B3BDCACB20EEF22D0034F7F5 /* STPPaymentIntentEnums.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2330,6 +2358,7 @@ C1BD9B2E1E3940A200CEE925 /* STPSourceRedirect.h in Headers */, 04BC29A41CD8697900318357 /* STPTheme.h in Headers */, 04B31DF21D09F0A800EF1631 /* UIViewController+Stripe_NavigationItemProxy.h in Headers */, + B3BDCAC420EEF2150034F7F5 /* STPPaymentIntent+Private.h in Headers */, 8BD87B881EFB131700269C2B /* STPSourceCardDetails+Private.h in Headers */, 8BD87B8D1EFB152B00269C2B /* STPSourceRedirect+Private.h in Headers */, 04CDB4D31A5F30A700B854EE /* Stripe.h in Headers */, @@ -2359,6 +2388,8 @@ F152322A1EA9306100D65C67 /* NSURLComponents+Stripe.h in Headers */, F1D3A25A1EB014BD0095BFA9 /* UIImage+Stripe.h in Headers */, C124A17C1CCAA0C2007D42EE /* NSMutableURLRequest+Stripe.h in Headers */, + B3BDCAC820EEF22D0034F7F5 /* STPPaymentIntent.h in Headers */, + B3BDCACA20EEF22D0034F7F5 /* STPPaymentIntentEnums.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2507,6 +2538,7 @@ files = ( F1343BEA1D652CAD00F102D8 /* Customer.json in Resources */, 8BD2133E1F045D31007F6FD1 /* SEPADebitSource.json in Resources */, + B3BDCADF20F0142C0034F7F5 /* PaymentIntent.json in Resources */, 8BD213371F044B57007F6FD1 /* BankAccount.json in Resources */, C1C02CCC1ECCD0ED00DF5643 /* EphemeralKey.json in Resources */, F1BA24211E57BECA00E4A1CF /* 3DSSource.json in Resources */, @@ -2781,6 +2813,7 @@ 0438EF4D1B741B0100D506CC /* STPPaymentCardTextFieldViewModelTest.m in Sources */, 8BD87B951EFB1CB100269C2B /* STPSourceVerificationTest.m in Sources */, C124A1811CCAA1BF007D42EE /* NSMutableURLRequest+StripeTest.m in Sources */, + B3BDCACD20EEF4540034F7F5 /* STPPaymentIntentTest.m in Sources */, C1EEDCC61CA2126000A54582 /* STPDelegateProxyTest.m in Sources */, F1D777C01D81DD520076FA19 /* STPStringUtilsTest.m in Sources */, C1FEE5991CBFF24000A7632B /* STPPostalCodeValidatorTest.m in Sources */, @@ -2798,6 +2831,7 @@ C11810991CC6D46D0022FB55 /* NSDecimalNumber+StripeTest.m in Sources */, 8B5B4B441EFDD925005CF475 /* STPSourceOwnerTest.m in Sources */, 8B82C5CA1F2BC78F009639F7 /* STPApplePayPaymentMethodTest.m in Sources */, + B3BDCACF20EEF4640034F7F5 /* STPPaymentIntentFunctionalTest.m in Sources */, 8B013C891F1E784A00DD831B /* STPPaymentConfigurationTest.m in Sources */, C1EEDCC81CA2172700A54582 /* NSString+StripeTest.m in Sources */, 8BD87B901EFB17AA00269C2B /* STPSourceRedirectTest.m in Sources */, @@ -2913,6 +2947,7 @@ 04F94DB41D229F71004FC826 /* STPPaymentActivityIndicatorView.m in Sources */, C1BD9B251E393FFE00CEE925 /* STPSourceReceiver.m in Sources */, 04827D131D2575C6002DB3E8 /* STPImageLibrary.m in Sources */, + B3BDCAC320EEF2150034F7F5 /* STPPaymentIntent.m in Sources */, 045D712F1CF4ED7600F6CD65 /* STPBINRange.m in Sources */, F148ABCA1D5D334B0014FD92 /* STPLocalizationUtils.m in Sources */, 045D71111CEEE30500F6CD65 /* STPAspects.m in Sources */, @@ -2983,6 +3018,7 @@ C1BD9B2A1E39406C00CEE925 /* STPSourceOwner.m in Sources */, F1DEB89B1E2074480066B8E8 /* STPCoreViewController.m in Sources */, C1D7B5221E36C32F002181F5 /* STPSource.m in Sources */, + B3BDCAC220EEF2150034F7F5 /* STPPaymentIntent.m in Sources */, 049A3F8A1CC73C7100F57DE7 /* STPPaymentContext.m in Sources */, C192269F1EBA9A0800BED563 /* STPCustomerContext.m in Sources */, 0426B9731CEAE3EB006AC8DD /* UITableViewCell+Stripe_Borders.m in Sources */, diff --git a/Stripe/PublicHeaders/STPAPIClient.h b/Stripe/PublicHeaders/STPAPIClient.h index f62d1a72016..4f09fd2bb52 100644 --- a/Stripe/PublicHeaders/STPAPIClient.h +++ b/Stripe/PublicHeaders/STPAPIClient.h @@ -130,7 +130,7 @@ static NSString *const STPSDKVersion = @"13.0.3"; #pragma mark Connect Accounts /** - Stripe extensions for working with Connect Accounts + STPAPIClient extensions for working with Connect Accounts */ @interface STPAPIClient (ConnectAccounts) @@ -320,6 +320,23 @@ static NSString *const STPSDKVersion = @"13.0.3"; @end +#pragma mark Payment Intents + +/** + STPAPIClient extensions for working with PaymentIntent objects. + */ +@interface STPAPIClient (PaymentIntents) + +/** + Retrieves the PaymentIntent object using the given secret. @see https://stripe.com/docs/api#retrieve_payment_intent + + @param secret The client secret of the payment intent to be retrieved. Cannot be nil. + @param completion The callback to run with the returned PaymentIntent object, or an error. + */ +- (void)retrievePaymentIntentWithClientSecret:(NSString *)secret + completion:(STPPaymentIntentCompletionBlock)completion; +@end + #pragma mark URL callbacks /** diff --git a/Stripe/PublicHeaders/STPBlocks.h b/Stripe/PublicHeaders/STPBlocks.h index 90df4962795..d686b994e7e 100644 --- a/Stripe/PublicHeaders/STPBlocks.h +++ b/Stripe/PublicHeaders/STPBlocks.h @@ -14,6 +14,7 @@ @class STPSource; @class STPCustomer; @protocol STPSourceProtocol; +@class STPPaymentIntent; /** These values control the labels used in the shipping info collection form. @@ -86,7 +87,7 @@ typedef void (^STPJSONResponseCompletionBlock)(NSDictionary * __nullable jsonRes A callback to be run with a token response from the Stripe API. @param token The Stripe token from the response. Will be nil if an error occurs. @see STPToken - @param error The error returned from the response, or nil in one occurs. @see StripeError.h for possible values. + @param error The error returned from the response, or nil if none occurs. @see StripeError.h for possible values. */ typedef void (^STPTokenCompletionBlock)(STPToken * __nullable token, NSError * __nullable error); @@ -94,7 +95,7 @@ typedef void (^STPTokenCompletionBlock)(STPToken * __nullable token, NSError * _ A callback to be run with a source response from the Stripe API. @param source The Stripe source from the response. Will be nil if an error occurs. @see STPSource - @param error The error returned from the response, or nil in one occurs. @see StripeError.h for possible values. + @param error The error returned from the response, or nil if none occurs. @see StripeError.h for possible values. */ typedef void (^STPSourceCompletionBlock)(STPSource * __nullable source, NSError * __nullable error); @@ -102,10 +103,18 @@ typedef void (^STPSourceCompletionBlock)(STPSource * __nullable source, NSError A callback to be run with a source or card response from the Stripe API. @param source The Stripe source from the response. Will be nil if an error occurs. @see STPSourceProtocol - @param error The error returned from the response, or nil in one occurs. @see StripeError.h for possible values. + @param error The error returned from the response, or nil if none occurs. @see StripeError.h for possible values. */ typedef void (^STPSourceProtocolCompletionBlock)(id __nullable source, NSError * __nullable error); +/** + A callback to be run with a PaymentIntent response from the Stripe API. + + @param paymentIntent The Stripe PaymentIntent from the response. Will be nil if an error occurs. @see STPPaymentIntent + @param error The error returned from the response, or nil if none occurs. @see StripeError.h for possible values. + */ +typedef void (^STPPaymentIntentCompletionBlock)(STPPaymentIntent * __nullable paymentIntent, NSError * __nullable error); + /** A callback to be run with a validation result and shipping methods for a shipping address. @@ -121,7 +130,7 @@ typedef void (^STPShippingMethodsCompletionBlock)(STPShippingStatus status, NSEr A callback to be run with a file response from the Stripe API. @param file The Stripe file from the response. Will be nil if an error occurs. @see STPFile - @param error The error returned from the response, or nil in none occurs. @see StripeError.h for possible values. + @param error The error returned from the response, or nil if none occurs. @see StripeError.h for possible values. */ typedef void (^STPFileCompletionBlock)(STPFile * __nullable file, NSError * __nullable error); @@ -129,6 +138,6 @@ typedef void (^STPFileCompletionBlock)(STPFile * __nullable file, NSError * __nu A callback to be run with a customer response from the Stripe API. @param customer The Stripe customer from the response, or nil if an error occurred. @see STPCustomer - @param error The error returned from the response, or nil in none occurs. + @param error The error returned from the response, or nil if none occurs. */ typedef void (^STPCustomerCompletionBlock)(STPCustomer * __nullable customer, NSError * __nullable error); diff --git a/Stripe/PublicHeaders/STPPaymentIntent.h b/Stripe/PublicHeaders/STPPaymentIntent.h new file mode 100644 index 00000000000..af01d2f13c9 --- /dev/null +++ b/Stripe/PublicHeaders/STPPaymentIntent.h @@ -0,0 +1,98 @@ +// +// STPPaymentIntent.h +// Stripe +// +// Created by Daniel Jackson on 6/27/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +#import + +#import "STPAPIResponseDecodable.h" +#import "STPPaymentIntentEnums.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + A PaymentIntent tracks the process of collecting a payment from your customer. + + @see https://stripe.com/docs/api#payment_intents + @see https://stripe.com/docs/payments/dynamic-authentication + */ +@interface STPPaymentIntent : NSObject + +/** + You cannot directly instantiate an `STPPaymentIntent`. You should only use one that + has been returned from an `STPAPIClient` callback. + */ +- (instancetype)init __attribute__((unavailable("You cannot directly instantiate an STPPaymentIntent. You should only use one that has been returned from an STPAPIClient callback."))); + +/** + The Stripe ID of the PaymentIntent. + */ +@property (nonatomic, readonly) NSString *stripeId; + +/** + The client secret used to fetch this PaymentIntent + */ +@property (nonatomic, readonly) NSString *clientSecret; + +/** + Amount intended to be collected by this PaymentIntent. + */ +@property (nonatomic, readonly) NSNumber *amount; + +/** + If status is `STPPaymentIntentStatusCanceled`, when the PaymentIntent was canceled. + */ +@property (nonatomic, nullable, readonly) NSDate *canceledAt; + +/** + Capture method of this PaymentIntent + */ +@property (nonatomic, readonly) STPPaymentIntentCaptureMethod captureMethod; + +/** + Confirmation method of this PaymentIntent + */ +@property (nonatomic, readonly) STPPaymentIntentConfirmationMethod confirmationMethod; + +/** + When the PaymentIntent was created. + */ +@property (nonatomic, nullable, readonly) NSDate *created; + +/** + The currency associated with the PaymentIntent. + */ +@property (nonatomic, readonly) NSString *currency; + +/** + The `description` field of the PaymentIntent. + An arbitrary string attached to the object. Often useful for displaying to users. + */ +@property (nonatomic, nullable, readonly) NSString *stripeDescription; + +/** + Whether or not this PaymentIntent was created in livemode. + */ +@property (nonatomic, readonly) BOOL livemode; + +/** + Email address that the receipt for the resulting payment will be sent to. + */ +@property (nonatomic, nullable, readonly) NSString *receiptEmail; + +/** + The Stripe ID of the Source used in this PaymentIntent. + */ +@property (nonatomic, nullable, readonly) NSString *sourceId; + +/** + Status of the PaymentIntent + */ +@property (nonatomic, readonly) STPPaymentIntentStatus status; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe/PublicHeaders/STPPaymentIntentEnums.h b/Stripe/PublicHeaders/STPPaymentIntentEnums.h new file mode 100644 index 00000000000..b8431d16570 --- /dev/null +++ b/Stripe/PublicHeaders/STPPaymentIntentEnums.h @@ -0,0 +1,94 @@ +// +// STPPaymentIntentEnums.h +// Stripe +// +// Created by Daniel Jackson on 6/27/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +/** + Status types for an STPPaymentIntent + */ +typedef NS_ENUM(NSInteger, STPPaymentIntentStatus) { + /** + Unknown status + */ + STPPaymentIntentStatusUnknown, + + /** + This PaymentIntent requires a Source + */ + STPPaymentIntentStatusRequiresSource, + + /** + This PaymentIntent needs to be confirmed + */ + STPPaymentIntentStatusRequiresConfirmation, + + /** + The selected Source requires additional authentication steps. + Additional actions found via `next_source_action` + */ + STPPaymentIntentStatusRequiresSourceAction, + + /** + Stripe is processing this PaymentIntent + */ + STPPaymentIntentStatusProcessing, + + /** + The payment has succeeded + */ + STPPaymentIntentStatusSucceeded, + + /** + Indicates the payment must be captured, for STPPaymentIntentCaptureMethodManual + */ + STPPaymentIntentStatusRequiresCapture, + + /** + This PaymentIntent was canceled and cannot be changed. + */ + STPPaymentIntentStatusCanceled, +}; + +/** + Capture methods for a STPPaymentIntent + */ +typedef NS_ENUM(NSInteger, STPPaymentIntentCaptureMethod) { + /** + Unknown capture method + */ + STPPaymentIntentCaptureMethodUnknown, + + /** + The PaymentIntent will be automatically captured + */ + STPPaymentIntentCaptureMethodAutomatic, + + /** + The PaymentIntent must be manually captured once it has the status + `STPPaymentIntentStatusRequiresCapture` + */ + STPPaymentIntentCaptureMethodManual, +}; + +/** + Confirmation methods for a STPPaymentIntent + */ +typedef NS_ENUM(NSInteger, STPPaymentIntentConfirmationMethod) { + /** + Unknown confirmation method + */ + STPPaymentIntentConfirmationMethodUnknown, + + /** + Confirmed via publishable key + */ + STPPaymentIntentConfirmationMethodPublishable, + + /** + Confirmed via secret key + */ + STPPaymentIntentConfirmationMethodSecret, +}; diff --git a/Stripe/PublicHeaders/Stripe.h b/Stripe/PublicHeaders/Stripe.h index 7e2a1a5ffb9..f22104d14a4 100644 --- a/Stripe/PublicHeaders/Stripe.h +++ b/Stripe/PublicHeaders/Stripe.h @@ -37,6 +37,8 @@ #import "STPPaymentCardTextField.h" #import "STPPaymentConfiguration.h" #import "STPPaymentContext.h" +#import "STPPaymentIntent.h" +#import "STPPaymentIntentEnums.h" #import "STPPaymentMethod.h" #import "STPPaymentMethodsViewController.h" #import "STPPaymentResult.h" diff --git a/Stripe/STPAPIClient.m b/Stripe/STPAPIClient.m index b6cd4bd8167..ea1355981c7 100644 --- a/Stripe/STPAPIClient.m +++ b/Stripe/STPAPIClient.m @@ -26,6 +26,7 @@ #import "STPMultipartFormDataPart.h" #import "NSMutableURLRequest+Stripe.h" #import "STPPaymentConfiguration.h" +#import "STPPaymentIntent+Private.h" #import "STPSource+Private.h" #import "STPSourceParams.h" #import "STPSourceParams+Private.h" @@ -49,6 +50,7 @@ static NSString * const APIEndpointSources = @"sources"; static NSString * const APIEndpointCustomers = @"customers"; static NSString * const FileUploadURL = @"https://uploads.stripe.com/v1/files"; +static NSString * const APIEndpointPaymentIntents = @"payment_intents"; #pragma mark - Stripe @@ -552,3 +554,26 @@ + (void)deleteSource:(NSString *)sourceID fromCustomerUsingKey:(STPEphemeralKey } @end + +#pragma mark - Payment Intents + +@implementation STPAPIClient (PaymentIntents) + +- (void)retrievePaymentIntentWithClientSecret:(NSString *)secret + completion:(STPPaymentIntentCompletionBlock)completion { + NSCAssert(secret != nil, @"'secret' is required to retrieve a PaymentIntent"); + NSCAssert(completion != nil, @"'completion' is required to use the PaymentIntent that is retrieved"); + NSString *identifier = [STPPaymentIntent idFromClientSecret:secret]; + + NSString *endpoint = [NSString stringWithFormat:@"%@/%@", APIEndpointPaymentIntents, identifier]; + + [STPAPIRequest getWithAPIClient:self + endpoint:endpoint + parameters:@{ @"client_secret": secret } + deserializer:[STPPaymentIntent new] + completion:^(STPPaymentIntent *paymentIntent, __unused NSHTTPURLResponse *response, NSError *error) { + completion(paymentIntent, error); + }]; +} + +@end diff --git a/Stripe/STPPaymentIntent+Private.h b/Stripe/STPPaymentIntent+Private.h new file mode 100644 index 00000000000..1dba1665b59 --- /dev/null +++ b/Stripe/STPPaymentIntent+Private.h @@ -0,0 +1,49 @@ +// +// STPPaymentIntent+Private.h +// Stripe +// +// Created by Daniel Jackson on 6/27/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +#import "STPPaymentIntent.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface STPPaymentIntent () + +/** + Helper function for extracting PaymentIntent id from the Client Secret. + This avoids having to pass around both the id and the secret. + + @param clientSecret The `client_secret` from the PaymentIntent + */ ++ (nullable NSString *)idFromClientSecret:(NSString *)clientSecret; + +/** + Parse the string and return the correct `STPPaymentIntentStatus`, + or `STPPaymentIntentStatusUnknown` if it's unrecognized by this version of the SDK. + + @param string the NSString with the status + */ ++ (STPPaymentIntentStatus)statusFromString:(NSString *)string; + +/** + Parse the string and return the correct `STPPaymentIntentCaptureMethod`, + or `STPPaymentIntentCaptureMethodUnknown` if it's unrecognized by this version of the SDK. + + @param string the NSString with the capture method + */ ++ (STPPaymentIntentCaptureMethod)captureMethodFromString:(NSString *)string; + +/** + Parse the string and return the correct `STPPaymentIntentConfirmationMethod`, + or `STPPaymentIntentConfirmationMethodUnknown` if it's unrecognized by this version of the SDK. + + @param string the NSString with the capture method + */ ++ (STPPaymentIntentConfirmationMethod)confirmationMethodFromString:(NSString *)string; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe/STPPaymentIntent.m b/Stripe/STPPaymentIntent.m new file mode 100644 index 00000000000..f780c4efec5 --- /dev/null +++ b/Stripe/STPPaymentIntent.m @@ -0,0 +1,161 @@ +// +// STPPaymentIntent.m +// Stripe +// +// Created by Daniel Jackson on 6/27/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +#import "STPPaymentIntent.h" +#import "STPPaymentIntent+Private.h" + +#import "NSDictionary+Stripe.h" + +@interface STPPaymentIntent () +@property (nonatomic, copy, readwrite) NSString *stripeId; +@property (nonatomic, copy, readwrite) NSString *clientSecret; +@property (nonatomic, copy, readwrite) NSNumber *amount; +@property (nonatomic, strong, nullable, readwrite) NSDate *canceledAt; +@property (nonatomic, assign, readwrite) STPPaymentIntentCaptureMethod captureMethod; +@property (nonatomic, assign, readwrite) STPPaymentIntentConfirmationMethod confirmationMethod; +@property (nonatomic, strong, nullable, readwrite) NSDate *created; +@property (nonatomic, copy, readwrite) NSString *currency; +@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) NSString *sourceId; +@property (nonatomic, assign, readwrite) STPPaymentIntentStatus status; + +@property (nonatomic, copy, nonnull, readwrite) NSDictionary *allResponseFields; +@end + +@implementation STPPaymentIntent + +- (NSString *)description { + NSArray *props = @[ + // Object + [NSString stringWithFormat:@"%@: %p", NSStringFromClass([self class]), self], + + // Identifier + [NSString stringWithFormat:@"stripeId = %@", self.stripeId], + + // PaymentIntent details (alphabetical) + [NSString stringWithFormat:@"amount = %@", self.amount], + [NSString stringWithFormat:@"canceledAt = %@", self.canceledAt], + [NSString stringWithFormat:@"captureMethod = %@", [self.allResponseFields stp_stringForKey:@"capture_method"]], + [NSString stringWithFormat:@"clientSecret = %@", (self.clientSecret) ? @"" : nil], + [NSString stringWithFormat:@"confirmationMethod = %@", [self.allResponseFields stp_stringForKey:@"confirmation_method"]], + [NSString stringWithFormat:@"created = %@", self.created], + [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:@"receiptEmail = %@", self.receiptEmail], + [NSString stringWithFormat:@"shipping = %@", self.allResponseFields[@"shipping"]], + [NSString stringWithFormat:@"sourceId = %@", self.sourceId], + [NSString stringWithFormat:@"status = %@", [self.allResponseFields stp_stringForKey:@"status"]], + ]; + + return [NSString stringWithFormat:@"<%@>", [props componentsJoinedByString:@"; "]]; +} + +#pragma mark - STPPaymentIntent+Private.h + ++ (nullable NSString *)idFromClientSecret:(NSString *)clientSecret { + // see parseClientSecret from stripe-js-v3 + NSArray *components = [clientSecret componentsSeparatedByString:@"_secret_"]; + if (components.count >= 2 && [components[0] hasPrefix:@"pi_"]) { + return components[0]; + } + else { + return nil; + } +} + +#pragma mark - STPPaymentIntentEnum support + ++ (STPPaymentIntentStatus)statusFromString:(NSString *)string { + NSDictionary *map = @{ + @"requires_source": @(STPPaymentIntentStatusRequiresSource), + @"requires_confirmation": @(STPPaymentIntentStatusRequiresConfirmation), + @"requires_source_action": @(STPPaymentIntentStatusRequiresSourceAction), + @"processing": @(STPPaymentIntentStatusProcessing), + @"succeeded": @(STPPaymentIntentStatusSucceeded), + @"requires_capture": @(STPPaymentIntentStatusRequiresCapture), + @"canceled": @(STPPaymentIntentStatusCanceled), + }; + + NSString *key = string.lowercaseString; + NSNumber *statusNumber = map[key] ?: @(STPPaymentIntentStatusUnknown); + return statusNumber.integerValue; +} + ++ (STPPaymentIntentCaptureMethod)captureMethodFromString:(NSString *)string { + NSDictionary *map = @{ + @"manual": @(STPPaymentIntentCaptureMethodManual), + @"automatic": @(STPPaymentIntentCaptureMethodAutomatic), + }; + + NSString *key = string.lowercaseString; + NSNumber *statusNumber = map[key] ?: @(STPPaymentIntentCaptureMethodUnknown); + return statusNumber.integerValue; +} + ++ (STPPaymentIntentConfirmationMethod)confirmationMethodFromString:(NSString *)string { + NSDictionary *map = @{ + @"secret": @(STPPaymentIntentConfirmationMethodSecret), + @"publishable": @(STPPaymentIntentConfirmationMethodPublishable), + }; + + NSString *key = string.lowercaseString; + NSNumber *statusNumber = map[key] ?: @(STPPaymentIntentConfirmationMethodUnknown); + return statusNumber.integerValue; +} + + +#pragma mark - STPAPIResponseDecodable + ++ (nullable instancetype)decodedObjectFromAPIResponse:(nullable NSDictionary *)response { + NSDictionary *dict = [response stp_dictionaryByRemovingNulls]; + if (!dict) { + return nil; + } + + // required fields + NSString *stripeId = [dict stp_stringForKey:@"id"]; + NSString *clientSecret = [dict stp_stringForKey:@"client_secret"]; + NSNumber *amount = [dict stp_numberForKey:@"amount"]; + NSString *currency = [dict stp_stringForKey:@"currency"]; + NSString *rawStatus = [dict stp_stringForKey:@"status"]; + if (!stripeId || !clientSecret || amount == nil || !currency || !rawStatus || !dict[@"livemode"]) { + return nil; + } + + STPPaymentIntent *paymentIntent = [self new]; + + paymentIntent.stripeId = stripeId; + paymentIntent.clientSecret = clientSecret; + paymentIntent.amount = amount; + paymentIntent.canceledAt = [dict stp_dateForKey:@"canceled_at"]; + NSString *rawCaptureMethod = [dict stp_stringForKey:@"capture_method"]; + paymentIntent.captureMethod = [[self class] captureMethodFromString:rawCaptureMethod]; + NSString *rawConfirmationMethod = [dict stp_stringForKey:@"confirmation_method"]; + paymentIntent.confirmationMethod = [[self class] confirmationMethodFromString:rawConfirmationMethod]; + paymentIntent.created = [dict stp_dateForKey:@"created"]; + 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. + paymentIntent.receiptEmail = [dict stp_stringForKey:@"receipt_email"]; + // FIXME: add support for `shipping` + paymentIntent.sourceId = [dict stp_stringForKey:@"source"]; + paymentIntent.status = [[self class] statusFromString:rawStatus]; + + paymentIntent.allResponseFields = dict; + + return paymentIntent; +} + +@end diff --git a/Tests/Tests/PaymentIntent.json b/Tests/Tests/PaymentIntent.json new file mode 100644 index 00000000000..97bb95c05c8 --- /dev/null +++ b/Tests/Tests/PaymentIntent.json @@ -0,0 +1,40 @@ +{ + "id": "pi_1Cl15wIl4IdHmuTbCWrpJXN6", + "object": "payment_intent", + "allowed_source_types": [ + "card" + ], + "amount": 2345, + "canceled_at": 1530911045, + "capture_method": "manual", + "client_secret": "pi_1Cl15wIl4IdHmuTbCWrpJXN6_secret_EkKtQ7Sg75hLDFKqFG8DtWcaK", + "confirmation_method": "publishable", + "created": 1530911040, + "currency": "usd", + "description": "My Sample PaymentIntent", + "livemode": false, + "next_source_action": { + "type": "authorize_with_url", + "value": { + "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", + "country": "USA", + "line1": "123 Main St", + "line2": "Apt 456", + "postal_code": "94107", + "state": "CA" + }, + "carrier": "USPS", + "name": "Dan", + "phone": "1-415-555-1234", + "tracking_number": "xyz123abc" + }, + "source": "src_1Cl1AdIl4IdHmuTbseiDWq6m", + "status": "requires_source_action" +} diff --git a/Tests/Tests/STPPaymentIntentFunctionalTest.m b/Tests/Tests/STPPaymentIntentFunctionalTest.m new file mode 100644 index 00000000000..462ea7c0fe9 --- /dev/null +++ b/Tests/Tests/STPPaymentIntentFunctionalTest.m @@ -0,0 +1,82 @@ +// +// STPPaymentIntentFunctionalTest.m +// StripeiOS Tests +// +// Created by Daniel Jackson on 6/27/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +#import +@import Stripe; + +#import "STPPaymentIntent+Private.h" + +@interface STPPaymentIntentFunctionalTest : XCTestCase + +@end + +@implementation STPPaymentIntentFunctionalTest + +- (void)testRetrievePreviousCreatedPaymentIntent { + STPAPIClient *client = [[STPAPIClient alloc] initWithPublishableKey:@"pk_test_kFIsmbqInGw6ynJJDMGvsjRi"]; + XCTestExpectation *expectation = [self expectationWithDescription:@"Payment Intent retrieve"]; + + [client retrievePaymentIntentWithClientSecret:@"pi_1ChlnaIl4IdHmuTbVnM2HCCf_secret_0T6n3wuf21l04Jun2ZCOB8rOZ" + completion:^(STPPaymentIntent *paymentIntent, NSError *error) { + XCTAssertNil(error); + + XCTAssertNotNil(paymentIntent); + XCTAssertEqualObjects(paymentIntent.stripeId, @"pi_1ChlnaIl4IdHmuTbVnM2HCCf"); + XCTAssertEqualObjects(paymentIntent.amount, @(100)); + XCTAssertEqualObjects(paymentIntent.currency, @"usd"); + XCTAssertFalse(paymentIntent.livemode); + XCTAssertNil(paymentIntent.sourceId); + XCTAssertEqual(paymentIntent.status, STPPaymentIntentStatusCanceled); + + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:5 handler:nil]; +} + +- (void)testRetrieveWithWrongSecret { + STPAPIClient *client = [[STPAPIClient alloc] initWithPublishableKey:@"pk_test_kFIsmbqInGw6ynJJDMGvsjRi"]; + XCTestExpectation *expectation = [self expectationWithDescription:@"Payment Intent retrieve"]; + + [client retrievePaymentIntentWithClientSecret:@"pi_1ChlnaIl4IdHmuTbVnM2HCCf_secret_bad-secret" + completion:^(STPPaymentIntent *paymentIntent, NSError *error) { + XCTAssertNil(paymentIntent); + + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, StripeDomain); + XCTAssertEqual(error.code, STPInvalidRequestError); + XCTAssertEqualObjects(error.userInfo[STPErrorParameterKey], + @"clientSecret"); + + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:5 handler:nil]; +} + +- (void)testRetrieveMismatchedPublishableKey { + STPAPIClient *client = [[STPAPIClient alloc] initWithPublishableKey:@"pk_test_dCyfhfyeO2CZkcvT5xyIDdJj"]; + XCTestExpectation *expectation = [self expectationWithDescription:@"Payment Intent retrieve"]; + + [client retrievePaymentIntentWithClientSecret:@"pi_1ChlnaIl4IdHmuTbVnM2HCCf_secret_0T6n3wuf21l04Jun2ZCOB8rOZ" + completion:^(STPPaymentIntent *paymentIntent, NSError *error) { + XCTAssertNil(paymentIntent); + + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, StripeDomain); + XCTAssertEqual(error.code, STPInvalidRequestError); + XCTAssertEqualObjects(error.userInfo[STPErrorParameterKey], + @"intent"); + + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +@end diff --git a/Tests/Tests/STPPaymentIntentTest.m b/Tests/Tests/STPPaymentIntentTest.m new file mode 100644 index 00000000000..59586d39c66 --- /dev/null +++ b/Tests/Tests/STPPaymentIntentTest.m @@ -0,0 +1,168 @@ +// +// STPPaymentIntentTest.m +// StripeiOS Tests +// +// Created by Daniel Jackson on 6/27/18. +// Copyright © 2018 Stripe, Inc. All rights reserved. +// + +#import + +#import "STPPaymentIntent.h" +#import "STPPaymentIntent+Private.h" + +#import "STPTestUtils.h" + +@interface STPPaymentIntentTest : XCTestCase + +@end + +@implementation STPPaymentIntentTest + +- (void)testIdentifierFromSecret { + XCTAssertEqualObjects([STPPaymentIntent idFromClientSecret:@"pi_123_secret_XYZ"], + @"pi_123"); + XCTAssertEqualObjects([STPPaymentIntent idFromClientSecret:@"pi_123_secret_RandomlyContains_secret_WhichIsFine"], + @"pi_123"); + + XCTAssertNil([STPPaymentIntent idFromClientSecret:@""]); + XCTAssertNil([STPPaymentIntent idFromClientSecret:@"po_123_secret_HasBadPrefix"]); + XCTAssertNil([STPPaymentIntent idFromClientSecret:@"MissingSentinalForSplitting"]); +} + +- (void)testStatusFromString { + XCTAssertEqual([STPPaymentIntent statusFromString:@"requires_source"], + STPPaymentIntentStatusRequiresSource); + XCTAssertEqual([STPPaymentIntent statusFromString:@"REQUIRES_SOURCE"], + STPPaymentIntentStatusRequiresSource); + + XCTAssertEqual([STPPaymentIntent statusFromString:@"requires_confirmation"], + STPPaymentIntentStatusRequiresConfirmation); + XCTAssertEqual([STPPaymentIntent statusFromString:@"REQUIRES_CONFIRMATION"], + STPPaymentIntentStatusRequiresConfirmation); + + XCTAssertEqual([STPPaymentIntent statusFromString:@"requires_source_action"], + STPPaymentIntentStatusRequiresSourceAction); + XCTAssertEqual([STPPaymentIntent statusFromString:@"REQUIRES_SOURCE_ACTION"], + STPPaymentIntentStatusRequiresSourceAction); + + XCTAssertEqual([STPPaymentIntent statusFromString:@"processing"], + STPPaymentIntentStatusProcessing); + XCTAssertEqual([STPPaymentIntent statusFromString:@"PROCESSING"], + STPPaymentIntentStatusProcessing); + + XCTAssertEqual([STPPaymentIntent statusFromString:@"succeeded"], + STPPaymentIntentStatusSucceeded); + XCTAssertEqual([STPPaymentIntent statusFromString:@"SUCCEEDED"], + STPPaymentIntentStatusSucceeded); + + XCTAssertEqual([STPPaymentIntent statusFromString:@"requires_capture"], + STPPaymentIntentStatusRequiresCapture); + XCTAssertEqual([STPPaymentIntent statusFromString:@"REQUIRES_CAPTURE"], + STPPaymentIntentStatusRequiresCapture); + + XCTAssertEqual([STPPaymentIntent statusFromString:@"canceled"], + STPPaymentIntentStatusCanceled); + XCTAssertEqual([STPPaymentIntent statusFromString:@"CANCELED"], + STPPaymentIntentStatusCanceled); + + XCTAssertEqual([STPPaymentIntent statusFromString:@"garbage"], + STPPaymentIntentStatusUnknown); + XCTAssertEqual([STPPaymentIntent statusFromString:@"GARBAGE"], + STPPaymentIntentStatusUnknown); +} + +- (void)testCaptureMethodFromString { + XCTAssertEqual([STPPaymentIntent captureMethodFromString:@"manual"], + STPPaymentIntentCaptureMethodManual); + XCTAssertEqual([STPPaymentIntent captureMethodFromString:@"MANUAL"], + STPPaymentIntentCaptureMethodManual); + + XCTAssertEqual([STPPaymentIntent captureMethodFromString:@"automatic"], + STPPaymentIntentCaptureMethodAutomatic); + XCTAssertEqual([STPPaymentIntent captureMethodFromString:@"AUTOMATIC"], + STPPaymentIntentCaptureMethodAutomatic); + + XCTAssertEqual([STPPaymentIntent captureMethodFromString:@"garbage"], + STPPaymentIntentCaptureMethodUnknown); + XCTAssertEqual([STPPaymentIntent captureMethodFromString:@"GARBAGE"], + STPPaymentIntentCaptureMethodUnknown); +} + +- (void)testConfirmationMethodFromString { + XCTAssertEqual([STPPaymentIntent confirmationMethodFromString:@"secret"], + STPPaymentIntentConfirmationMethodSecret); + XCTAssertEqual([STPPaymentIntent confirmationMethodFromString:@"SECRET"], + STPPaymentIntentConfirmationMethodSecret); + + XCTAssertEqual([STPPaymentIntent confirmationMethodFromString:@"publishable"], + STPPaymentIntentConfirmationMethodPublishable); + XCTAssertEqual([STPPaymentIntent confirmationMethodFromString:@"PUBLISHABLE"], + STPPaymentIntentConfirmationMethodPublishable); + + XCTAssertEqual([STPPaymentIntent confirmationMethodFromString:@"garbage"], + STPPaymentIntentConfirmationMethodUnknown); + XCTAssertEqual([STPPaymentIntent confirmationMethodFromString:@"GARBAGE"], + STPPaymentIntentConfirmationMethodUnknown); +} + +#pragma mark - Description Tests + +- (void)testDescription { + STPPaymentIntent *paymentIntent = [STPPaymentIntent decodedObjectFromAPIResponse:[STPTestUtils jsonNamed:@"PaymentIntent"]]; + + XCTAssertNotNil(paymentIntent); + NSString *desc = paymentIntent.description; + XCTAssertTrue([desc containsString:NSStringFromClass([paymentIntent class])]); + XCTAssertGreaterThan(desc.length, 500UL, @"Custom description should be long"); +} + +#pragma mark - STPAPIResponseDecodable Tests + +- (void)testDecodedObjectFromAPIResponseRequiredFields { + NSDictionary *fullJson = [STPTestUtils jsonNamed:@"PaymentIntent"]; + + XCTAssertNotNil([STPPaymentIntent decodedObjectFromAPIResponse:fullJson], @"can decode with full json"); + + NSArray *requiredFields = @[ + @"id", + @"client_secret", + @"amount", + @"currency", + @"livemode", + @"status", + ]; + + for (NSString *field in requiredFields) { + NSMutableDictionary *partialJson = [fullJson mutableCopy]; + + XCTAssertNotNil(partialJson[field], @"json should contain %@", field); + [partialJson removeObjectForKey:field]; + + XCTAssertNil([STPPaymentIntent decodedObjectFromAPIResponse:partialJson], @"should not decode without %@", field); + } +} + +- (void)testDecodedObjectFromAPIResponseMapping { + NSDictionary *response = [STPTestUtils jsonNamed:@"PaymentIntent"]; + STPPaymentIntent *paymentIntent = [STPPaymentIntent decodedObjectFromAPIResponse:response]; + + XCTAssertEqualObjects(paymentIntent.stripeId, @"pi_1Cl15wIl4IdHmuTbCWrpJXN6"); + XCTAssertEqualObjects(paymentIntent.clientSecret, @"pi_1Cl15wIl4IdHmuTbCWrpJXN6_secret_EkKtQ7Sg75hLDFKqFG8DtWcaK"); + XCTAssertEqualObjects(paymentIntent.amount, @2345); + XCTAssertEqualObjects(paymentIntent.canceledAt, [NSDate dateWithTimeIntervalSince1970:1530911045]); + XCTAssertEqual(paymentIntent.captureMethod, STPPaymentIntentCaptureMethodManual); + XCTAssertEqual(paymentIntent.confirmationMethod, STPPaymentIntentConfirmationMethodPublishable); + XCTAssertEqualObjects(paymentIntent.created, [NSDate dateWithTimeIntervalSince1970:1530911040]); + XCTAssertEqualObjects(paymentIntent.currency, @"usd"); + XCTAssertEqualObjects(paymentIntent.stripeDescription, @"My Sample PaymentIntent"); + XCTAssertFalse(paymentIntent.livemode); + XCTAssertEqualObjects(paymentIntent.receiptEmail, @"danj@example.com"); + XCTAssertEqualObjects(paymentIntent.sourceId, @"src_1Cl1AdIl4IdHmuTbseiDWq6m"); + XCTAssertEqual(paymentIntent.status, STPPaymentIntentStatusRequiresSourceAction); + + XCTAssertNotEqual(paymentIntent.allResponseFields, response, @"should have own copy of fields"); + XCTAssertEqualObjects(paymentIntent.allResponseFields, response, @"fields values should match"); +} + +@end