diff --git a/Example/Custom Integration.xcodeproj/project.pbxproj b/Example/Custom Integration.xcodeproj/project.pbxproj index 0595bd94a50..04582d48e5e 100644 --- a/Example/Custom Integration.xcodeproj/project.pbxproj +++ b/Example/Custom Integration.xcodeproj/project.pbxproj @@ -18,6 +18,9 @@ 04D076171A69C11600094431 /* Stripe.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 04533F0C1A68812D00C7E52E /* Stripe.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 31A8934D230F6ABD007ABE37 /* FPXExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 31A8934C230F6ABD007ABE37 /* FPXExampleViewController.m */; }; 366F93B0225FF2A2005CFBF6 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 366F93AF225FF2A2005CFBF6 /* README.md */; }; + 36B6CB5A234BE3FA00331C38 /* PaymentExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 36B6CB59234BE3FA00331C38 /* PaymentExampleViewController.m */; }; + 36B6CB5D234BEB8400331C38 /* SEPADebitExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 36B6CB5C234BEB8400331C38 /* SEPADebitExampleViewController.m */; }; + 36B6CB64234FD9AA00331C38 /* iDEALExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 36B6CB63234FD9AA00331C38 /* iDEALExampleViewController.m */; }; 36D4EA6422DFEF1300619BA8 /* CardSetupIntentBackendExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 36D4EA6322DFEF1300619BA8 /* CardSetupIntentBackendExampleViewController.m */; }; 8BBD79C6207FD2F900F85BED /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8BBD79C8207FD2F900F85BED /* Localizable.strings */; }; B3BDCADD20EF03010034F7F5 /* CardAutomaticConfirmationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B3BDCADB20EF03010034F7F5 /* CardAutomaticConfirmationViewController.m */; }; @@ -61,6 +64,12 @@ 31A8934B230F6ABD007ABE37 /* FPXExampleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FPXExampleViewController.h; sourceTree = ""; }; 31A8934C230F6ABD007ABE37 /* FPXExampleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FPXExampleViewController.m; sourceTree = ""; }; 366F93AF225FF2A2005CFBF6 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = "Custom Integration/README.md"; sourceTree = ""; }; + 36B6CB58234BE3FA00331C38 /* PaymentExampleViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PaymentExampleViewController.h; sourceTree = ""; }; + 36B6CB59234BE3FA00331C38 /* PaymentExampleViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PaymentExampleViewController.m; sourceTree = ""; }; + 36B6CB5B234BEB8400331C38 /* SEPADebitExampleViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SEPADebitExampleViewController.h; sourceTree = ""; }; + 36B6CB5C234BEB8400331C38 /* SEPADebitExampleViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SEPADebitExampleViewController.m; sourceTree = ""; }; + 36B6CB62234FD9AA00331C38 /* iDEALExampleViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iDEALExampleViewController.h; sourceTree = ""; }; + 36B6CB63234FD9AA00331C38 /* iDEALExampleViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iDEALExampleViewController.m; sourceTree = ""; }; 36D4EA6222DFEF1300619BA8 /* CardSetupIntentBackendExampleViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CardSetupIntentBackendExampleViewController.h; sourceTree = ""; }; 36D4EA6322DFEF1300619BA8 /* CardSetupIntentBackendExampleViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CardSetupIntentBackendExampleViewController.m; sourceTree = ""; }; 8BBD79C7207FD2F900F85BED /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; @@ -148,6 +157,10 @@ 8BBD79C8207FD2F900F85BED /* Localizable.strings */, B607FFBB2321DA99004203E0 /* MyAPIClient.h */, B607FFBC2321DA99004203E0 /* MyAPIClient.m */, + 36B6CB58234BE3FA00331C38 /* PaymentExampleViewController.h */, + 36B6CB59234BE3FA00331C38 /* PaymentExampleViewController.m */, + 36B6CB5B234BEB8400331C38 /* SEPADebitExampleViewController.h */, + 36B6CB5C234BEB8400331C38 /* SEPADebitExampleViewController.m */, 04533EB01A68802E00C7E52E /* ShippingManager.h */, 04533EB11A68802E00C7E52E /* ShippingManager.m */, C1CACE921E5E3DF6002D0821 /* SofortExampleViewController.h */, @@ -155,6 +168,8 @@ 04533E8A1A687F5D00C7E52E /* Supporting Files */, B65E8FCA22FA078A0057E64A /* WeChatPayExampleViewController.h */, B65E8FCB22FA078A0057E64A /* WeChatPayExampleViewController.m */, + 36B6CB62234FD9AA00331C38 /* iDEALExampleViewController.h */, + 36B6CB63234FD9AA00331C38 /* iDEALExampleViewController.m */, ); path = "Custom Integration"; sourceTree = ""; @@ -281,6 +296,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 36B6CB64234FD9AA00331C38 /* iDEALExampleViewController.m in Sources */, 04533F191A688A0A00C7E52E /* Constants.m in Sources */, C1CACE941E5E3DF6002D0821 /* SofortExampleViewController.m in Sources */, C1CACE861E5DE6C3002D0821 /* CardManualConfirmationExampleViewController.m in Sources */, @@ -290,10 +306,12 @@ 36D4EA6422DFEF1300619BA8 /* CardSetupIntentBackendExampleViewController.m in Sources */, 04533E901A687F5D00C7E52E /* AppDelegate.m in Sources */, B65E8FCC22FA078A0057E64A /* WeChatPayExampleViewController.m in Sources */, + 36B6CB5A234BE3FA00331C38 /* PaymentExampleViewController.m in Sources */, C1CACE891E5DF7A9002D0821 /* ApplePayExampleViewController.m in Sources */, B607FFBD2321DA99004203E0 /* MyAPIClient.m in Sources */, 04533E8D1A687F5D00C7E52E /* main.m in Sources */, 31A8934D230F6ABD007ABE37 /* FPXExampleViewController.m in Sources */, + 36B6CB5D234BEB8400331C38 /* SEPADebitExampleViewController.m in Sources */, C12C50DD1E57B3C800EC6D58 /* BrowseExamplesViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -376,7 +394,7 @@ "$(inherited)", "$(PROJECT_DIR)/**", ); - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -431,7 +449,7 @@ "$(inherited)", "$(PROJECT_DIR)/**", ); - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/Example/Custom Integration/BrowseExamplesViewController.m b/Example/Custom Integration/BrowseExamplesViewController.m index d10c1c0acc5..85b8bfc52b7 100644 --- a/Example/Custom Integration/BrowseExamplesViewController.m +++ b/Example/Custom Integration/BrowseExamplesViewController.m @@ -15,8 +15,10 @@ #import "CardManualConfirmationExampleViewController.h" #import "CardSetupIntentBackendExampleViewController.h" #import "CardSetupIntentExampleViewController.h" +#import "iDEALExampleViewController.h" #import "SofortExampleViewController.h" #import "FPXExampleViewController.h" +#import "SEPADebitExampleViewController.h" #import "WeChatPayExampleViewController.h" /** @@ -39,7 +41,7 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return 8; + return 10; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { @@ -69,6 +71,12 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N case 7: cell.textLabel.text = @"FPX"; break; + case 8: + cell.textLabel.text = @"SEPA Debit"; + break; + case 9: + cell.textLabel.text = @"iDEAL"; + break; } return cell; } @@ -124,6 +132,18 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath viewController = exampleVC; break; } + case 8: { + SEPADebitExampleViewController *exampleVC = [SEPADebitExampleViewController new]; + exampleVC.delegate = self; + viewController = exampleVC; + break; + } + case 9: { + iDEALExampleViewController *exampleVC = [iDEALExampleViewController new]; + exampleVC.delegate = self; + viewController = exampleVC; + break; + } } [self.navigationController pushViewController:viewController animated:YES]; } diff --git a/Example/Custom Integration/PaymentExampleViewController.h b/Example/Custom Integration/PaymentExampleViewController.h new file mode 100644 index 00000000000..a5dd95630d5 --- /dev/null +++ b/Example/Custom Integration/PaymentExampleViewController.h @@ -0,0 +1,30 @@ +// +// PaymentExampleViewController.h +// Custom Integration +// +// Created by Cameron Sabol on 10/7/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import + +#import + +#import "BrowseExamplesViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface PaymentExampleViewController : UIViewController + +@property (nonatomic, weak) id delegate; + +@property (nonatomic, weak) UIButton *payButton; +@property (nonatomic, weak) UILabel *waitingLabel; +@property (nonatomic, weak) UIActivityIndicatorView *activityIndicator; + +- (void)payButtonSelected; +- (void)updateUIForPaymentInProgress:(BOOL)paymentInProgress; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Custom Integration/PaymentExampleViewController.m b/Example/Custom Integration/PaymentExampleViewController.m new file mode 100644 index 00000000000..e1a34264084 --- /dev/null +++ b/Example/Custom Integration/PaymentExampleViewController.m @@ -0,0 +1,97 @@ +// +// PaymentExampleViewController.m +// Custom Integration +// +// Created by Cameron Sabol on 10/7/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "PaymentExampleViewController.h" + +@interface PaymentExampleViewController () + +@end + +@implementation PaymentExampleViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor whiteColor]; +#ifdef __IPHONE_13_0 + if (@available(iOS 13.0, *)) { + self.view.backgroundColor = [UIColor systemBackgroundColor]; + } +#endif + self.title = @"Payment Example"; + self.edgesForExtendedLayout = UIRectEdgeNone; + + UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; + [button setTitle:@"Pay" forState:UIControlStateNormal]; + [button sizeToFit]; + [button addTarget:self action:@selector(payButtonSelected) forControlEvents:UIControlEventTouchUpInside]; + self.payButton = button; + [self.view addSubview:button]; + + UILabel *label = [UILabel new]; + label.text = @"Waiting for payment authorization"; + [label sizeToFit]; + label.textColor = [UIColor grayColor]; +#ifdef __IPHONE_13_0 + if (@available(iOS 13.0, *)) { + label.textColor = [UIColor secondaryLabelColor]; + } +#endif + label.alpha = 0; + [self.view addSubview:label]; + self.waitingLabel = label; + + UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + activityIndicator.hidesWhenStopped = YES; + self.activityIndicator = activityIndicator; + [self.view addSubview:activityIndicator]; +} + +- (void)viewDidLayoutSubviews { + [super viewDidLayoutSubviews]; + CGFloat padding = 15; + CGRect bounds = self.view.bounds; + self.payButton.center = CGPointMake(CGRectGetMidX(bounds), CGRectGetHeight(bounds)/3.0); + self.activityIndicator.center = CGPointMake(CGRectGetMidX(bounds), + CGRectGetMaxY(self.payButton.frame) + padding*2); + self.waitingLabel.center = CGPointMake(CGRectGetMidX(bounds), + CGRectGetMaxY(self.activityIndicator.frame) + padding*2); +} + +- (void)updateUIForPaymentInProgress:(BOOL)paymentInProgress { + self.navigationController.navigationBar.userInteractionEnabled = !paymentInProgress; + self.payButton.enabled = !paymentInProgress; + [UIView animateWithDuration:0.2 animations:^{ + self.waitingLabel.alpha = paymentInProgress ? 1 : 0; + }]; + if (paymentInProgress) { + [self.activityIndicator startAnimating]; + } else { + [self.activityIndicator stopAnimating]; + } +} + +- (void)payButtonSelected { + if (![Stripe defaultPublishableKey]) { + [self.delegate exampleViewController:self didFinishWithMessage:@"Please set a Stripe Publishable Key in Constants.m"]; + return; + } + // no-op, to be implemented by subclasses +} + + +#pragma mark - STPAuthenticationContext + +- (UIViewController *)authenticationPresentingViewController { + return self.navigationController.topViewController; +} + +- (void)authenticationContextWillDismissViewController:(UIViewController *)viewController { + // no-op, to be implemented by subclasses as needed +} + +@end diff --git a/Example/Custom Integration/SEPADebitExampleViewController.h b/Example/Custom Integration/SEPADebitExampleViewController.h new file mode 100644 index 00000000000..1a4de4de291 --- /dev/null +++ b/Example/Custom Integration/SEPADebitExampleViewController.h @@ -0,0 +1,17 @@ +// +// SEPADebitExampleViewController.h +// Custom Integration +// +// Created by Cameron Sabol on 10/7/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "PaymentExampleViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SEPADebitExampleViewController : PaymentExampleViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Custom Integration/SEPADebitExampleViewController.m b/Example/Custom Integration/SEPADebitExampleViewController.m new file mode 100644 index 00000000000..4b5c466963c --- /dev/null +++ b/Example/Custom Integration/SEPADebitExampleViewController.m @@ -0,0 +1,96 @@ +// +// SEPADebitExampleViewController.m +// Custom Integration +// +// Created by Cameron Sabol on 10/7/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "SEPADebitExampleViewController.h" + +#import "MyAPIClient.h" + +/** + This example demonstrates using PaymentIntents to accept payments using SEPA Debit + First, we ask our server to set up a PaymentIntent. We create a PaymentMethodParams with the required + SEPA Debit details. In this example we have hard-coded a Stripe Test IBAN number, but in production + code you would collect this from your customer. + SEPA Debit also required that we provide a mandate for the user to agree to https://www.europeanpaymentscouncil.eu/what-we-do/sepa-schemes/sepa-direct-debit/sdd-mandate + Finally we call STPPaymentHandler to confirm the PaymentIntent using the Stripe API. + + For more details see https://www.stripe.com/docs/sources/sepa-debit + */ +@interface SEPADebitExampleViewController () + +@end + +@implementation SEPADebitExampleViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. + self.title = @"SEPA Debit"; + + [self.payButton setTitle:@"Pay with SEPA Debit" forState:UIControlStateNormal]; + [self.payButton sizeToFit]; + + UILabel *mandateAuthLabel = [[UILabel alloc] init]; + mandateAuthLabel.numberOfLines = 0; + mandateAuthLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCallout]; + mandateAuthLabel.textAlignment = NSTextAlignmentCenter; + // This text is required by https://www.europeanpaymentscouncil.eu/what-we-do/sepa-schemes/sepa-direct-debit/sdd-mandate + mandateAuthLabel.text = @"By providing your IBAN and confirming this payment, you are authorizing EXAMPLE COMPANY NAME and Stripe, our payment service provider, to send instructions to your bank to debit your account and your bank to debit your account in accordance with those instructions. You are entitled to a refund from your bank under the terms and conditions of your agreement with your bank. A refund must be claimed within 8 weeks starting from the date on which your account was debited."; + mandateAuthLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:mandateAuthLabel]; + + [NSLayoutConstraint activateConstraints:@[ + [mandateAuthLabel.leadingAnchor constraintEqualToSystemSpacingAfterAnchor:self.view.safeAreaLayoutGuide.leadingAnchor multiplier:2], + [self.view.safeAreaLayoutGuide.trailingAnchor constraintEqualToSystemSpacingAfterAnchor:mandateAuthLabel.trailingAnchor multiplier:2], + + [mandateAuthLabel.topAnchor constraintEqualToSystemSpacingBelowAnchor:self.view.safeAreaLayoutGuide.topAnchor multiplier:2], + ]]; +} + +- (void)payButtonSelected { + [self updateUIForPaymentInProgress:YES]; + + [[MyAPIClient sharedClient] createPaymentIntentWithCompletion:^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { + if (status == MyAPIClientResultFailure || clientSecret == nil) { + [self.delegate exampleViewController:self didFinishWithError:error]; + return; + } + + STPPaymentIntentParams *paymentIntentParams = [[STPPaymentIntentParams alloc] initWithClientSecret:clientSecret]; + + STPPaymentMethodBillingDetails *billingDetails = [[STPPaymentMethodBillingDetails alloc] init]; + billingDetails.name = @"SEPA Test Customer"; + billingDetails.email = @"test@example.com"; + + STPPaymentMethodSEPADebitParams *sepaDebitDetails = [[STPPaymentMethodSEPADebitParams alloc] init]; + sepaDebitDetails.iban = @"DE89370400440532013000"; + + paymentIntentParams.paymentMethodParams = [STPPaymentMethodParams paramsWithSEPADebit:sepaDebitDetails + billingDetails:billingDetails + metadata:nil]; + + paymentIntentParams.returnURL = @"payments-example://stripe-redirect"; + [[STPPaymentHandler sharedHandler] confirmPayment:paymentIntentParams + withAuthenticationContext:self.delegate + completion:^(STPPaymentHandlerActionStatus handlerStatus, STPPaymentIntent * handledIntent, NSError * _Nullable handlerError) { + switch (handlerStatus) { + case STPPaymentHandlerActionStatusFailed: + [self.delegate exampleViewController:self didFinishWithError:handlerError]; + break; + case STPPaymentHandlerActionStatusCanceled: + [self.delegate exampleViewController:self didFinishWithMessage:@"Canceled"]; + break; + case STPPaymentHandlerActionStatusSucceeded: + [self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"]; + break; + } + }]; + } additionalParameters:@"country=nl"]; + +} + +@end diff --git a/Example/Custom Integration/iDEALExampleViewController.h b/Example/Custom Integration/iDEALExampleViewController.h new file mode 100644 index 00000000000..90aa71522a1 --- /dev/null +++ b/Example/Custom Integration/iDEALExampleViewController.h @@ -0,0 +1,17 @@ +// +// iDEALExampleViewController.h +// Custom Integration +// +// Created by Cameron Sabol on 10/10/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "PaymentExampleViewController.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface iDEALExampleViewController : PaymentExampleViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Custom Integration/iDEALExampleViewController.m b/Example/Custom Integration/iDEALExampleViewController.m new file mode 100644 index 00000000000..a7694600416 --- /dev/null +++ b/Example/Custom Integration/iDEALExampleViewController.m @@ -0,0 +1,66 @@ +// +// iDEALExampleViewController.m +// Custom Integration +// +// Created by Cameron Sabol on 10/10/19. +// Copyright © 2019 Stripe. All rights reserved. +// + +#import "iDEALExampleViewController.h" + +#import "MyAPIClient.h" + +@interface iDEALExampleViewController () + +@end + +@implementation iDEALExampleViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. + self.title = @"iDEAL"; + + [self.payButton setTitle:@"Pay with iDEAL" forState:UIControlStateNormal]; + [self.payButton sizeToFit]; +} + +- (void)payButtonSelected { + [self updateUIForPaymentInProgress:YES]; + + [[MyAPIClient sharedClient] createPaymentIntentWithCompletion:^(MyAPIClientResult status, NSString *clientSecret, NSError *error) { + if (status == MyAPIClientResultFailure || clientSecret == nil) { + [self.delegate exampleViewController:self didFinishWithError:error]; + return; + } + + STPPaymentIntentParams *paymentIntentParams = [[STPPaymentIntentParams alloc] initWithClientSecret:clientSecret]; + + STPPaymentMethodiDEALParams *iDEALParams = [[STPPaymentMethodiDEALParams alloc] init]; + iDEALParams.bankName = @"ing"; + + paymentIntentParams.paymentMethodParams = [STPPaymentMethodParams paramsWithiDEAL:iDEALParams + billingDetails:nil + metadata:nil]; + + paymentIntentParams.returnURL = @"payments-example://stripe-redirect"; + [[STPPaymentHandler sharedHandler] confirmPayment:paymentIntentParams + withAuthenticationContext:self.delegate + completion:^(STPPaymentHandlerActionStatus handlerStatus, STPPaymentIntent * handledIntent, NSError * _Nullable handlerError) { + switch (handlerStatus) { + case STPPaymentHandlerActionStatusFailed: + [self.delegate exampleViewController:self didFinishWithError:handlerError]; + break; + case STPPaymentHandlerActionStatusCanceled: + [self.delegate exampleViewController:self didFinishWithMessage:@"Canceled"]; + break; + case STPPaymentHandlerActionStatusSucceeded: + [self.delegate exampleViewController:self didFinishWithMessage:@"Payment successfully created"]; + break; + } + }]; + } additionalParameters:@"country=nl"]; + +} + +@end diff --git a/Stripe.xcodeproj/project.pbxproj b/Stripe.xcodeproj/project.pbxproj index 82f93a64107..0c3761cf8df 100644 --- a/Stripe.xcodeproj/project.pbxproj +++ b/Stripe.xcodeproj/project.pbxproj @@ -596,6 +596,15 @@ 3691EB722119111A008C49E1 /* STPCardValidator+Private.m in Sources */ = {isa = PBXBuildFile; fileRef = 3691EB702119111A008C49E1 /* STPCardValidator+Private.m */; }; 3691EB74211A4F31008C49E1 /* STPShippingAddressViewControllerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 3691EB73211A4F31008C49E1 /* STPShippingAddressViewControllerTest.m */; }; 36A734282121F8A700784615 /* STPCardValidator+Private.m in Sources */ = {isa = PBXBuildFile; fileRef = 3691EB702119111A008C49E1 /* STPCardValidator+Private.m */; }; + 36B6CB51234BCC1F00331C38 /* STPPaymentMethodSEPADebit.m in Sources */ = {isa = PBXBuildFile; fileRef = 36B6CB4F234BCC1F00331C38 /* STPPaymentMethodSEPADebit.m */; }; + 36B6CB55234BD59F00331C38 /* STPPaymentMethodSEPADebitParams.m in Sources */ = {isa = PBXBuildFile; fileRef = 36B6CB53234BD59F00331C38 /* STPPaymentMethodSEPADebitParams.m */; }; + 36B6CB57234BDC4400331C38 /* STPPaymentMethodSEPADebitTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 36B6CB56234BDC4400331C38 /* STPPaymentMethodSEPADebitTest.m */; }; + 36B6CB68235519CB00331C38 /* STPPaymentMethodSEPADebit.h in Headers */ = {isa = PBXBuildFile; fileRef = 36B6CB67235519CB00331C38 /* STPPaymentMethodSEPADebit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 36B6CB6A23551A0200331C38 /* STPPaymentMethodSEPADebitParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 36B6CB6923551A0200331C38 /* STPPaymentMethodSEPADebitParams.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 36B6CB6B2355282600331C38 /* STPPaymentMethodSEPADebit.m in Sources */ = {isa = PBXBuildFile; fileRef = 36B6CB4F234BCC1F00331C38 /* STPPaymentMethodSEPADebit.m */; }; + 36B6CB6C2355282A00331C38 /* STPPaymentMethodSEPADebitParams.m in Sources */ = {isa = PBXBuildFile; fileRef = 36B6CB53234BD59F00331C38 /* STPPaymentMethodSEPADebitParams.m */; }; + 36B6CB6D2355283E00331C38 /* STPPaymentMethodSEPADebitParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 36B6CB6923551A0200331C38 /* STPPaymentMethodSEPADebitParams.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 36B6CB6E2355284300331C38 /* STPPaymentMethodSEPADebit.h in Headers */ = {isa = PBXBuildFile; fileRef = 36B6CB67235519CB00331C38 /* STPPaymentMethodSEPADebit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 36D4EA6122DD33DF00619BA8 /* STPSetupIntentConfirmParamsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 36D4EA6022DD33DF00619BA8 /* STPSetupIntentConfirmParamsTest.m */; }; 36E582FB22B4566A0044F82C /* STPPaymentHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 365BE89C2285F6080068D824 /* STPPaymentHandler.m */; }; 36E582FC22B4566D0044F82C /* STPThreeDSCustomizationSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 367B46D522A0969000730BE0 /* STPThreeDSCustomizationSettings.m */; }; @@ -1708,6 +1717,11 @@ 3691EB6F2119111A008C49E1 /* STPCardValidator+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "STPCardValidator+Private.h"; sourceTree = ""; }; 3691EB702119111A008C49E1 /* STPCardValidator+Private.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "STPCardValidator+Private.m"; sourceTree = ""; }; 3691EB73211A4F31008C49E1 /* STPShippingAddressViewControllerTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPShippingAddressViewControllerTest.m; sourceTree = ""; }; + 36B6CB4F234BCC1F00331C38 /* STPPaymentMethodSEPADebit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPPaymentMethodSEPADebit.m; sourceTree = ""; }; + 36B6CB53234BD59F00331C38 /* STPPaymentMethodSEPADebitParams.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPPaymentMethodSEPADebitParams.m; sourceTree = ""; }; + 36B6CB56234BDC4400331C38 /* STPPaymentMethodSEPADebitTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPPaymentMethodSEPADebitTest.m; sourceTree = ""; }; + 36B6CB67235519CB00331C38 /* STPPaymentMethodSEPADebit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = STPPaymentMethodSEPADebit.h; path = PublicHeaders/STPPaymentMethodSEPADebit.h; sourceTree = ""; }; + 36B6CB6923551A0200331C38 /* STPPaymentMethodSEPADebitParams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = STPPaymentMethodSEPADebitParams.h; path = PublicHeaders/STPPaymentMethodSEPADebitParams.h; sourceTree = ""; }; 36BE41CC21CB0B600054EAE5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/LaunchScreen.strings; sourceTree = ""; }; 36BE41CE21CB0B610054EAE5 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/LaunchScreen.strings"; sourceTree = ""; }; 36BE41D021CB0B620054EAE5 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/LaunchScreen.strings; sourceTree = ""; }; @@ -2646,8 +2660,6 @@ C18867D81E8B093300A77634 /* Unit */ = { isa = PBXGroup; children = ( - B64763B822FE1AF700C01BC0 /* STPSetupIntentLastSetupErrorTest.m */, - B6A46F7A22FCE579001991B2 /* STPPaymentIntentLastPaymentErrorTest.m */, 04A4C3911C4F263300B3B290 /* NSArray+StripeTest.m */, C11810981CC6D46D0022FB55 /* NSDecimalNumber+StripeTest.m */, 8BB97F071F26645B0095122A /* NSDictionary+StripeTest.m */, @@ -2665,7 +2677,6 @@ C1AED1551EE0C8C6008BEFBF /* STPApplePayTest.m */, 8B8DDBB21EF887A4004B141F /* STPBankAccountParamsTest.m */, 04CDB5231A5F3A9300B854EE /* STPBankAccountTest.m */, - 3194CF5D231487A100E1940F /* STPFPXBankBrandTest.m */, 045D71301CF514BB00F6CD65 /* STPBinRangeTest.m */, B634497722A5BC91003881DC /* STPCardBrandTest.m */, 8BE5AE8A1EF8905B0081A33C /* STPCardParamsTest.m */, @@ -2684,6 +2695,7 @@ C1CFCB701ED5E11500BE45DF /* STPFileTest.m */, 04CDB51F1A5F3A9300B854EE /* STPFormEncoderTest.m */, C16F66AA1CA21BAC006A21B5 /* STPFormTextFieldTest.m */, + 3194CF5D231487A100E1940F /* STPFPXBankBrandTest.m */, B32B176220F6D722000D6EF8 /* STPGenericStripeObjectTest.m */, 04827D171D257A6C002DB3E8 /* STPImageLibraryTest.m */, B36C6D772193A16F00D17575 /* STPIntentActionTest.m */, @@ -2691,6 +2703,7 @@ 0438EF4B1B741B0100D506CC /* STPPaymentCardTextFieldViewModelTest.m */, 8B013C881F1E784A00DD831B /* STPPaymentConfigurationTest.m */, F14C872E1D4FCDBA00C7CC6A /* STPPaymentContextApplePayTest.m */, + B6A46F7A22FCE579001991B2 /* STPPaymentIntentLastPaymentErrorTest.m */, B3BDCAD020EEF5B90034F7F5 /* STPPaymentIntentParamsTest.m */, B3BDCACC20EEF4540034F7F5 /* STPPaymentIntentTest.m */, B6D6C932223076600092AFC8 /* STPPaymentMethodAddressTest.m */, @@ -2700,9 +2713,10 @@ B6B41F70223476AE0020BA7F /* STPPaymentMethodCardWalletMasterpassTest.m */, B68F1C782234740B0030B438 /* STPPaymentMethodCardWalletTest.m */, B6B41F72223476B90020BA7F /* STPPaymentMethodCardWalletVisaCheckoutTest.m */, - B6EC63C922348D4600E4C0FB /* STPPaymentMethodiDEALTest.m */, 3194CF5B2314869400E1940F /* STPPaymentMethodFPXTest.m */, + B6EC63C922348D4600E4C0FB /* STPPaymentMethodiDEALTest.m */, B63E42782231F8FE007B5B95 /* STPPaymentMethodParamsTest.m */, + 36B6CB56234BDC4400331C38 /* STPPaymentMethodSEPADebitTest.m */, B66B39B3223044A2006D1CAD /* STPPaymentMethodTest.m */, B66D5023222F5A27004A9210 /* STPPaymentMethodThreeDSecureUsageTest.m */, F1DE87FF1F8D410D00602F4C /* STPPaymentOptionsViewControllerTest.m */, @@ -2710,6 +2724,7 @@ C1FEE5981CBFF24000A7632B /* STPPostalCodeValidatorTest.m */, F152321A1EA92F9D00D65C67 /* STPRedirectContextTest.m */, 36D4EA6022DD33DF00619BA8 /* STPSetupIntentConfirmParamsTest.m */, + B64763B822FE1AF700C01BC0 /* STPSetupIntentLastSetupErrorTest.m */, B613DD3B22C54AA800C7603F /* STPSetupIntentTest.m */, 3691EB73211A4F31008C49E1 /* STPShippingAddressViewControllerTest.m */, 8BD87B8A1EFB136F00269C2B /* STPSourceCardDetailsTest.m */, @@ -3065,8 +3080,6 @@ 04CDB4CB1A5F30A700B854EE /* STPCard.m */, 0438EF461B74183100D506CC /* STPCardBrand.h */, B6CF3134229D8C3500BA8AC2 /* STPCardBrand.m */, - 31F5B33322FCAC4000A71C64 /* STPFPXBankBrand.h */, - 31F5B33422FCAC4000A71C64 /* STPFPXBankBrand.m */, 04CDE5BB1BC1F21500548833 /* STPCardParams.h */, 04CDE5B41BC1F1F100548833 /* STPCardParams.m */, 04EBC7511B7533C300A0E6AE /* STPCardValidationState.h */, @@ -3083,6 +3096,8 @@ F1D3A2501EB0120F0095BFA9 /* STPFile.h */, F1D3A2461EB012010095BFA9 /* STPFile.m */, 04F213301BCEAB61001D6F22 /* STPFormEncodable.h */, + 31F5B33322FCAC4000A71C64 /* STPFPXBankBrand.h */, + 31F5B33422FCAC4000A71C64 /* STPFPXBankBrand.m */, B613DD3F22C55F9500C7603F /* STPIntentAction.h */, B613DD4022C55F9500C7603F /* STPIntentAction.m */, B604CF1F22C56E9B00A23CC4 /* STPIntentActionRedirectToURL.h */, @@ -3121,16 +3136,20 @@ B621F05D223465EE002141B7 /* STPPaymentMethodCardWalletVisaCheckout.h */, B621F05E223465EE002141B7 /* STPPaymentMethodCardWalletVisaCheckout.m */, B6DB0CA5223817A300AEF640 /* STPPaymentMethodEnums.h */, + 31F5A50822F0EFB00033663B /* STPPaymentMethodFPX.h */, + 31F5A50D22F0EFDB0033663B /* STPPaymentMethodFPX.m */, + 31F5A50722F0EFB00033663B /* STPPaymentMethodFPXParams.h */, + 31F5A50E22F0EFDB0033663B /* STPPaymentMethodFPXParams.m */, B6B41F77223484280020BA7F /* STPPaymentMethodiDEAL.h */, B6B41F78223484280020BA7F /* STPPaymentMethodiDEAL.m */, B6B41F7D22348A1E0020BA7F /* STPPaymentMethodiDEALParams.h */, B6B41F7E22348A1E0020BA7F /* STPPaymentMethodiDEALParams.m */, - 31F5A50822F0EFB00033663B /* STPPaymentMethodFPX.h */, - 31F5A50722F0EFB00033663B /* STPPaymentMethodFPXParams.h */, - 31F5A50D22F0EFDB0033663B /* STPPaymentMethodFPX.m */, - 31F5A50E22F0EFDB0033663B /* STPPaymentMethodFPXParams.m */, B6DE52D92230981200B70A66 /* STPPaymentMethodParams.h */, B6DE52DA2230981200B70A66 /* STPPaymentMethodParams.m */, + 36B6CB67235519CB00331C38 /* STPPaymentMethodSEPADebit.h */, + 36B6CB4F234BCC1F00331C38 /* STPPaymentMethodSEPADebit.m */, + 36B6CB6923551A0200331C38 /* STPPaymentMethodSEPADebitParams.h */, + 36B6CB53234BD59F00331C38 /* STPPaymentMethodSEPADebitParams.m */, B6B5FC3F222F4C0200440249 /* STPPaymentMethodThreeDSecureUsage.h */, B6B5FC40222F4C0200440249 /* STPPaymentMethodThreeDSecureUsage.m */, B613DD3022C536C900C7603F /* STPSetupIntent.h */, @@ -3390,6 +3409,7 @@ F1BEB2FE1F3508BB0043F48C /* NSError+Stripe.h in Headers */, 319A60A022E9186B00AACF66 /* Stripe3DS2.h in Headers */, 04E32AA01B7A9490009C9E35 /* STPPaymentCardTextField.h in Headers */, + 36B6CB6D2355283E00331C38 /* STPPaymentMethodSEPADebitParams.h in Headers */, B68D52E522A739BE00D4E8BA /* STPSourceWeChatPayDetails.h in Headers */, 04F94DA81D229F2F004FC826 /* STPPaymentOptionTuple.h in Headers */, 0438EF491B74183100D506CC /* STPCardBrand.h in Headers */, @@ -3498,6 +3518,7 @@ C1CFCB681ED4E38900BE45DF /* STPInternalAPIResponseDecodable.h in Headers */, C113D21A1EBB9A36006FACC2 /* STPEphemeralKey.h in Headers */, 04A4C38E1C4F25F900B3B290 /* UIViewController+Stripe_ParentViewController.h in Headers */, + 36B6CB6E2355284300331C38 /* STPPaymentMethodSEPADebit.h in Headers */, B640DB1322C58E82003C8810 /* STPSetupIntentConfirmParams.h in Headers */, C18410771EC2529400178149 /* STPEphemeralKeyManager.h in Headers */, 319A608422E9186B00AACF66 /* STDSAuthenticationResponse.h in Headers */, @@ -3532,6 +3553,7 @@ C11810861CC6AF4C0022FB55 /* STPPaymentOption.h in Headers */, B640DB1222C58E82003C8810 /* STPSetupIntentConfirmParams.h in Headers */, 3635C33322B03E00004298B8 /* STPEmptyStripeResponse.h in Headers */, + 36B6CB6A23551A0200331C38 /* STPPaymentMethodSEPADebitParams.h in Headers */, B690DDEC222F01BF000B902D /* STPPaymentMethodBillingDetails.h in Headers */, 04695AD91C77F9EF00E08063 /* STPDelegateProxy.h in Headers */, 31F5B33522FCAC4000A71C64 /* STPFPXBankBrand.h in Headers */, @@ -3561,6 +3583,7 @@ F1DEB88A1E2047CA0066B8E8 /* STPCoreTableViewController.h in Headers */, C1A06F101E1D8A7F004DCA06 /* STPCard+Private.h in Headers */, F1D3A24C1EB012010095BFA9 /* STPMultipartFormDataEncoder.h in Headers */, + 36B6CB68235519CB00331C38 /* STPPaymentMethodSEPADebit.h in Headers */, C113D2191EBB9A36006FACC2 /* STPEphemeralKey.h in Headers */, 04E39F6A1CED48D500AF3B96 /* UIBarButtonItem+Stripe.h in Headers */, 31B9609022FE128C00DC6FD9 /* STPBankSelectionViewController.h in Headers */, @@ -4398,6 +4421,7 @@ B66B39B4223044A2006D1CAD /* STPPaymentMethodTest.m in Sources */, C1EEDCC61CA2126000A54582 /* STPDelegateProxyTest.m in Sources */, B6D13947230C68FF007AFF8A /* STPConnectAccountAddressTest.m in Sources */, + 36B6CB57234BDC4400331C38 /* STPPaymentMethodSEPADebitTest.m in Sources */, F1D777C01D81DD520076FA19 /* STPStringUtilsTest.m in Sources */, C1FEE5991CBFF24000A7632B /* STPPostalCodeValidatorTest.m in Sources */, 8BD87B8B1EFB136F00269C2B /* STPSourceCardDetailsTest.m in Sources */, @@ -4466,7 +4490,9 @@ B66AC61722C6E6590064C551 /* STPPaymentHandlerActionParams.m in Sources */, 31B9609722FE20DF00DC6FD9 /* STPBankSelectionTableViewCell.m in Sources */, 04F94DBB1D229F8D004FC826 /* PKPaymentAuthorizationViewController+Stripe_Blocks.m in Sources */, + 36B6CB6C2355282A00331C38 /* STPPaymentMethodSEPADebitParams.m in Sources */, B6B41F7C223484280020BA7F /* STPPaymentMethodiDEAL.m in Sources */, + 36B6CB6B2355282600331C38 /* STPPaymentMethodSEPADebit.m in Sources */, C1271A3E1E3FA4E800F25DFE /* STPSectionHeaderView.m in Sources */, F12829DD1D7747E4008B10D6 /* STPBundleLocator.m in Sources */, 31F5A51022F0EFDC0033663B /* STPPaymentMethodFPX.m in Sources */, @@ -4633,6 +4659,7 @@ B621F055223454E9002141B7 /* STPPaymentMethodCardWallet.m in Sources */, 04B31E011D131D9000EF1631 /* STPPaymentCardTextFieldCell.m in Sources */, 04633B0D1CD44F6C009D4FB5 /* PKPayment+Stripe.m in Sources */, + 36B6CB51234BCC1F00331C38 /* STPPaymentMethodSEPADebit.m in Sources */, F1852F951D80B6EC00367C86 /* STPStringUtils.m in Sources */, F1D3A24D1EB012010095BFA9 /* STPMultipartFormDataEncoder.m in Sources */, 04CDB5101A5F30A700B854EE /* STPCard.m in Sources */, @@ -4721,6 +4748,7 @@ C124A17E1CCAA0C2007D42EE /* NSMutableURLRequest+Stripe.m in Sources */, 04695ADA1C77F9EF00E08063 /* STPDelegateProxy.m in Sources */, B664D65722B817C800E6354B /* STPThreeDSFooterCustomization.m in Sources */, + 36B6CB55234BD59F00331C38 /* STPPaymentMethodSEPADebitParams.m in Sources */, B621F05B22346243002141B7 /* STPPaymentMethodCardWalletMasterpass.m in Sources */, 045D712E1CF4ED7600F6CD65 /* STPBINRange.m in Sources */, 049A3FB31CC9FEFC00F57DE7 /* UIToolbar+Stripe_InputAccessory.m in Sources */, diff --git a/Stripe/Payments/STPPaymentHandler.m b/Stripe/Payments/STPPaymentHandler.m index 952247c949d..855566aaf5c 100644 --- a/Stripe/Payments/STPPaymentHandler.m +++ b/Stripe/Payments/STPPaymentHandler.m @@ -17,7 +17,7 @@ #import "STPAPIClient+Private.h" #import "STPAuthenticationContext.h" #import "STPLocalizationUtils.h" -#import "STPPaymentIntent.h" +#import "STPPaymentIntent+Private.h" #import "STPPaymentIntentLastPaymentError.h" #import "STPPaymentIntentParams.h" #import "STPPaymentHandlerActionParams.h" @@ -27,6 +27,7 @@ #import "STPSetupIntent.h" #import "STPSetupIntentConfirmParams.h" #import "STPSetupIntentLastSetupError.h" +#import "STPPaymentMethod.h" #import "STPThreeDSCustomizationSettings.h" #import "STPThreeDSCustomization+Private.h" #import "STPURLCallbackHandler.h" @@ -75,7 +76,11 @@ - (void)confirmPayment:(STPPaymentIntentParams *)paymentParams strongSelf.inProgress = NO; // Ensure the .succeeded case returns a PaymentIntent in the expected state. if (status == STPPaymentHandlerActionStatusSucceeded) { - if (error == nil && paymentIntent != nil && (paymentIntent.status == STPPaymentIntentStatusSucceeded || paymentIntent.status == STPPaymentIntentStatusRequiresCapture)) { + BOOL successIntentState = paymentIntent.status == STPPaymentIntentStatusSucceeded || + paymentIntent.status == STPPaymentIntentStatusRequiresCapture || + (paymentIntent.status == STPPaymentIntentStatusProcessing && [[self class] _isProcessingIntentSuccessForType:paymentIntent.paymentMethod.type]); + + if (error == nil && paymentIntent != nil && successIntentState) { completion(STPPaymentHandlerActionStatusSucceeded, paymentIntent, nil); } else { NSAssert(NO, @"Calling completion with invalid state"); @@ -105,6 +110,7 @@ - (void)confirmPayment:(STPPaymentIntentParams *)paymentParams params.useStripeSDK = @YES; } [self.apiClient confirmPaymentIntentWithParams:params + expand:@[@"payment_method"] completion:confirmCompletionBlock]; } @@ -155,7 +161,9 @@ - (void)handleNextActionForPayment:(NSString *)paymentIntentClientSecret } }; - [self.apiClient retrievePaymentIntentWithClientSecret:paymentIntentClientSecret completion:retrieveCompletionBlock]; + [self.apiClient retrievePaymentIntentWithClientSecret:paymentIntentClientSecret + expand:@[@"payment_method"] + completion:retrieveCompletionBlock]; } - (void)confirmSetupIntent:(STPSetupIntentConfirmParams *)setupIntentConfirmParams @@ -271,6 +279,25 @@ - (void)handleNextActionForSetupIntent:(NSString *)setupIntentClientSecret #pragma mark - Private Helpers +/** + Depending on the PaymentMethod Type, after handling next action and confirming, + we should either expect a success state on the PaymentIntent, or for certain asynchronous + PaymentMethods like SEPA Debit, processing is considered a completed PaymentIntent flow + because the funds can take up to 14 days to transfer from the customer's bank. + */ ++ (BOOL)_isProcessingIntentSuccessForType:(STPPaymentMethodType)type { + switch (type) { + case STPPaymentMethodTypeSEPADebit: + return YES; + case STPPaymentMethodTypeCard: + case STPPaymentMethodTypeiDEAL: + case STPPaymentMethodTypeFPX: + case STPPaymentMethodTypeCardPresent: + case STPPaymentMethodTypeUnknown: + return NO; + } +} + - (void)_handleNextActionForPayment:(STPPaymentIntent *)paymentIntent withAuthenticationContext:(id)authenticationContext returnURL:(nullable NSString *)returnURLString @@ -408,7 +435,11 @@ - (BOOL)_handlePaymentIntentStatusForAction:(STPPaymentHandlerPaymentIntentActio case STPPaymentIntentStatusRequiresAction: return YES; case STPPaymentIntentStatusProcessing: - [action completeWithStatus:STPPaymentHandlerActionStatusFailed error:[self _errorForCode:STPPaymentHandlerIntentStatusErrorCode userInfo:nil]]; + if ([[self class] _isProcessingIntentSuccessForType:paymentIntent.paymentMethod.type]) { + [action completeWithStatus:STPPaymentHandlerActionStatusSucceeded error:nil]; + } else { + [action completeWithStatus:STPPaymentHandlerActionStatusFailed error:[self _errorForCode:STPPaymentHandlerIntentStatusErrorCode userInfo:nil]]; + } break; case STPPaymentIntentStatusSucceeded: [action completeWithStatus:STPPaymentHandlerActionStatusSucceeded error:nil]; @@ -539,6 +570,7 @@ - (void)_retrieveAndCheckIntentForCurrentAction { if ([_currentAction isKindOfClass:[STPPaymentHandlerPaymentIntentActionParams class]]) { STPPaymentHandlerPaymentIntentActionParams *currentAction = (STPPaymentHandlerPaymentIntentActionParams *)_currentAction; [_currentAction.apiClient retrievePaymentIntentWithClientSecret:currentAction.paymentIntent.clientSecret + expand:@[@"payment_method"] completion:^(STPPaymentIntent * _Nullable paymentIntent, NSError * _Nullable error) { if (error != nil) { [currentAction completeWithStatus:STPPaymentHandlerActionStatusFailed error:error]; @@ -796,6 +828,7 @@ - (void)_markChallengeCompletedWithCompletion:(STPBooleanSuccessBlock)completion if ([self->_currentAction isKindOfClass:[STPPaymentHandlerPaymentIntentActionParams class]]) { STPPaymentHandlerPaymentIntentActionParams *currentAction = (STPPaymentHandlerPaymentIntentActionParams *)self->_currentAction; [currentAction.apiClient retrievePaymentIntentWithClientSecret:currentAction.paymentIntent.clientSecret + expand:@[@"payment_method"] completion:^(STPPaymentIntent * _Nullable paymentIntent, NSError * _Nullable retrieveError) { currentAction.paymentIntent = paymentIntent; completion(paymentIntent != nil, retrieveError); diff --git a/Stripe/PublicHeaders/STPPaymentMethod.h b/Stripe/PublicHeaders/STPPaymentMethod.h index 9bb533f860a..aa325abe411 100644 --- a/Stripe/PublicHeaders/STPPaymentMethod.h +++ b/Stripe/PublicHeaders/STPPaymentMethod.h @@ -12,7 +12,12 @@ #import "STPPaymentMethodEnums.h" #import "STPPaymentOption.h" -@class STPPaymentMethodBillingDetails, STPPaymentMethodCard, STPPaymentMethodiDEAL, STPPaymentMethodFPX, STPPaymentMethodCardPresent; +@class STPPaymentMethodBillingDetails, +STPPaymentMethodCard, +STPPaymentMethodCardPresent, +STPPaymentMethodFPX, +STPPaymentMethodiDEAL, +STPPaymentMethodSEPADebit; NS_ASSUME_NONNULL_BEGIN @@ -69,6 +74,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, nullable, readonly) STPPaymentMethodCardPresent *cardPresent; +/** + If this is a SEPA Debit PaymentMethod (ie `self.type == STPPaymentMethodTypeSEPADebit`), this contains additional details. + */ +@property (nonatomic, nullable, readonly) STPPaymentMethodSEPADebit *sepaDebit; + /** The ID of the Customer to which this PaymentMethod is saved. Nil when the PaymentMethod has not been saved to a Customer. */ diff --git a/Stripe/PublicHeaders/STPPaymentMethodEnums.h b/Stripe/PublicHeaders/STPPaymentMethodEnums.h index 503b3697278..a31a251c5d5 100644 --- a/Stripe/PublicHeaders/STPPaymentMethodEnums.h +++ b/Stripe/PublicHeaders/STPPaymentMethodEnums.h @@ -29,6 +29,11 @@ typedef NS_ENUM(NSUInteger, STPPaymentMethodType) { A card present payment method. */ STPPaymentMethodTypeCardPresent, + + /** + A SEPA Debit payment method. + */ + STPPaymentMethodTypeSEPADebit, /** An unknown type. diff --git a/Stripe/PublicHeaders/STPPaymentMethodParams.h b/Stripe/PublicHeaders/STPPaymentMethodParams.h index 3e34d59ad2b..b884c5f6cdd 100644 --- a/Stripe/PublicHeaders/STPPaymentMethodParams.h +++ b/Stripe/PublicHeaders/STPPaymentMethodParams.h @@ -12,7 +12,12 @@ #import "STPPaymentMethodEnums.h" #import "STPPaymentOption.h" -@class STPPaymentMethodBillingDetails, STPPaymentMethodCardParams, STPPaymentMethodiDEALParams, STPPaymentMethodFPXParams, STPPaymentMethod; +@class STPPaymentMethod, +STPPaymentMethodBillingDetails, +STPPaymentMethodCardParams, +STPPaymentMethodFPXParams, +STPPaymentMethodiDEALParams, +STPPaymentMethodSEPADebitParams; NS_ASSUME_NONNULL_BEGIN @@ -62,6 +67,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, nullable) STPPaymentMethodFPXParams *fpx; +/** + If this is a SEPA Debit PaymentMethod, this contains details about the bank to debit. + */ +@property (nonatomic, nullable) STPPaymentMethodSEPADebitParams *sepaDebit; + /** Set of key-value pairs that you can attach to the PaymentMethod. This can be useful for storing additional information about the PaymentMethod in a structured format. */ @@ -100,6 +110,17 @@ NS_ASSUME_NONNULL_BEGIN billingDetails:(nullable STPPaymentMethodBillingDetails *)billingDetails metadata:(nullable NSDictionary *)metadata; +/** + Creates params for a SEPA Debit PaymentMethod; + + @param sepaDebit An object containing the SEPA bank debit details. + @param billingDetails An object containing the user's billing details. Note that `billingDetails.name` is required for SEPA Debit PaymentMethods. + @param metadata Additional information to attach to the PaymentMethod. + */ ++ (nullable STPPaymentMethodParams *)paramsWithSEPADebit:(STPPaymentMethodSEPADebitParams *)sepaDebit + billingDetails:(STPPaymentMethodBillingDetails *)billingDetails + metadata:(nullable NSDictionary *)metadata; + /** Creates params from a single-use PaymentMethod. This is useful for recreating a new payment method with similar settings. It will return nil if used with a reusable PaymentMethod. diff --git a/Stripe/PublicHeaders/STPPaymentMethodSEPADebit.h b/Stripe/PublicHeaders/STPPaymentMethodSEPADebit.h new file mode 100644 index 00000000000..d4ff4b33bb9 --- /dev/null +++ b/Stripe/PublicHeaders/STPPaymentMethodSEPADebit.h @@ -0,0 +1,60 @@ +// +// STPPaymentMethodSEPADebit.h +// StripeiOS +// +// Created by Cameron Sabol on 10/7/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +#import + +#import "STPAPIResponseDecodable.h" + +NS_ASSUME_NONNULL_BEGIN + +/** +A SEPA Debit Payment Method. + +@see https://stripe.com/docs/api/payment_methods/object#payment_method_object-sepa_debit +*/ +@interface STPPaymentMethodSEPADebit : NSObject + +/** +You cannot directly instantiate an `STPPaymentMethodSEPADebit`. +You should only use one that is part of an existing `STPPaymentMethod` object. +*/ +- (instancetype)init NS_UNAVAILABLE; + +/** + The last 4 digits of the account number. + */ +@property (nonatomic, nullable, readonly) NSString *last4; + +/** + The account's bank code. + */ +@property (nonatomic, nullable, readonly) NSString *bankCode; + +/** + The account's branch code + */ +@property (nonatomic, nullable, readonly) NSString *branchCode; + +/** + Two-letter ISO code representing the country of the bank account. + */ +@property (nonatomic, nullable, readonly) NSString *country; + +/** + The account's fingerprint. + */ +@property (nonatomic, nullable, readonly) NSString *fingerprint; + +/** + The reference of the mandate accepted by your customer. @see https://stripe.com/docs/api/sources/create#create_source-mandate + */ +@property (nonatomic, nullable, readonly) NSString *mandate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe/PublicHeaders/STPPaymentMethodSEPADebitParams.h b/Stripe/PublicHeaders/STPPaymentMethodSEPADebitParams.h new file mode 100644 index 00000000000..88fcd521991 --- /dev/null +++ b/Stripe/PublicHeaders/STPPaymentMethodSEPADebitParams.h @@ -0,0 +1,27 @@ +// +// STPPaymentMethodSEPADebitParams.h +// StripeiOS +// +// Created by Cameron Sabol on 10/7/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +#import + +#import "STPFormEncodable.h" + +NS_ASSUME_NONNULL_BEGIN + +/** +An object representing parameters used to create a SEPA Debit Payment Method +*/ +@interface STPPaymentMethodSEPADebitParams : NSObject + +/** + The IBAN number for the bank account you wish to debit. Required. + */ +@property (nonatomic, nullable, copy) NSString *iban; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe/PublicHeaders/Stripe.h b/Stripe/PublicHeaders/Stripe.h index 5614f644fb5..bbb5d3bc92a 100644 --- a/Stripe/PublicHeaders/Stripe.h +++ b/Stripe/PublicHeaders/Stripe.h @@ -72,6 +72,8 @@ #import "STPPaymentMethodFPX.h" #import "STPPaymentMethodFPXParams.h" #import "STPPaymentMethodParams.h" +#import "STPPaymentMethodSEPADebit.h" +#import "STPPaymentMethodSEPADebitParams.h" #import "STPPaymentMethodThreeDSecureUsage.h" #import "STPPaymentOption.h" #import "STPPaymentOptionsViewController.h" diff --git a/Stripe/STPAPIClient+Private.h b/Stripe/STPAPIClient+Private.h index 3eb15994949..eaae844a69b 100644 --- a/Stripe/STPAPIClient+Private.h +++ b/Stripe/STPAPIClient+Private.h @@ -129,6 +129,38 @@ fromCustomerUsingKey:(STPEphemeralKey *)ephemeralKey @end +@interface STPAPIClient (PaymentIntentPrivate) + +/** + 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 expand An array of string keys to expand on the returned PaymentIntent object. These strings should match one or more of the parameter names that are marked as expandable. @see https://stripe.com/docs/api/payment_intents/object + @param completion The callback to run with the returned PaymentIntent object, or an error. +*/ +- (void)retrievePaymentIntentWithClientSecret:(NSString *)secret + expand:(nullable NSArray *)expand + completion:(STPPaymentIntentCompletionBlock)completion; + +/** + Confirms the PaymentIntent object with the provided params object. + + At a minimum, the params object must include the `clientSecret`. + + @see https://stripe.com/docs/api#confirm_payment_intent + + @note Use the `confirmPayment:withAuthenticationContext:completion:` method on `STPPaymentHandler` instead + of calling this method directly. It handles any authentication necessary for you. @see https://stripe.com/docs/mobile/ios/authentication + @param paymentIntentParams The `STPPaymentIntentParams` to pass to `/confirm` + @param expand An array of string keys to expand on the returned PaymentIntent object. These strings should match one or more of the parameter names that are marked as expandable. @see https://stripe.com/docs/api/payment_intents/object + @param completion The callback to run with the returned PaymentIntent object, or an error. +*/ +- (void)confirmPaymentIntentWithParams:(STPPaymentIntentParams *)paymentIntentParams + expand:(nullable NSArray *)expand + completion:(STPPaymentIntentCompletionBlock)completion; + +@end + @interface Stripe (Private) + (NSArray *)supportedPKPaymentNetworks; diff --git a/Stripe/STPAPIClient.m b/Stripe/STPAPIClient.m index 700d2b9d6f5..9b9a5323481 100644 --- a/Stripe/STPAPIClient.m +++ b/Stripe/STPAPIClient.m @@ -681,15 +681,29 @@ @implementation STPAPIClient (PaymentIntents) - (void)retrievePaymentIntentWithClientSecret:(NSString *)secret completion:(STPPaymentIntentCompletionBlock)completion { + [self retrievePaymentIntentWithClientSecret:secret + expand:nil + completion:completion]; +} + +- (void)retrievePaymentIntentWithClientSecret:(NSString *)secret + expand:(nullable NSArray *)expand + 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]; + NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init]; + parameters[@"client_secret"] = secret; + if (expand.count > 0) { + parameters[@"expand"] = expand; + } + [STPAPIRequest getWithAPIClient:self endpoint:endpoint - parameters:@{ @"client_secret": secret } + parameters:[parameters copy] deserializer:[STPPaymentIntent new] completion:^(STPPaymentIntent *paymentIntent, __unused NSHTTPURLResponse *response, NSError *error) { completion(paymentIntent, error); @@ -698,6 +712,14 @@ - (void)retrievePaymentIntentWithClientSecret:(NSString *)secret - (void)confirmPaymentIntentWithParams:(STPPaymentIntentParams *)paymentIntentParams completion:(STPPaymentIntentCompletionBlock)completion { + [self confirmPaymentIntentWithParams:paymentIntentParams + expand:nil + completion:completion]; +} + +- (void)confirmPaymentIntentWithParams:(STPPaymentIntentParams *)paymentIntentParams + expand:(nullable NSArray *)expand + completion:(STPPaymentIntentCompletionBlock)completion { NSCAssert(paymentIntentParams.clientSecret != nil, @"'clientSecret' is required to confirm a PaymentIntent"); NSString *identifier = paymentIntentParams.stripeId; NSString *sourceType = [STPSource stringFromType:paymentIntentParams.sourceParams.type]; @@ -712,6 +734,9 @@ - (void)confirmPaymentIntentWithParams:(STPPaymentIntentParams *)paymentIntentPa [[STPTelemetryClient sharedInstance] addTelemetryFieldsToParams:sourceParamsDict]; params[@"source_data"] = [sourceParamsDict copy]; } + if (expand.count > 0) { + params[@"expand"] = expand; + } [STPAPIRequest postWithAPIClient:self endpoint:endpoint diff --git a/Stripe/STPPaymentIntent+Private.h b/Stripe/STPPaymentIntent+Private.h index d947fc75081..3b2d99d08d2 100644 --- a/Stripe/STPPaymentIntent+Private.h +++ b/Stripe/STPPaymentIntent+Private.h @@ -8,9 +8,16 @@ #import "STPPaymentIntent.h" +@class STPPaymentMethod; + NS_ASSUME_NONNULL_BEGIN -@interface STPPaymentIntent () +@interface STPPaymentIntent (Private) + +/** + The optionally expanded PaymentMethod used in this PaymentIntent. + */ +@property (nonatomic, nullable, readonly) STPPaymentMethod *paymentMethod; /** Helper function for extracting PaymentIntent id from the Client Secret. diff --git a/Stripe/STPPaymentIntent.m b/Stripe/STPPaymentIntent.m index 3de70abcf73..f91f7dbd0e3 100644 --- a/Stripe/STPPaymentIntent.m +++ b/Stripe/STPPaymentIntent.m @@ -31,6 +31,7 @@ @interface STPPaymentIntent () @property (nonatomic, copy, nullable, readwrite) NSString *receiptEmail; @property (nonatomic, copy, nullable, readwrite) NSString *sourceId; @property (nonatomic, copy, nullable, readwrite) NSString *paymentMethodId; +@property (nonatomic, nullable, readwrite) STPPaymentMethod *paymentMethod; @property (nonatomic, assign, readwrite) STPPaymentIntentStatus status; @property (nonatomic, copy, nullable, readwrite) NSArray *paymentMethodTypes; @property (nonatomic) STPPaymentIntentSetupFutureUsage setupFutureUsage; @@ -62,6 +63,7 @@ - (NSString *)description { [NSString stringWithFormat:@"livemode = %@", self.livemode ? @"YES" : @"NO"], [NSString stringWithFormat:@"nextAction = %@", self.nextAction], [NSString stringWithFormat:@"paymentMethodId = %@", self.paymentMethodId], + [NSString stringWithFormat:@"paymentMethod = %@", self.paymentMethod], [NSString stringWithFormat:@"paymentMethodTypes = %@", [self.allResponseFields stp_arrayForKey:@"payment_method_types"]], [NSString stringWithFormat:@"receiptEmail = %@", self.receiptEmail], [NSString stringWithFormat:@"setupFutureUsage = %@", self.allResponseFields[@"setup_future_usage"]], @@ -180,7 +182,15 @@ + (nullable instancetype)decodedObjectFromAPIResponse:(nullable NSDictionary *)r paymentIntent.receiptEmail = [dict stp_stringForKey:@"receipt_email"]; // FIXME: add support for `shipping` paymentIntent.sourceId = [dict stp_stringForKey:@"source"]; - paymentIntent.paymentMethodId = [dict stp_stringForKey:@"payment_method"]; + NSDictionary *paymentMethodDict = [dict stp_dictionaryForKey:@"payment_method"]; + if (paymentMethodDict != nil) { + paymentIntent.paymentMethod = [STPPaymentMethod decodedObjectFromAPIResponse:paymentMethodDict]; + paymentIntent.paymentMethodId = paymentIntent.paymentMethod.stripeId; + } else { + paymentIntent.paymentMethodId = [dict stp_stringForKey:@"payment_method"]; + paymentIntent.paymentMethod = nil; + } + NSArray *rawPaymentMethodTypes = [[dict stp_arrayForKey:@"payment_method_types"] stp_arrayByRemovingNulls]; if (rawPaymentMethodTypes) { paymentIntent.paymentMethodTypes = [STPPaymentMethod typesFromStrings:rawPaymentMethodTypes]; diff --git a/Stripe/STPPaymentIntentParams.m b/Stripe/STPPaymentIntentParams.m index 10db6eec6a4..88eb1996844 100644 --- a/Stripe/STPPaymentIntentParams.m +++ b/Stripe/STPPaymentIntentParams.m @@ -12,6 +12,12 @@ #import "STPPaymentMethodParams.h" #import "STPPaymentResult.h" +@interface STPPaymentIntentParams () + +@property (nonatomic, nullable, readonly) NSDictionary *mandateData; + +@end + @implementation STPPaymentIntentParams @synthesize additionalAPIParameters = _additionalAPIParameters; @@ -89,6 +95,15 @@ - (void)configureWithPaymentResult:(STPPaymentResult *)paymentResult { } } +- (NSDictionary *)mandateData { + if (self.paymentMethodParams.type == STPPaymentMethodTypeSEPADebit) { + return @{@"type": @"online", + @"online": @{@"infer_from_client": @YES}}; + } else { + return nil; + } +} + #pragma mark - Deprecated Properties - (NSString *)returnUrl { @@ -145,6 +160,7 @@ + (nonnull NSDictionary *)propertyNamesToFormFieldNamesMapping { NSStringFromSelector(@selector(savePaymentMethod)): @"save_payment_method", NSStringFromSelector(@selector(returnURL)): @"return_url", NSStringFromSelector(@selector(useStripeSDK)) : @"use_stripe_sdk", + NSStringFromSelector(@selector(mandateData)) : @"mandate_data", }; } diff --git a/Stripe/STPPaymentMethod.m b/Stripe/STPPaymentMethod.m index 9cb66e9ab44..c09c271227d 100644 --- a/Stripe/STPPaymentMethod.m +++ b/Stripe/STPPaymentMethod.m @@ -14,8 +14,9 @@ #import "STPPaymentMethodBillingDetails.h" #import "STPPaymentMethodCard.h" #import "STPPaymentMethodCardPresent.h" -#import "STPPaymentMethodiDEAL.h" #import "STPPaymentMethodFPX.h" +#import "STPPaymentMethodiDEAL.h" +#import "STPPaymentMethodSEPADebit.h" @interface STPPaymentMethod () @@ -28,6 +29,7 @@ @interface STPPaymentMethod () @property (nonatomic, strong, nullable, readwrite) STPPaymentMethodiDEAL *iDEAL; @property (nonatomic, strong, nullable, readwrite) STPPaymentMethodFPX *fpx; @property (nonatomic, strong, nullable, readwrite) STPPaymentMethodCardPresent *cardPresent; +@property (nonatomic, strong, nullable, readwrite) STPPaymentMethodSEPADebit *sepaDebit; @property (nonatomic, copy, nullable, readwrite) NSString *customerId; @property (nonatomic, copy, nullable, readwrite) NSDictionary *metadata; @property (nonatomic, copy, nonnull, readwrite) NSDictionary *allResponseFields; @@ -53,6 +55,7 @@ - (NSString *)description { [NSString stringWithFormat:@"customerId = %@", self.customerId], [NSString stringWithFormat:@"ideal = %@", self.iDEAL], [NSString stringWithFormat:@"fpx = %@", self.fpx], + [NSString stringWithFormat:@"sepaDebit = %@", self.sepaDebit], [NSString stringWithFormat:@"liveMode = %@", self.liveMode ? @"YES" : @"NO"], [NSString stringWithFormat:@"metadata = %@", self.metadata], [NSString stringWithFormat:@"type = %@", [self.allResponseFields stp_stringForKey:@"type"]], @@ -68,6 +71,7 @@ - (NSString *)description { @"ideal": @(STPPaymentMethodTypeiDEAL), @"fpx": @(STPPaymentMethodTypeFPX), @"card_present": @(STPPaymentMethodTypeCardPresent), + @"sepa_debit": @(STPPaymentMethodTypeSEPADebit), }; } @@ -119,6 +123,7 @@ + (nullable instancetype)decodedObjectFromAPIResponse:(nullable NSDictionary *)r paymentMethod.iDEAL = [STPPaymentMethodiDEAL decodedObjectFromAPIResponse:[dict stp_dictionaryForKey:@"ideal"]]; paymentMethod.fpx = [STPPaymentMethodFPX decodedObjectFromAPIResponse:[dict stp_dictionaryForKey:@"fpx"]]; paymentMethod.cardPresent = [STPPaymentMethodCardPresent decodedObjectFromAPIResponse:[dict stp_dictionaryForKey:@"card_present"]]; + paymentMethod.sepaDebit = [STPPaymentMethodSEPADebit decodedObjectFromAPIResponse:[dict stp_dictionaryForKey:@"sepa_debit"]]; paymentMethod.customerId = [dict stp_stringForKey:@"customer"]; paymentMethod.metadata = [[dict stp_dictionaryForKey:@"metadata"] stp_dictionaryByRemovingNonStrings]; return paymentMethod; @@ -159,6 +164,8 @@ - (NSString *)label { } else { return STPLocalizedString(@"FPX", @"Payment Method type brand name"); } + case STPPaymentMethodTypeSEPADebit: + return STPLocalizedString(@"SEPA Debit", @"Payment method brand name"); case STPPaymentMethodTypeCardPresent: case STPPaymentMethodTypeUnknown: return STPLocalizedString(@"Unknown", @"Default missing source type label"); diff --git a/Stripe/STPPaymentMethodParams.m b/Stripe/STPPaymentMethodParams.m index 5a7283f7827..690c69185d5 100644 --- a/Stripe/STPPaymentMethodParams.m +++ b/Stripe/STPPaymentMethodParams.m @@ -6,18 +6,20 @@ // Copyright © 2019 Stripe, Inc. All rights reserved. // -#import "STPCardValidator+Private.h" #import "STPPaymentMethodParams.h" + +#import "STPCardValidator+Private.h" +#import "STPFormEncoder.h" +#import "STPFPXBankBrand.h" +#import "STPImageLibrary+Private.h" +#import "STPLocalizationUtils.h" #import "STPPaymentMethod+Private.h" +#import "STPPaymentMethodCardParams.h" #import "STPPaymentMethodFPX.h" #import "STPPaymentMethodFPXParams.h" #import "STPPaymentMethodiDEAL.h" #import "STPPaymentMethodiDEALParams.h" -#import "STPImageLibrary+Private.h" -#import "STPFPXBankBrand.h" -#import "STPPaymentMethodCardParams.h" -#import "STPLocalizationUtils.h" -#import "STPFormEncoder.h" +#import "STPPaymentMethodSEPADebitParams.h" @implementation STPPaymentMethodParams @@ -50,6 +52,17 @@ + (STPPaymentMethodParams *)paramsWithFPX:(STPPaymentMethodFPXParams *)fpx billi return params; } ++ (nullable STPPaymentMethodParams *)paramsWithSEPADebit:(STPPaymentMethodSEPADebitParams *)sepaDebit +billingDetails:(STPPaymentMethodBillingDetails *)billingDetails + metadata:(nullable NSDictionary *)metadata { + STPPaymentMethodParams *params = [self new]; + params.type = STPPaymentMethodTypeSEPADebit; + params.sepaDebit = sepaDebit; + params.billingDetails = billingDetails; + params.metadata = metadata; + return params; +} + + (nullable STPPaymentMethodParams *)paramsWithSingleUsePaymentMethod:(STPPaymentMethod *)paymentMethod { STPPaymentMethodParams *params = [self new]; switch ([paymentMethod type]) { @@ -73,6 +86,7 @@ + (nullable STPPaymentMethodParams *)paramsWithSingleUsePaymentMethod:(STPPaymen params.metadata = paymentMethod.metadata; break; } + case STPPaymentMethodTypeSEPADebit: case STPPaymentMethodTypeCard: case STPPaymentMethodTypeCardPresent: case STPPaymentMethodTypeUnknown: @@ -104,6 +118,7 @@ + (nonnull NSDictionary *)propertyNamesToFormFieldNamesMapping { NSStringFromSelector(@selector(card)): @"card", NSStringFromSelector(@selector(iDEAL)): @"ideal", NSStringFromSelector(@selector(fpx)): @"fpx", + NSStringFromSelector(@selector(sepaDebit)): @"sepa_debit", NSStringFromSelector(@selector(metadata)): @"metadata", }; } @@ -147,6 +162,8 @@ - (NSString *)label { } else { return @"FPX"; } + case STPPaymentMethodTypeSEPADebit: + return @"SEPA Debit"; case STPPaymentMethodTypeCardPresent: case STPPaymentMethodTypeUnknown: return STPLocalizedString(@"Unknown", @"Default missing source type label"); diff --git a/Stripe/STPPaymentMethodSEPADebit.m b/Stripe/STPPaymentMethodSEPADebit.m new file mode 100644 index 00000000000..3c24ca94c6a --- /dev/null +++ b/Stripe/STPPaymentMethodSEPADebit.m @@ -0,0 +1,66 @@ +// +// STPPaymentMethodSEPADebit.m +// StripeiOS +// +// Created by Cameron Sabol on 10/7/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +#import "STPPaymentMethodSEPADebit.h" + +#import "NSDictionary+Stripe.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STPPaymentMethodSEPADebit + +@synthesize allResponseFields = _allResponseFields; + +#pragma mark - Description + +- (NSString *)description { + NSArray *props = @[ + // Object + [NSString stringWithFormat:@"%@: %p", NSStringFromClass([self class]), self], + + // Basic SEPA debit details + [NSString stringWithFormat:@"last4 = %@", self.last4], + + // Additional SEPA debit details (alphabetical) + [NSString stringWithFormat:@"bankCode = %@", self.bankCode], + [NSString stringWithFormat:@"branchCode = %@", self.branchCode], + [NSString stringWithFormat:@"country = %@", self.country], + [NSString stringWithFormat:@"fingerprint = %@", self.fingerprint], + [NSString stringWithFormat:@"mandate = %@", self.mandate], + ]; + + return [NSString stringWithFormat:@"<%@>", [props componentsJoinedByString:@"; "]]; +} + +#pragma mark - STPAPIResponseDecodable + ++ (nullable instancetype)decodedObjectFromAPIResponse:(nullable NSDictionary *)response { + NSDictionary *dict = [response stp_dictionaryByRemovingNulls]; + if (!dict) { + return nil; + } + return [[self alloc] initWithDictionary:dict]; +} + +- (instancetype)initWithDictionary:(NSDictionary *)dict { + self = [super init]; + if (self) { + _last4 = [[dict stp_stringForKey:@"last4"] copy]; + _bankCode = [[dict stp_stringForKey:@"bank_code"] copy]; + _branchCode = [[dict stp_stringForKey:@"branch_code"] copy]; + _country = [[dict stp_stringForKey:@"country"] copy]; + _fingerprint = [[dict stp_stringForKey:@"fingerprint"] copy]; + _mandate = [[dict stp_stringForKey:@"mandate"] copy]; + + _allResponseFields = dict.copy; + } + return self; +} +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe/STPPaymentMethodSEPADebitParams.m b/Stripe/STPPaymentMethodSEPADebitParams.m new file mode 100644 index 00000000000..45a30cc07e9 --- /dev/null +++ b/Stripe/STPPaymentMethodSEPADebitParams.m @@ -0,0 +1,30 @@ +// +// STPPaymentMethodSEPADebitParams.m +// StripeiOS +// +// Created by Cameron Sabol on 10/7/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +#import "STPPaymentMethodSEPADebitParams.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation STPPaymentMethodSEPADebitParams + +@synthesize additionalAPIParameters = _additionalAPIParameters; + +#pragma mark - STPFormEncodable + ++ (nullable NSString *)rootObjectName { + return @"sepa_debit"; +} + ++ (NSDictionary *)propertyNamesToFormFieldNamesMapping { + return @{ + NSStringFromSelector(@selector(iban)): @"iban", + }; +} +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe/STPSetupIntentConfirmParams.m b/Stripe/STPSetupIntentConfirmParams.m index d59f2c85f31..e439aeeaf40 100644 --- a/Stripe/STPSetupIntentConfirmParams.m +++ b/Stripe/STPSetupIntentConfirmParams.m @@ -8,6 +8,14 @@ #import "STPSetupIntentConfirmParams.h" +#import "STPPaymentMethodParams.h" + +@interface STPSetupIntentConfirmParams () + +@property (nonatomic, nullable, readonly) NSDictionary *mandateData; + +@end + @implementation STPSetupIntentConfirmParams @synthesize additionalAPIParameters = _additionalAPIParameters; @@ -45,6 +53,15 @@ - (NSString *)description { return [NSString stringWithFormat:@"<%@>", [props componentsJoinedByString:@"; "]]; } +- (NSDictionary *)mandateData { + if (self.paymentMethodParams.type == STPPaymentMethodTypeSEPADebit) { + return @{@"type": @"online", + @"online": @{@"infer_from_client": @YES}}; + } else { + return nil; + } +} + #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone { @@ -73,6 +90,7 @@ + (nonnull NSDictionary *)propertyNamesToFormFieldNamesMapping { NSStringFromSelector(@selector(paymentMethodID)): @"payment_method", NSStringFromSelector(@selector(returnURL)): @"return_url", NSStringFromSelector(@selector(useStripeSDK)): @"use_stripe_sdk", + NSStringFromSelector(@selector(mandateData)): @"mandate_data", }; } diff --git a/Tests/Tests/SEPADebitSource.json b/Tests/Tests/SEPADebitSource.json index eb7fb7959af..18abb191717 100644 --- a/Tests/Tests/SEPADebitSource.json +++ b/Tests/Tests/SEPADebitSource.json @@ -23,9 +23,11 @@ "usage": "reusable", "sepa_debit": { "bank_code": "37040044", + "branch_code": "a_branch", "country": "DE", "fingerprint": "NxdSyRegc9PsMkWy", "last4": "3001", + "mandate": "NXDSYREGC9PSMKWY", "mandate_reference": "NXDSYREGC9PSMKWY", "mandate_url": "https://hooks.stripe.com/adapter/sepa_debit/file/src_18HgGjHNCLa1Vra6Y9TIP6tU/src_client_secret_XcBmS94nTg5o0xc9MSliSlDW" }, diff --git a/Tests/Tests/STPPaymentMethodSEPADebitTest.m b/Tests/Tests/STPPaymentMethodSEPADebitTest.m new file mode 100644 index 00000000000..adb3cb06f4b --- /dev/null +++ b/Tests/Tests/STPPaymentMethodSEPADebitTest.m @@ -0,0 +1,51 @@ +// +// STPPaymentMethodSEPADebitTest.m +// StripeiOS Tests +// +// Created by Cameron Sabol on 10/7/19. +// Copyright © 2019 Stripe, Inc. All rights reserved. +// + +#import + +#import "STPPaymentMethodSEPADebit.h" +#import "STPTestUtils.h" + +@interface STPPaymentMethodSEPADebitTest : XCTestCase + +@end + +@implementation STPPaymentMethodSEPADebitTest + +#pragma mark - STPAPIResponseDecodable Tests + +- (void)testDecodedObjectFromAPIResponseRequiredFields { + NSArray *requiredFields = @[]; + + for (NSString *field in requiredFields) { + NSMutableDictionary *response = [[STPTestUtils jsonNamed:@"SEPADebitSource"][@"sepa_debit"] mutableCopy]; + [response removeObjectForKey:field]; + + XCTAssertNil([STPPaymentMethodSEPADebit decodedObjectFromAPIResponse:response]); + } + + XCTAssert([STPPaymentMethodSEPADebit decodedObjectFromAPIResponse:[STPTestUtils jsonNamed:@"SEPADebitSource"][@"sepa_debit"]]); +} + +- (void)testDecodedObjectFromAPIResponseMapping { + NSDictionary *response = [STPTestUtils jsonNamed:@"SEPADebitSource"][@"sepa_debit"]; + STPPaymentMethodSEPADebit *sepaDebit = [STPPaymentMethodSEPADebit decodedObjectFromAPIResponse:response]; + + XCTAssertEqualObjects(sepaDebit.bankCode, @"37040044"); + XCTAssertEqualObjects(sepaDebit.branchCode, @"a_branch"); + XCTAssertEqualObjects(sepaDebit.country, @"DE"); + XCTAssertEqualObjects(sepaDebit.fingerprint, @"NxdSyRegc9PsMkWy"); + XCTAssertEqualObjects(sepaDebit.last4, @"3001"); + XCTAssertEqualObjects(sepaDebit.mandate, @"NXDSYREGC9PSMKWY"); + + XCTAssertNotEqual(sepaDebit.allResponseFields, response); + XCTAssertEqualObjects(sepaDebit.allResponseFields, response); +} + + +@end diff --git a/Tests/Tests/STPPaymentMethodTest.m b/Tests/Tests/STPPaymentMethodTest.m index 75f5d443199..4e0c665c716 100644 --- a/Tests/Tests/STPPaymentMethodTest.m +++ b/Tests/Tests/STPPaymentMethodTest.m @@ -27,14 +27,16 @@ - (void)testTypeFromString { XCTAssertEqual([STPPaymentMethod typeFromString:@"IDEAL"], STPPaymentMethodTypeiDEAL); XCTAssertEqual([STPPaymentMethod typeFromString:@"fpx"], STPPaymentMethodTypeFPX); XCTAssertEqual([STPPaymentMethod typeFromString:@"FPX"], STPPaymentMethodTypeFPX); + XCTAssertEqual([STPPaymentMethod typeFromString:@"sepa_debit"], STPPaymentMethodTypeSEPADebit); + XCTAssertEqual([STPPaymentMethod typeFromString:@"SEPA_DEBIT"], STPPaymentMethodTypeSEPADebit); XCTAssertEqual([STPPaymentMethod typeFromString:@"card_present"], STPPaymentMethodTypeCardPresent); XCTAssertEqual([STPPaymentMethod typeFromString:@"CARD_PRESENT"], STPPaymentMethodTypeCardPresent); XCTAssertEqual([STPPaymentMethod typeFromString:@"unknown_string"], STPPaymentMethodTypeUnknown); } - (void)testTypesFromStrings { - NSArray *rawTypes = @[@"card", @"ideal", @"card_present", @"fpx"]; - NSArray *expectedTypes = @[@(STPPaymentMethodTypeCard), @(STPPaymentMethodTypeiDEAL), @(STPPaymentMethodTypeCardPresent), @(STPPaymentMethodTypeFPX)]; + NSArray *rawTypes = @[@"card", @"ideal", @"card_present", @"fpx", @"sepa_debit"]; + NSArray *expectedTypes = @[@(STPPaymentMethodTypeCard), @(STPPaymentMethodTypeiDEAL), @(STPPaymentMethodTypeCardPresent), @(STPPaymentMethodTypeFPX), @(STPPaymentMethodTypeSEPADebit)]; XCTAssertEqualObjects([STPPaymentMethod typesFromStrings:rawTypes], expectedTypes); } @@ -44,6 +46,7 @@ - (void)testStringFromType { @(STPPaymentMethodTypeiDEAL), @(STPPaymentMethodTypeCardPresent), @(STPPaymentMethodTypeFPX), + @(STPPaymentMethodTypeSEPADebit), @(STPPaymentMethodTypeUnknown), ]; for (NSNumber *typeNumber in values) { @@ -63,6 +66,9 @@ - (void)testStringFromType { case STPPaymentMethodTypeFPX: XCTAssertEqualObjects(string, @"fpx"); break; + case STPPaymentMethodTypeSEPADebit: + XCTAssertEqualObjects(string, @"sepa_debit"); + break; case STPPaymentMethodTypeUnknown: XCTAssertNil(string); break;