From a1df69a479ef199cbd70c5b0533aa64e213b7803 Mon Sep 17 00:00:00 2001 From: davidme-stripe <52758633+davidme-stripe@users.noreply.github.com> Date: Fri, 27 Sep 2019 16:59:00 -0700 Subject: [PATCH] Add FPX to Basic Integration (#1390) * Add FPX to Basic Integration * Fix comments and custom integration * Fix entitlements * Fix bank icons * Fix test * Add analytics, fix up table view to handle 0 cards with APMs * Fix CollectionView to not lose items on reload * Fix Apple Pay appearing when not supported * Update based on feedback * Fix previous bad fix, the individual responsible has been sacked --- .../ApplePayExampleViewController.m | 2 +- .../CardAutomaticConfirmationViewController.m | 2 +- .../FPXExampleViewController.m | 2 +- Example/Custom Integration/MyAPIClient.h | 3 +- Example/Custom Integration/MyAPIClient.m | 7 +- .../BrowseProductsViewController.swift | 26 +++- .../CheckoutViewController.swift | 21 ++- Example/Standard Integration/EmojiCell.swift | 4 +- .../EmojiCheckoutCell.swift | 4 +- .../Standard Integration/MyAPIClient.swift | 3 +- .../SettingsViewController.swift | 141 ++++++++++++++++-- .../StandardIntegrationUITests.swift | 2 +- .../UI Examples/BrowseViewController.swift | 19 ++- LocalizationTester/ViewController.m | 4 +- Stripe.xcodeproj/project.pbxproj | 18 +++ .../PublicHeaders/STPPaymentConfiguration.h | 5 +- Stripe/PublicHeaders/STPPaymentIntentParams.h | 8 +- Stripe/PublicHeaders/STPPaymentMethodParams.h | 2 +- Stripe/PublicHeaders/STPPaymentOption.h | 22 ++- Stripe/PublicHeaders/STPPaymentResult.h | 21 ++- Stripe/Resources/Images/stp_icon_bank.png | Bin 0 -> 475 bytes Stripe/Resources/Images/stp_icon_bank@2x.png | Bin 0 -> 815 bytes Stripe/Resources/Images/stp_icon_bank@3x.png | Bin 0 -> 1083 bytes Stripe/STPAnalyticsClient.m | 28 +++- Stripe/STPImageLibrary+Private.h | 1 + Stripe/STPImageLibrary.m | 4 + Stripe/STPPaymentConfiguration.m | 10 +- Stripe/STPPaymentContext.m | 12 +- Stripe/STPPaymentIntentParams.m | 11 ++ Stripe/STPPaymentMethodParams.m | 49 ++++++ Stripe/STPPaymentOptionTableViewCell.h | 1 + Stripe/STPPaymentOptionTableViewCell.m | 53 +++++++ Stripe/STPPaymentOptionTuple.h | 3 +- Stripe/STPPaymentOptionTuple.m | 12 +- .../STPPaymentOptionsInternalViewController.h | 2 +- .../STPPaymentOptionsInternalViewController.m | 84 +++++++++-- Stripe/STPPaymentOptionsViewController.m | 12 +- Stripe/STPPaymentResult.m | 20 ++- ...TPAddCardViewControllerLocalizationTests.m | 2 +- Tests/Tests/STPPaymentConfigurationTest.m | 8 +- Tests/Tests/STPPaymentContextSnapshotTests.m | 2 +- ...ntOptionsViewControllerLocalizationTests.m | 2 +- .../STPPaymentOptionsViewControllerTest.m | 2 +- 43 files changed, 530 insertions(+), 104 deletions(-) create mode 100644 Stripe/Resources/Images/stp_icon_bank.png create mode 100644 Stripe/Resources/Images/stp_icon_bank@2x.png create mode 100644 Stripe/Resources/Images/stp_icon_bank@3x.png diff --git a/Example/Custom Integration/ApplePayExampleViewController.m b/Example/Custom Integration/ApplePayExampleViewController.m index f4e15bbceef..0aa9f56d9b9 100644 --- a/Example/Custom Integration/ApplePayExampleViewController.m +++ b/Example/Custom Integration/ApplePayExampleViewController.m @@ -185,7 +185,7 @@ - (void)_createAndConfirmPaymentIntentWithPaymentMethod:(STPPaymentMethod *)paym [[STPPaymentHandler sharedHandler] confirmPayment:paymentIntentParams withAuthenticationContext:self completion:paymentHandlerCompletion]; - }]; + } additionalParameters:nil]; } - (void)paymentAuthorizationViewControllerDidFinish:(PKPaymentAuthorizationViewController *)controller { diff --git a/Example/Custom Integration/CardAutomaticConfirmationViewController.m b/Example/Custom Integration/CardAutomaticConfirmationViewController.m index 30ebe9feac2..52fb16f1be0 100644 --- a/Example/Custom Integration/CardAutomaticConfirmationViewController.m +++ b/Example/Custom Integration/CardAutomaticConfirmationViewController.m @@ -161,7 +161,7 @@ - (void)pay { break; } }]; - }]; + } additionalParameters:nil]; } @end diff --git a/Example/Custom Integration/FPXExampleViewController.m b/Example/Custom Integration/FPXExampleViewController.m index b70ada8e66c..b32429c643e 100644 --- a/Example/Custom Integration/FPXExampleViewController.m +++ b/Example/Custom Integration/FPXExampleViewController.m @@ -125,7 +125,7 @@ - (void)payWithBankAccount:(STPPaymentMethodParams *)paymentMethodParams { break; } }]; - }]; + } additionalParameters:@"country=my"]; } - (void)bankSelectionViewController:(nonnull STPBankSelectionViewController *)bankViewController didCreatePaymentMethodParams:(STPPaymentMethodParams *)paymentMethodParams { diff --git a/Example/Custom Integration/MyAPIClient.h b/Example/Custom Integration/MyAPIClient.h index ba06447f6e3..c3e053135a1 100644 --- a/Example/Custom Integration/MyAPIClient.h +++ b/Example/Custom Integration/MyAPIClient.h @@ -33,10 +33,11 @@ typedef void (^STPCreateSetupIntentCompletionHandler)(MyAPIClientResult status, method signature is the most interesting part: you need some way to ask *your* backend to create a PaymentIntent with the correct properties, and then it needs to pass the client secret back. + @param additionalParameters additional parameters to pass to the example backend @param completion completion block called with status of backend call & the client secret if successful. @see https://stripe.com/docs/payments/payment-intents/ios */ -- (void)createPaymentIntentWithCompletion:(STPPaymentIntentCreationHandler)completion; +- (void)createPaymentIntentWithCompletion:(STPPaymentIntentCreationHandler)completion additionalParameters:(NSString * _Nullable)additionalParameters; #pragma mark - PaymentIntents (manual confirmation) diff --git a/Example/Custom Integration/MyAPIClient.m b/Example/Custom Integration/MyAPIClient.m index 63db1ba21a0..81ad6551ca4 100644 --- a/Example/Custom Integration/MyAPIClient.m +++ b/Example/Custom Integration/MyAPIClient.m @@ -39,10 +39,10 @@ - (void)_callOnMainThread:(void (^)(void))block { method signature is the most interesting part: you need some way to ask *your* backend to create a PaymentIntent with the correct properties, and then it needs to pass the client secret back. - @param amount Amount to charge the customer + @param additionalParameters additional parameters to pass to the example backend @param completion completion block called with status of backend call & the client secret if successful. */ -- (void)createPaymentIntentWithCompletion:(STPPaymentIntentCreationHandler)completion { +- (void)createPaymentIntentWithCompletion:(STPPaymentIntentCreationHandler)completion additionalParameters:(NSString *)additionalParameters { if (!BackendBaseURL) { NSError *error = [NSError errorWithDomain:@"MyAPIClientErrorDomain" code:0 @@ -64,6 +64,9 @@ - (void)createPaymentIntentWithCompletion:(STPPaymentIntentCreationHandler)compl // example-ios-backend allows passing metadata through to Stripe @"B3E611D1-5FA1-4410-9CEC-00958A5126CB" ]; + if (additionalParameters != nil) { + postBody = [postBody stringByAppendingFormat:@"&%@", additionalParameters]; + } NSData *data = [postBody dataUsingEncoding:NSUTF8StringEncoding]; NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request diff --git a/Example/Standard Integration/BrowseProductsViewController.swift b/Example/Standard Integration/BrowseProductsViewController.swift index fab1ff9d212..5d2d562bd77 100644 --- a/Example/Standard Integration/BrowseProductsViewController.swift +++ b/Example/Standard Integration/BrowseProductsViewController.swift @@ -11,10 +11,6 @@ import UIKit struct Product { let emoji: String let price: Int - - var priceText: String { - return "$\(price/100).00" - } } class BrowseProductsViewController: UICollectionViewController { @@ -41,7 +37,7 @@ class BrowseProductsViewController: UICollectionViewController { var shoppingCart = [Product]() { didSet { let price = shoppingCart.reduce(0) { result, product in result + product.price } - buyButton.priceLabel.text = "$\(price/100).00" + buyButton.priceLabel.text = numberFormatter.string(from: NSNumber(value: Float(price)/100))! let enabled = price > 0 if enabled == buyButton.isEnabled { return @@ -55,6 +51,13 @@ class BrowseProductsViewController: UICollectionViewController { }, completion: nil) } } + + var numberFormatter : NumberFormatter = { + let numberFormatter = NumberFormatter() + numberFormatter.numberStyle = .currency + numberFormatter.usesGroupingSeparator = true + return numberFormatter + }() let settingsVC = SettingsViewController() @@ -88,6 +91,8 @@ class BrowseProductsViewController: UICollectionViewController { self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "Products", style: .plain, target: nil, action: nil) self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Settings", style: .plain, target: self, action: #selector(showSettings)) + self.numberFormatter.locale = self.settingsVC.settings.currencyLocale + collectionView?.register(EmojiCell.self, forCellWithReuseIdentifier: "Cell") collectionView?.allowsMultipleSelection = true @@ -127,6 +132,14 @@ class BrowseProductsViewController: UICollectionViewController { self.navigationController?.navigationBar.titleTextAttributes = titleAttributes self.navigationItem.leftBarButtonItem?.setTitleTextAttributes(buttonAttributes, for: UIControl.State()) self.navigationItem.backBarButtonItem?.setTitleTextAttributes(buttonAttributes, for: UIControl.State()) + + self.numberFormatter.locale = self.settingsVC.settings.currencyLocale + self.view.setNeedsLayout() + let selectedItems = self.collectionView.indexPathsForSelectedItems ?? [] + self.collectionView.reloadData() + for item in selectedItems { + self.collectionView.selectItem(at: item, animated: false, scrollPosition: []) + } } @objc func showSettings() { @@ -168,7 +181,8 @@ extension BrowseProductsViewController: UICollectionViewDelegateFlowLayout { } let product = self.productsAndPrices[indexPath.item] - cell.configure(with: product) + + cell.configure(with: product, numberFormatter: self.numberFormatter) return cell } diff --git a/Example/Standard Integration/CheckoutViewController.swift b/Example/Standard Integration/CheckoutViewController.swift index 87501b4a956..ea735f93aef 100644 --- a/Example/Standard Integration/CheckoutViewController.swift +++ b/Example/Standard Integration/CheckoutViewController.swift @@ -27,7 +27,7 @@ class CheckoutViewController: UIViewController { // These values will be shown to the user when they purchase with Apple Pay. let companyName = "Emoji Apparel" - let paymentCurrency = "usd" + let paymentCurrency: String let paymentContext: STPPaymentContext @@ -40,6 +40,7 @@ class CheckoutViewController: UIViewController { let rowHeight: CGFloat = 52 let activityIndicator = UIActivityIndicatorView(style: .gray) let numberFormatter: NumberFormatter + let country: String var products: [Product] var paymentInProgress: Bool = false { didSet { @@ -83,7 +84,9 @@ class CheckoutViewController: UIViewController { config.requiredShippingAddressFields = settings.requiredShippingAddressFields config.shippingType = settings.shippingType config.additionalPaymentOptions = settings.additionalPaymentOptions - + self.country = settings.country + self.paymentCurrency = settings.currency + let customerContext = STPCustomerContext(keyProvider: MyAPIClient.sharedClient) let paymentContext = STPPaymentContext(customerContext: customerContext, configuration: config, @@ -131,13 +134,8 @@ See https://stripe.com/docs/testing. } self.totalRow = CheckoutRowView(title: "Total", detail: "", tappable: false) self.buyButton = BuyButton(enabled: false, title: "Buy") - var localeComponents: [String: String] = [ - NSLocale.Key.currencyCode.rawValue: self.paymentCurrency, - ] - localeComponents[NSLocale.Key.languageCode.rawValue] = NSLocale.preferredLanguages.first - let localeID = NSLocale.localeIdentifier(fromComponents: localeComponents) let numberFormatter = NumberFormatter() - numberFormatter.locale = Locale(identifier: localeID) + numberFormatter.locale = settings.currencyLocale numberFormatter.numberStyle = .currency numberFormatter.usesGroupingSeparator = true self.numberFormatter = numberFormatter @@ -261,15 +259,14 @@ extension CheckoutViewController: STPPaymentContextDelegate { } } func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPPaymentStatusBlock) { - // Create the PaymentIntent on the backend // To speed this up, create the PaymentIntent earlier in the checkout flow and update it as necessary (e.g. when the cart subtotal updates or when shipping fees and taxes are calculated, instead of re-creating a PaymentIntent for every payment attempt. - MyAPIClient.sharedClient.createPaymentIntent(products: self.products, shippingMethod: paymentContext.selectedShippingMethod) { result in + MyAPIClient.sharedClient.createPaymentIntent(products: self.products, shippingMethod: paymentContext.selectedShippingMethod, country: self.country) { result in switch result { case .success(let clientSecret): // Confirm the PaymentIntent let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret) - paymentIntentParams.paymentMethodId = paymentResult.paymentMethod.stripeId + paymentIntentParams.configure(with: paymentResult) paymentIntentParams.returnURL = "payments-example://stripe-redirect" STPPaymentHandler.shared().confirmPayment(withParams: paymentIntentParams, authenticationContext: paymentContext) { status, paymentIntent, error in switch status { @@ -399,7 +396,7 @@ extension CheckoutViewController: UITableViewDelegate, UITableViewDataSource { } let product = self.products[indexPath.item] - cell.configure(with: product) + cell.configure(with: product, numberFormatter: self.numberFormatter) cell.selectionStyle = .none return cell } diff --git a/Example/Standard Integration/EmojiCell.swift b/Example/Standard Integration/EmojiCell.swift index 706c36bbd6e..9da7ba2bb57 100644 --- a/Example/Standard Integration/EmojiCell.swift +++ b/Example/Standard Integration/EmojiCell.swift @@ -65,8 +65,8 @@ class EmojiCell: UICollectionViewCell { fatalError() } - public func configure(with product: Product) { - priceLabel.text = product.priceText + public func configure(with product: Product, numberFormatter: NumberFormatter) { + priceLabel.text = numberFormatter.string(from: NSNumber(value: Float(product.price)/100))! emojiLabel.text = product.emoji } diff --git a/Example/Standard Integration/EmojiCheckoutCell.swift b/Example/Standard Integration/EmojiCheckoutCell.swift index 27480a6e9eb..9da25b2c09d 100644 --- a/Example/Standard Integration/EmojiCheckoutCell.swift +++ b/Example/Standard Integration/EmojiCheckoutCell.swift @@ -48,8 +48,8 @@ class EmojiCheckoutCell: UITableViewCell { ]) } - public func configure(with product: Product) { - priceLabel.text = product.priceText + public func configure(with product: Product, numberFormatter: NumberFormatter) { + priceLabel.text = numberFormatter.string(from: NSNumber(value: Float(product.price)/100))! emojiLabel.text = product.emoji detailLabel.text = product.emoji.unicodeScalars.first?.properties.name?.localizedCapitalized } diff --git a/Example/Standard Integration/MyAPIClient.swift b/Example/Standard Integration/MyAPIClient.swift index 5f835642e44..74e3b399a75 100644 --- a/Example/Standard Integration/MyAPIClient.swift +++ b/Example/Standard Integration/MyAPIClient.swift @@ -31,7 +31,7 @@ class MyAPIClient: NSObject, STPCustomerEphemeralKeyProvider { } } - func createPaymentIntent(products: [Product], shippingMethod: PKShippingMethod?, completion: @escaping ((Result) -> Void)) { + func createPaymentIntent(products: [Product], shippingMethod: PKShippingMethod?, country: String? = nil, completion: @escaping ((Result) -> Void)) { let url = self.baseURL.appendingPathComponent("create_payment_intent") var params: [String: Any] = [ "metadata": [ @@ -45,6 +45,7 @@ class MyAPIClient: NSObject, STPCustomerEphemeralKeyProvider { if let shippingMethod = shippingMethod { params["shipping"] = shippingMethod.identifier } + params["country"] = country let jsonData = try? JSONSerialization.data(withJSONObject: params) var request = URLRequest(url: url) request.httpMethod = "POST" diff --git a/Example/Standard Integration/SettingsViewController.swift b/Example/Standard Integration/SettingsViewController.swift index aca14272035..8aea9c9f055 100644 --- a/Example/Standard Integration/SettingsViewController.swift +++ b/Example/Standard Integration/SettingsViewController.swift @@ -15,26 +15,34 @@ struct Settings { let requiredBillingAddressFields: STPBillingAddressFields let requiredShippingAddressFields: Set let shippingType: STPShippingType + let country: String + let currency: String + let currencyLocale: Locale } class SettingsViewController: UITableViewController { var settings: Settings { return Settings(theme: self.theme.stpTheme, - additionalPaymentOptions: self.applePay.enabled ? .all : STPPaymentOptionType(), + additionalPaymentOptions: self.additionalPaymentOptions, requiredBillingAddressFields: self.requiredBillingAddressFields.stpBillingAddressFields, requiredShippingAddressFields: self.requiredShippingAddressFields.stpContactFields, - shippingType: self.shippingType.stpShippingType) + shippingType: self.shippingType.stpShippingType, + country: self.country.countryID, + currency: self.country.currency, + currencyLocale: self.country.currencyLocale) } private var theme: Theme = .Default - private var applePay: Switch = .Enabled + private var additionalPaymentOptions: STPPaymentOptionType = .default private var requiredBillingAddressFields: RequiredBillingAddressFields = .None private var requiredShippingAddressFields: RequiredShippingAddressFields = .PostalAddressPhone private var shippingType: ShippingType = .Shipping + private var country: Country = .US fileprivate enum Section: String { case Theme = "Theme" - case ApplePay = "Apple Pay" + case AdditionalPaymentOptions = "Additional Payment Options" + case Country = "Country (For Currency and Supported Payment Options)" case RequiredBillingAddressFields = "Required Billing Address Fields" case RequiredShippingAddressFields = "Required Shipping Address Fields" case ShippingType = "Shipping Type" @@ -43,13 +51,33 @@ class SettingsViewController: UITableViewController { init(section: Int) { switch section { case 0: self = .Theme - case 1: self = .ApplePay - case 2: self = .RequiredBillingAddressFields - case 3: self = .RequiredShippingAddressFields - case 4: self = .ShippingType + case 1: self = .AdditionalPaymentOptions + case 2: self = .Country + case 3: self = .RequiredBillingAddressFields + case 4: self = .RequiredShippingAddressFields + case 5: self = .ShippingType default: self = .Session } } + + var intValue: Int { + switch self { + case .Theme: + return 0 + case .AdditionalPaymentOptions: + return 1 + case .Country: + return 2 + case .RequiredBillingAddressFields: + return 3 + case .RequiredShippingAddressFields: + return 4 + case .ShippingType: + return 5 + case .Session: + return 6 + } + } } fileprivate enum Theme: String { @@ -115,6 +143,45 @@ class SettingsViewController: UITableViewController { } } } + + fileprivate enum Country: String { + case US = "United States" + case MY = "Malaysia" + + init(row: Int) { + switch row { + case 0: self = .US + default: self = .MY + } + } + + var countryID: String { + switch self { + case .US: + return "us" + case .MY: + return "my" + } + } + + var currency: String { + switch self { + case .US: + return "usd" + case .MY: + return "myr" + } + } + + var currencyLocale: Locale { + var localeComponents: [String: String] = [ + NSLocale.Key.currencyCode.rawValue: self.currency, + ] + localeComponents[NSLocale.Key.languageCode.rawValue] = NSLocale.preferredLanguages.first + let localeID = NSLocale.localeIdentifier(fromComponents: localeComponents) + return Locale(identifier: localeID) + } + } fileprivate enum Switch: String { case Enabled = "Enabled" @@ -129,6 +196,25 @@ class SettingsViewController: UITableViewController { } } + fileprivate enum AdditionalPaymentOptionsFields: String { + case ApplePay = "Apple Pay" + case FPX = "FPX" + + init(row: Int) { + switch row { + case 0: self = .ApplePay + default: self = .FPX + } + } + + var stpPaymentOptionType: STPPaymentOptionType { + switch self { + case .ApplePay: return .applePay + case .FPX: return .FPX + } + } + } + fileprivate enum RequiredBillingAddressFields: String { case None = "None" case Zip = "Zip" @@ -213,13 +299,14 @@ class SettingsViewController: UITableViewController { } override func numberOfSections(in tableView: UITableView) -> Int { - return 6 + return 7 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { switch Section(section: section) { case .Theme: return 2 - case .ApplePay: return 2 + case .AdditionalPaymentOptions: return 2 + case .Country: return 2 case .RequiredBillingAddressFields: return 4 case .RequiredShippingAddressFields: return 4 case .ShippingType: return 2 @@ -238,10 +325,14 @@ class SettingsViewController: UITableViewController { let value = Theme(row: (indexPath as NSIndexPath).row) cell.textLabel?.text = value.rawValue cell.accessoryType = value == self.theme ? .checkmark : .none - case .ApplePay: - let value = Switch(row: (indexPath as NSIndexPath).row) + case .AdditionalPaymentOptions: + let value = AdditionalPaymentOptionsFields(row: (indexPath as NSIndexPath).row) + cell.textLabel?.text = value.rawValue + cell.accessoryType = self.additionalPaymentOptions.contains(value.stpPaymentOptionType) ? .checkmark : .none + case .Country: + let value = Country(row: (indexPath as NSIndexPath).row) cell.textLabel?.text = value.rawValue - cell.accessoryType = value == self.applePay ? .checkmark : .none + cell.accessoryType = value == self.country ? .checkmark : .none case .RequiredBillingAddressFields: let value = RequiredBillingAddressFields(row: (indexPath as NSIndexPath).row) cell.textLabel?.text = value.rawValue @@ -266,8 +357,28 @@ class SettingsViewController: UITableViewController { switch Section(section: (indexPath as NSIndexPath).section) { case .Theme: self.theme = Theme(row: (indexPath as NSIndexPath).row) - case .ApplePay: - self.applePay = Switch(row: (indexPath as NSIndexPath).row) + case .AdditionalPaymentOptions: + let field = AdditionalPaymentOptionsFields(row: (indexPath as NSIndexPath).row) + var options = self.additionalPaymentOptions + switch field { + case .ApplePay: + if options.contains(.applePay) { + options.remove(.applePay) + } else { + options.insert(.applePay) + } + case .FPX: + if options.contains(.FPX) { + options.remove(.FPX) + } else { + options.insert(.FPX) + self.country = .MY + tableView.reloadSections(IndexSet(integer: Section.Country.intValue), with: .automatic) + } + } + self.additionalPaymentOptions = options + case .Country: + self.country = Country(row: (indexPath as NSIndexPath).row) case .RequiredBillingAddressFields: self.requiredBillingAddressFields = RequiredBillingAddressFields(row: (indexPath as NSIndexPath).row) case .RequiredShippingAddressFields: diff --git a/Example/StandardIntegrationUITests/StandardIntegrationUITests.swift b/Example/StandardIntegrationUITests/StandardIntegrationUITests.swift index c6944e82e69..36ca514a19b 100644 --- a/Example/StandardIntegrationUITests/StandardIntegrationUITests.swift +++ b/Example/StandardIntegrationUITests/StandardIntegrationUITests.swift @@ -28,7 +28,7 @@ class StandardIntegrationUITests: XCTestCase { func disableAddressEntry(_ app: XCUIApplication) { app.navigationBars["Emoji Apparel"].buttons["Settings"].tap() - app.tables.children(matching: .cell).element(boundBy: 8).staticTexts["None"].tap() + app.tables.children(matching: .cell).element(boundBy: 10).staticTexts["None"].tap() app.navigationBars["Settings"].buttons["Done"].tap() } diff --git a/Example/UI Examples/BrowseViewController.swift b/Example/UI Examples/BrowseViewController.swift index b228d5b0ca4..3415590a9e3 100644 --- a/Example/UI Examples/BrowseViewController.swift +++ b/Example/UI Examples/BrowseViewController.swift @@ -16,6 +16,7 @@ class BrowseViewController: UITableViewController, STPAddCardViewControllerDeleg case STPPaymentCardTextField case STPAddCardViewController case STPPaymentOptionsViewController + case STPPaymentOptionsFPXViewController case STPShippingInfoViewController case ChangeTheme @@ -24,6 +25,7 @@ class BrowseViewController: UITableViewController, STPAddCardViewControllerDeleg case .STPPaymentCardTextField: return "Card Field" case .STPAddCardViewController: return "Card Form with Billing Address" case .STPPaymentOptionsViewController: return "Payment Option Picker" + case .STPPaymentOptionsFPXViewController: return "Payment Option Picker (With FPX)" case .STPShippingInfoViewController: return "Shipping Info Form" case .ChangeTheme: return "Change Theme" } @@ -34,6 +36,7 @@ class BrowseViewController: UITableViewController, STPAddCardViewControllerDeleg case .STPPaymentCardTextField: return "STPPaymentCardTextField" case .STPAddCardViewController: return "STPAddCardViewController" case .STPPaymentOptionsViewController: return "STPPaymentOptionsViewController" + case .STPPaymentOptionsFPXViewController: return "STPPaymentOptionsViewController" case .STPShippingInfoViewController: return "STPShippingInfoViewController" case .ChangeTheme: return "" } @@ -91,9 +94,21 @@ class BrowseViewController: UITableViewController, STPAddCardViewControllerDeleg let navigationController = UINavigationController(rootViewController: viewController) navigationController.navigationBar.stp_theme = theme present(navigationController, animated: true, completion: nil) + case .STPPaymentOptionsFPXViewController: + let config = STPPaymentConfiguration() + config.additionalPaymentOptions = [.default, .FPX] + config.requiredBillingAddressFields = .none + config.appleMerchantIdentifier = "dummy-merchant-id" + let viewController = STPPaymentOptionsViewController(configuration: config, + theme: theme, + customerContext: self.customerContext, + delegate: self) + let navigationController = UINavigationController(rootViewController: viewController) + navigationController.navigationBar.stp_theme = theme + present(navigationController, animated: true, completion: nil) case .STPPaymentOptionsViewController: let config = STPPaymentConfiguration() - config.additionalPaymentOptions = .all + config.additionalPaymentOptions = .default config.requiredBillingAddressFields = .none config.appleMerchantIdentifier = "dummy-merchant-id" let viewController = STPPaymentOptionsViewController(configuration: config, @@ -139,7 +154,7 @@ class BrowseViewController: UITableViewController, STPAddCardViewControllerDeleg } func paymentOptionsViewControllerDidFinish(_ paymentOptionsViewController: STPPaymentOptionsViewController) { - paymentOptionsViewController.navigationController?.popViewController(animated: true) + dismiss(animated: true, completion: nil) } func paymentOptionsViewController(_ paymentOptionsViewController: STPPaymentOptionsViewController, didFailToLoadWithError error: Error) { diff --git a/LocalizationTester/ViewController.m b/LocalizationTester/ViewController.m index dfe18ed411f..8ea30e35f28 100644 --- a/LocalizationTester/ViewController.m +++ b/LocalizationTester/ViewController.m @@ -192,7 +192,7 @@ - (void)tableView:(__unused UITableView *)tableView didSelectRowAtIndexPath:(__u case LocalizedScreenPaymentOptionsVC: { STPPaymentConfiguration *configuration = [[STPPaymentConfiguration alloc] init]; - configuration.additionalPaymentOptions = STPPaymentOptionTypeAll; + configuration.additionalPaymentOptions = STPPaymentOptionTypeDefault; configuration.requiredBillingAddressFields = STPBillingAddressFieldsFull; configuration.appleMerchantIdentifier = @"dummy-merchant-id"; vc = [[STPPaymentOptionsViewController alloc] initWithConfiguration:configuration @@ -205,7 +205,7 @@ - (void)tableView:(__unused UITableView *)tableView didSelectRowAtIndexPath:(__u case LocalizedScreenPaymentOptionsVCLoading: { STPPaymentConfiguration *configuration = [[STPPaymentConfiguration alloc] init]; - configuration.additionalPaymentOptions = STPPaymentOptionTypeAll; + configuration.additionalPaymentOptions = STPPaymentOptionTypeDefault; configuration.requiredBillingAddressFields = STPBillingAddressFieldsFull; configuration.appleMerchantIdentifier = @"dummy-merchant-id"; MockCustomerContext *customerContext = [[MockCustomerContext alloc] init]; diff --git a/Stripe.xcodeproj/project.pbxproj b/Stripe.xcodeproj/project.pbxproj index 9c6288e991d..82f93a64107 100644 --- a/Stripe.xcodeproj/project.pbxproj +++ b/Stripe.xcodeproj/project.pbxproj @@ -539,6 +539,12 @@ 31C8271A232C19BA002ECB0A /* stp_bank_fpx_standard_chartered@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 31C82627232C1640002ECB0A /* stp_bank_fpx_standard_chartered@3x.png */; }; 31C8271B232C19BA002ECB0A /* stp_bank_fpx_uob.png in Resources */ = {isa = PBXBuildFile; fileRef = 31C8260F232C163C002ECB0A /* stp_bank_fpx_uob.png */; }; 31C8271C232C19BA002ECB0A /* stp_bank_fpx_uob@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 31C82636232C1643002ECB0A /* stp_bank_fpx_uob@3x.png */; }; + 31C8277623398B0B002ECB0A /* stp_icon_bank.png in Resources */ = {isa = PBXBuildFile; fileRef = 31C8277323398B0B002ECB0A /* stp_icon_bank.png */; }; + 31C8277723398B0B002ECB0A /* stp_icon_bank.png in Resources */ = {isa = PBXBuildFile; fileRef = 31C8277323398B0B002ECB0A /* stp_icon_bank.png */; }; + 31C8277823398B0B002ECB0A /* stp_icon_bank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 31C8277423398B0B002ECB0A /* stp_icon_bank@2x.png */; }; + 31C8277923398B0B002ECB0A /* stp_icon_bank@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 31C8277423398B0B002ECB0A /* stp_icon_bank@2x.png */; }; + 31C8277A23398B0B002ECB0A /* stp_icon_bank@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 31C8277523398B0B002ECB0A /* stp_icon_bank@3x.png */; }; + 31C8277B23398B0B002ECB0A /* stp_icon_bank@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 31C8277523398B0B002ECB0A /* stp_icon_bank@3x.png */; }; 31F5A50922F0EFB10033663B /* STPPaymentMethodFPXParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 31F5A50722F0EFB00033663B /* STPPaymentMethodFPXParams.h */; settings = {ATTRIBUTES = (Public, ); }; }; 31F5A50A22F0EFB10033663B /* STPPaymentMethodFPXParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 31F5A50722F0EFB00033663B /* STPPaymentMethodFPXParams.h */; settings = {ATTRIBUTES = (Public, ); }; }; 31F5A50B22F0EFB10033663B /* STPPaymentMethodFPX.h in Headers */ = {isa = PBXBuildFile; fileRef = 31F5A50822F0EFB00033663B /* STPPaymentMethodFPX.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1653,6 +1659,9 @@ 31C8269C232C1713002ECB0A /* stp_fpx_logo@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "stp_fpx_logo@3x.png"; sourceTree = ""; }; 31C8269D232C1713002ECB0A /* stp_fpx_logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = stp_fpx_logo.png; sourceTree = ""; }; 31C8269E232C1714002ECB0A /* stp_fpx_logo@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "stp_fpx_logo@2x.png"; sourceTree = ""; }; + 31C8277323398B0B002ECB0A /* stp_icon_bank.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = stp_icon_bank.png; sourceTree = ""; }; + 31C8277423398B0B002ECB0A /* stp_icon_bank@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "stp_icon_bank@2x.png"; sourceTree = ""; }; + 31C8277523398B0B002ECB0A /* stp_icon_bank@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "stp_icon_bank@3x.png"; sourceTree = ""; }; 31F5A50722F0EFB00033663B /* STPPaymentMethodFPXParams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = STPPaymentMethodFPXParams.h; path = PublicHeaders/STPPaymentMethodFPXParams.h; sourceTree = ""; }; 31F5A50822F0EFB00033663B /* STPPaymentMethodFPX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = STPPaymentMethodFPX.h; path = PublicHeaders/STPPaymentMethodFPX.h; sourceTree = ""; }; 31F5A50D22F0EFDB0033663B /* STPPaymentMethodFPX.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPPaymentMethodFPX.m; sourceTree = ""; }; @@ -2342,6 +2351,9 @@ 049A3F9C1CC8006800F57DE7 /* stp_icon_add.png */, 049A3F9D1CC8006800F57DE7 /* stp_icon_add@2x.png */, 049A3F9E1CC8006800F57DE7 /* stp_icon_add@3x.png */, + 31C8277323398B0B002ECB0A /* stp_icon_bank.png */, + 31C8277423398B0B002ECB0A /* stp_icon_bank@2x.png */, + 31C8277523398B0B002ECB0A /* stp_icon_bank@3x.png */, C1363BAC1D76337400EB82B4 /* stp_icon_checkmark.png */, C1363BAD1D76337400EB82B4 /* stp_icon_checkmark@2x.png */, C1363BAE1D76337400EB82B4 /* stp_icon_checkmark@3x.png */, @@ -4042,6 +4054,7 @@ 31C826DF232C19B9002ECB0A /* stp_bank_fpx_uob.png in Resources */, 31C826DB232C19B9002ECB0A /* stp_bank_fpx_rhb.png in Resources */, 31C826B6232C19B9002ECB0A /* stp_bank_fpx_bank_muamalat.png in Resources */, + 31C8277623398B0B002ECB0A /* stp_icon_bank.png in Resources */, C1300D221EB8D38A0080AF7B /* stp_card_unknown@3x.png in Resources */, 0438EFBE1B741C2800D506CC /* stp_card_discover@2x.png in Resources */, 315CB85522E7BD1400E612A3 /* Stripe3DS2.bundle in Resources */, @@ -4054,6 +4067,8 @@ 31C826B7232C19B9002ECB0A /* stp_bank_fpx_bank_muamalat@2x.png in Resources */, F148ABE81D5E805A0014FD92 /* Localizable.strings in Resources */, 0438EFB41B741C2800D506CC /* stp_card_cvc_amex@3x.png in Resources */, + 31C8277823398B0B002ECB0A /* stp_icon_bank@2x.png in Resources */, + 31C8277A23398B0B002ECB0A /* stp_icon_bank@3x.png in Resources */, 31C826D6232C19B9002ECB0A /* stp_bank_fpx_maybank2u@3x.png in Resources */, 31C826B5232C19B9002ECB0A /* stp_bank_fpx_bank_islam@3x.png in Resources */, 31C826AE232C19B9002ECB0A /* stp_bank_fpx_alliance_bank.png in Resources */, @@ -4146,11 +4161,13 @@ C1B630BE1D1D860100A05285 /* stp_card_cvc.png in Resources */, C1300D341EB8F16B0080AF7B /* stp_card_error@3x.png in Resources */, F1510B581D5A4CC4000731AD /* stp_card_applepay@2x.png in Resources */, + 31C8277B23398B0B002ECB0A /* stp_icon_bank@3x.png in Resources */, 31C82700232C19BA002ECB0A /* stp_bank_fpx_hong_leong_bank@3x.png in Resources */, 31C82713232C19BA002ECB0A /* stp_bank_fpx_ocbc.png in Resources */, C1B630BF1D1D860100A05285 /* stp_card_cvc@2x.png in Resources */, 31C826E3232C19BA002ECB0A /* stp_fpx_big_logo@3x.png in Resources */, 31C8270C232C19BA002ECB0A /* stp_bank_fpx_maybank2e@2x.png in Resources */, + 31C8277923398B0B002ECB0A /* stp_icon_bank@2x.png in Resources */, 8BB890362056E56700EB51AB /* stp_card_unionpay_template_zh@2x.png in Resources */, 8BB890342056E56200EB51AB /* stp_card_unionpay_template_en@3x.png in Resources */, 31C82708232C19BA002ECB0A /* stp_bank_fpx_uob@2x.png in Resources */, @@ -4238,6 +4255,7 @@ C1300D351EB8F1780080AF7B /* stp_card_unknown.png in Resources */, 31C826FB232C19BA002ECB0A /* stp_bank_fpx_hsbc.png in Resources */, C1300D301EB8F16B0080AF7B /* stp_card_error_amex@2x.png in Resources */, + 31C8277723398B0B002ECB0A /* stp_icon_bank.png in Resources */, 31C82705232C19BA002ECB0A /* stp_bank_fpx_maybank2u@2x.png in Resources */, C15993451D8829C00047950D /* stp_shipping_form.png in Resources */, B3213F9321A3903D00FB4FC7 /* stp_card_form_amex_cvc@3x.png in Resources */, diff --git a/Stripe/PublicHeaders/STPPaymentConfiguration.h b/Stripe/PublicHeaders/STPPaymentConfiguration.h index cb4a8498705..bb58ac46902 100644 --- a/Stripe/PublicHeaders/STPPaymentConfiguration.h +++ b/Stripe/PublicHeaders/STPPaymentConfiguration.h @@ -39,8 +39,9 @@ NS_ASSUME_NONNULL_BEGIN /** An enum value representing which payment options you will accept from your user - in addition to credit cards. Unless you have a very specific reason not to, you - should leave this at the default, `STPPaymentOptionTypeAll`. + in addition to credit cards. + + The default value is `STPPaymentOptionTypeDefault`, which includes only Apple Pay. */ @property (nonatomic, assign, readwrite) STPPaymentOptionType additionalPaymentOptions; diff --git a/Stripe/PublicHeaders/STPPaymentIntentParams.h b/Stripe/PublicHeaders/STPPaymentIntentParams.h index ac25a567318..3d25cab8622 100644 --- a/Stripe/PublicHeaders/STPPaymentIntentParams.h +++ b/Stripe/PublicHeaders/STPPaymentIntentParams.h @@ -12,7 +12,7 @@ NS_ASSUME_NONNULL_BEGIN -@class STPSourceParams, STPPaymentMethodParams; +@class STPSourceParams, STPPaymentMethodParams, STPPaymentResult; /** An object representing parameters used to confirm a PaymentIntent object. @@ -62,6 +62,12 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, copy, nullable) NSString *paymentMethodId; +/** + Provide an STPPaymentResult from STPPaymentContext, and this will populate + the proper field (either paymentMethodId or paymentMethodParams) for your PaymentMethod. + */ +- (void)configureWithPaymentResult:(STPPaymentResult *)paymentResult; + /** Provide a supported `STPSourceParams` object into here, and Stripe will create a Source during PaymentIntent confirmation. diff --git a/Stripe/PublicHeaders/STPPaymentMethodParams.h b/Stripe/PublicHeaders/STPPaymentMethodParams.h index 8bfafec678b..3e34d59ad2b 100644 --- a/Stripe/PublicHeaders/STPPaymentMethodParams.h +++ b/Stripe/PublicHeaders/STPPaymentMethodParams.h @@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN @see https://stripe.com/docs/api/payment_methods/create */ -@interface STPPaymentMethodParams : NSObject +@interface STPPaymentMethodParams : NSObject /** The type of payment method. The associated property will contain additional information (e.g. `type == STPPaymentMethodTypeCard` means `card` should also be populated). diff --git a/Stripe/PublicHeaders/STPPaymentOption.h b/Stripe/PublicHeaders/STPPaymentOption.h index 8a272978a3c..2540571a78d 100644 --- a/Stripe/PublicHeaders/STPPaymentOption.h +++ b/Stripe/PublicHeaders/STPPaymentOption.h @@ -28,9 +28,15 @@ typedef NS_OPTIONS(NSUInteger, STPPaymentOptionType) { STPPaymentOptionTypeApplePay = 1 << 0, /** - The user is allowed to use any available payment method to pay. + The user is allowed to pay with FPX. */ - STPPaymentOptionTypeAll = STPPaymentOptionTypeApplePay + STPPaymentOptionTypeFPX = 1 << 1, + + /** + The user is allowed to use the default payment methods to pay. + */ + STPPaymentOptionTypeAll __attribute__((deprecated("use STPPaymentOptionTypeDefault instead"))) = STPPaymentOptionTypeApplePay, + STPPaymentOptionTypeDefault = STPPaymentOptionTypeApplePay }; /** @@ -41,10 +47,14 @@ typedef NS_OPTIONS(NSUInteger, STPPaymentOptionType) { - `STPApplePay`, which represents that the user wants to pay with Apple Pay - - `STPPaymentMethod`. Only `STPPaymentMethod.type == STPPaymentMethodTypeCard` is - supported by `STPPaymentContext` and `STPPaymentOptionsViewController` - - @note card-based Sources and Cards support for this protocol for use + - `STPPaymentMethod`. Only `STPPaymentMethod.type == STPPaymentMethodTypeCard` and +`STPPaymentMethod.type == STPPaymentMethodTypeFPX` are supported by `STPPaymentContext` + and `STPPaymentOptionsViewController` + - `STPPaymentMethodParams`. This should be used with non-reusable payment method, such + as FPX and iDEAL. Instead of reaching out to Stripe to create a PaymentMethod, you can + pass an STPPaymentMethodParams directly to Stripe when confirming a PaymentIntent. + + @note card-based Sources, Cards, and FPX support this protocol for use in a custom integration. */ @protocol STPPaymentOption diff --git a/Stripe/PublicHeaders/STPPaymentResult.h b/Stripe/PublicHeaders/STPPaymentResult.h index daea5612507..4ef6ef8646f 100644 --- a/Stripe/PublicHeaders/STPPaymentResult.h +++ b/Stripe/PublicHeaders/STPPaymentResult.h @@ -10,7 +10,9 @@ NS_ASSUME_NONNULL_BEGIN +@protocol STPPaymentOption; @class STPPaymentMethod; +@class STPPaymentMethodParams; /** When you're using `STPPaymentContext` to request your user's payment details, this is the object that will be returned to your application when they've successfully made a payment. @@ -20,13 +22,26 @@ NS_ASSUME_NONNULL_BEGIN /** The payment method that the user has selected. This may come from a variety of different payment methods, such as an Apple Pay payment or a stored credit card. @see STPPaymentMethod.h + If paymentMethod is nil, paymentMethodParams will be populated instead. */ -@property (nonatomic, readonly) STPPaymentMethod *paymentMethod; +@property (nonatomic, nullable, readonly) STPPaymentMethod *paymentMethod; /** - Initializes the payment result with a given source. This is invoked by `STPPaymentContext` internally; you shouldn't have to call it directly. + The parameters for a payment method that the user has selected. This is + populated for non-reusable payment methods, such as FPX and iDEAL. @see STPPaymentMethodParams.h + If paymentMethodParams is nil, paymentMethod will be populated instead. */ -- (nonnull instancetype)initWithPaymentMethod:(STPPaymentMethod *)paymentMethod; +@property (nonatomic, nullable, readonly) STPPaymentMethodParams *paymentMethodParams; + +/** + The STPPaymentOption that was used to initialize this STPPaymentResult, either an STPPaymentMethod or an STPPaymentMethodParams. + */ +@property (nonatomic, nonnull, readonly) id paymentOption; + +/** + Initializes the payment result with a given payment option. This is invoked by `STPPaymentContext` internally; you shouldn't have to call it directly. + */ +- (instancetype)initWithPaymentOption:(id)paymentOption; @end diff --git a/Stripe/Resources/Images/stp_icon_bank.png b/Stripe/Resources/Images/stp_icon_bank.png new file mode 100644 index 0000000000000000000000000000000000000000..66b102e04fbc23026e87c138b94734fa482ede31 GIT binary patch literal 475 zcmV<10VMv3P)Px$l}SWFR5%gslrc-gKorOGE~UnTg9t7~(9xkd=-}X>tM~!j6-%q8ji7^rgM))2 zsYz`>boT>@gOh`!Zi3)qQK-1IothMzJKw`zTdam!J9==r_x|s_-#w1Yff6t*>#~pY zr!^sX0)RP;jb(LxzWP%Md>0TxKqix0R1~p?_NdGOfV`r>b~>FrM_Wi;u5B>Wv}dv4 z03lN{>FbK1Tij^VFxIbIV-1*T9*-knACneZ6M}8HK=77ftUgd4IF2*ydHyci%Lr40 zi`%0J^HDw3G<7HBy0t5GCfd_FQ|b-Gku%M(q}*9qa29r00wjZFos4kK3Eb6SjB#CG zd0|KtsZ{(q5@Drqn0queO{o}0A|H(o6{JQh`J_`^>Z&?WqPx%=}AOER9Fe^Sj}q_K@{JaNm@DJ?o`-)QcXf2c-x~3yIn^X?J$~ZRjLhvb&moC6p{I^FDs_ z-fv&t?#zTJyKu$h>2sWKJTXEs=Drt+1V8LFEZIqbc>Gxp=cR{2h--*LH)H99kmroG zKZr!GFKsvewur^!FOG7a8v&tVBtC4Dd<_Jnj2)YbM6Uj_!PuJXB9_Ty+=aq=PzW-n zD5A?Y>J|rCCPYklJZ+T`Z!S26u_cyFCVNXIF$F?HP64*>BlC>8MpZSqXceP| z82YHIP}o2ZDd;8PwwknCHoP20&!s&c_XPTH*&Me*Y;JDuP(Ht^>bf$DDtXPIy_ZRz zQf1QX?TGpP{-TkuOe_{lT+wy$7>#{$?@8EaAVm2_DY>tzp_gQOdNv4X${yYj&kc&- z`B6hsJ0vwY8OuoC0n-o{Ub-BJ(ObyA)({5PNwJi%+vVm@BodvRlRjP7UDnwBu~JGk z#>A&kD73Oa84psH$_G|HR5qLKSXudPxll94*0BlvD6y$js=HX!k4xP=x?K9ta5(&3 z+OV1I=bX1m-8^=79$EDHe5+<#O;6==e=Z22zcGWPtZCvaahb;F)}lg?8A0s9NU)@##7;h;)Q(P>Ire~E8*<NVABrIV0E=<#$Mjd2a8?2VkYdfa8TeAw;fU=4&bZ5$Ls-RJ2qQoKw(3Xo tlL0xoc}6lo!LVj~-DrXar>6Z}`~~lq`3hebSBU@s002ovPDHLkV1nB%e2)MC literal 0 HcmV?d00001 diff --git a/Stripe/Resources/Images/stp_icon_bank@3x.png b/Stripe/Resources/Images/stp_icon_bank@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..bf056f8112233a3cd64888de67060dd158227ca2 GIT binary patch literal 1083 zcmV-B1jPG^P)Px&^+`lQRA>e5T2Dw6Q5=5n&9I7=P@srPC|#_GItW3Oc!)&t)WJN|sYB`N>Mn|4 zQ3z#};;y^ynnXfd2lLRS@DLr!A_%%flwB$b1xYkA6_ z{=D~nzi;Nv+viY{q{HnVP7}o+^^zpkQp)-mE9$k`DwmQmJWcc-FXa0#ZAMz6A9+eGt~8psIkpfwVMs-D*G(@BuMk)NOQpt`zRi1h2T#7xrV>a7B~ySTj$=^HBcDBQ-Qn50`B4(qd6diigX z-QAuN3@Xo6l?Dmn z+wAt*F+EwEg7x`)`xY0)tLPi66_Tc>(`3ZpSzSV{d(+d|t-89p-&$ivC5cJJGCn?K zB}BZ24)$wxvdz93LfFlMf^07)r+^w3Rgx~3_q-^Iw~UDvp#f$^2MeUQRofYFH+v;z0*xerxZ^?(W_r97m3dB270sUB;wEK_H(T zj{46==VIbFE0Bs6Setm%$YG146jN}9nonVy#e(>q!bao;KWuxHc9r(r#sziAkNlIk z&I+}oo$-TWp{c2UB$}U(=8)}J7CppIu0pMtRo_RPk1OK@LA(!JvC^*6{t8>fUJ>$r zupd*24>LiCF&K)%j2jyAiYMg{lVqw$Y$|CgX`l+2x2u7~(u8U%saYNaIZY)EB$g&r zQ%TM87|3ZVX&|vQp_)o+md8L&Q%M7fr3rOcD7m&J>2^QP5`@2suRMZpuwz%CStdBK zTQ*7vR~Z=>e2M&nk@j#4{v-Si-{qVMx5_PH7~72zyLietSI-~a+#m}Ojhh9 za0$Rt!Jy)%+wch(IB8wV{C36cYLZOaJ(b{r{sC(TFHM;|`5OQL002ovPDHLkV1nn( B1Tp{s literal 0 HcmV?d00001 diff --git a/Stripe/STPAnalyticsClient.m b/Stripe/STPAnalyticsClient.m index 876c69f8f1d..8ee40d83f71 100644 --- a/Stripe/STPAnalyticsClient.m +++ b/Stripe/STPAnalyticsClient.m @@ -91,6 +91,13 @@ + (void)initializeIfNeeded { [client setApiUsage:[client.apiUsage setByAddingObject:NSStringFromClass([STPPaymentOptionsViewController class])]]; } error:nil]; + [STPBankSelectionViewController stp_aspect_hookSelector:@selector(initWithBankMethod:configuration:theme:) + withOptions:STPAspectPositionAfter + usingBlock:^{ + STPAnalyticsClient *client = [self sharedClient]; + [client setApiUsage:[client.apiUsage setByAddingObject:NSStringFromClass([STPBankSelectionViewController class])]]; + } error:nil]; + [STPShippingAddressViewController stp_aspect_hookSelector:@selector(initWithConfiguration:theme:currency:shippingAddress:selectedShippingMethod:prefilledInformation:) withOptions:STPAspectPositionAfter usingBlock:^{ @@ -404,11 +411,22 @@ + (NSMutableDictionary *)commonPayload { + (NSDictionary *)serializeConfiguration:(STPPaymentConfiguration *)configuration { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; dictionary[@"publishable_key"] = configuration.publishableKey ?: @"unknown"; - switch (configuration.additionalPaymentOptions) { - case STPPaymentOptionTypeAll: - dictionary[@"additional_payment_methods"] = @"all"; - case STPPaymentOptionTypeNone: - dictionary[@"additional_payment_methods"] = @"none"; + + if (configuration.additionalPaymentOptions == STPPaymentOptionTypeDefault) { + dictionary[@"additional_payment_methods"] = @"default"; + } + else if (configuration.additionalPaymentOptions == STPPaymentOptionTypeNone) { + dictionary[@"additional_payment_methods"] = @"none"; + } + else { + NSMutableArray *methods = [[NSMutableArray alloc] init]; + if (configuration.additionalPaymentOptions & STPPaymentOptionTypeApplePay) { + [methods addObject:@"applepay"]; + } + if (configuration.additionalPaymentOptions & STPPaymentOptionTypeFPX) { + [methods addObject:@"fpx"]; + } + dictionary[@"additional_payment_methods"] = [methods componentsJoinedByString:@","]; } switch (configuration.requiredBillingAddressFields) { case STPBillingAddressFieldsNone: diff --git a/Stripe/STPImageLibrary+Private.h b/Stripe/STPImageLibrary+Private.h index 516e6c44fca..655a5a297de 100644 --- a/Stripe/STPImageLibrary+Private.h +++ b/Stripe/STPImageLibrary+Private.h @@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN @interface STPImageLibrary (Private) + (UIImage *)addIcon; ++ (UIImage *)bankIcon; + (UIImage *)checkmarkIcon; + (UIImage *)largeCardFrontImage; + (UIImage *)largeCardBackImage; diff --git a/Stripe/STPImageLibrary.m b/Stripe/STPImageLibrary.m index 9917e25367a..1b2423adc66 100644 --- a/Stripe/STPImageLibrary.m +++ b/Stripe/STPImageLibrary.m @@ -96,6 +96,10 @@ + (UIImage *)addIcon { return [self safeImageNamed:@"stp_icon_add" templateIfAvailable:YES]; } ++ (UIImage *)bankIcon { + return [self safeImageNamed:@"stp_icon_bank" templateIfAvailable:YES]; +} + + (UIImage *)checkmarkIcon { return [self safeImageNamed:@"stp_icon_checkmark" templateIfAvailable:YES]; } diff --git a/Stripe/STPPaymentConfiguration.m b/Stripe/STPPaymentConfiguration.m index 54e70ebd91b..93c01658fe4 100644 --- a/Stripe/STPPaymentConfiguration.m +++ b/Stripe/STPPaymentConfiguration.m @@ -39,7 +39,7 @@ + (instancetype)sharedConfiguration { - (instancetype)init { self = [super init]; if (self) { - _additionalPaymentOptions = STPPaymentOptionTypeAll; + _additionalPaymentOptions = STPPaymentOptionTypeDefault; _requiredBillingAddressFields = STPBillingAddressFieldsNone; _requiredShippingAddressFields = nil; _verifyPrefilledShippingAddress = YES; @@ -73,8 +73,8 @@ - (BOOL)applePayEnabled { - (NSString *)description { NSString *additionalPaymentOptionsDescription; - if (self.additionalPaymentOptions == STPPaymentOptionTypeAll) { - additionalPaymentOptionsDescription = @"STPPaymentOptionTypeAll"; + if (self.additionalPaymentOptions == STPPaymentOptionTypeDefault) { + additionalPaymentOptionsDescription = @"STPPaymentOptionTypeDefault"; } else if (self.additionalPaymentOptions == STPPaymentOptionTypeNone) { additionalPaymentOptionsDescription = @"STPPaymentOptionTypeNone"; } else { @@ -84,6 +84,10 @@ - (NSString *)description { [paymentOptions addObject:@"STPPaymentOptionTypeApplePay"]; } + if (self.additionalPaymentOptions & STPPaymentOptionTypeFPX) { + [paymentOptions addObject:@"STPPaymentOptionTypeFPX"]; + } + additionalPaymentOptionsDescription = [paymentOptions componentsJoinedByString:@"|"]; } diff --git a/Stripe/STPPaymentContext.m b/Stripe/STPPaymentContext.m index d9853857bda..e8f6c68a43b 100644 --- a/Stripe/STPPaymentContext.m +++ b/Stripe/STPPaymentContext.m @@ -233,7 +233,9 @@ - (void)setPaymentOptions:(NSArray> *)paymentOptions { - (void)setSelectedPaymentOption:(id)selectedPaymentOption { if (selectedPaymentOption && ![self.paymentOptions containsObject:selectedPaymentOption]) { - self.paymentOptions = [self.paymentOptions arrayByAddingObject:selectedPaymentOption]; + if (selectedPaymentOption.reusable) { + self.paymentOptions = [self.paymentOptions arrayByAddingObject:selectedPaymentOption]; + } } if (![_selectedPaymentOption isEqual:selectedPaymentOption]) { _selectedPaymentOption = selectedPaymentOption; @@ -588,9 +590,9 @@ - (void)requestPayment { [strongSelf presentPaymentOptionsViewControllerWithNewState:STPPaymentContextStateRequestingPayment]; } else if ([strongSelf requestPaymentShouldPresentShippingViewController]) { [strongSelf presentShippingViewControllerWithNewState:STPPaymentContextStateRequestingPayment]; - } else if ([strongSelf.selectedPaymentOption isKindOfClass:[STPPaymentMethod class]]) { + } else if ([strongSelf.selectedPaymentOption isKindOfClass:[STPPaymentMethod class]] || [self.selectedPaymentOption isKindOfClass:[STPPaymentMethodParams class]]) { strongSelf.state = STPPaymentContextStateRequestingPayment; - STPPaymentResult *result = [[STPPaymentResult alloc] initWithPaymentMethod:(STPPaymentMethod *)strongSelf.selectedPaymentOption]; + STPPaymentResult *result = [[STPPaymentResult alloc] initWithPaymentOption:strongSelf.selectedPaymentOption]; [strongSelf.delegate paymentContext:self didCreatePaymentResult:result completion:^(STPPaymentStatus status, NSError * _Nullable error) { stpDispatchToMainThreadIfNecessary(^{ [strongSelf didFinishWithStatus:status error:error]; @@ -632,8 +634,8 @@ - (void)requestPayment { if (attachPaymentMethodError) { completion(STPPaymentStatusError, attachPaymentMethodError); } else { - STPPaymentResult *result = [[STPPaymentResult alloc] initWithPaymentMethod:paymentMethod]; - [strongSelf.delegate paymentContext:self didCreatePaymentResult:result completion:^(STPPaymentStatus status, NSError * error) { + STPPaymentResult *result = [[STPPaymentResult alloc] initWithPaymentOption:paymentMethod]; + [strongSelf.delegate paymentContext:strongSelf didCreatePaymentResult:result completion:^(STPPaymentStatus status, NSError * error) { // for Apple Pay, the didFinishWithStatus callback is fired later when Apple Pay VC finishes completion(status, error); }]; diff --git a/Stripe/STPPaymentIntentParams.m b/Stripe/STPPaymentIntentParams.m index 396765190fd..10db6eec6a4 100644 --- a/Stripe/STPPaymentIntentParams.m +++ b/Stripe/STPPaymentIntentParams.m @@ -8,6 +8,9 @@ #import "STPPaymentIntentParams.h" #import "STPPaymentIntent+Private.h" +#import "STPPaymentMethod.h" +#import "STPPaymentMethodParams.h" +#import "STPPaymentResult.h" @implementation STPPaymentIntentParams @@ -78,6 +81,14 @@ - (nullable NSString *)setupFutureUsageRawString { } } +- (void)configureWithPaymentResult:(STPPaymentResult *)paymentResult { + if (paymentResult.paymentMethod) { + _paymentMethodId = [paymentResult.paymentMethod.stripeId copy]; + } else if (paymentResult.paymentMethodParams) { + _paymentMethodParams = paymentResult.paymentMethodParams; + } +} + #pragma mark - Deprecated Properties - (NSString *)returnUrl { diff --git a/Stripe/STPPaymentMethodParams.m b/Stripe/STPPaymentMethodParams.m index 56e2707fb44..5a7283f7827 100644 --- a/Stripe/STPPaymentMethodParams.m +++ b/Stripe/STPPaymentMethodParams.m @@ -108,4 +108,53 @@ + (nonnull NSDictionary *)propertyNamesToFormFieldNamesMapping { }; } +#pragma mark - STPPaymentOption + +- (UIImage *)image { + if (self.type == STPPaymentMethodTypeCard && self.card != nil) { + STPCardBrand brand = [STPCardValidator brandForNumber:self.card.number]; + return [STPImageLibrary brandImageForCardBrand:brand]; + } else { + return [STPImageLibrary brandImageForCardBrand:STPCardBrandUnknown]; + }} + +- (UIImage *)templateImage { + if (self.type == STPPaymentMethodTypeCard && self.card != nil) { + STPCardBrand brand = [STPCardValidator brandForNumber:self.card.number]; + return [STPImageLibrary templatedBrandImageForCardBrand:brand]; + } else if (self.type == STPPaymentMethodTypeFPX) { + return [STPImageLibrary bankIcon]; + } else { + return [STPImageLibrary templatedBrandImageForCardBrand:STPCardBrandUnknown]; + } +} + +- (NSString *)label { + switch (self.type) { + case STPPaymentMethodTypeCard: + if (self.card != nil) { + STPCardBrand brand = [STPCardValidator brandForNumber:self.card.number]; + NSString *brandString = STPStringFromCardBrand(brand); + return [NSString stringWithFormat:@"%@ %@", brandString, self.card.last4]; + } else { + return STPStringFromCardBrand(STPCardBrandUnknown); + } + case STPPaymentMethodTypeiDEAL: + return @"iDEAL"; + case STPPaymentMethodTypeFPX: + if (self.fpx != nil) { + return STPStringFromFPXBankBrand(self.fpx.bank); + } else { + return @"FPX"; + } + case STPPaymentMethodTypeCardPresent: + case STPPaymentMethodTypeUnknown: + return STPLocalizedString(@"Unknown", @"Default missing source type label"); + } +} + +- (BOOL)isReusable { + return (self.type == STPPaymentMethodTypeCard); +} + @end diff --git a/Stripe/STPPaymentOptionTableViewCell.h b/Stripe/STPPaymentOptionTableViewCell.h index 50dce83aeca..99d8413c0a4 100644 --- a/Stripe/STPPaymentOptionTableViewCell.h +++ b/Stripe/STPPaymentOptionTableViewCell.h @@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)configureForNewCardRowWithTheme:(STPTheme *)theme; - (void)configureWithPaymentOption:(id)paymentOption theme:(STPTheme *)theme selected:(BOOL)selected; +- (void)configureForFPXRowWithTheme:(STPTheme *)theme; @end diff --git a/Stripe/STPPaymentOptionTableViewCell.m b/Stripe/STPPaymentOptionTableViewCell.m index fcb2527e00f..d2f4b38f5d1 100644 --- a/Stripe/STPPaymentOptionTableViewCell.m +++ b/Stripe/STPPaymentOptionTableViewCell.m @@ -10,10 +10,15 @@ #import "STPApplePayPaymentOption.h" #import "STPCard.h" +#import "STPCardValidator+Private.h" #import "STPImageLibrary+Private.h" #import "STPLocalizationUtils.h" #import "STPPaymentMethod.h" #import "STPPaymentMethodCard.h" +#import "STPPaymentMethodCardParams.h" +#import "STPPaymentMethodFPX.h" +#import "STPPaymentMethodFPXParams.h" +#import "STPPaymentMethodParams.h" #import "STPPaymentOption.h" #import "STPSource.h" #import "STPTheme.h" @@ -117,6 +122,27 @@ - (void)configureWithPaymentOption:(id)paymentOption theme:(ST [self setNeedsLayout]; } +- (void)configureForFPXRowWithTheme:(STPTheme *)theme { + self.paymentOption = nil; + self.theme = theme; + + self.backgroundColor = theme.secondaryBackgroundColor; + + // Left icon + self.leftIcon.image = [STPImageLibrary bankIcon]; + self.leftIcon.tintColor = [self primaryColorForPaymentOptionWithSelected:NO]; + + // Title label + self.titleLabel.font = theme.font; + self.titleLabel.textColor = self.theme.primaryForegroundColor; + self.titleLabel.text = STPLocalizedString(@"Online Banking (FPX)", @"Button to pay with a Bank Account (using FPX)."); + + // Checkmark icon + self.checkmarkIcon.hidden = YES; + self.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + [self setNeedsLayout]; +} + - (UIColor *)primaryColorForPaymentOptionWithSelected:(BOOL)selected { UIColor *fadedColor = nil; #ifdef __IPHONE_13_0 @@ -149,10 +175,24 @@ - (NSAttributedString *)buildAttributedStringWithPaymentOption:(id> *)paymentOptions selectedPaymentOption:(nullable id)selectedPaymentOption - addApplePayOption:(BOOL)applePayEnabled; + addApplePayOption:(BOOL)applePayEnabled + additionalOptions:(STPPaymentOptionType)additionalPaymentOptions; /** Returns a tuple for the given array of STPPaymentMethod, filtered to only include the diff --git a/Stripe/STPPaymentOptionTuple.m b/Stripe/STPPaymentOptionTuple.m index 991bdbd5c7f..0380dd20f7c 100644 --- a/Stripe/STPPaymentOptionTuple.m +++ b/Stripe/STPPaymentOptionTuple.m @@ -32,7 +32,8 @@ + (instancetype)tupleWithPaymentOptions:(NSArray> *)payment + (instancetype)tupleWithPaymentOptions:(NSArray> *)paymentOptions selectedPaymentOption:(nullable id)selectedPaymentOption - addApplePayOption:(BOOL)applePayEnabled { + addApplePayOption:(BOOL)applePayEnabled + additionalOptions:(STPPaymentOptionType)additionalPaymentOptions { NSMutableArray *mutablePaymentOptions = paymentOptions.mutableCopy; id _Nullable selected = selectedPaymentOption; @@ -45,6 +46,12 @@ + (instancetype)tupleWithPaymentOptions:(NSArray> *)payment } } + if (additionalPaymentOptions & STPPaymentOptionTypeFPX) { + STPPaymentMethodFPXParams *fpx = [[STPPaymentMethodFPXParams alloc] init]; + STPPaymentMethodParams *fpxPaymentOption = [STPPaymentMethodParams paramsWithFPX:fpx billingDetails:nil metadata:nil]; + [mutablePaymentOptions addObject:fpxPaymentOption]; + } + return [self tupleWithPaymentOptions:mutablePaymentOptions.copy selectedPaymentOption:selected]; } @@ -65,7 +72,8 @@ + (instancetype)tupleFilteredForUIWithPaymentMethods:(NSArray)paymentOption; - (void)internalViewControllerDidDeletePaymentOption:(id)paymentOption; -- (void)internalViewControllerDidCreatePaymentMethod:(STPPaymentMethod *)paymentMethod completion:(STPErrorBlock)completion; +- (void)internalViewControllerDidCreatePaymentOption:(id)paymentOption completion:(STPErrorBlock)completion; - (void)internalViewControllerDidCancel; @end diff --git a/Stripe/STPPaymentOptionsInternalViewController.m b/Stripe/STPPaymentOptionsInternalViewController.m index 431cf49523d..912e6aa6e8d 100644 --- a/Stripe/STPPaymentOptionsInternalViewController.m +++ b/Stripe/STPPaymentOptionsInternalViewController.m @@ -11,6 +11,7 @@ #import "NSArray+Stripe.h" #import "STPAddCardViewController.h" #import "STPAddCardViewController+Private.h" +#import "STPBankSelectionViewController.h" #import "STPCoreTableViewController.h" #import "STPCoreTableViewController+Private.h" #import "STPCustomerContext.h" @@ -18,6 +19,7 @@ #import "STPImageLibrary+Private.h" #import "STPLocalizationUtils.h" #import "STPPaymentMethod.h" +#import "STPPaymentMethodParams.h" #import "STPPaymentOptionTableViewCell.h" #import "STPPaymentOptionTuple.h" #import "STPPromise.h" @@ -29,8 +31,9 @@ static NSInteger const PaymentOptionSectionCardList = 0; static NSInteger const PaymentOptionSectionAddCard = 1; +static NSInteger const PaymentOptionSectionAPM = 2; -@interface STPPaymentOptionsInternalViewController () +@interface STPPaymentOptionsInternalViewController () @property (nonatomic, strong, readwrite) STPPaymentConfiguration *configuration; @property (nonatomic, strong, nullable, readwrite) id apiAdapter; @@ -126,7 +129,7 @@ - (void)reloadRightBarButtonItemWithTableViewIsEditing:(BOOL)tableViewIsEditing } - (BOOL)isAnyPaymentOptionDetachable { - for (id paymentOption in self.paymentOptions) { + for (id paymentOption in self.cardPaymentOptions) { if ([self isPaymentOptionDetachable:paymentOption]) { return YES; } @@ -166,6 +169,31 @@ - (BOOL)isPaymentOptionDetachable:(id)paymentOption { return YES; } +- (NSArray> *)cardPaymentOptions { + return [self.paymentOptions filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * __unused _Nullable bindings) { + if ([evaluatedObject isKindOfClass:[STPPaymentMethodParams class]]) { + STPPaymentMethodParams *paymentMethodParams = (STPPaymentMethodParams *)evaluatedObject; + if (paymentMethodParams.type != STPPaymentMethodTypeCard) { + return NO; + } + } + return YES; + }]]; +} + +- (NSArray> *)apmPaymentOptions { + return [self.paymentOptions filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * __unused _Nullable bindings) { + if ([evaluatedObject isKindOfClass:[STPPaymentMethodParams class]]) { + STPPaymentMethodParams *paymentMethodParams = (STPPaymentMethodParams *)evaluatedObject; + if (paymentMethodParams.type == STPPaymentMethodTypeFPX) { // Add other APMs as we gain support for them in Basic Integration + return YES; + } + } + return NO; + }]]; +} + + - (void)updateWithPaymentOptionTuple:(STPPaymentOptionTuple *)tuple { if ([self.paymentOptions isEqualToArray:tuple.paymentOptions] && [self.selectedPaymentOption isEqual:tuple.selectedPaymentOption]) { @@ -213,17 +241,25 @@ - (void)_endTableViewEditing { #pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView:(__unused UITableView *)tableView { - return 2; + if (self.apmPaymentOptions.count > 0) { + return 3; + } else { + return 2; + } } - (NSInteger)tableView:(__unused UITableView *)tableView numberOfRowsInSection:(NSInteger)section { if (section == PaymentOptionSectionCardList) { - return self.paymentOptions.count; + return self.cardPaymentOptions.count; } if (section == PaymentOptionSectionAddCard) { return 1; } + + if (section == PaymentOptionSectionAPM) { + return self.apmPaymentOptions.count; + } return 0; } @@ -232,13 +268,22 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N STPPaymentOptionTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:PaymentOptionCellReuseIdentifier forIndexPath:indexPath]; if (indexPath.section == PaymentOptionSectionCardList) { - id paymentOption = [self.paymentOptions stp_boundSafeObjectAtIndex:indexPath.row]; + id paymentOption = [self.cardPaymentOptions stp_boundSafeObjectAtIndex:indexPath.row]; BOOL selected = [paymentOption isEqual:self.selectedPaymentOption]; [cell configureWithPaymentOption:paymentOption theme:self.theme selected:selected]; - } else { + } else if (indexPath.section == PaymentOptionSectionAddCard) { [cell configureForNewCardRowWithTheme:self.theme]; cell.accessibilityIdentifier = @"PaymentOptionsTableViewAddNewCardButtonIdentifier"; + } else if (indexPath.section == PaymentOptionSectionAPM) { + id paymentOption = [self.apmPaymentOptions stp_boundSafeObjectAtIndex:indexPath.row]; + if ([paymentOption isKindOfClass:[STPPaymentMethodParams class]]) { + STPPaymentMethodParams *paymentMethodParams = (STPPaymentMethodParams *)paymentOption; + if (paymentMethodParams.type == STPPaymentMethodTypeFPX) { + [cell configureForFPXRowWithTheme:self.theme]; + cell.accessibilityIdentifier = @"PaymentOptionsTableViewFPXButtonIdentifier"; + } + } } return cell; @@ -246,7 +291,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N - (BOOL)tableView:(__unused UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == PaymentOptionSectionCardList) { - id paymentOption = [self.paymentOptions stp_boundSafeObjectAtIndex:indexPath.row]; + id paymentOption = [self.cardPaymentOptions stp_boundSafeObjectAtIndex:indexPath.row]; if ([self isPaymentOptionDetachable:paymentOption]) { return YES; @@ -264,13 +309,13 @@ - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEd return; } - if (!(indexPath.row < (NSInteger)self.paymentOptions.count)) { + if (!(indexPath.row < (NSInteger)self.cardPaymentOptions.count)) { // Data source and table view out of sync for some reason [tableView reloadData]; return; } - id paymentOptionToDelete = [self.paymentOptions stp_boundSafeObjectAtIndex:indexPath.row]; + id paymentOptionToDelete = [self.cardPaymentOptions stp_boundSafeObjectAtIndex:indexPath.row]; if (![self isPaymentOptionDetachable:paymentOptionToDelete]) { // Showed the user a delete option for a payment method when we shouldn't have @@ -285,7 +330,7 @@ - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEd // Optimistically remove payment method from data source NSMutableArray *paymentOptions = [self.paymentOptions mutableCopy]; - [paymentOptions removeObjectAtIndex:indexPath.row]; + [paymentOptions removeObject:paymentOptionToDelete]; self.paymentOptions = paymentOptions; // Perform deletion animation for single row @@ -317,7 +362,7 @@ - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEd - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == PaymentOptionSectionCardList) { // Update data source - id paymentOption = [self.paymentOptions stp_boundSafeObjectAtIndex:indexPath.row]; + id paymentOption = [self.cardPaymentOptions stp_boundSafeObjectAtIndex:indexPath.row]; self.selectedPaymentOption = paymentOption; // Perform selection animation @@ -333,6 +378,17 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath paymentCardViewController.customFooterView = self.addCardViewControllerCustomFooterView; [self.navigationController pushViewController:paymentCardViewController animated:YES]; + } else if (indexPath.section == PaymentOptionSectionAPM) { + id paymentOption = [self.apmPaymentOptions stp_boundSafeObjectAtIndex:indexPath.row]; + if ([paymentOption isKindOfClass:[STPPaymentMethodParams class]]) { + STPPaymentMethodParams *paymentMethodParams = (STPPaymentMethodParams *)paymentOption; + if (paymentMethodParams.type == STPPaymentMethodTypeFPX) { + STPBankSelectionViewController *bankSelectionViewController = [[STPBankSelectionViewController alloc] initWithBankMethod:STPBankSelectionMethodFPX configuration:self.configuration theme:self.theme]; + bankSelectionViewController.delegate = self; + + [self.navigationController pushViewController:bankSelectionViewController animated:YES]; + } + } } [tableView deselectRowAtIndexPath:indexPath animated:YES]; @@ -384,7 +440,11 @@ - (void)addCardViewControllerDidCancel:(__unused STPAddCardViewController *)addC } - (void)addCardViewController:(__unused STPAddCardViewController *)addCardViewController didCreatePaymentMethod:(nonnull STPPaymentMethod *)paymentMethod completion:(nonnull STPErrorBlock)completion { - [self.delegate internalViewControllerDidCreatePaymentMethod:paymentMethod completion:completion]; + [self.delegate internalViewControllerDidCreatePaymentOption:paymentMethod completion:completion]; +} + +- (void)bankSelectionViewController:(__unused STPBankSelectionViewController *)bankViewController didCreatePaymentMethodParams:(STPPaymentMethodParams *)paymentMethodParams { + [self.delegate internalViewControllerDidCreatePaymentOption:(id)paymentMethodParams completion:^(NSError * _Nullable __unused error) {}]; } @end diff --git a/Stripe/STPPaymentOptionsViewController.m b/Stripe/STPPaymentOptionsViewController.m index 3043c16036f..31bf8ab956c 100644 --- a/Stripe/STPPaymentOptionsViewController.m +++ b/Stripe/STPPaymentOptionsViewController.m @@ -183,8 +183,14 @@ - (void)internalViewControllerDidDeletePaymentOption:(id)payme [paymentContext removePaymentOption:paymentOption]; } } - -- (void)internalViewControllerDidCreatePaymentMethod:(STPPaymentMethod *)paymentMethod completion:(STPErrorBlock)completion { + +- (void)internalViewControllerDidCreatePaymentOption:(id)paymentOption completion:(STPErrorBlock)completion { + if (!paymentOption.reusable) { + // Don't save a non-reusable payment option + [self finishWithPaymentOption:paymentOption]; + return; + } + STPPaymentMethod *paymentMethod = (STPPaymentMethod *)paymentOption; [self.apiAdapter attachPaymentMethodToCustomer:paymentMethod completion:^(NSError *error) { stpDispatchToMainThreadIfNecessary(^{ completion(error); @@ -225,7 +231,7 @@ - (void)addCardViewControllerDidCancel:(__unused STPAddCardViewController *)addC - (void)addCardViewController:(__unused STPAddCardViewController *)addCardViewController didCreatePaymentMethod:(STPPaymentMethod *)paymentMethod completion:(STPErrorBlock)completion { - [self internalViewControllerDidCreatePaymentMethod:paymentMethod completion:completion]; + [self internalViewControllerDidCreatePaymentOption:paymentMethod completion:completion]; } - (void)dismissWithCompletion:(STPVoidBlock)completion { diff --git a/Stripe/STPPaymentResult.m b/Stripe/STPPaymentResult.m index e301062a21a..8cca1cfbf31 100644 --- a/Stripe/STPPaymentResult.m +++ b/Stripe/STPPaymentResult.m @@ -9,19 +9,35 @@ #import "STPPaymentResult.h" #import "STPPaymentMethod.h" +#import "STPPaymentMethodParams.h" @interface STPPaymentResult() @property (nonatomic) STPPaymentMethod *paymentMethod; +@property (nonatomic) STPPaymentMethodParams *paymentMethodParams; @end @implementation STPPaymentResult -- (nonnull instancetype)initWithPaymentMethod:(STPPaymentMethod *)paymentMethod { +- (instancetype)initWithPaymentOption:(id)paymentOption { self = [super init]; if (self) { - _paymentMethod = paymentMethod; + if ([paymentOption isKindOfClass:[STPPaymentMethod class]]) { + _paymentMethod = (STPPaymentMethod *)paymentOption; + } else if ([paymentOption isKindOfClass:[STPPaymentMethodParams class]]) { + _paymentMethodParams = (STPPaymentMethodParams *)paymentOption; + } else { + return nil; + } } return self; } +- (id)paymentOption { + if (_paymentMethod != nil) { + return _paymentMethod; + } else { + return _paymentMethodParams; + } +} + @end diff --git a/Tests/Tests/STPAddCardViewControllerLocalizationTests.m b/Tests/Tests/STPAddCardViewControllerLocalizationTests.m index 111c2c14643..34622ab6b96 100644 --- a/Tests/Tests/STPAddCardViewControllerLocalizationTests.m +++ b/Tests/Tests/STPAddCardViewControllerLocalizationTests.m @@ -44,7 +44,7 @@ - (void)performSnapshotTestForLanguage:(NSString *)language delivery:(BOOL)deliv STPPaymentConfiguration *config = [STPFixtures paymentConfiguration]; config.companyName = @"Test Company"; config.requiredBillingAddressFields = STPBillingAddressFieldsFull; - config.additionalPaymentOptions = STPPaymentOptionTypeAll; + config.additionalPaymentOptions = STPPaymentOptionTypeDefault; config.shippingType = (delivery) ? STPShippingTypeDelivery : STPShippingTypeShipping; [STPLocalizationUtils overrideLanguageTo:language]; diff --git a/Tests/Tests/STPPaymentConfigurationTest.m b/Tests/Tests/STPPaymentConfigurationTest.m index 0ffb6e24059..f5276573161 100644 --- a/Tests/Tests/STPPaymentConfigurationTest.m +++ b/Tests/Tests/STPPaymentConfigurationTest.m @@ -32,7 +32,7 @@ - (void)testInit { STPPaymentConfiguration *paymentConfiguration = [[STPPaymentConfiguration alloc] init]; XCTAssertNil(paymentConfiguration.publishableKey); - XCTAssertEqual(paymentConfiguration.additionalPaymentOptions, STPPaymentOptionTypeAll); + XCTAssertEqual(paymentConfiguration.additionalPaymentOptions, STPPaymentOptionTypeDefault); XCTAssertEqual(paymentConfiguration.requiredBillingAddressFields, STPBillingAddressFieldsNone); XCTAssertNil(paymentConfiguration.requiredShippingAddressFields); XCTAssert(paymentConfiguration.verifyPrefilledShippingAddress); @@ -48,7 +48,7 @@ - (void)testApplePayEnabledSatisfied { STPPaymentConfiguration *paymentConfiguration = [[STPPaymentConfiguration alloc] init]; paymentConfiguration.appleMerchantIdentifier = @"appleMerchantIdentifier"; - paymentConfiguration.additionalPaymentOptions = STPPaymentOptionTypeAll; + paymentConfiguration.additionalPaymentOptions = STPPaymentOptionTypeDefault; XCTAssert([paymentConfiguration applePayEnabled]); } @@ -59,7 +59,7 @@ - (void)testApplePayEnabledMissingAppleMerchantIdentifier { STPPaymentConfiguration *paymentConfiguration = [[STPPaymentConfiguration alloc] init]; paymentConfiguration.appleMerchantIdentifier = nil; - paymentConfiguration.additionalPaymentOptions = STPPaymentOptionTypeAll; + paymentConfiguration.additionalPaymentOptions = STPPaymentOptionTypeDefault; XCTAssertFalse([paymentConfiguration applePayEnabled]); } @@ -81,7 +81,7 @@ - (void)testApplePayEnabledMisisngDeviceSupport { STPPaymentConfiguration *paymentConfiguration = [[STPPaymentConfiguration alloc] init]; paymentConfiguration.appleMerchantIdentifier = @"appleMerchantIdentifier"; - paymentConfiguration.additionalPaymentOptions = STPPaymentOptionTypeAll; + paymentConfiguration.additionalPaymentOptions = STPPaymentOptionTypeDefault; XCTAssertFalse([paymentConfiguration applePayEnabled]); } diff --git a/Tests/Tests/STPPaymentContextSnapshotTests.m b/Tests/Tests/STPPaymentContextSnapshotTests.m index 806f4548ba9..1cec6acf003 100644 --- a/Tests/Tests/STPPaymentContextSnapshotTests.m +++ b/Tests/Tests/STPPaymentContextSnapshotTests.m @@ -30,7 +30,7 @@ - (void)setUp { STPPaymentConfiguration *config = [STPFixtures paymentConfiguration]; config.companyName = @"Test Company"; config.requiredBillingAddressFields = STPBillingAddressFieldsFull; - config.additionalPaymentOptions = STPPaymentOptionTypeAll; + config.additionalPaymentOptions = STPPaymentOptionTypeDefault; config.shippingType = STPShippingTypeShipping; self.config = config; STPCustomerContext *customerContext = [STPMocks staticCustomerContextWithCustomer:[STPFixtures customerWithCardTokenAndSourceSources] paymentMethods:@[[STPFixtures paymentMethod], [STPFixtures paymentMethod]]]; diff --git a/Tests/Tests/STPPaymentOptionsViewControllerLocalizationTests.m b/Tests/Tests/STPPaymentOptionsViewControllerLocalizationTests.m index a59870c6898..b71ba49c8de 100644 --- a/Tests/Tests/STPPaymentOptionsViewControllerLocalizationTests.m +++ b/Tests/Tests/STPPaymentOptionsViewControllerLocalizationTests.m @@ -29,7 +29,7 @@ - (void)performSnapshotTestForLanguage:(NSString *)language { STPPaymentConfiguration *config = [STPFixtures paymentConfiguration]; config.companyName = @"Test Company"; config.requiredBillingAddressFields = STPBillingAddressFieldsFull; - config.additionalPaymentOptions = STPPaymentOptionTypeAll; + config.additionalPaymentOptions = STPPaymentOptionTypeDefault; STPTheme *theme = [STPTheme defaultTheme]; NSArray *paymentMethods = @[[STPFixtures paymentMethod], [STPFixtures paymentMethod]]; id customerContext = [STPMocks staticCustomerContextWithCustomer:[STPFixtures customerWithCardTokenAndSourceSources] paymentMethods:paymentMethods]; diff --git a/Tests/Tests/STPPaymentOptionsViewControllerTest.m b/Tests/Tests/STPPaymentOptionsViewControllerTest.m index 283ab74ef46..5a20ea764c8 100644 --- a/Tests/Tests/STPPaymentOptionsViewControllerTest.m +++ b/Tests/Tests/STPPaymentOptionsViewControllerTest.m @@ -61,7 +61,7 @@ - (void)testInitWithSingleCardTokenSourceAndCardAvailable { STPCustomer *customer = [STPFixtures customerWithSingleCardTokenSource]; NSArray *paymentMethods = @[[STPFixtures paymentMethod]]; STPPaymentConfiguration *config = [STPFixtures paymentConfiguration]; - config.additionalPaymentOptions = STPPaymentOptionTypeAll; + config.additionalPaymentOptions = STPPaymentOptionTypeDefault; iddelegate = OCMProtocolMock(@protocol(STPPaymentOptionsViewControllerDelegate)); STPPaymentOptionsViewController *sut = [self buildViewControllerWithCustomer:customer paymentMethods:paymentMethods