diff --git a/.periphery.yml b/.periphery.yml
index 00c4eb71840..a9be58b729b 100644
--- a/.periphery.yml
+++ b/.periphery.yml
@@ -6,98 +6,30 @@
workspace: Stripe.xcworkspace
schemes:
- - AllStripeFrameworks
- # - IntegrationTester
- # - Stripe3DS2
- # - Stripe3DS2DemoUI
- # - StripeApplePay
- # - StripeCameraCore
- # - StripeCardScan
- - StripeConnect
- # - StripeCore
- # - StripeFinancialConnections
- # - StripeIdentity
- # - StripePaymentSheet
- # - StripePayments
- # - StripePaymentsUI
- # - StripeUICore
- # - StripeiOS
- # - PaymentSheet Example
- # - AppClipExample
- # - CardImageVerification Example
- # - FinancialConnections Example
- # - IdentityVerification Example
- # - StripeConnect Example
- # - Non-Card Payment Examples
- # - UI Examples
+ - AllStripeFrameworks-DeadCodeDetection
targets:
- # - AppClipExample
- # - AppClipExampleClip
- # - AppClipExampleClipTests
- # - AppClipExampleClipUITests
- # - AppClipExampleTests iOS
- # - CardImageVerification Example
- # - CardImageVerification ExampleUITests
- # - Common
- # - FinancialConnections Example
- # - FinancialConnectionsUITests
- # - IdentityVerification Example
- # - IntegrationTester
- # - IntegrationTesterUITests
- # - Non-Card Payment Examples
- # - PaymentSheet Example
- # - PaymentSheetLocalizationScreenshotGenerator
- # - PaymentSheetUITest
- Stripe3DS2
- # - Stripe3DS2Tests
- StripeApplePay
- # - StripeApplePayTests
- StripeCameraCore
- # - StripeCameraCoreTestUtils
- # - StripeCameraCoreTests
- StripeCardScan
- # - StripeCardScanTests
- StripeConnect
- # - StripeConnect Example
- # - StripeConnect ExampleUITests
- # - StripeConnectTests
- StripeCore
- # - StripeCoreTestUtils
- # - StripeCoreTests
- StripeFinancialConnections
- # - StripeFinancialConnectionsTests
- StripeIdentity
- # - StripeIdentityTests
- StripePaymentSheet
- # - StripePaymentSheetTestHostApp
- # - StripePaymentSheetTests
- StripePayments
- # - StripePaymentsObjcTestUtils
- # - StripePaymentsTestHostApp
- # - StripePaymentsTestUtils
- # - StripePaymentsTests
- StripePaymentsUI
- # - StripePaymentsUITests
- StripeUICore
- # - StripeUICoreTests
- StripeiOS
- # - StripeiOSAppHostedTests
- # - StripeiOSTestHostApp
- # - StripeiOSTests
- # - UI Examples
-retain_public: true
+retain_public: false
retain_objc_accessible: false
retain_objc_annotated: true
-retain_objc_protocols: true
-retain_ibaction: true
-retain_iboutlet: true
-retain_ibinspectable: true
verbose: true
build_arguments:
- -destination
- 'generic/platform=iOS Simulator'
-
\ No newline at end of file
+
diff --git a/Example/AppClipExample/AppClipExample.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/Example/AppClipExample/AppClipExample.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index 66e40fceadb..829976756e7 100644
--- a/Example/AppClipExample/AppClipExample.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Example/AppClipExample/AppClipExample.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,14 +9,14 @@
isShown
orderHint
- 3
+ 0
AppClipExampleClip.xcscheme_^#shared#^_
isShown
orderHint
- 4
+ 1
diff --git a/Example/CardImageVerification Example/CardImageVerification Example.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/Example/CardImageVerification Example/CardImageVerification Example.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index 56ce112ebc2..9b9df653d6b 100644
--- a/Example/CardImageVerification Example/CardImageVerification Example.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Example/CardImageVerification Example/CardImageVerification Example.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,7 @@
isShown
orderHint
- 6
+ 2
diff --git a/Example/FinancialConnections Example/FinancialConnections Example.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/Example/FinancialConnections Example/FinancialConnections Example.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index 7e75fcb5086..fbbd87d305e 100644
--- a/Example/FinancialConnections Example/FinancialConnections Example.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Example/FinancialConnections Example/FinancialConnections Example.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,7 @@
isShown
orderHint
- 7
+ 3
diff --git a/Example/IdentityVerification Example/IdentityVerification Example.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/Example/IdentityVerification Example/IdentityVerification Example.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index f3daab1aa9e..fee7280f70f 100644
--- a/Example/IdentityVerification Example/IdentityVerification Example.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Example/IdentityVerification Example/IdentityVerification Example.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,7 @@
isShown
orderHint
- 8
+ 4
diff --git a/Example/Non-Card Payment Examples/Non-Card Payment Examples.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/Example/Non-Card Payment Examples/Non-Card Payment Examples.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index b8612c7666a..a8546668927 100644
--- a/Example/Non-Card Payment Examples/Non-Card Payment Examples.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Example/Non-Card Payment Examples/Non-Card Payment Examples.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,7 @@
isShown
orderHint
- 10
+ 6
diff --git a/Example/PaymentSheet Example/PaymentSheet Example.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/Example/PaymentSheet Example/PaymentSheet Example.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index 959bc18a5bf..531aa3f23b1 100644
--- a/Example/PaymentSheet Example/PaymentSheet Example.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Example/PaymentSheet Example/PaymentSheet Example.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,7 @@
isShown
orderHint
- 11
+ 7
diff --git a/Example/UI Examples/UI Examples.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/Example/UI Examples/UI Examples.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index 0f84d033b80..e0ff9b53a2a 100644
--- a/Example/UI Examples/UI Examples.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Example/UI Examples/UI Examples.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,7 @@
isShown
orderHint
- 26
+ 22
diff --git a/Example/UI Examples/UI Examples/Source/BrowseViewController.swift b/Example/UI Examples/UI Examples/Source/BrowseViewController.swift
index a9ada5fcbdf..801e052440f 100644
--- a/Example/UI Examples/UI Examples/Source/BrowseViewController.swift
+++ b/Example/UI Examples/UI Examples/Source/BrowseViewController.swift
@@ -13,18 +13,14 @@ import UIKit
@testable import Stripe
@_spi(STP) import StripePaymentsUI
-class BrowseViewController: UITableViewController, STPAddCardViewControllerDelegate,
- STPPaymentOptionsViewControllerDelegate, STPShippingAddressViewControllerDelegate
+class BrowseViewController: UITableViewController
{
enum Demo: Int {
- static var count: Int = 11
+ static var count: Int = 8
case STPPaymentCardTextField
case STPPaymentCardTextFieldWithCBC
- case STPPaymentOptionsViewController
- case STPPaymentOptionsFPXViewController
- case STPShippingInfoViewController
case STPAUBECSFormViewController
case STPCardFormViewController
case STPCardFormViewControllerCBC
@@ -36,9 +32,6 @@ class BrowseViewController: UITableViewController, STPAddCardViewControllerDeleg
switch self {
case .STPPaymentCardTextField: return "Card Field"
case .STPPaymentCardTextFieldWithCBC: return "Card Field (CBC)"
- case .STPPaymentOptionsViewController: return "Payment Option Picker"
- case .STPPaymentOptionsFPXViewController: return "Payment Option Picker (With FPX)"
- case .STPShippingInfoViewController: return "Shipping Info Form"
case .STPAUBECSFormViewController: return "AU BECS Form"
case .STPCardFormViewController: return "Card Form"
case .STPCardFormViewControllerCBC: return "Card Form (CBC)"
@@ -52,9 +45,6 @@ class BrowseViewController: UITableViewController, STPAddCardViewControllerDeleg
switch self {
case .STPPaymentCardTextField: return "STPPaymentCardTextField"
case .STPPaymentCardTextFieldWithCBC: return "STPPaymentCardTextField"
- case .STPPaymentOptionsViewController: return "STPPaymentOptionsViewController"
- case .STPPaymentOptionsFPXViewController: return "STPPaymentOptionsViewController"
- case .STPShippingInfoViewController: return "STPShippingInfoViewController"
case .STPAUBECSFormViewController: return "STPAUBECSFormViewController"
case .STPCardFormViewController: return "STPCardFormViewController"
case .STPCardFormViewControllerCBC: return "STPCardFormViewController (CBC)"
@@ -65,13 +55,6 @@ class BrowseViewController: UITableViewController, STPAddCardViewControllerDeleg
}
}
- let customerContext: MockCustomerContext = {
- let keyManager = STPEphemeralKeyManager(
- keyProvider: MockKeyProvider(),
- apiVersion: STPAPIClient.apiVersion,
- performsEagerFetching: true)
- return MockCustomerContext(keyManager: keyManager, apiClient: .shared)
- }()
let themeViewController = ThemeViewController()
override func viewDidLoad() {
@@ -121,47 +104,6 @@ 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.fpxEnabled = true
- config.requiredBillingAddressFields = .none
- config.appleMerchantIdentifier = "dummy-merchant-id"
- config.cardScanningEnabled = true
- 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.requiredBillingAddressFields = .none
- config.appleMerchantIdentifier = "dummy-merchant-id"
- config.cardScanningEnabled = true
- 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 .STPShippingInfoViewController:
- let config = STPPaymentConfiguration()
- config.requiredShippingAddressFields = [.postalAddress]
- let viewController = STPShippingAddressViewController(
- configuration: config,
- theme: theme,
- currency: "usd",
- shippingAddress: nil,
- selectedShippingMethod: nil,
- prefilledInformation: nil)
- viewController.delegate = self
- let navigationController = UINavigationController(rootViewController: viewController)
- navigationController.navigationBar.stp_theme = theme
- present(navigationController, animated: true, completion: nil)
case .STPAUBECSFormViewController:
let viewController = AUBECSDebitFormViewController()
viewController.theme = theme
@@ -193,95 +135,4 @@ class BrowseViewController: UITableViewController, STPAddCardViewControllerDeleg
}
}
- // MARK: STPAddCardViewControllerDelegate
-
- func addCardViewControllerDidCancel(_ addCardViewController: STPAddCardViewController) {
- dismiss(animated: true, completion: nil)
- }
-
- func addCardViewController(
- _ addCardViewController: STPAddCardViewController,
- didCreatePaymentMethod paymentMethod: STPPaymentMethod,
- completion: @escaping STPErrorBlock
- ) {
- dismiss(animated: true, completion: nil)
- }
-
- // MARK: STPPaymentOptionsViewControllerDelegate
-
- func paymentOptionsViewControllerDidCancel(
- _ paymentOptionsViewController: STPPaymentOptionsViewController
- ) {
- dismiss(animated: true, completion: nil)
- }
-
- func paymentOptionsViewControllerDidFinish(
- _ paymentOptionsViewController: STPPaymentOptionsViewController
- ) {
- dismiss(animated: true, completion: nil)
- }
-
- func paymentOptionsViewController(
- _ paymentOptionsViewController: STPPaymentOptionsViewController,
- didFailToLoadWithError error: Error
- ) {
- dismiss(animated: true, completion: nil)
- }
-
- // MARK: STPShippingAddressViewControllerDelegate
-
- func shippingAddressViewControllerDidCancel(
- _ addressViewController: STPShippingAddressViewController
- ) {
- dismiss(animated: true, completion: nil)
- }
-
- func shippingAddressViewController(
- _ addressViewController: STPShippingAddressViewController,
- didFinishWith address: STPAddress,
- shippingMethod method: PKShippingMethod?
- ) {
- self.customerContext.updateCustomer(withShippingAddress: address, completion: nil)
- dismiss(animated: true, completion: nil)
- }
-
- func shippingAddressViewController(
- _ addressViewController: STPShippingAddressViewController,
- didEnter address: STPAddress,
- completion: @escaping STPShippingMethodsCompletionBlock
- ) {
- let upsGround = PKShippingMethod()
- upsGround.amount = 0
- upsGround.label = "UPS Ground"
- upsGround.detail = "Arrives in 3-5 days"
- upsGround.identifier = "ups_ground"
- let upsWorldwide = PKShippingMethod()
- upsWorldwide.amount = 10.99
- upsWorldwide.label = "UPS Worldwide Express"
- upsWorldwide.detail = "Arrives in 1-3 days"
- upsWorldwide.identifier = "ups_worldwide"
- let fedEx = PKShippingMethod()
- fedEx.amount = 5.99
- fedEx.label = "FedEx"
- fedEx.detail = "Arrives tomorrow"
- fedEx.identifier = "fedex"
-
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
- if address.country == nil || address.country == "US" {
- completion(.valid, nil, [upsGround, fedEx], fedEx)
- } else if address.country == "AQ" {
- let error = NSError(
- domain: "ShippingError", code: 123,
- userInfo: [
- NSLocalizedDescriptionKey: "Invalid Shipping Address",
- NSLocalizedFailureReasonErrorKey: "We can't ship to this country.",
- ])
- completion(.invalid, error, nil, nil)
- } else {
- fedEx.amount = 20.99
- completion(.valid, nil, [upsWorldwide, fedEx], fedEx)
- }
- }
- }
-
}
diff --git a/Example/UI Examples/UI Examples/Source/MockCustomerContext.swift b/Example/UI Examples/UI Examples/Source/MockCustomerContext.swift
index 68095395ff3..311995e6a54 100644
--- a/Example/UI Examples/UI Examples/Source/MockCustomerContext.swift
+++ b/Example/UI Examples/UI Examples/Source/MockCustomerContext.swift
@@ -110,74 +110,3 @@ class MockKeyProvider: NSObject, STPCustomerEphemeralKeyProvider {
completion(nil, NSError.stp_ephemeralKeyDecodingError())
}
}
-
-class MockCustomerContext: STPCustomerContext {
-
- let customer = MockCustomer()
-
- override func retrieveCustomer(_ completion: STPCustomerCompletionBlock? = nil) {
- if let completion = completion {
- completion(customer, nil)
- }
- }
-
- override func attachPaymentMethod(
- toCustomer paymentMethod: STPPaymentMethod, completion: STPErrorBlock? = nil
- ) {
- customer.paymentMethods.append(paymentMethod)
- if let completion = completion {
- completion(nil)
- }
- }
-
- override func detachPaymentMethod(
- fromCustomer paymentMethod: STPPaymentMethod, completion: STPErrorBlock? = nil
- ) {
- if let index = customer.paymentMethods.firstIndex(where: {
- $0.stripeId == paymentMethod.stripeId
- }) {
- customer.paymentMethods.remove(at: index)
- }
- if let completion = completion {
- completion(nil)
- }
- }
-
- override func listPaymentMethodsForCustomer(completion: STPPaymentMethodsCompletionBlock? = nil)
- {
- if let completion = completion {
- completion(customer.paymentMethods, nil)
- }
- }
-
- func selectDefaultCustomerPaymentMethod(
- _ paymentMethod: STPPaymentMethod, completion: @escaping STPErrorBlock
- ) {
- if customer.paymentMethods.contains(where: { $0.stripeId == paymentMethod.stripeId }) {
- customer.defaultPaymentMethod = paymentMethod
- }
- completion(nil)
- }
-
- override func updateCustomer(
- withShippingAddress shipping: STPAddress, completion: STPErrorBlock?
- ) {
- customer.shippingAddress = shipping
- if let completion = completion {
- completion(nil)
- }
- }
-
- override func retrieveLastSelectedPaymentMethodIDForCustomer(
- completion: @escaping (String?, Error?) -> Void
- ) {
- completion(nil, nil)
- }
- override func saveLastSelectedPaymentMethodID(
- forCustomer paymentMethodID: String?, completion: STPErrorBlock?
- ) {
- if let completion = completion {
- completion(nil)
- }
- }
-}
diff --git a/Stripe.xcworkspace/xcshareddata/xcschemes/AllStripeFrameworks-DeadCodeDetection.xcscheme b/Stripe.xcworkspace/xcshareddata/xcschemes/AllStripeFrameworks-DeadCodeDetection.xcscheme
new file mode 100644
index 00000000000..2e2289395f8
--- /dev/null
+++ b/Stripe.xcworkspace/xcshareddata/xcschemes/AllStripeFrameworks-DeadCodeDetection.xcscheme
@@ -0,0 +1,251 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Stripe.xcworkspace/xcshareddata/xcschemes/AllStripeFrameworks.xcscheme b/Stripe.xcworkspace/xcshareddata/xcschemes/AllStripeFrameworks.xcscheme
index 0981da72bfe..25d97fbb3ba 100644
--- a/Stripe.xcworkspace/xcshareddata/xcschemes/AllStripeFrameworks.xcscheme
+++ b/Stripe.xcworkspace/xcshareddata/xcschemes/AllStripeFrameworks.xcscheme
@@ -174,34 +174,6 @@
ReferencedContainer = "container:Stripe3DS2/Stripe3DS2.xcodeproj">
-
-
-
-
-
-
-
-
isShown
orderHint
- 24
+ 20
StripeiOSTestHostApp.xcscheme_^#shared#^_
isShown
orderHint
- 25
+ 21
+
+
+ SuppressBuildableAutocreation
+
+ 8BE23AD5D9A3D939AF46F31E
+
+ primary
+
+
+ ADF894AA8F6022D9BED17346
+
+ primary
+
diff --git a/Stripe/StripeiOS/Resources/Localizations/en.lproj/Localizable.strings b/Stripe/StripeiOS/Resources/Localizations/en.lproj/Localizable.strings
index 40b3a29493a..c164a506aa7 100644
--- a/Stripe/StripeiOS/Resources/Localizations/en.lproj/Localizable.strings
+++ b/Stripe/StripeiOS/Resources/Localizations/en.lproj/Localizable.strings
@@ -4,57 +4,12 @@
/* Source type brand name */
"3D Secure" = "3D Secure";
-/* Title for Add a Card view */
-"Add a Card" = "Add a Card";
-
-/* Button to add a new credit card. */
-"Add New Card…" = "Add New Card…";
-
/* Caption for Apartment/Address line 2 field on address form */
"Apt." = "Apt.";
-/* Title for contact info form */
-"Contact" = "Contact";
-
-/* Title for delivery info form */
-"Delivery" = "Delivery";
-
-/* Title for delivery address entry section */
-"Delivery Address" = "Delivery Address";
-
-/* Label for free shipping method */
-"Free" = "Free";
-
-/* Shipping form error message */
-"Invalid Shipping Address" = "Invalid Shipping Address";
-
-/* Title for screen when data is still loading from the network. */
-"Loading…" = "Loading…";
-
/* Source type brand name */
"Multibanco" = "Multibanco";
/* Button to move to the next text entry field */
"Next" = "Next";
-/* Button to pay with a Bank Account (using FPX). */
-"Online Banking (FPX)" = "Online Banking (FPX)";
-
-/* Title for Payment Method screen */
-"Payment Method" = "Payment Method";
-
-/* Title for shipping info form */
-"Shipping" = "Shipping";
-
-/* Label for shipping method form */
-"Shipping Method" = "Shipping Method";
-
-/* Button to fill shipping address from billing address. */
-"Use Billing" = "Use Billing";
-
-/* Button to fill billing address from delivery address. */
-"Use Delivery" = "Use Delivery";
-
-/* Button to fill billing address from shipping address. */
-"Use Shipping" = "Use Shipping";
-
diff --git a/Stripe/StripeiOS/Source/PKPaymentAuthorizationViewController+Stripe_Blocks.swift b/Stripe/StripeiOS/Source/PKPaymentAuthorizationViewController+Stripe_Blocks.swift
index 590d3342b90..bee369573e4 100644
--- a/Stripe/StripeiOS/Source/PKPaymentAuthorizationViewController+Stripe_Blocks.swift
+++ b/Stripe/StripeiOS/Source/PKPaymentAuthorizationViewController+Stripe_Blocks.swift
@@ -8,180 +8,21 @@
import ObjectiveC
import PassKit
-@_spi(STP) import StripeCore
typealias STPApplePayPaymentMethodHandlerBlock = (STPPaymentMethod, @escaping STPPaymentStatusBlock)
-> Void
typealias STPPaymentCompletionBlock = (STPPaymentStatus, Error?) -> Void
typealias STPPaymentSummaryItemCompletionBlock = ([PKPaymentSummaryItem]) -> Void
-typealias STPShippingMethodSelectionBlock = (
- PKShippingMethod, @escaping STPPaymentSummaryItemCompletionBlock
-) -> Void
typealias STPShippingAddressValidationBlock = (
STPShippingStatus, [PKShippingMethod], [PKPaymentSummaryItem]
) -> Void
-typealias STPShippingAddressSelectionBlock = (
- STPAddress, @escaping STPShippingAddressValidationBlock
-) -> Void
typealias STPPaymentAuthorizationBlock = (PKPayment) -> Void
-extension PKPaymentAuthorizationViewController {
- class func stp_controller(
- with paymentRequest: PKPaymentRequest,
- apiClient: STPAPIClient,
- onShippingAddressSelection: @escaping STPShippingAddressSelectionBlock,
- onShippingMethodSelection: @escaping STPShippingMethodSelectionBlock,
- onPaymentAuthorization: @escaping STPPaymentAuthorizationBlock,
- onPaymentMethodCreation: @escaping STPApplePayPaymentMethodHandlerBlock,
- onFinish: @escaping STPPaymentCompletionBlock
- ) -> Self? {
- let delegate = STPBlockBasedApplePayDelegate()
- delegate.apiClient = apiClient
- delegate.onShippingAddressSelection = onShippingAddressSelection
- delegate.onShippingMethodSelection = onShippingMethodSelection
- delegate.onPaymentAuthorization = onPaymentAuthorization
- delegate.onPaymentMethodCreation = onPaymentMethodCreation
- delegate.onFinish = onFinish
- let viewController = self.init(paymentRequest: paymentRequest)
- viewController?.delegate = delegate
- if let viewController = viewController {
- objc_setAssociatedObject(
- viewController,
- UnsafeRawPointer(&kSTPBlockBasedApplePayDelegateAssociatedObjectKey),
- delegate,
- .OBJC_ASSOCIATION_RETAIN_NONATOMIC
- )
- }
- return viewController
- }
-}
-private var kSTPBlockBasedApplePayDelegateAssociatedObjectKey = 0
typealias STPApplePayShippingMethodCompletionBlock = (
PKPaymentAuthorizationStatus, [PKPaymentSummaryItem]?
) -> Void
typealias STPApplePayShippingAddressCompletionBlock = (
PKPaymentAuthorizationStatus, [PKShippingMethod]?, [PKPaymentSummaryItem]?
) -> Void
-class STPBlockBasedApplePayDelegate: NSObject, PKPaymentAuthorizationViewControllerDelegate {
- var apiClient: STPAPIClient?
- var onShippingAddressSelection: STPShippingAddressSelectionBlock?
- var onShippingMethodSelection: STPShippingMethodSelectionBlock?
- var onPaymentAuthorization: STPPaymentAuthorizationBlock?
- var onPaymentMethodCreation: STPApplePayPaymentMethodHandlerBlock?
- var onFinish: STPPaymentCompletionBlock?
- var lastError: Error?
- var didSucceed = false
-
- // Remove all this once we drop iOS 11 support
- func paymentAuthorizationViewController(
- _ controller: PKPaymentAuthorizationViewController,
- didAuthorizePayment payment: PKPayment,
- completion: @escaping (PKPaymentAuthorizationStatus) -> Void
- ) {
- onPaymentAuthorization?(payment)
-
- let paymentMethodCreateCompletion: ((STPPaymentMethod?, Error?) -> Void)? = {
- result,
- paymentMethodCreateError in
- if let paymentMethodCreateError = paymentMethodCreateError {
- self.lastError = paymentMethodCreateError
- completion(.failure)
- return
- }
- guard let result = result else {
- self.lastError = NSError.stp_genericFailedToParseResponseError()
- completion(.failure)
- return
- }
- self.onPaymentMethodCreation?(
- result,
- { status, error in
- if status != .success || error != nil {
- self.lastError = error
- completion(.failure)
- if controller.presentingViewController == nil {
- // If we call completion() after dismissing, didFinishWithStatus is NOT called.
- self._finish()
- }
- return
- }
- self.didSucceed = true
- completion(.success)
- if controller.presentingViewController == nil {
- // If we call completion() after dismissing, didFinishWithStatus is NOT called.
- self._finish()
- }
- }
- )
- }
- if let paymentMethodCreateCompletion = paymentMethodCreateCompletion {
- apiClient?.createPaymentMethod(with: payment, completion: paymentMethodCreateCompletion)
- }
- }
-
- func paymentAuthorizationViewController(
- _ controller: PKPaymentAuthorizationViewController,
- didSelect shippingMethod: PKShippingMethod,
- completion: @escaping (PKPaymentAuthorizationStatus, [PKPaymentSummaryItem]) -> Void
- ) {
- onShippingMethodSelection?(
- shippingMethod,
- { summaryItems in
- completion(PKPaymentAuthorizationStatus.success, summaryItems)
- }
- )
- }
-
- func paymentAuthorizationViewController(
- _ controller: PKPaymentAuthorizationViewController,
- didSelectShippingContact contact: PKContact,
- handler completion: @escaping (PKPaymentRequestShippingContactUpdate) -> Void
- ) {
- let stpAddress = STPAddress(pkContact: contact)
- onShippingAddressSelection?(
- stpAddress,
- { status, shippingMethods, summaryItems in
- if status == .invalid {
- let genericShippingError = NSError(
- domain: PKPaymentErrorDomain,
- code: PKPaymentError.shippingContactInvalidError.rawValue,
- userInfo: nil
- )
- completion(
- PKPaymentRequestShippingContactUpdate(
- errors: [genericShippingError],
- paymentSummaryItems: summaryItems,
- shippingMethods: shippingMethods
- )
- )
- } else {
- completion(
- PKPaymentRequestShippingContactUpdate(
- errors: nil,
- paymentSummaryItems: summaryItems,
- shippingMethods: shippingMethods
- )
- )
- }
- }
- )
- }
-
- func paymentAuthorizationViewControllerDidFinish(
- _ controller: PKPaymentAuthorizationViewController
- ) {
- _finish()
- }
-
- func _finish() {
- if didSucceed {
- onFinish?(.success, nil)
- } else if let lastError = lastError {
- onFinish?(.error, lastError)
- } else {
- onFinish?(.userCancellation, nil)
- }
- }
-}
typealias STPPaymentAuthorizationStatusCallback = (PKPaymentAuthorizationStatus) -> Void
diff --git a/Stripe/StripeiOS/Source/STPAPIClient+BasicUI.swift b/Stripe/StripeiOS/Source/STPAPIClient+BasicUI.swift
index 00e78896600..f7ee681e4d5 100644
--- a/Stripe/StripeiOS/Source/STPAPIClient+BasicUI.swift
+++ b/Stripe/StripeiOS/Source/STPAPIClient+BasicUI.swift
@@ -47,109 +47,6 @@ extension STPAPIClient {
}
}
- /// Update a customer with parameters
- /// - seealso: https://stripe.com/docs/api#update_customer
- func updateCustomer(
- withParameters parameters: [String: Any],
- using ephemeralKey: STPEphemeralKey,
- completion: @escaping STPCustomerCompletionBlock
- ) {
- let endpoint = "\(APIEndpointCustomers)/\(ephemeralKey.customerID ?? "")"
- APIRequest.post(
- with: self,
- endpoint: endpoint,
- additionalHeaders: authorizationHeader(using: ephemeralKey),
- parameters: parameters
- ) { object, _, error in
- completion(object, error)
- }
- }
-
- /// Attach a Payment Method to a customer
- /// - seealso: https://stripe.com/docs/api/payment_methods/attach
- func attachPaymentMethod(
- _ paymentMethodID: String,
- toCustomerUsing ephemeralKey: STPEphemeralKey,
- completion: @escaping STPErrorBlock
- ) {
- guard let customerID = ephemeralKey.customerID else {
- assertionFailure()
- completion(nil)
- return
- }
- let endpoint = "\(APIEndpointPaymentMethods)/\(paymentMethodID)/attach"
- APIRequest.post(
- with: self,
- endpoint: endpoint,
- additionalHeaders: authorizationHeader(using: ephemeralKey),
- parameters: [
- "customer": customerID
- ]
- ) { _, _, error in
- completion(error)
- }
- }
-
- /// Detach a Payment Method from a customer
- /// - seealso: https://stripe.com/docs/api/payment_methods/detach
- func detachPaymentMethod(
- _ paymentMethodID: String,
- fromCustomerUsing ephemeralKey: STPEphemeralKey,
- completion: @escaping STPErrorBlock
- ) {
- let endpoint = "\(APIEndpointPaymentMethods)/\(paymentMethodID)/detach"
- APIRequest.post(
- with: self,
- endpoint: endpoint,
- additionalHeaders: authorizationHeader(using: ephemeralKey),
- parameters: [:]
- ) { _, _, error in
- completion(error)
- }
- }
-
- /// Retrieves a list of Payment Methods attached to a customer.
- /// @note This only fetches card type Payment Methods
- func listPaymentMethodsForCustomer(
- using ephemeralKey: STPEphemeralKey,
- completion: @escaping STPPaymentMethodsCompletionBlock
- ) {
- let header = authorizationHeader(using: ephemeralKey)
- let params: [String: Any] = [
- "customer": ephemeralKey.customerID ?? "",
- "type": "card",
- ]
- APIRequest.getWith(
- self,
- endpoint: APIEndpointPaymentMethods,
- additionalHeaders: header,
- parameters: params as [String: Any]
- ) { deserializer, _, error in
- if let error = error {
- completion(nil, error)
- } else if let paymentMethods = deserializer?.paymentMethods {
- completion(paymentMethods, nil)
- }
- }
- }
-
- /// Retrieve a customer
- /// - seealso: https://stripe.com/docs/api#retrieve_customer
- func retrieveCustomer(
- using ephemeralKey: STPEphemeralKey,
- completion: @escaping STPCustomerCompletionBlock
- ) {
- let endpoint = "\(APIEndpointCustomers)/\(ephemeralKey.customerID ?? "")"
- APIRequest.getWith(
- self,
- endpoint: endpoint,
- additionalHeaders: authorizationHeader(using: ephemeralKey),
- parameters: [:]
- ) { object, _, error in
- completion(object, error)
- }
- }
-
// MARK: FPX
/// Retrieves the online status of the FPX banks from the Stripe API.
/// - Parameter completion: The callback to run with the returned FPX bank list, or an error.
diff --git a/Stripe/StripeiOS/Source/STPAddCardViewController.swift b/Stripe/StripeiOS/Source/STPAddCardViewController.swift
deleted file mode 100644
index 853908fd4ba..00000000000
--- a/Stripe/StripeiOS/Source/STPAddCardViewController.swift
+++ /dev/null
@@ -1,1001 +0,0 @@
-//
-// STPAddCardViewController.swift
-// StripeiOS
-//
-// Created by Jack Flintermann on 3/23/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-@_spi(STP) import StripeCore
-@_spi(STP) import StripePayments
-@_spi(STP) import StripePaymentsUI
-@_spi(STP) import StripeUICore
-import UIKit
-
-/// This view controller contains a credit card entry form that the user can fill out. On submission, it will use the Stripe API to convert the user's card details to a Stripe token. It renders a right bar button item that submits the form, so it must be shown inside a `UINavigationController`.
-public class STPAddCardViewController: STPCoreTableViewController, STPAddressViewModelDelegate,
- STPCardScannerDelegate, STPPaymentCardTextFieldDelegate, UITableViewDelegate,
- UITableViewDataSource
-{
-
- /// A convenience initializer; equivalent to calling `init(configuration: STPPaymentConfiguration.shared, theme: STPTheme.defaultTheme)`.
- @objc
- public convenience init() {
- self.init(configuration: STPPaymentConfiguration.shared, theme: STPTheme.defaultTheme)
- }
-
- /// Initializes a new `STPAddCardViewController` with the provided configuration and theme. Don't forget to set the `delegate` property after initialization.
- /// - Parameters:
- /// - configuration: The configuration to use (this determines the Stripe publishable key to use, the required billing address fields, whether or not to use SMS autofill, etc). - seealso: STPPaymentConfiguration
- /// - theme: The theme to use to inform the view controller's visual appearance. - seealso: STPTheme
- @objc(initWithConfiguration:theme:)
- public init(
- configuration: STPPaymentConfiguration,
- theme: STPTheme
- ) {
- addressViewModel = STPAddressViewModel(
- requiredBillingFields: configuration.requiredBillingAddressFields,
- availableCountries: configuration.availableCountries
- )
- super.init(theme: theme)
- commonInit(with: configuration)
- }
-
- /// The view controller's delegate. This must be set before showing the view controller in order for it to work properly. - seealso: STPAddCardViewControllerDelegate
- @objc public weak var delegate: STPAddCardViewControllerDelegate?
- /// You can set this property to pre-fill any information you've already collected from your user. - seealso: STPUserInformation.h
- @objc public var prefilledInformation: STPUserInformation? {
- didSet {
- if let address = prefilledInformation?.billingAddress {
- addressViewModel.address = address
- }
- }
- }
- // Should be overwritten if this class is used by STPPaymentContext or STPPaymentOptionsViewController
- internal var analyticsLogger: STPPaymentContext.AnalyticsLogger = .init(product: STPAddCardViewController.self)
- private var _customFooterView: UIView?
- /// Provide this view controller with a footer view.
- /// When the footer view needs to be resized, it will be sent a
- /// `sizeThatFits:` call. The view should respond correctly to this method in order
- /// to be sized and positioned properly.
- @objc public var customFooterView: UIView? {
- get {
- _customFooterView
- }
- set(footerView) {
- _customFooterView = footerView
- _configureFooterView()
- }
- }
-
- func _configureFooterView() {
- if isViewLoaded, let footerView = _customFooterView {
- let size = footerView.sizeThatFits(
- CGSize(width: view.bounds.size.width, height: CGFloat.greatestFiniteMagnitude)
- )
- footerView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
-
- tableView?.tableFooterView = footerView
- }
- }
-
- /// The API Client to use to make requests.
- /// Defaults to `STPAPIClient.shared`
- public var apiClient: STPAPIClient = STPAPIClient.shared
-
- /// Use init: or initWithConfiguration:theme:
- required init(
- theme: STPTheme?
- ) {
- let configuration = STPPaymentConfiguration.shared
- addressViewModel = STPAddressViewModel(
- requiredBillingFields: configuration.requiredBillingAddressFields,
- availableCountries: configuration.availableCountries
- )
- super.init(theme: theme)
- }
-
- /// Use init: or initWithConfiguration:theme:
- required init(
- nibName nibNameOrNil: String?,
- bundle nibBundleOrNil: Bundle?
- ) {
- let configuration = STPPaymentConfiguration.shared
- addressViewModel = STPAddressViewModel(
- requiredBillingFields: configuration.requiredBillingAddressFields,
- availableCountries: configuration.availableCountries
- )
- super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
- }
-
- /// Use init: or initWithConfiguration:theme:
- required init?(
- coder aDecoder: NSCoder
- ) {
- let configuration = STPPaymentConfiguration.shared
- addressViewModel = STPAddressViewModel(
- requiredBillingFields: configuration.requiredBillingAddressFields,
- availableCountries: configuration.availableCountries
- )
- super.init(coder: aDecoder)
- }
-
- private var _alwaysEnableDoneButton = false
- @objc var alwaysEnableDoneButton: Bool {
- get {
- _alwaysEnableDoneButton
- }
- set(alwaysEnableDoneButton) {
- if alwaysEnableDoneButton != _alwaysEnableDoneButton {
- _alwaysEnableDoneButton = alwaysEnableDoneButton
- updateDoneButton()
- }
- }
- }
- private var configuration: STPPaymentConfiguration?
- @objc var shippingAddress: STPAddress?
- private var hasUsedShippingAddress = false
- private weak var cardImageView: UIImageView?
- private var doneItem: UIBarButtonItem?
- private var cardHeaderView: STPSectionHeaderView?
-
- @available(macCatalyst 14.0, *)
- private var cardScanner: STPCardScanner? {
- get {
- _cardScanner as? STPCardScanner
- }
- set {
- _cardScanner = newValue
- }
- }
-
- /// Storage for `cardScanner`.
- private var _cardScanner: NSObject?
-
- @available(macCatalyst 14.0, *)
- private var scannerCell: STPCardScannerTableViewCell? {
- get {
- _scannerCell as? STPCardScannerTableViewCell
- }
- set {
- _scannerCell = newValue
- }
- }
-
- /// Storage for `scannerCell`.
- private var _scannerCell: NSObject?
-
- private var _isScanning = false
- private var isScanning: Bool {
- get {
- _isScanning
- }
- set(isScanning) {
- if _isScanning == isScanning {
- return
- }
- _isScanning = isScanning
-
- cardHeaderView?.button?.isEnabled = !isScanning
- let indexPath = IndexPath(
- row: 0,
- section: STPPaymentCardSection.stpPaymentCardScannerSection.rawValue
- )
- tableView?.beginUpdates()
- if isScanning {
- tableView?.insertRows(at: [indexPath], with: .automatic)
- } else {
- tableView?.deleteRows(at: [indexPath], with: .automatic)
- }
- tableView?.endUpdates()
- if isScanning {
- tableView?.scrollToRow(at: indexPath, at: .middle, animated: true)
- }
- updateInputAccessoryVisiblity()
- }
- }
- private var addressHeaderView: STPSectionHeaderView?
- var paymentCell: STPPaymentCardTextFieldCell?
- var viewDidAppearStartTime: Date?
-
- private var _loading = false
- @objc var loading: Bool {
- get {
- _loading
- }
- set(loading) {
- if loading == _loading {
- return
- }
- _loading = loading
- stp_navigationItemProxy?.setHidesBackButton(loading, animated: true)
- stp_navigationItemProxy?.leftBarButtonItem?.isEnabled = !loading
- activityIndicator?.animating = loading
- if loading {
- tableView?.endEditing(true)
- var loadingItem: UIBarButtonItem?
- if let activityIndicator = activityIndicator {
- loadingItem = UIBarButtonItem(customView: activityIndicator)
- }
- stp_navigationItemProxy?.setRightBarButton(loadingItem, animated: true)
- cardHeaderView?.buttonHidden = true
- } else {
- stp_navigationItemProxy?.setRightBarButton(doneItem, animated: true)
- cardHeaderView?.buttonHidden = false
- }
- var cells = addressViewModel.addressCells as [UITableViewCell]
-
- if let paymentCell = paymentCell {
- cells.append(paymentCell)
- }
- for cell in cells {
- cell.isUserInteractionEnabled = !loading
- UIView.animate(
- withDuration: 0.1,
- animations: {
- cell.alpha = loading ? 0.7 : 1.0
- }
- )
- }
- }
- }
- private var activityIndicator: STPPaymentActivityIndicatorView?
- private weak var lookupActivityIndicator: STPPaymentActivityIndicatorView?
- var addressViewModel: STPAddressViewModel
- private var inputAccessoryToolbar: UIToolbar?
- private var lookupSucceeded = false
- private var scannerCompleteAnimationTimer: Timer?
- var didSendFormInteractedAnalytic = false
-
- @objc(commonInitWithConfiguration:) func commonInit(with configuration: STPPaymentConfiguration)
- {
- STPAnalyticsClient.sharedClient.addClass(
- toProductUsageIfNecessary: STPAddCardViewController.self
- )
-
- self.configuration = configuration
- shippingAddress = nil
- hasUsedShippingAddress = false
- addressViewModel.delegate = self
- title = STPLocalizedString("Add a Card", "Title for Add a Card view")
-
- if #available(macCatalyst 14.0, *) {
- cardScanner = STPCardScanner()
- }
- }
-
- /// :nodoc:
- @objc
- public func tableView(
- _ tableView: UITableView,
- estimatedHeightForRowAt indexPath: IndexPath
- ) -> CGFloat {
- return 44.0
- }
-
- @objc override func createAndSetupViews() {
- super.createAndSetupViews()
-
- let doneItem = UIBarButtonItem(
- barButtonSystemItem: .done,
- target: self,
- action: #selector(nextPressed(_:))
- )
- self.doneItem = doneItem
- stp_navigationItemProxy?.rightBarButtonItem = doneItem
- updateDoneButton()
-
- stp_navigationItemProxy?.leftBarButtonItem?.accessibilityIdentifier =
- "AddCardViewControllerNavBarCancelButtonIdentifier"
- stp_navigationItemProxy?.rightBarButtonItem?.accessibilityIdentifier =
- "AddCardViewControllerNavBarDoneButtonIdentifier"
-
- let cardImageView = UIImageView(image: STPLegacyImageLibrary.largeCardFrontImage())
- cardImageView.contentMode = .center
- cardImageView.frame = CGRect(
- x: 0,
- y: 0,
- width: view.bounds.size.width,
- height: cardImageView.bounds.size.height + (57 * 2)
- )
- self.cardImageView = cardImageView
- tableView?.tableHeaderView = cardImageView
-
- let paymentCell = STPPaymentCardTextFieldCell(
- style: .default,
- reuseIdentifier: "STPAddCardViewControllerPaymentCardTextFieldCell"
- )
- paymentCell.paymentField?.delegate = self
- if configuration?.requiredBillingAddressFields == .postalCode {
- // If postal code collection is enabled, move the postal code field into the card entry field.
- // Otherwise, this will be picked up by the billing address fields below.
- paymentCell.paymentField?.postalCodeEntryEnabled = true
- }
- self.paymentCell = paymentCell
-
- activityIndicator = STPPaymentActivityIndicatorView(
- frame: CGRect(x: 0, y: 0, width: 20.0, height: 20.0)
- )
-
- inputAccessoryToolbar = UIToolbar.stp_inputAccessoryToolbar(
- withTarget: self,
- action: #selector(paymentFieldNextTapped)
- )
- inputAccessoryToolbar?.stp_setEnabled(false)
- updateInputAccessoryVisiblity()
- tableView?.dataSource = self
- tableView?.delegate = self
- tableView?.reloadData()
- if let address = prefilledInformation?.billingAddress {
- addressViewModel.address = address
- }
-
- let addressHeaderView = STPSectionHeaderView()
- addressHeaderView.theme = theme
- addressHeaderView.title = String.Localized.billing_address
- switch configuration?.shippingType {
- case .shipping:
- addressHeaderView.button?.setTitle(
- STPLocalizedString(
- "Use Shipping",
- "Button to fill billing address from shipping address."
- ),
- for: .normal
- )
- case .delivery:
- addressHeaderView.button?.setTitle(
- STPLocalizedString(
- "Use Delivery",
- "Button to fill billing address from delivery address."
- ),
- for: .normal
- )
- default:
- break
- }
- addressHeaderView.button?.addTarget(
- self,
- action: #selector(useShippingAddress(_:)),
- for: .touchUpInside
- )
- let requiredFields = configuration?.requiredBillingAddressFields ?? .none
- let needsAddress = requiredFields != .none && !addressViewModel.isValid
- let buttonVisible =
- needsAddress && shippingAddress?.containsContent(for: requiredFields) != nil
- && !hasUsedShippingAddress
- addressHeaderView.buttonHidden = !buttonVisible
- addressHeaderView.setNeedsLayout()
- self.addressHeaderView = addressHeaderView
- let cardHeaderView = STPSectionHeaderView()
- cardHeaderView.theme = theme
- cardHeaderView.title = STPPaymentMethodType.card.displayName
- cardHeaderView.buttonHidden = true
- self.cardHeaderView = cardHeaderView
-
- // re-set the custom footer view if it was added before we loaded
- _configureFooterView()
-
- view.addGestureRecognizer(
- UITapGestureRecognizer(target: self, action: #selector(endEditing))
- )
-
- setUpCardScanningIfAvailable()
-
- STPAnalyticsClient.sharedClient.clearAdditionalInfo()
- }
-
- /// :nodoc:
- @objc
- public override func viewDidLayoutSubviews() {
- super.viewDidLayoutSubviews()
-
- // Resetting it re-calculates the size based on new view width
- // UITableView requires us to call setter again to actually pick up frame
- // change on footers
- if tableView?.tableFooterView != nil {
- customFooterView = tableView?.tableFooterView
- }
- }
-
- func setUpCardScanningIfAvailable() {
- if #available(macCatalyst 14.0, *) {
- if !STPCardScanner.cardScanningAvailable || configuration?.cardScanningEnabled != true {
- return
- }
- let scannerCell = STPCardScannerTableViewCell()
- self.scannerCell = scannerCell
-
- let cardScanner = STPCardScanner(delegate: self)
- cardScanner.cameraView = scannerCell.cameraView
- self.cardScanner = cardScanner
-
- cardHeaderView?.buttonHidden = false
- cardHeaderView?.button?.setTitle(
- String.Localized.scan_card_title_capitalization,
- for: .normal
- )
- cardHeaderView?.button?.addTarget(
- self,
- action: #selector(scanCard),
- for: .touchUpInside
- )
- cardHeaderView?.setNeedsLayout()
- }
- }
-
- @available(macCatalyst 14.0, *)
- @objc func scanCard() {
- view.endEditing(true)
- isScanning = true
- cardScanner?.start()
- sendFormInteractedAnalyticIfNecessary()
- }
-
- @objc func endEditing() {
- view.endEditing(false)
- }
-
- /// :nodoc:
- @objc
- public override func updateAppearance() {
- super.updateAppearance()
-
- view.backgroundColor = theme.primaryBackgroundColor
-
- let navBarTheme = navigationController?.navigationBar.stp_theme ?? theme
- doneItem?.stp_setTheme(navBarTheme)
- tableView?.allowsSelection = false
-
- cardImageView?.tintColor = theme.accentColor
- activityIndicator?.tintColor = theme.accentColor
-
- paymentCell?.theme = theme
- cardHeaderView?.theme = theme
- addressHeaderView?.theme = theme
- for cell in addressViewModel.addressCells {
- cell.theme = theme
- }
- setNeedsStatusBarAppearanceUpdate()
- }
-
- /// :nodoc:
- @objc
- public override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
- viewDidAppearStartTime = Date()
- analyticsLogger.logFormShown(paymentMethodType: .card)
-
- stp_beginObservingKeyboardAndInsettingScrollView(
- tableView,
- onChange: nil
- )
- firstEmptyField()?.becomeFirstResponder()
- }
-
- func firstEmptyField() -> UIResponder? {
-
- if paymentCell?.isEmpty != nil {
- return paymentCell!
- }
-
- for cell in addressViewModel.addressCells {
- if cell.contents?.count ?? 0 == 0 {
- return cell
- }
- }
- return nil
- }
-
- /// :nodoc:
- @objc
- public override func handleCancelTapped(_ sender: Any?) {
- delegate?.addCardViewControllerDidCancel(self)
- }
-
- @objc func nextPressed(_ sender: Any?) {
- analyticsLogger.logDoneButtonTapped(paymentMethodType: .card, shownStartDate: viewDidAppearStartTime)
- loading = true
- guard let cardParams = paymentCell?.paymentField?.paymentMethodParams.card else {
- return
- }
- // Create and return a Payment Method
- let billingDetails = STPPaymentMethodBillingDetails()
- if configuration?.requiredBillingAddressFields == .postalCode {
- let address = STPAddress()
- address.postalCode = paymentCell?.paymentField?.postalCode
- billingDetails.address = STPPaymentMethodAddress(address: address)
- } else {
- billingDetails.address = STPPaymentMethodAddress(address: addressViewModel.address)
- billingDetails.email = addressViewModel.address.email
- billingDetails.name = addressViewModel.address.name
- billingDetails.phone = addressViewModel.address.phone
- }
- let paymentMethodParams = STPPaymentMethodParams(
- card: cardParams,
- billingDetails: billingDetails,
- metadata: nil
- )
- apiClient.createPaymentMethod(with: paymentMethodParams) {
- paymentMethod,
- createPaymentMethodError in
- if let createPaymentMethodError = createPaymentMethodError {
- self.handleError(createPaymentMethodError)
- } else {
- if let paymentMethod = paymentMethod {
- self.delegate?.addCardViewController(
- self,
- didCreatePaymentMethod: paymentMethod
- ) {
- attachToCustomerError in
- stpDispatchToMainThreadIfNecessary({
- if let attachToCustomerError = attachToCustomerError {
- self.handleError(attachToCustomerError)
- } else {
- self.loading = false
- }
- })
- }
- }
- }
- }
- }
-
- func handleError(_ error: Error) {
- loading = false
- firstEmptyField()?.becomeFirstResponder()
-
- let alertController = UIAlertController(
- title: error.localizedDescription,
- message: (error as NSError).localizedFailureReason,
- preferredStyle: .alert
- )
-
- alertController.addAction(
- UIAlertAction(
- title: String.Localized.ok,
- style: .cancel,
- handler: nil
- )
- )
-
- present(alertController, animated: true)
- }
-
- func updateDoneButton() {
- stp_navigationItemProxy?.rightBarButtonItem?.isEnabled =
- (paymentCell?.paymentField?.isValid ?? false && addressViewModel.isValid)
- || alwaysEnableDoneButton
- }
-
- func updateInputAccessoryVisiblity() {
- // The inputAccessoryToolbar switches from the paymentCell to the first address field.
- // It should only be shown when there *is* an address field. This compensates for the lack
- // of a 'Return' key on the number pad used for paymentCell entry
- let hasAddressCells = (addressViewModel.addressCells.count) > 0
- paymentCell?.inputAccessoryView = hasAddressCells ? inputAccessoryToolbar : nil
- }
-
- /// Only send form interacted analytic once per time this screen is shown
- func sendFormInteractedAnalyticIfNecessary() {
- if !didSendFormInteractedAnalytic {
- didSendFormInteractedAnalytic = true
- analyticsLogger.logFormInteracted(paymentMethodType: .card)
- }
- }
-
- /// Only send card number completed analytic once per time the card number changes from invalid to valid
- var shouldSendCardNumberCompletedAnalytic = true
- func sendCardNumberCompletedAnalyticIfNecessary(cardNumber: String?) {
- let isCardNumberValid = STPCardValidator.validationState(forNumber: cardNumber, validatingCardBrand: true) == .valid
- if isCardNumberValid, shouldSendCardNumberCompletedAnalytic {
- analyticsLogger.logCardNumberCompleted()
- shouldSendCardNumberCompletedAnalytic = false
- } else if !isCardNumberValid {
- // Reset shouldSendCardNumberCompletedAnalytic when the card number is invalid, so that it gets sent when the card number becomes valid.
- shouldSendCardNumberCompletedAnalytic = true
- }
- }
-
- // MARK: - STPPaymentCardTextField
- @objc
- public func paymentCardTextFieldDidChange(_ textField: STPPaymentCardTextField) {
- sendFormInteractedAnalyticIfNecessary()
- sendCardNumberCompletedAnalyticIfNecessary(cardNumber: textField.cardNumber)
- inputAccessoryToolbar?.stp_setEnabled(textField.isValid)
- updateDoneButton()
- }
-
- @objc func paymentFieldNextTapped() {
- _ = addressViewModel.addressCells.stp_boundSafeObject(at: 0)?.becomeFirstResponder()
- }
-
- @objc
- public func paymentCardTextFieldWillEndEditing(forReturn textField: STPPaymentCardTextField) {
- paymentFieldNextTapped()
- }
-
- @objc
- public func paymentCardTextFieldDidBeginEditingCVC(_ textField: STPPaymentCardTextField) {
- let isAmex = STPCardValidator.brand(forNumber: textField.cardNumber ?? "") == .amex
- var newImage: UIImage?
- var animationTransition: UIView.AnimationOptions
-
- if isAmex {
- newImage = STPLegacyImageLibrary.largeCardAmexCVCImage()
- animationTransition = .transitionCrossDissolve
- } else {
- newImage = STPLegacyImageLibrary.largeCardBackImage()
- animationTransition = .transitionFlipFromRight
- }
-
- if let cardImageView = cardImageView {
- UIView.transition(
- with: cardImageView,
- duration: 0.2,
- options: animationTransition,
- animations: {
- self.cardImageView?.image = newImage
- }
- )
- }
- }
-
- @objc
- public func paymentCardTextFieldDidEndEditingCVC(_ textField: STPPaymentCardTextField) {
- let isAmex = STPCardValidator.brand(forNumber: textField.cardNumber ?? "") == .amex
- let animationTransition: UIView.AnimationOptions =
- isAmex ? .transitionCrossDissolve : .transitionFlipFromLeft
-
- if let cardImageView = cardImageView {
- UIView.transition(
- with: cardImageView,
- duration: 0.2,
- options: animationTransition,
- animations: {
- self.cardImageView?.image = STPLegacyImageLibrary.largeCardFrontImage()
- }
- )
- }
- }
-
- @objc
- public func paymentCardTextFieldDidBeginEditing(_ textField: STPPaymentCardTextField) {
- if #available(macCatalyst 14.0, *) {
- cardScanner?.stop()
- }
- }
-
- // MARK: - STPAddressViewModelDelegate
- func addressViewModel(_ addressViewModel: STPAddressViewModel, addedCellAt index: Int) {
- let indexPath = IndexPath(
- row: index,
- section: STPPaymentCardSection.stpPaymentCardBillingAddressSection.rawValue
- )
- tableView?.insertRows(at: [indexPath], with: .automatic)
- updateInputAccessoryVisiblity()
- }
-
- func addressViewModel(_ addressViewModel: STPAddressViewModel, removedCellAt index: Int) {
- let indexPath = IndexPath(
- row: Int(index),
- section: STPPaymentCardSection.stpPaymentCardBillingAddressSection.rawValue
- )
- tableView?.deleteRows(at: [indexPath], with: .automatic)
- updateInputAccessoryVisiblity()
- }
-
- func addressViewModelDidChange(_ addressViewModel: STPAddressViewModel) {
- updateDoneButton()
- }
-
- func addressViewModelWillUpdate(_ addressViewModel: STPAddressViewModel) {
- tableView?.beginUpdates()
- }
-
- func addressViewModelDidUpdate(_ addressViewModel: STPAddressViewModel) {
- tableView?.endUpdates()
- }
-
- // MARK: - UITableView
- /// :nodoc:
- @objc
- public func numberOfSections(in tableView: UITableView) -> Int {
- return 3
- }
-
- /// :nodoc:
- @objc
- public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- if section == STPPaymentCardSection.stpPaymentCardNumberSection.rawValue {
- return 1
- } else if section == STPPaymentCardSection.stpPaymentCardScannerSection.rawValue {
- return isScanning ? 1 : 0
- } else if section == STPPaymentCardSection.stpPaymentCardBillingAddressSection.rawValue {
- return addressViewModel.addressCells.count
- }
- return 0
- }
-
- /// :nodoc:
- @objc
- public func tableView(
- _ tableView: UITableView,
- cellForRowAt indexPath: IndexPath
- ) -> UITableViewCell {
- var cell: UITableViewCell?
- switch indexPath.section {
- case STPPaymentCardSection.stpPaymentCardNumberSection.rawValue:
- cell = paymentCell
- case STPPaymentCardSection.stpPaymentCardScannerSection.rawValue:
- if #available(macCatalyst 14.0, *) {
- cell = scannerCell
- } else {
- return UITableViewCell()
- }
- case STPPaymentCardSection.stpPaymentCardBillingAddressSection.rawValue:
- cell = addressViewModel.addressCells.stp_boundSafeObject(at: indexPath.row)
- default:
- return UITableViewCell() // won't be called; exists to make the static analyzer happy
- }
- cell?.backgroundColor = theme.secondaryBackgroundColor
- cell?.contentView.backgroundColor = UIColor.clear
- return cell!
- }
-
- /// :nodoc:
- @objc
- public func tableView(
- _ tableView: UITableView,
- willDisplay cell: UITableViewCell,
- forRowAt indexPath: IndexPath
- ) {
- let topRow = indexPath.row == 0
- let bottomRow =
- self.tableView(tableView, numberOfRowsInSection: indexPath.section) - 1 == indexPath.row
- cell.stp_setBorderColor(theme.tertiaryBackgroundColor)
- cell.stp_setTopBorderHidden(!topRow)
- cell.stp_setBottomBorderHidden(!bottomRow)
- cell.stp_setFakeSeparatorColor(theme.quaternaryBackgroundColor)
- cell.stp_setFakeSeparatorLeftInset(15.0)
- }
-
- /// :nodoc:
- @objc
- public func tableView(
- _ tableView: UITableView,
- heightForFooterInSection section: Int
- )
- -> CGFloat
- {
- if self.tableView(tableView, numberOfRowsInSection: section) == 0 {
- return 0.01
- }
- return 27.0
- }
-
- /// :nodoc:
- @objc
- public override func tableView(
- _ tableView: UITableView,
- heightForHeaderInSection section: Int
- ) -> CGFloat {
- let fittingSize = CGSize(
- width: view.bounds.size.width,
- height: CGFloat.greatestFiniteMagnitude
- )
- let numberOfRows = self.tableView(tableView, numberOfRowsInSection: section)
- if section == STPPaymentCardSection.stpPaymentCardNumberSection.rawValue {
- return cardHeaderView?.sizeThatFits(fittingSize).height ?? 0.0
- } else if section == STPPaymentCardSection.stpPaymentCardBillingAddressSection.rawValue
- && numberOfRows != 0
- {
- return addressHeaderView?.sizeThatFits(fittingSize).height ?? 0.0
- } else if section == STPPaymentCardSection.stpPaymentCardScannerSection.rawValue {
- return 0.01
- } else if numberOfRows != 0 {
- return tableView.sectionHeaderHeight
- }
- return 0.01
- }
-
- /// :nodoc:
- @objc
- public func tableView(
- _ tableView: UITableView,
- viewForHeaderInSection section: Int
- )
- -> UIView?
- {
- if self.tableView(tableView, numberOfRowsInSection: section) == 0 {
- return UIView()
- } else {
- if section == STPPaymentCardSection.stpPaymentCardNumberSection.rawValue {
- return cardHeaderView
- } else if section == STPPaymentCardSection.stpPaymentCardBillingAddressSection.rawValue
- {
- return addressHeaderView
- }
- }
- return nil
- }
-
- /// :nodoc:
- @objc
- public func tableView(
- _ tableView: UITableView,
- viewForFooterInSection section: Int
- )
- -> UIView?
- {
- return UIView()
- }
-
- @objc func useShippingAddress(_ sender: UIButton) {
- tableView?.beginUpdates()
- addressViewModel.address = shippingAddress ?? STPAddress()
- hasUsedShippingAddress = true
- firstEmptyField()?.becomeFirstResponder()
- UIView.animate(
- withDuration: 0.2,
- animations: {
- self.addressHeaderView?.buttonHidden = true
- }
- )
- tableView?.endUpdates()
- }
-
- // MARK: - STPCardScanner
- /// :nodoc:
- @objc
- public override func viewWillTransition(
- to size: CGSize,
- with coordinator: UIViewControllerTransitionCoordinator
- ) {
- super.viewWillTransition(to: size, with: coordinator)
- let orientation = UIDevice.current.orientation
- if orientation.isPortrait || orientation.isLandscape {
- if #available(macCatalyst 14.0, *) {
- cardScanner?.deviceOrientation = orientation
- }
- }
- if isScanning {
- let indexPath = IndexPath(
- row: 0,
- section: STPPaymentCardSection.stpPaymentCardScannerSection.rawValue
- )
- DispatchQueue.main.async(execute: {
- self.tableView?.scrollToRow(at: indexPath, at: .middle, animated: true)
- })
- }
- }
-
- static let cardScannerKSTPCardScanAnimationTime: TimeInterval = 0.04
-
- @available(macCatalyst 14.0, *)
- func cardScanner(
- _ scanner: STPCardScanner,
- didFinishWith cardParams: STPPaymentMethodCardParams?,
- error: Error?
- ) {
- if let error = error {
- handleError(error)
- }
- if let cardParams = cardParams {
- view.isUserInteractionEnabled = false
- paymentCell?.paymentField?.inputView = UIView() as? UIInputView
- var i = 0
- scannerCompleteAnimationTimer = Timer.scheduledTimer(
- withTimeInterval: STPAddCardViewController.cardScannerKSTPCardScanAnimationTime,
- repeats: true,
- block: { timer in
- i += 1
- let newParams = STPPaymentMethodCardParams()
- guard let number = cardParams.number else {
- timer.invalidate()
- self.view.isUserInteractionEnabled = false
- return
- }
- if i < number.count {
- newParams.number = String(
- number[...number.index(number.startIndex, offsetBy: i)]
- )
- } else {
- newParams.number = number
- }
- self.paymentCell?.paymentField?.paymentMethodParams = STPPaymentMethodParams(
- card: newParams,
- billingDetails: nil,
- metadata: nil
- )
- if i > number.count {
- self.paymentCell?.paymentField?.paymentMethodParams =
- STPPaymentMethodParams(
- card: cardParams,
- billingDetails: nil,
- metadata: nil
- )
- self.isScanning = false
- self.paymentCell?.paymentField?.inputView = nil
- // Force the inputView to reload by asking the text field to resign/become first responder:
- _ = self.paymentCell?.paymentField?.resignFirstResponder()
- _ = self.paymentCell?.paymentField?.becomeFirstResponder()
- timer.invalidate()
- self.view.isUserInteractionEnabled = true
- }
- }
- )
- } else {
- isScanning = false
- }
- }
-
-}
-
-/// An `STPAddCardViewControllerDelegate` is notified when an `STPAddCardViewController`
-/// successfully creates a card token or is cancelled. It has internal error-handling
-/// logic, so there's no error case to deal with.
-@objc public protocol STPAddCardViewControllerDelegate: NSObjectProtocol {
- /// Called when the user cancels adding a card. You should dismiss (or pop) the
- /// view controller at this point.
- /// - Parameter addCardViewController: the view controller that has been cancelled
- func addCardViewControllerDidCancel(_ addCardViewController: STPAddCardViewController)
-
- /// This is called when the user successfully adds a card and Stripe returns a
- /// Payment Method.
- /// You should send the PaymentMethod to your backend to store it on a customer, and then
- /// call the provided `completion` block when that call is finished. If an error
- /// occurs while talking to your backend, call `completion(error)`, otherwise,
- /// dismiss (or pop) the view controller.
- /// - Parameters:
- /// - addCardViewController: the view controller that successfully created a token
- /// - paymentMethod: the Payment Method that was created. - seealso: STPPaymentMethod
- /// - completion: call this callback when you're done sending the token to your backend
- @objc func addCardViewController(
- _ addCardViewController: STPAddCardViewController,
- didCreatePaymentMethod paymentMethod: STPPaymentMethod,
- completion: @escaping STPErrorBlock
- )
-
- // MARK: - Deprecated
-
- /// This method is deprecated as of v16.0.0 (https://github.com/stripe/stripe-ios/blob/master/MIGRATING.md#migrating-from-versions--1600).
- /// To use this class, migrate your integration from Charges to PaymentIntents. See https://stripe.com/docs/payments/payment-intents/migration/charges#read
- @available(
- *,
- deprecated,
- message:
- "Use addCardViewController(_:didCreatePaymentMethod:completion:) instead and migrate your integration to PaymentIntents. See https://stripe.com/docs/payments/payment-intents/migration/charges#read",
- renamed: "addCardViewController(_:didCreatePaymentMethod:completion:)"
- )
- @objc optional func addCardViewController(
- _ addCardViewController: STPAddCardViewController,
- didCreateToken token: STPToken,
- completion: STPErrorBlock
- )
- /// This method is deprecated as of v16.0.0 (https://github.com/stripe/stripe-ios/blob/master/MIGRATING.md#migrating-from-versions--1600).
- /// To use this class, migrate your integration from Charges to PaymentIntents. See https://stripe.com/docs/payments/payment-intents/migration/charges#read
- @available(
- *,
- deprecated,
- message:
- "Use addCardViewController(_:didCreatePaymentMethod:completion:) instead and migrate your integration to PaymentIntents. See https://stripe.com/docs/payments/payment-intents/migration/charges#read",
- renamed: "addCardViewController(_:didCreatePaymentMethod:completion:)"
- )
- @objc optional func addCardViewController(
- _ addCardViewController: STPAddCardViewController,
- didCreateSource source: STPSource,
- completion: STPErrorBlock
- )
-}
-
-private let STPPaymentCardCellReuseIdentifier = "STPPaymentCardCellReuseIdentifier"
-enum STPPaymentCardSection: Int {
- case stpPaymentCardNumberSection = 0
- case stpPaymentCardScannerSection = 1
- case stpPaymentCardBillingAddressSection = 2
-}
-
-/// :nodoc:
-@_spi(STP) extension STPAddCardViewController: STPAnalyticsProtocol {
- @_spi(STP) public static let stp_analyticsIdentifier = "STPAddCardViewController"
-}
diff --git a/Stripe/StripeiOS/Source/STPAddressViewModel.swift b/Stripe/StripeiOS/Source/STPAddressViewModel.swift
deleted file mode 100644
index aa3fa68b6dd..00000000000
--- a/Stripe/StripeiOS/Source/STPAddressViewModel.swift
+++ /dev/null
@@ -1,434 +0,0 @@
-//
-// STPAddressViewModel.swift
-// StripeiOS
-//
-// Created by Jack Flintermann on 4/21/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-import Contacts
-import CoreLocation
-@_spi(STP) import StripeCore
-@_spi(STP) import StripePaymentsUI
-import UIKit
-
-protocol STPAddressViewModelDelegate: AnyObject {
- func addressViewModelDidChange(_ addressViewModel: STPAddressViewModel)
- func addressViewModel(_ addressViewModel: STPAddressViewModel, addedCellAt index: Int)
- func addressViewModel(_ addressViewModel: STPAddressViewModel, removedCellAt index: Int)
- func addressViewModelWillUpdate(_ addressViewModel: STPAddressViewModel)
- func addressViewModelDidUpdate(_ addressViewModel: STPAddressViewModel)
-}
-
-class STPAddressViewModel: STPAddressFieldTableViewCellDelegate {
- private(set) var addressCells: [STPAddressFieldTableViewCell] = []
- weak var delegate: STPAddressViewModelDelegate?
-
- var addressFieldTableViewCountryCode: String? = Locale.autoupdatingCurrent.regionCode {
- didSet {
- updatePostalCodeCellIfNecessary()
- if let addressFieldTableViewCountryCode = addressFieldTableViewCountryCode {
- for cell in addressCells {
- cell.delegateCountryCodeDidChange(countryCode: addressFieldTableViewCountryCode)
- }
- }
- }
- }
-
- var address: STPAddress {
- get {
- let address = STPAddress()
- for cell in addressCells {
-
- switch cell.type {
- case .name:
- address.name = cell.contents
- case .line1:
- address.line1 = cell.contents
- case .line2:
- address.line2 = cell.contents
- case .city:
- address.city = cell.contents
- case .state:
- address.state = cell.contents
- case .zip:
- address.postalCode = cell.contents
- case .country:
- address.country = cell.contents
- case .email:
- address.email = cell.contents
- case .phone:
- address.phone = cell.contents
- }
- }
- // Prefer to use the contents of STPAddressFieldTypeCountry, but fallback to
- // `addressFieldTableViewCountryCode` if nil (important for STPBillingAddressFieldsPostalCode)
- address.country = address.country ?? addressFieldTableViewCountryCode
- return address
- }
- set(address) {
- if let country = address.country {
- addressFieldTableViewCountryCode = country
- }
-
- for cell in addressCells {
- switch cell.type {
- case .name:
- cell.contents = address.name
- case .line1:
- cell.contents = address.line1
- case .line2:
- cell.contents = address.line2
- case .city:
- cell.contents = address.city
- case .state:
- cell.contents = address.state
- case .zip:
- cell.contents = address.postalCode
- case .country:
- cell.contents = address.country
- case .email:
- cell.contents = address.email
- case .phone:
- cell.contents = address.phone
- }
- }
- }
- }
-
- // The default value of availableCountries is nil, which will allow all known countries.
- var availableCountries: Set?
-
- var isValid: Bool {
- if isBillingAddress {
- // The AddressViewModel is only for address fields.
- // Determining whether the postal code is present is up to the
- // STPCardTextFieldViewModel.
- if requiredBillingAddressFields == .postalCode {
- return true
- } else {
- return address.containsRequiredFields(requiredBillingAddressFields)
- }
-
- } else {
- if let requiredShippingAddressFields = requiredShippingAddressFields {
- return address.containsRequiredShippingAddressFields(requiredShippingAddressFields)
- }
- return false
- }
- }
-
- // The default value of availableCountries is nil, which will allow all known countries.
- init(
- requiredBillingFields requiredBillingAddressFields: STPBillingAddressFields,
- availableCountries: Set? = nil
- ) {
- isBillingAddress = true
- self.availableCountries = availableCountries
- self.requiredBillingAddressFields = requiredBillingAddressFields
- switch requiredBillingAddressFields {
- case .none:
- addressCells = []
- case .zip, .postalCode:
- addressCells = [] // Postal code cell will be added later if necessary
- case .full:
- addressCells = [
- STPAddressFieldTableViewCell(
- type: .name,
- contents: "",
- lastInList: false,
- delegate: self
- ),
- STPAddressFieldTableViewCell(
- type: .line1,
- contents: "",
- lastInList: false,
- delegate: self
- ),
- STPAddressFieldTableViewCell(
- type: .line2,
- contents: "",
- lastInList: false,
- delegate: self
- ),
- STPAddressFieldTableViewCell(
- type: .country,
- contents: addressFieldTableViewCountryCode,
- lastInList: false,
- delegate: self
- ),
- // Postal code cell will be added here later if necessary
- STPAddressFieldTableViewCell(
- type: .city,
- contents: "",
- lastInList: false,
- delegate: self
- ),
- STPAddressFieldTableViewCell(
- type: .state,
- contents: "",
- lastInList: true,
- delegate: self
- ),
- ]
- case .name:
- addressCells = [
- STPAddressFieldTableViewCell(
- type: .name,
- contents: "",
- lastInList: true,
- delegate: self
- ),
- ]
- default:
- fatalError()
- }
- commonInit()
- }
-
- init(
- requiredShippingFields requiredShippingAddressFields: Set,
- availableCountries: Set? = nil
- ) {
- isBillingAddress = false
- self.availableCountries = availableCountries
- self.requiredShippingAddressFields = requiredShippingAddressFields
- var cells: [STPAddressFieldTableViewCell] = []
-
- if requiredShippingAddressFields.contains(STPContactField.name) {
- cells.append(
- STPAddressFieldTableViewCell(
- type: .name,
- contents: "",
- lastInList: false,
- delegate: self
- )
- )
- }
- if requiredShippingAddressFields.contains(.emailAddress) {
- cells.append(
- STPAddressFieldTableViewCell(
- type: .email,
- contents: "",
- lastInList: false,
- delegate: self
- )
- )
- }
- if requiredShippingAddressFields.contains(STPContactField.postalAddress) {
- var postalCells = [
- STPAddressFieldTableViewCell(
- type: .name,
- contents: "",
- lastInList: false,
- delegate: self
- ),
- STPAddressFieldTableViewCell(
- type: .line1,
- contents: "",
- lastInList: false,
- delegate: self
- ),
- STPAddressFieldTableViewCell(
- type: .line2,
- contents: "",
- lastInList: false,
- delegate: self
- ),
- STPAddressFieldTableViewCell(
- type: .country,
- contents: addressFieldTableViewCountryCode,
- lastInList: false,
- delegate: self
- ),
- // Postal code cell will be added here later if necessary
- STPAddressFieldTableViewCell(
- type: .city,
- contents: "",
- lastInList: false,
- delegate: self
- ),
- STPAddressFieldTableViewCell(
- type: .state,
- contents: "",
- lastInList: false,
- delegate: self
- ),
- ]
- if requiredShippingAddressFields.contains(.name) {
- postalCells.remove(at: 0)
- }
- cells.append(contentsOf: postalCells.compactMap { $0 })
- }
- if requiredShippingAddressFields.contains(.phoneNumber) {
- cells.append(
- STPAddressFieldTableViewCell(
- type: .phone,
- contents: "",
- lastInList: false,
- delegate: self
- )
- )
- }
- if let lastCell = cells.last {
- lastCell.lastInList = true
- }
- addressCells = cells
- commonInit()
- }
-
- private func cell(at index: Int) -> STPAddressFieldTableViewCell? {
- guard index > 0,
- index < addressCells.count
- else {
- return nil
- }
- return addressCells[index]
- }
-
- private var isBillingAddress = false
- private var requiredBillingAddressFields: STPBillingAddressFields = .none
- private var requiredShippingAddressFields: Set?
- private var showingPostalCodeCell = false
- private var geocodeInProgress = false
-
- private func commonInit() {
- if let countryCode = Locale.autoupdatingCurrent.regionCode {
- addressFieldTableViewCountryCode = countryCode
- }
- updatePostalCodeCellIfNecessary()
- }
-
- private func updatePostalCodeCellIfNecessary() {
- delegate?.addressViewModelWillUpdate(self)
- let shouldBeShowingPostalCode = STPPostalCodeValidator.postalCodeIsRequired(
- forCountryCode: addressFieldTableViewCountryCode
- )
-
- if shouldBeShowingPostalCode && !showingPostalCodeCell {
- if containsStateAndPostalFields() {
- // Add before city
- let zipFieldIndex = addressCells.firstIndex(where: { $0.type == .city }) ?? 0
-
- var mutableAddressCells = addressCells
- mutableAddressCells.insert(
- STPAddressFieldTableViewCell(
- type: .zip,
- contents: "",
- lastInList: false,
- delegate: self
- ),
- at: zipFieldIndex
- )
- addressCells = mutableAddressCells
- delegate?.addressViewModel(self, addedCellAt: zipFieldIndex)
- delegate?.addressViewModelDidChange(self)
- }
- } else if !shouldBeShowingPostalCode && showingPostalCodeCell {
- if containsStateAndPostalFields() {
- if let zipFieldIndex = addressCells.firstIndex(where: { $0.type == .zip }) {
-
- var mutableAddressCells = addressCells
- mutableAddressCells.remove(at: zipFieldIndex)
- addressCells = mutableAddressCells
- delegate?.addressViewModel(self, removedCellAt: zipFieldIndex)
- delegate?.addressViewModelDidChange(self)
- }
- }
- }
- showingPostalCodeCell = shouldBeShowingPostalCode
- delegate?.addressViewModelDidUpdate(self)
- }
-
- private func containsStateAndPostalFields() -> Bool {
- if isBillingAddress {
- return requiredBillingAddressFields == .full
- } else {
- return requiredShippingAddressFields?.contains(.postalAddress) ?? false
- }
- }
-
- func updateCityAndState(fromZipCodeCell zipCell: STPAddressFieldTableViewCell?) {
-
- let zipCode = zipCell?.contents
-
- if geocodeInProgress || zipCode == nil || !(zipCell?.textField.validText ?? false)
- || !(addressFieldTableViewCountryCode == "US")
- {
- return
- }
-
- var cityCell: STPAddressFieldTableViewCell?
- var stateCell: STPAddressFieldTableViewCell?
- for cell in addressCells {
- if cell.type == .city {
- cityCell = cell
- } else if cell.type == .state {
- stateCell = cell
- }
- }
-
- if (cityCell == nil && stateCell == nil)
- || ((cityCell?.contents?.count ?? 0) > 0 || (stateCell?.contents?.count ?? 0) > 0)
- {
- // Don't auto fill if either have text already
- // Or if neither are non-nil
- return
- } else {
- geocodeInProgress = true
- let geocoder = CLGeocoder()
-
- let onCompletion: CLGeocodeCompletionHandler = { placemarks, error in
- stpDispatchToMainThreadIfNecessary({
- if (placemarks?.count ?? 0) > 0 && error == nil {
- let placemark = placemarks?.first
- if (cityCell?.contents?.count ?? 0) == 0
- && (stateCell?.contents?.count ?? 0) == 0
- && (zipCell?.contents == zipCode)
- {
- // Check contents again to make sure they're still empty
- // And that zipcode hasn't changed to something else
- cityCell?.contents = placemark?.locality
- stateCell?.contents = placemark?.administrativeArea
- }
- }
- self.geocodeInProgress = false
- })
- }
-
- let address = CNMutablePostalAddress()
- address.postalCode = zipCode ?? ""
- address.isoCountryCode = addressFieldTableViewCountryCode ?? ""
-
- geocoder.geocodePostalAddress(
- address,
- completionHandler: onCompletion
- )
- }
- }
-
- private func cell(after cell: STPAddressFieldTableViewCell?) -> STPAddressFieldTableViewCell? {
- guard let cell = cell,
- let cellIndex = addressCells.firstIndex(of: cell),
- cellIndex + 1 < addressCells.count
- else {
- return nil
- }
- return addressCells[cellIndex + 1]
- }
-
- func addressFieldTableViewCellDidUpdateText(_ cell: STPAddressFieldTableViewCell) {
- delegate?.addressViewModelDidChange(self)
- }
-
- func addressFieldTableViewCellDidReturn(_ cell: STPAddressFieldTableViewCell) {
- _ = self.cell(after: cell)?.becomeFirstResponder()
- }
-
- func addressFieldTableViewCellDidEndEditing(_ cell: STPAddressFieldTableViewCell) {
- if cell.type == .zip {
- updateCityAndState(fromZipCodeCell: cell)
- }
- }
-
-}
diff --git a/Stripe/StripeiOS/Source/STPAnalyticsClient+BasicUI.swift b/Stripe/StripeiOS/Source/STPAnalyticsClient+BasicUI.swift
deleted file mode 100644
index 809037ab7a5..00000000000
--- a/Stripe/StripeiOS/Source/STPAnalyticsClient+BasicUI.swift
+++ /dev/null
@@ -1,160 +0,0 @@
-//
-// STPAnalyticsClient+BasicUI.swift
-// StripeiOS
-//
-// Created by Yuki Tokuhiro on 1/24/24.
-//
-
-import Foundation
-@_spi(STP) import StripeCore
-@_spi(STP) import StripePayments
-
-extension STPPaymentContext {
- final class AnalyticsLogger {
- let analyticsClient = STPAnalyticsClient.sharedClient
- var apiClient: STPAPIClient = .shared
- var product: String
- lazy var commonParameters: [String: Any] = {
- [
- "product": product,
- ]
- }()
-
- init(product: T.Type) {
- self.product = product.stp_analyticsIdentifier
- }
-
- // MARK: - Loading
-
- func logLoadStarted() {
- log(event: .biLoadStarted, params: [:])
- }
-
- func logLoadSucceeded(loadStartDate: Date, defaultPaymentOption: STPPaymentOption?) {
- let event: STPAnalyticEvent = .biLoadSucceeded
- let duration = Date().timeIntervalSince(loadStartDate)
- let defaultPaymentMethod: String = {
- guard let defaultPaymentOption else {
- return "none"
- }
- switch defaultPaymentOption {
- case is STPApplePayPaymentOption:
- return "apple_pay"
- case let defaultPaymentMethod as STPPaymentMethod:
- return defaultPaymentMethod.type.identifier
- default:
- assertionFailure()
- return "unknown"
- }
- }()
- let params: [String: Any] = [
- "duration": duration,
- "selected_lpm": defaultPaymentMethod,
- ]
- log(event: event, params: params)
- }
-
- func logLoadFailed(loadStartDate: Date, error: Error) {
- let event: STPAnalyticEvent = .biLoadFailed
- let duration = Date().timeIntervalSince(loadStartDate)
- var params: [String: Any] = [
- "duration": duration,
- ]
- params.mergeAssertingOnOverwrites(error.serializeForV1Analytics())
- log(event: event, params: params)
- }
-
- // MARK: - Payment
-
- func logPayment(status: STPPaymentStatus, loadStartDate: Date?, paymentOption: STPPaymentOption, error: Error?) {
- let didSucceed: Bool
- switch status {
- case .userCancellation:
- // Don't send analytic for cancels
- return
- case .success:
- didSucceed = true
- case .error:
- didSucceed = false
- @unknown default:
- return
- }
-
- let event: STPAnalyticEvent
- let paymentMethodType: String
- switch paymentOption {
- case let paymentMethod as STPPaymentMethod:
- paymentMethodType = paymentMethod.type.identifier
- event = didSucceed ? .biPaymentCompleteSavedPMSuccess : .biPaymentCompleteSavedPMFailure
- case let params as STPPaymentMethodParams:
- paymentMethodType = params.type.identifier
- event = didSucceed ? .biPaymentCompleteNewPMSuccess : .biPaymentCompleteNewPMFailure
- case is STPApplePayPaymentOption:
- paymentMethodType = "apple_pay"
- event = didSucceed ? .biPaymentCompleteApplePaySuccess : .biPaymentCompleteApplePayFailure
- default:
- assertionFailure("Unknown payment option!")
- return
- }
-
- var params: [String: Any] = ["selected_lpm": paymentMethodType]
- if let error {
- params.mergeAssertingOnOverwrites(error.serializeForV1Analytics())
- }
- if let loadStartDate {
- params["duration"] = Date().timeIntervalSince(loadStartDate)
- }
-
- log(event: event, params: params)
- }
-
- // MARK: - UI
-
- func logPaymentOptionsScreenAppeared() {
- log(event: .biOptionsShown, params: [:])
- }
-
- func logFormShown(paymentMethodType: STPPaymentMethodType) {
- let event = STPAnalyticEvent.biFormShown
- let params = ["selected_lpm": paymentMethodType]
- log(event: event, params: params)
- }
-
- /// - Parameter shownStartDate: The date when the form was first shown. This should never be nil.
- func logDoneButtonTapped(paymentMethodType: STPPaymentMethodType, shownStartDate: Date?) {
- let event = STPAnalyticEvent.biDoneButtonTapped
-
- var params: [String: Any] = [
- "selected_lpm": paymentMethodType,
- ]
- if let shownStartDate {
- let duration = Date().timeIntervalSince(shownStartDate)
- params["duration"] = duration
- } else if NSClassFromString("XCTest") == nil {
- assertionFailure("Shown start date should never be nil!")
- }
-
- log(event: event, params: params)
- }
-
- func logFormInteracted(paymentMethodType: STPPaymentMethodType) {
- log(event: .biFormInteracted, params: [
- "selected_lpm": paymentMethodType,
- ])
- }
-
- func logCardNumberCompleted() {
- log(event: .biCardNumberCompleted, params: [:])
- }
-
- // MARK: - Helpers
-
- private func log(event: STPAnalyticEvent, params: [String: Any]) {
- let analytic = GenericAnalytic(event: event, params: params.merging(commonParameters, uniquingKeysWith: { new, _ in
- assertionFailure("Constructing analytics parameters with duplicate keys")
- return new
- }))
- analyticsClient.log(analytic: analytic, apiClient: apiClient)
- }
- }
-}
diff --git a/Stripe/StripeiOS/Source/STPBankSelectionViewController.swift b/Stripe/StripeiOS/Source/STPBankSelectionViewController.swift
index b6fb8cdd235..84a113faae7 100644
--- a/Stripe/StripeiOS/Source/STPBankSelectionViewController.swift
+++ b/Stripe/StripeiOS/Source/STPBankSelectionViewController.swift
@@ -91,7 +91,6 @@ public class STPBankSelectionViewController: STPCoreTableViewController, UITable
private var selectedBank: STPFPXBankBrand = .unknown
private var configuration: STPPaymentConfiguration?
private weak var imageView: UIImageView?
- private var headerView: STPSectionHeaderView?
private var loading = false
private var bankStatus: STPFPXBankStatusResponse?
@@ -128,10 +127,6 @@ public class STPBankSelectionViewController: STPCoreTableViewController, UITable
tableView?.reloadData()
}
- @objc override func useSystemBackButton() -> Bool {
- return true
- }
-
func _update(withBankStatus bankStatusResponse: STPFPXBankStatusResponse) {
bankStatus = bankStatusResponse
diff --git a/Stripe/StripeiOS/Source/STPBlocks.swift b/Stripe/StripeiOS/Source/STPBlocks.swift
index e750cc71983..78b8014f087 100644
--- a/Stripe/StripeiOS/Source/STPBlocks.swift
+++ b/Stripe/StripeiOS/Source/STPBlocks.swift
@@ -31,14 +31,3 @@ typealias STPFPXBankStatusCompletionBlock = (STPFPXBankStatusResponse?, Error?)
/// The shipping address is invalid.
case invalid
}
-
-/// A callback to be run with a validation result and shipping methods for a
-/// shipping address.
-/// - Parameters:
-/// - status: An enum representing whether the shipping address is valid.
-/// - shippingValidationError: If the shipping address is invalid, an error describing the issue with the address. If no error is given and the address is invalid, the default error message will be used.
-/// - shippingMethods: The shipping methods available for the address.
-/// - selectedShippingMethod: The default selected shipping method for the address.
-public typealias STPShippingMethodsCompletionBlock = (
- STPShippingStatus, Error?, [PKShippingMethod]?, PKShippingMethod?
-) -> Void
diff --git a/Stripe/StripeiOS/Source/STPCameraView.swift b/Stripe/StripeiOS/Source/STPCameraView.swift
deleted file mode 100644
index 17fa42470b5..00000000000
--- a/Stripe/StripeiOS/Source/STPCameraView.swift
+++ /dev/null
@@ -1,77 +0,0 @@
-//
-// STPCameraView.swift
-// StripeiOS
-//
-// Created by David Estes on 8/17/20.
-// Copyright © 2020 Stripe, Inc. All rights reserved.
-//
-
-import AVFoundation
-import UIKit
-
-@available(macCatalyst 14.0, *)
-class STPCameraView: UIView {
- private var flashLayer: CALayer?
-
- var captureSession: AVCaptureSession? {
- get {
- return (videoPreviewLayer.session)!
- }
- set(captureSession) {
- videoPreviewLayer.session = captureSession
- }
- }
-
- var videoPreviewLayer: AVCaptureVideoPreviewLayer {
- return layer as! AVCaptureVideoPreviewLayer
- }
-
- func playSnapshotAnimation() {
- CATransaction.begin()
- CATransaction.setValue(
- kCFBooleanTrue,
- forKey: kCATransactionDisableActions
- )
- flashLayer?.frame = CGRect(
- x: 0,
- y: 0,
- width: layer.bounds.size.width,
- height: layer.bounds.size.height
- )
- flashLayer?.opacity = 1.0
- CATransaction.commit()
- DispatchQueue.main.async(execute: {
- let fadeAnim = CABasicAnimation(keyPath: "opacity")
- fadeAnim.fromValue = NSNumber(value: 1.0)
- fadeAnim.toValue = NSNumber(value: 0.0)
- fadeAnim.duration = 1.0
- self.flashLayer?.add(fadeAnim, forKey: "opacity")
- self.flashLayer?.opacity = 0.0
- })
- }
-
- override init(
- frame: CGRect
- ) {
- super.init(frame: frame)
- flashLayer = CALayer()
- if let flashLayer = flashLayer {
- layer.addSublayer(flashLayer)
- }
- flashLayer?.masksToBounds = true
- flashLayer?.backgroundColor = UIColor.black.cgColor
- flashLayer?.opacity = 0.0
- layer.masksToBounds = true
- videoPreviewLayer.videoGravity = .resizeAspectFill
- }
-
- override class var layerClass: AnyClass {
- return AVCaptureVideoPreviewLayer.self
- }
-
- required init?(
- coder aDecoder: NSCoder
- ) {
- super.init(coder: aDecoder)
- }
-}
diff --git a/Stripe/StripeiOS/Source/STPCardScanner.swift b/Stripe/StripeiOS/Source/STPCardScanner.swift
deleted file mode 100644
index c55e104b467..00000000000
--- a/Stripe/StripeiOS/Source/STPCardScanner.swift
+++ /dev/null
@@ -1,488 +0,0 @@
-//
-// STPCardScanner.swift
-// StripeiOS
-//
-// Created by David Estes on 8/17/20.
-// Copyright © 2020 Stripe, Inc. All rights reserved.
-//
-
-import AVFoundation
-import Foundation
-@_spi(STP) import StripeCore
-@_spi(STP) import StripePayments
-@_spi(STP) import StripePaymentsUI
-import UIKit
-import Vision
-
-enum STPCardScannerError: Int {
- /// Camera not available.
- case cameraNotAvailable
-}
-
-@available(macCatalyst 14.0, *)
-@objc protocol STPCardScannerDelegate: NSObjectProtocol {
- @objc(cardScanner:didFinishWithCardParams:error:) func cardScanner(
- _ scanner: STPCardScanner,
- didFinishWith cardParams:
- STPPaymentMethodCardParams?,
- error: Error?)
-}
-
-@available(macCatalyst 14.0, *)
-@objc(STPCardScanner_legacy)
-class STPCardScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
- // iOS will kill the app if it tries to request the camera without an NSCameraUsageDescription
- static let cardScanningAvailableCameraHasUsageDescription = {
- return
- (Bundle.main.infoDictionary?["NSCameraUsageDescription"] != nil
- || Bundle.main.localizedInfoDictionary?["NSCameraUsageDescription"] != nil)
- }()
-
- static var cardScanningAvailable: Bool {
- // Always allow in tests:
- if NSClassFromString("XCTest") != nil {
- return true
- }
- return cardScanningAvailableCameraHasUsageDescription
- }
-
- weak var cameraView: STPCameraView?
-
- var feedbackGenerator: UINotificationFeedbackGenerator?
-
- @objc public var deviceOrientation: UIDeviceOrientation {
- get {
- return stp_deviceOrientation
- }
- set(newDeviceOrientation) {
- stp_deviceOrientation = newDeviceOrientation
-
- // This is an optimization for portrait mode: The card will be centered in the screen,
- // so we can ignore the top and bottom. We'll use the whole frame in landscape.
- let kSTPCardScanningScreenCenter = CGRect(
- x: 0, y: CGFloat(0.3), width: 1, height: CGFloat(0.4))
-
- // iOS camera image data is returned in LandcapeLeft orientation by default. We'll flip it as needed:
- switch newDeviceOrientation {
- case .portraitUpsideDown:
- videoOrientation = .portraitUpsideDown
- textOrientation = .left
- regionOfInterest = kSTPCardScanningScreenCenter
- case .landscapeLeft:
- videoOrientation = .landscapeRight
- textOrientation = .up
- regionOfInterest = CGRect(x: 0, y: 0, width: 1, height: 1)
- case .landscapeRight:
- videoOrientation = .landscapeLeft
- textOrientation = .down
- regionOfInterest = CGRect(x: 0, y: 0, width: 1, height: 1)
- case .portrait, .faceUp, .faceDown, .unknown:
- fallthrough
- default:
- videoOrientation = .portrait
- textOrientation = .right
- regionOfInterest = kSTPCardScanningScreenCenter
- }
- cameraView?.videoPreviewLayer.connection?.videoOrientation = videoOrientation
- }
- }
-
- override init() {
- }
-
- init(delegate: STPCardScannerDelegate?) {
- super.init()
- self.delegate = delegate
- captureSessionQueue = DispatchQueue(label: "com.stripe.CardScanning.CaptureSessionQueue")
- deviceOrientation = UIDevice.current.orientation
- }
-
- func start() {
- if isScanning {
- return
- }
- STPAnalyticsClient.sharedClient.addClass(toProductUsageIfNecessary: STPCardScanner.self)
- startTime = Date()
-
- isScanning = true
- timeoutTime = nil
- feedbackGenerator = UINotificationFeedbackGenerator()
- feedbackGenerator?.prepare()
-
- captureSessionQueue?.async(execute: {
- #if targetEnvironment(simulator)
- // Camera not supported on Simulator
- self.stopWithError(STPCardScanner.stp_cardScanningError())
- return
- #else
- self.detectedNumbers = NSCountedSet()
- self.detectedExpirations = NSCountedSet()
- self.setupCamera()
- DispatchQueue.main.async(execute: {
- self.cameraView?.captureSession = self.captureSession
- self.cameraView?.videoPreviewLayer.connection?.videoOrientation =
- self.videoOrientation
- })
- #endif
- })
- }
-
- func stop() {
- stopWithError(nil)
- }
-
- private weak var delegate: STPCardScannerDelegate?
- private var captureDevice: AVCaptureDevice?
- private var captureSession: AVCaptureSession?
- private var captureSessionQueue: DispatchQueue?
- private var videoDataOutput: AVCaptureVideoDataOutput?
- private var videoDataOutputQueue: DispatchQueue?
- private var textRequest: VNRecognizeTextRequest?
- private var isScanning = false
-
- private var timeoutTime: Date?
- private var didTimeout: Bool {
- if let timeoutTime = timeoutTime {
- return timeoutTime <= Date()
- }
- return false
- }
-
- private var stp_deviceOrientation: UIDeviceOrientation!
- private var videoOrientation: AVCaptureVideoOrientation!
- private var textOrientation: CGImagePropertyOrientation!
- private var regionOfInterest = CGRect.zero
- private var detectedNumbers = NSCountedSet()
- private var detectedExpirations = NSCountedSet()
- private var startTime: Date?
-
- // MARK: Public
-
- class func stp_cardScanningError() -> Error {
- let userInfo = [
- NSLocalizedDescriptionKey: String.Localized.allow_camera_access,
- STPError.errorMessageKey: "The camera couldn't be used.",
- ]
- return NSError(
- domain: STPCardScannerErrorDomain,
- code: STPCardScannerError.cameraNotAvailable.rawValue,
- userInfo: userInfo)
- }
-
- deinit {
- if isScanning {
- captureDevice?.unlockForConfiguration()
- captureSession?.stopRunning()
- }
- }
-
- func stopWithError(_ error: Error?) {
- if isScanning {
- finish(with: nil, error: error)
- }
- }
-
- // MARK: Setup
- func setupCamera() {
- weak var weakSelf = self
- textRequest = VNRecognizeTextRequest(completionHandler: { request, error in
- let strongSelf = weakSelf
- if !(strongSelf?.isScanning ?? false) {
- return
- }
- if error != nil {
- strongSelf?.stopWithError(STPCardScanner.stp_cardScanningError())
- return
- }
- strongSelf?.processVNRequest(request)
- })
-
- let captureDevice = AVCaptureDevice.default(
- .builtInWideAngleCamera, for: .video, position: .back)
- self.captureDevice = captureDevice
-
- captureSession = AVCaptureSession()
- captureSession?.sessionPreset = .hd1920x1080
-
- var deviceInput: AVCaptureDeviceInput?
- do {
- if let captureDevice = captureDevice {
- deviceInput = try AVCaptureDeviceInput(device: captureDevice)
- }
- } catch {
- stopWithError(STPCardScanner.stp_cardScanningError())
- return
- }
-
- if let deviceInput = deviceInput {
- if captureSession?.canAddInput(deviceInput) ?? false {
- captureSession?.addInput(deviceInput)
- } else {
- stopWithError(STPCardScanner.stp_cardScanningError())
- return
- }
- }
-
- videoDataOutputQueue = DispatchQueue(label: "com.stripe.CardScanning.VideoDataOutputQueue")
- videoDataOutput = AVCaptureVideoDataOutput()
- videoDataOutput?.alwaysDiscardsLateVideoFrames = true
- videoDataOutput?.setSampleBufferDelegate(self, queue: videoDataOutputQueue)
-
- // This is the recommended pixel buffer format for Vision:
- videoDataOutput?.videoSettings = [
- kCVPixelBufferPixelFormatTypeKey as String:
- kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
- ]
-
- if let videoDataOutput = videoDataOutput {
- if captureSession?.canAddOutput(videoDataOutput) ?? false {
- captureSession?.addOutput(videoDataOutput)
- } else {
- stopWithError(STPCardScanner.stp_cardScanningError())
- return
- }
- }
-
- // This improves recognition quality, but means the VideoDataOutput buffers won't match what we're seeing on screen.
- videoDataOutput?.connection(with: .video)?.preferredVideoStabilizationMode = .auto
-
- captureSession?.startRunning()
-
- do {
- try self.captureDevice?.lockForConfiguration()
- self.captureDevice?.autoFocusRangeRestriction = .near
- } catch {
- }
- }
-
- // MARK: Processing
- func captureOutput(
- _ output: AVCaptureOutput,
- didOutput sampleBuffer: CMSampleBuffer,
- from connection: AVCaptureConnection
- ) {
- if !isScanning {
- return
- }
- let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
- if pixelBuffer == nil {
- return
- }
- textRequest?.recognitionLevel = .accurate
- textRequest?.usesLanguageCorrection = false
- textRequest?.regionOfInterest = regionOfInterest
- var handler: VNImageRequestHandler?
- if let pixelBuffer = pixelBuffer {
- handler = VNImageRequestHandler(
- cvPixelBuffer: pixelBuffer, orientation: textOrientation, options: [:])
- }
- do {
- try handler?.perform([textRequest].compactMap { $0 })
- } catch {
- }
- }
-
- func processVNRequest(_ request: VNRequest) {
- var allNumbers: [String] = []
- for observation in request.results ?? [] {
- guard let observation = observation as? VNRecognizedTextObservation else {
- continue
- }
- let candidates = observation.topCandidates(5)
- let topCandidate = candidates.first?.string
- if STPCardValidator.sanitizedNumericString(for: topCandidate ?? "").count >= 4 {
- allNumbers.append(topCandidate ?? "")
- }
- for recognizedText in candidates {
- let possibleNumber = STPCardValidator.sanitizedNumericString(
- for: recognizedText.string)
- if possibleNumber.count < 4 {
- continue // This probably isn't something we're interested in, so don't bother processing it.
- }
-
- // First strategy: We check if Vision sent us a number in a group on its own. If that fails, we'll try
- // to catch it later when we iterate over all the numbers.
- if STPCardValidator.validationState(
- forNumber: possibleNumber, validatingCardBrand: true)
- == .valid
- {
- addDetectedNumber(possibleNumber)
- } else if possibleNumber.count >= 4 && possibleNumber.count <= 6
- && STPStringUtils.stringMayContainExpirationDate(recognizedText.string)
- {
- // Try to parse anything that looks like an expiration date.
- let expirationString = STPStringUtils.expirationDateString(
- from: recognizedText.string)
- let sanitizedExpiration = STPCardValidator.sanitizedNumericString(
- for: expirationString ?? "")
- let month = (sanitizedExpiration as NSString).substring(to: 2)
- let year = (sanitizedExpiration as NSString).substring(from: 2)
-
- // Ignore expiration dates 10+ years in the future, as they're likely to be incorrect recognitions
- let calendar = Calendar(identifier: .gregorian)
- let presentYear = calendar.component(.year, from: Date())
- let maxYear = (presentYear % 100) + 10
-
- if STPCardValidator.validationState(forExpirationYear: year, inMonth: month)
- == .valid
- && Int(year) ?? 0 < maxYear
- {
- addDetectedExpiration(sanitizedExpiration)
- }
- }
- }
- }
- // Second strategy: We look for consecutive groups of 4/4/4/4 or 4/6/5
- // Vision is sending us groups like ["1234 565", "1234 1"], so we'll normalize these into groups with spaces:
- let allGroups = allNumbers.joined(separator: " ").components(separatedBy: " ")
- if allGroups.count < 3 {
- return
- }
- for i in 0..<(allGroups.count - 3) {
- let string1 = allGroups[i]
- let string2 = allGroups[i + 1]
- let string3 = allGroups[i + 2]
- var string4 = ""
- if i + 3 < allGroups.count {
- string4 = allGroups[i + 3]
- }
- // Then we'll go through each group and build a potential match:
- let potentialCardString = "\(string1)\(string2)\(string3)\(string4)"
- let potentialAmexString = "\(string1)\(string2)\(string3)"
-
- // Then we'll add valid matches. It's okay if we add a number a second time after doing so above, as the success of that first pass means it's more likely to be a good match.
- if STPCardValidator.validationState(
- forNumber: potentialCardString, validatingCardBrand: true)
- == .valid
- {
- addDetectedNumber(potentialCardString)
- } else if STPCardValidator.validationState(
- forNumber: potentialAmexString, validatingCardBrand: true) == .valid
- {
- addDetectedNumber(potentialAmexString)
- }
- }
- }
-
- func addDetectedNumber(_ number: String) {
- detectedNumbers.add(number)
-
- // Set a timeout: If we don't get enough scans in the next 0.6 seconds, we'll use the best option we have.
- if timeoutTime == nil {
- self.timeoutTime = Date().addingTimeInterval(kSTPCardScanningTimeout)
- weak var weakSelf = self
- DispatchQueue.main.async(execute: {
- let strongSelf = weakSelf
- strongSelf?.cameraView?.playSnapshotAnimation()
- strongSelf?.feedbackGenerator?.notificationOccurred(.success)
- })
- // Just in case we don't get any frames, add another call to `finishIfReady` after timeoutTime to check
- videoDataOutputQueue?.asyncAfter(
- deadline: DispatchTime.now() + kSTPCardScanningTimeout,
- execute: {
- let strongSelf = weakSelf
- if strongSelf?.isScanning ?? false {
- strongSelf?.finishIfReady()
- }
- })
- }
-
- if (detectedNumbers.count(for: number)) >= kSTPCardScanningMinimumValidScans {
- finishIfReady()
- }
- }
-
- func addDetectedExpiration(_ expiration: String) {
- detectedExpirations.add(expiration)
- if (detectedExpirations.count(for: expiration)) >= kSTPCardScanningMinimumValidScans {
- finishIfReady()
- }
- }
-
- // MARK: Completion
- func finishIfReady() {
- if !isScanning {
- return
- }
- let detectedNumbers = self.detectedNumbers
- let detectedExpirations = self.detectedExpirations
-
- let topNumber = (detectedNumbers.allObjects as NSArray).sortedArray(comparator: {
- obj1, obj2 in
- let c1 = detectedNumbers.count(for: obj1)
- let c2 = detectedNumbers.count(for: obj2)
- if c1 < c2 {
- return .orderedAscending
- } else if c1 > c2 {
- return .orderedDescending
- } else {
- return .orderedSame
- }
- }).last
- let topExpiration = (detectedExpirations.allObjects as NSArray).sortedArray(comparator: {
- obj1, obj2 in
- let c1 = detectedExpirations.count(for: obj1)
- let c2 = detectedExpirations.count(for: obj2)
- if c1 < c2 {
- return .orderedAscending
- } else if c1 > c2 {
- return .orderedDescending
- } else {
- return .orderedSame
- }
- }).last
-
- var didSeeEnoughScans = false
- if let topNumber = topNumber, let topExpiration = topExpiration {
- didSeeEnoughScans = detectedNumbers.count(for: topNumber) >= kSTPCardScanningMinimumValidScans &&
- detectedExpirations.count(for: topExpiration) >= kSTPCardScanningMinimumValidScans
- }
- if didTimeout || didSeeEnoughScans {
- let params = STPPaymentMethodCardParams()
- params.number = topNumber as? String
- if let topExpiration = topExpiration {
- params.expMonth = NSNumber(
- value: Int((topExpiration as! NSString).substring(to: 2)) ?? 0)
- params.expYear = NSNumber(
- value: Int((topExpiration as! NSString).substring(from: 2)) ?? 0)
- }
- finish(with: params, error: nil)
- }
- }
-
- func finish(with params: STPPaymentMethodCardParams?, error: Error?) {
- var duration: TimeInterval?
- if let startTime = startTime {
- duration = Date().timeIntervalSince(startTime)
- }
- isScanning = false
- captureDevice?.unlockForConfiguration()
- captureSession?.stopRunning()
-
- DispatchQueue.main.async(execute: {
- if params == nil {
- STPAnalyticsClient.sharedClient.logCardScanCancelled(withDuration: duration ?? 0.0)
- } else {
- STPAnalyticsClient.sharedClient.logCardScanSucceeded(withDuration: duration ?? 0.0)
- }
- self.feedbackGenerator = nil
-
- self.cameraView?.captureSession = nil
- self.delegate?.cardScanner(self, didFinishWith: params, error: error)
- })
- }
-
- // MARK: Orientation
-}
-
-// The number of successful scans required for both card number and expiration date before returning a result.
-private let kSTPCardScanningMinimumValidScans = 2
-// Once one successful scan is found, we'll stop scanning after this many seconds.
-private let kSTPCardScanningTimeout: TimeInterval = 0.6
-let STPCardScannerErrorDomain = "STPCardScannerErrorDomain"
-
-/// :nodoc:
-@available(macCatalyst 14.0, *)
-extension STPCardScanner: STPAnalyticsProtocol {
- static var stp_analyticsIdentifier = "STPCardScanner"
-}
diff --git a/Stripe/StripeiOS/Source/STPCardScannerTableViewCell.swift b/Stripe/StripeiOS/Source/STPCardScannerTableViewCell.swift
deleted file mode 100644
index 7692e148785..00000000000
--- a/Stripe/StripeiOS/Source/STPCardScannerTableViewCell.swift
+++ /dev/null
@@ -1,67 +0,0 @@
-//
-// STPCardScannerTableViewCell.swift
-// StripeiOS
-//
-// Created by David Estes on 8/17/20.
-// Copyright © 2020 Stripe, Inc. All rights reserved.
-//
-
-import UIKit
-
-@available(macCatalyst 14.0, *)
-class STPCardScannerTableViewCell: UITableViewCell {
- private(set) weak var cameraView: STPCameraView?
-
- private var _theme: STPTheme?
- var theme: STPTheme? {
- get {
- _theme
- }
- set(theme) {
- _theme = theme
- updateAppearance()
- }
- }
-
- let cardSizeRatio: CGFloat = 2.125 / 3.370 // ID-1 card size (in inches)
-
- override init(
- style: UITableViewCell.CellStyle,
- reuseIdentifier: String?
- ) {
- super.init(style: style, reuseIdentifier: reuseIdentifier)
- let cameraView = STPCameraView(frame: bounds)
- contentView.addSubview(cameraView)
- self.cameraView = cameraView
- theme = STPTheme.defaultTheme
- self.cameraView?.translatesAutoresizingMaskIntoConstraints = false
- contentView.addConstraints(
- [
- cameraView.heightAnchor.constraint(
- equalTo: cameraView.widthAnchor,
- multiplier: cardSizeRatio
- ),
- cameraView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0),
- cameraView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 0),
- cameraView.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: 0),
- cameraView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0),
- ])
- updateAppearance()
- }
-
- override func layoutSubviews() {
-
- super.layoutSubviews()
- }
-
- @objc func updateAppearance() {
- // The first few frames of the camera view will be black, so our background should be black too.
- cameraView?.backgroundColor = UIColor.black
- }
-
- required init?(
- coder aDecoder: NSCoder
- ) {
- super.init(coder: aDecoder)
- }
-}
diff --git a/Stripe/StripeiOS/Source/STPCoreViewController.swift b/Stripe/StripeiOS/Source/STPCoreViewController.swift
index 105b544d8e6..a62803667e5 100644
--- a/Stripe/StripeiOS/Source/STPCoreViewController.swift
+++ b/Stripe/StripeiOS/Source/STPCoreViewController.swift
@@ -69,23 +69,6 @@ public class STPCoreViewController: UIViewController {
_theme = .defaultTheme
}
- if !useSystemBackButton() {
- cancelItem = UIBarButtonItem(
- barButtonSystemItem: .cancel,
- target: self,
- action: #selector(STPAddCardViewController.handleCancelTapped(_:))
- )
- cancelItem?.accessibilityIdentifier = "CoreViewControllerCancelIdentifier"
-
- stp_navigationItemProxy?.leftBarButtonItem = cancelItem
- }
-
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(STPAddCardViewController.updateAppearance),
- name: UIContentSizeCategory.didChangeNotification,
- object: nil
- )
}
/// Called in viewDidLoad after doing base implementation, before
@@ -151,12 +134,4 @@ public class STPCoreViewController: UIViewController {
}
}
- /// If you override this and return YES, then your CoreVC implementation will not
- /// create and set up a cancel and instead just use the default
- /// UIViewController back button behavior.
- /// You won't receive calls to `handleCancelTapped` if this is YES.
- /// Defaults to NO.
- func useSystemBackButton() -> Bool {
- return false
- }
}
diff --git a/Stripe/StripeiOS/Source/STPCustomerContext.swift b/Stripe/StripeiOS/Source/STPCustomerContext.swift
deleted file mode 100644
index 13820fa2094..00000000000
--- a/Stripe/StripeiOS/Source/STPCustomerContext.swift
+++ /dev/null
@@ -1,419 +0,0 @@
-//
-// STPCustomerContext.swift
-// StripeiOS
-//
-// Created by Ben Guo on 5/2/17.
-// Copyright © 2017 Stripe, Inc. All rights reserved.
-//
-
-import Foundation
-@_spi(STP) import StripeCore
-@_spi(STP) import StripePaymentsUI
-
-/// An `STPCustomerContext` retrieves and updates a Stripe customer and their attached
-/// payment methods using an ephemeral key, a short-lived API key scoped to a specific
-/// customer object. If your current user logs out of your app and a new user logs in,
-/// be sure to either create a new instance of `STPCustomerContext` or clear the current
-/// instance's cache. On your backend, be sure to create and return a
-/// new ephemeral key for the Customer object associated with the new user.
-open class STPCustomerContext: NSObject, STPBackendAPIAdapter {
- /// Initializes a new `STPCustomerContext` with the specified key provider.
- /// Upon initialization, a CustomerContext will fetch a new ephemeral key from
- /// your backend and use it to prefetch the customer object specified in the key.
- /// Subsequent customer and payment method retrievals (e.g. by `STPPaymentContext`)
- /// will return the prefetched customer / attached payment methods immediately if
- /// its age does not exceed 60 seconds.
- /// - Parameter keyProvider: The key provider the customer context will use.
- /// - Returns: the newly-instantiated customer context.
- @objc(initWithKeyProvider:)
- public convenience init(
- keyProvider: STPCustomerEphemeralKeyProvider
- ) {
- self.init(keyProvider: keyProvider, apiClient: STPAPIClient.shared)
- }
-
- /// Initializes a new `STPCustomerContext` with the specified key provider.
- /// Upon initialization, a CustomerContext will fetch a new ephemeral key from
- /// your backend and use it to prefetch the customer object specified in the key.
- /// Subsequent customer and payment method retrievals (e.g. by `STPPaymentContext`)
- /// will return the prefetched customer / attached payment methods immediately if
- /// its age does not exceed 60 seconds.
- /// - Parameters:
- /// - keyProvider: The key provider the customer context will use.
- /// - apiClient: The API Client to use to make requests.
- /// - Returns: the newly-instantiated customer context.
- @objc(initWithKeyProvider:apiClient:)
- public convenience init(
- keyProvider: STPCustomerEphemeralKeyProvider?,
- apiClient: STPAPIClient
- ) {
- let keyManager = STPEphemeralKeyManager(
- keyProvider: keyProvider,
- apiVersion: STPAPIClient.apiVersion,
- performsEagerFetching: true
- )
- self.init(keyManager: keyManager, apiClient: apiClient)
- }
-
- /// `STPCustomerContext` will cache its customer object and associated payment methods
- /// for up to 60 seconds. If your current user logs out of your app and a new user logs
- /// in, be sure to either call this method or create a new instance of `STPCustomerContext`.
- /// On your backend, be sure to create and return a new ephemeral key for the
- /// customer object associated with the new user.
- @objc
- public func clearCache() {
- clearCachedCustomer()
- clearCachedPaymentMethods()
- }
-
- private var _includeApplePayPaymentMethods = false
- /// By default, `STPCustomerContext` will filter Apple Pay when it retrieves
- /// Payment Methods. Apple Pay payment methods should generally not be re-used and
- /// shouldn't be offered to customers as a new payment method (Apple Pay payment
- /// methods may only be re-used for subscriptions).
- /// If you are using `STPCustomerContext` to back your own UI and would like to
- /// disable Apple Pay filtering, set this property to YES.
- /// Note: If you are using `STPPaymentContext`, you should not change this property.
- @objc public var includeApplePayPaymentMethods: Bool {
- get {
- _includeApplePayPaymentMethods
- }
- set(includeApplePayMethods) {
- _includeApplePayPaymentMethods = includeApplePayMethods
- customer?.updateSources(filteringApplePay: !includeApplePayMethods)
- }
- }
-
- private var _customer: STPCustomer?
- private var customer: STPCustomer? {
- get {
- _customer
- }
- set(customer) {
- _customer = customer
- customerRetrievedDate = (customer) != nil ? Date() : nil
- }
- }
- @objc internal var customerRetrievedDate: Date?
-
- private var _paymentMethods: [STPPaymentMethod]?
- private var paymentMethods: [STPPaymentMethod]? {
- get {
- if !includeApplePayPaymentMethods {
- var paymentMethodsExcludingApplePay: [STPPaymentMethod]? = []
- for paymentMethod in _paymentMethods ?? [] {
- let isApplePay =
- paymentMethod.type == .card && paymentMethod.card?.wallet?.type == .applePay
- if !isApplePay {
- paymentMethodsExcludingApplePay?.append(paymentMethod)
- }
- }
- return paymentMethodsExcludingApplePay ?? []
- } else {
- return _paymentMethods ?? []
- }
- }
- set(paymentMethods) {
- _paymentMethods = paymentMethods
- paymentMethodsRetrievedDate = paymentMethods != nil ? Date() : nil
- }
- }
- @objc internal var paymentMethodsRetrievedDate: Date?
- private var keyManager: STPEphemeralKeyManagerProtocol
- private var apiClient: STPAPIClient
-
- init(
- keyManager: STPEphemeralKeyManagerProtocol,
- apiClient: STPAPIClient
- ) {
- STPAnalyticsClient.sharedClient.addClass(toProductUsageIfNecessary: STPCustomerContext.self)
- self.keyManager = keyManager
- self.apiClient = apiClient
- _includeApplePayPaymentMethods = false
- super.init()
- retrieveCustomer(nil)
- listPaymentMethodsForCustomer(completion: nil)
- }
-
- func clearCachedCustomer() {
- customer = nil
- }
-
- func clearCachedPaymentMethods() {
- paymentMethods = nil
- }
-
- func shouldUseCachedCustomer() -> Bool {
- if customer == nil || customerRetrievedDate == nil {
- return false
- }
- let now = Date()
- if let customerRetrievedDate = customerRetrievedDate {
- return now.timeIntervalSince(customerRetrievedDate) < CachedCustomerMaxAge
- }
- return false
- }
-
- func shouldUseCachedPaymentMethods() -> Bool {
- if paymentMethods == nil || paymentMethodsRetrievedDate == nil {
- return false
- }
- let now = Date()
- if let paymentMethodsRetrievedDate = paymentMethodsRetrievedDate {
- return now.timeIntervalSince(paymentMethodsRetrievedDate) < CachedCustomerMaxAge
- }
- return false
- }
-
- // MARK: - STPBackendAPIAdapter
- @objc
- public func retrieveCustomer(_ completion: STPCustomerCompletionBlock? = nil) {
- if shouldUseCachedCustomer() {
- if let completion = completion {
- stpDispatchToMainThreadIfNecessary({
- completion(self.customer, nil)
- })
- }
- return
- }
- keyManager.getOrCreateKey({ ephemeralKey, retrieveKeyError in
- guard let ephemeralKey = ephemeralKey, retrieveKeyError == nil else {
- if let completion = completion {
- stpDispatchToMainThreadIfNecessary({
- completion(nil, retrieveKeyError)
- })
- }
- return
- }
- self.apiClient.retrieveCustomer(using: ephemeralKey) { customer, error in
- if let customer = customer {
- customer.updateSources(filteringApplePay: !self.includeApplePayPaymentMethods)
- self.customer = customer
- }
- if let completion = completion {
- stpDispatchToMainThreadIfNecessary({
- completion(self.customer, error)
- })
- }
- }
- })
- }
-
- @objc
- public func updateCustomer(
- withShippingAddress shipping: STPAddress,
- completion: STPErrorBlock?
- ) {
- keyManager.getOrCreateKey({ ephemeralKey, retrieveKeyError in
- guard let ephemeralKey = ephemeralKey, retrieveKeyError == nil else {
- if let completion = completion {
- stpDispatchToMainThreadIfNecessary({
- completion(retrieveKeyError)
- })
- }
- return
- }
- var params: [String: Any] = [:]
- params["shipping"] = STPAddress.shippingInfoForCharge(
- with: shipping,
- shippingMethod: nil
- )
- self.apiClient.updateCustomer(
- withParameters: params,
- using: ephemeralKey
- ) { customer, error in
- if let customer = customer {
- customer.updateSources(filteringApplePay: !self.includeApplePayPaymentMethods)
- self.customer = customer
- }
- if let completion = completion {
- stpDispatchToMainThreadIfNecessary({
- completion(error)
- })
- }
- }
- })
- }
-
- /// A convenience method for attaching the PaymentMethod to the current Customer
- @objc
- public func attachPaymentMethodToCustomer(
- paymentMethodId: String,
- completion: STPErrorBlock?
- ) {
- keyManager.getOrCreateKey({ ephemeralKey, retrieveKeyError in
- guard let ephemeralKey = ephemeralKey, retrieveKeyError == nil else {
- if let completion = completion {
- stpDispatchToMainThreadIfNecessary({
- completion(retrieveKeyError)
- })
- }
- return
- }
-
- self.apiClient.attachPaymentMethod(
- paymentMethodId,
- toCustomerUsing: ephemeralKey
- ) { error in
- self.clearCachedPaymentMethods()
- if let completion = completion {
- stpDispatchToMainThreadIfNecessary({
- completion(error)
- })
- }
- }
- })
- }
-
- @objc
- public func attachPaymentMethod(
- toCustomer paymentMethod: STPPaymentMethod,
- completion: STPErrorBlock?
- ) {
- attachPaymentMethodToCustomer(
- paymentMethodId: paymentMethod.stripeId,
- completion: completion
- )
- }
-
- /// A convenience method for detaching the PaymentMethod to the current Customer
- @objc
- public func detachPaymentMethodFromCustomer(
- paymentMethodId: String,
- completion: STPErrorBlock?
- ) {
- keyManager.getOrCreateKey({ ephemeralKey, retrieveKeyError in
- guard let ephemeralKey = ephemeralKey, retrieveKeyError == nil else {
- if let completion = completion {
- stpDispatchToMainThreadIfNecessary({
- completion(retrieveKeyError)
- })
- }
- return
- }
-
- self.apiClient.detachPaymentMethod(
- paymentMethodId,
- fromCustomerUsing: ephemeralKey
- ) { error in
- self.clearCachedPaymentMethods()
- if let completion = completion {
- stpDispatchToMainThreadIfNecessary({
- completion(error)
- })
- }
- }
- })
-
- }
-
- @objc
- public func detachPaymentMethod(
- fromCustomer paymentMethod: STPPaymentMethod,
- completion: STPErrorBlock?
- ) {
- detachPaymentMethodFromCustomer(
- paymentMethodId: paymentMethod.stripeId,
- completion: completion
- )
- }
-
- @objc
- public func listPaymentMethodsForCustomer(completion: STPPaymentMethodsCompletionBlock? = nil) {
- if shouldUseCachedPaymentMethods() {
- if let completion = completion {
- stpDispatchToMainThreadIfNecessary({
- completion(self.paymentMethods, nil)
- })
- }
- return
- }
-
- keyManager.getOrCreateKey({ ephemeralKey, retrieveKeyError in
- guard let ephemeralKey = ephemeralKey, retrieveKeyError == nil else {
- if let completion = completion {
- stpDispatchToMainThreadIfNecessary({
- completion(nil, retrieveKeyError)
- })
- }
- return
- }
-
- self.apiClient.listPaymentMethodsForCustomer(using: ephemeralKey) {
- paymentMethods,
- error in
- if paymentMethods != nil {
- self.paymentMethods = paymentMethods
- }
- if let completion = completion {
- stpDispatchToMainThreadIfNecessary({
- completion(self.paymentMethods, error)
- })
- }
- }
- })
- }
-
- func saveLastSelectedPaymentMethodID(
- forCustomer paymentMethodID: String?,
- completion: STPErrorBlock?
- ) {
- keyManager.getOrCreateKey({ ephemeralKey, retrieveKeyError in
- guard let ephemeralKey = ephemeralKey, retrieveKeyError == nil else {
- if let completion = completion {
- stpDispatchToMainThreadIfNecessary({
- completion(retrieveKeyError)
- })
- }
- return
- }
-
- var customerToDefaultPaymentMethodID =
- (UserDefaults.standard.dictionary(forKey: kLastSelectedPaymentMethodDefaultsKey))
- as? [String: String] ?? [:]
- if let customerID = ephemeralKey.customerID {
- customerToDefaultPaymentMethodID[customerID] = paymentMethodID
- UserDefaults.standard.set(
- customerToDefaultPaymentMethodID,
- forKey: kLastSelectedPaymentMethodDefaultsKey
- )
- }
-
- if let completion = completion {
- stpDispatchToMainThreadIfNecessary({
- completion(nil)
- })
- }
- })
- }
-
- func retrieveLastSelectedPaymentMethodIDForCustomer(
- completion: @escaping (String?, Error?) -> Void
- ) {
- keyManager.getOrCreateKey({ ephemeralKey, retrieveKeyError in
- guard let ephemeralKey = ephemeralKey, retrieveKeyError == nil else {
- stpDispatchToMainThreadIfNecessary({
- completion(nil, retrieveKeyError)
- })
- return
- }
-
- let customerToDefaultPaymentMethodID =
- (UserDefaults.standard.dictionary(forKey: kLastSelectedPaymentMethodDefaultsKey))
- as? [String: String] ?? [:]
- stpDispatchToMainThreadIfNecessary({
- completion(customerToDefaultPaymentMethodID[ephemeralKey.customerID ?? ""], nil)
- })
- })
- }
-}
-
-/// Stores the key we use in NSUserDefaults to save a dictionary of Customer id to their last selected payment method ID
-private let kLastSelectedPaymentMethodDefaultsKey =
- UserDefaults.StripeKeys.customerToLastSelectedPaymentMethod.rawValue
-private let CachedCustomerMaxAge: TimeInterval = 60
-
-/// :nodoc:
-@_spi(STP) extension STPCustomerContext: STPAnalyticsProtocol {
- @_spi(STP) public static var stp_analyticsIdentifier = "STPCustomerContext"
-}
diff --git a/Stripe/StripeiOS/Source/STPEphemeralKey.swift b/Stripe/StripeiOS/Source/STPEphemeralKey.swift
index 6ddf10b62a1..de27b5cf3db 100644
--- a/Stripe/StripeiOS/Source/STPEphemeralKey.swift
+++ b/Stripe/StripeiOS/Source/STPEphemeralKey.swift
@@ -15,7 +15,6 @@ class STPEphemeralKey: NSObject, STPAPIResponseDecodable {
private(set) var livemode = false
private(set) var secret: String
private(set) var expires: Date
- private(set) var customerID: String?
private(set) var issuingCardID: String?
/// You cannot directly instantiate an `STPEphemeralKey`. You should instead use
@@ -70,7 +69,6 @@ class STPEphemeralKey: NSObject, STPAPIResponseDecodable {
return nil
}
let key = self.init(stripeID: stripeId, created: created, secret: secret, expires: expires)
- key.customerID = customerID
key.issuingCardID = issuingCardID
key.stripeID = stripeId
key.livemode = dict.stp_bool(forKey: "livemode", or: true)
diff --git a/Stripe/StripeiOS/Source/STPPaymentActivityIndicatorView.swift b/Stripe/StripeiOS/Source/STPPaymentActivityIndicatorView.swift
deleted file mode 100644
index 68c03b6ea39..00000000000
--- a/Stripe/StripeiOS/Source/STPPaymentActivityIndicatorView.swift
+++ /dev/null
@@ -1,135 +0,0 @@
-//
-// STPPaymentActivityIndicatorView.swift
-// StripeiOS
-//
-// Created by Jack Flintermann on 5/12/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-import UIKit
-
-/// This class can be used wherever you'd use a `UIActivityIndicatorView` and is intended to have a similar API. It renders as a spinning circle with a gap in it, similar to what you see in the App Store app or in the Apple Pay dialog when making a purchase. To change its color, set the `tintColor` property.
-public class STPPaymentActivityIndicatorView: UIView {
- /// Tell the view to start or stop spinning. If `hidesWhenStopped` is true, it will fade in/out if animated is true.
- @objc
- public func setAnimating(
- _ animating: Bool,
- animated: Bool
- ) {
- if animating == _animating {
- return
- }
- _animating = animating
- if animating {
- if hidesWhenStopped {
- UIView.animate(
- withDuration: animated ? 0.2 : 0,
- animations: {
- self.alpha = 1.0
- }
- )
- }
- var currentRotation = Double(0)
- if let currentLayer = layer.presentation() {
- currentRotation = Double(
- truncating: (currentLayer.value(forKeyPath: "transform.rotation.z") as! NSNumber)
- )
- }
- let animation = CABasicAnimation(keyPath: "transform.rotation.z")
- animation.fromValue = NSNumber(value: Float(currentRotation))
- let toValue = NSNumber(value: currentRotation + 2 * Double.pi)
- animation.toValue = toValue
- animation.duration = 1.0
- animation.repeatCount = Float.infinity
- layer.add(animation, forKey: "rotation")
- } else {
- if hidesWhenStopped {
- UIView.animate(
- withDuration: animated ? 0.2 : 0,
- animations: {
- self.alpha = 0.0
- }
- )
- }
- }
- }
-
- private var _animating = false
- /// Whether or not the view is animating.
- @objc public var animating: Bool {
- get {
- _animating
- }
- set(animating) {
- setAnimating(animating, animated: false)
- }
- }
-
- private var _hidesWhenStopped = true
- /// If true, the view will hide when it is not spinning. Default is true.
- @objc public var hidesWhenStopped: Bool {
- get {
- _hidesWhenStopped
- }
- set(hidesWhenStopped) {
- _hidesWhenStopped = hidesWhenStopped
- if !animating && hidesWhenStopped {
- alpha = 0
- } else {
- alpha = 1
- }
- }
- }
- private weak var indicatorLayer: CAShapeLayer?
-
- /// :nodoc:
- @objc override init(
- frame: CGRect
- ) {
- var initialFrame = frame
- if initialFrame.isEmpty {
- initialFrame = CGRect(x: frame.origin.x, y: frame.origin.y, width: 40, height: 40)
- }
- super.init(frame: initialFrame)
- backgroundColor = UIColor.clear
- let layer = CAShapeLayer()
- layer.backgroundColor = UIColor.clear.cgColor
- layer.fillColor = UIColor.clear.cgColor
- layer.strokeColor = tintColor.cgColor
- layer.strokeStart = 0
- layer.lineCap = .round
- layer.strokeEnd = 0.75
- layer.lineWidth = 2.0
- indicatorLayer = layer
- self.layer.addSublayer(layer)
- alpha = 0
- }
-
- /// :nodoc:
- @objc public override var tintColor: UIColor! {
- get {
- return super.tintColor
- }
- set(tintColor) {
- super.tintColor = tintColor
- indicatorLayer?.strokeColor = tintColor.cgColor
- }
- }
-
- /// :nodoc:
- @objc
- public override func layoutSubviews() {
- super.layoutSubviews()
- var bounds = self.bounds
- bounds.size.width = CGFloat(min(bounds.size.width, bounds.size.height))
- bounds.size.height = bounds.size.width
- let path = UIBezierPath(ovalIn: bounds)
- indicatorLayer?.path = path.cgPath
- }
-
- required init?(
- coder aDecoder: NSCoder
- ) {
- super.init(coder: aDecoder)
- }
-}
diff --git a/Stripe/StripeiOS/Source/STPPaymentCardTextFieldCell.swift b/Stripe/StripeiOS/Source/STPPaymentCardTextFieldCell.swift
deleted file mode 100644
index 40112d5dea9..00000000000
--- a/Stripe/StripeiOS/Source/STPPaymentCardTextFieldCell.swift
+++ /dev/null
@@ -1,91 +0,0 @@
-//
-// STPPaymentCardTextFieldCell.swift
-// StripeiOS
-//
-// Created by Jack Flintermann on 6/16/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-@_spi(STP) import StripePaymentsUI
-import UIKit
-
-class STPPaymentCardTextFieldCell: UITableViewCell {
- private(set) weak var paymentField: STPPaymentCardTextField?
-
- var theme: STPTheme = STPTheme.defaultTheme {
- didSet {
- updateAppearance()
- }
- }
-
- private var _inputAccessoryView: UIView?
- override var inputAccessoryView: UIView? {
- get {
- _inputAccessoryView
- }
- set(inputAccessoryView) {
- _inputAccessoryView = inputAccessoryView
- paymentField?.inputAccessoryView = inputAccessoryView
- }
- }
-
- func isEmpty() -> Bool {
- return (paymentField?.cardNumber?.count ?? 0) == 0
- }
-
- override init(
- style: UITableViewCell.CellStyle,
- reuseIdentifier: String?
- ) {
- super.init(style: style, reuseIdentifier: reuseIdentifier)
- let paymentField = STPPaymentCardTextField(frame: bounds)
- paymentField.postalCodeEntryEnabled = false
- contentView.addSubview(paymentField)
- self.paymentField = paymentField
- theme = STPTheme.defaultTheme
- updateAppearance()
- }
-
- override func layoutSubviews() {
- super.layoutSubviews()
- paymentField?.frame = contentView.bounds
- }
-
- @objc func updateAppearance() {
- paymentField?.backgroundColor = UIColor.clear
- paymentField?.placeholderColor = theme.tertiaryForegroundColor
- paymentField?.borderColor = UIColor.clear
- paymentField?.textColor = theme.primaryForegroundColor
- paymentField?.textErrorColor = theme.errorColor
- paymentField?.font = theme.font
- backgroundColor = theme.secondaryBackgroundColor
- }
-
- @objc override func becomeFirstResponder() -> Bool {
- return paymentField?.becomeFirstResponder() ?? false
- }
-
- override func accessibilityElementCount() -> Int {
- return paymentField?.allFields.count ?? 0
- }
-
- override func accessibilityElement(at index: Int) -> Any? {
- return paymentField?.allFields[index]
- }
-
- override func index(ofAccessibilityElement element: Any) -> Int {
- let fields = paymentField?.allFields
- for i in 0..<(fields?.count ?? 0) {
- if (element as? AnyHashable) == fields?[i] {
- return i
- }
- }
- return 0
- }
-
- required init?(
- coder aDecoder: NSCoder
- ) {
- super.init(coder: aDecoder)
- }
-}
diff --git a/Stripe/StripeiOS/Source/STPPaymentContext.swift b/Stripe/StripeiOS/Source/STPPaymentContext.swift
deleted file mode 100644
index 45af07a5155..00000000000
--- a/Stripe/StripeiOS/Source/STPPaymentContext.swift
+++ /dev/null
@@ -1,1266 +0,0 @@
-//
-// STPPaymentContext.swift
-// StripeiOS
-//
-// Created by Jack Flintermann on 4/20/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-import Foundation
-import ObjectiveC
-import PassKit
-@_spi(STP) import StripeCore
-@_spi(STP) import StripePaymentsUI
-
-/// An `STPPaymentContext` keeps track of all of the state around a payment. It will manage fetching a user's saved payment methods, tracking any information they select, and prompting them for required additional information before completing their purchase. It can be used to power your application's "payment confirmation" page with just a few lines of code.
-/// `STPPaymentContext` also provides a unified interface to multiple payment methods - for example, you can write a single integration to accept both credit card payments and Apple Pay.
-/// `STPPaymentContext` saves information about a user's payment methods to a Stripe customer object, and requires an `STPCustomerContext` to manage retrieving and modifying the customer.
-@objc(STPPaymentContext)
-public class STPPaymentContext: NSObject, STPAuthenticationContext,
- STPPaymentOptionsViewControllerDelegate, STPShippingAddressViewControllerDelegate
-{
- /// This is a convenience initializer; it is equivalent to calling
- /// `init(customerContext:customerContext
- /// configuration:STPPaymentConfiguration.shared
- /// theme:STPTheme.defaultTheme`.
- /// - Parameter customerContext: The customer context the payment context will use to fetch
- /// and modify its Stripe customer. - seealso: STPCustomerContext.h
- /// - Returns: the newly-instantiated payment context
- @objc
- public convenience init(
- customerContext: STPCustomerContext
- ) {
- self.init(apiAdapter: customerContext)
- }
-
- /// Initializes a new Payment Context with the provided customer context, configuration,
- /// and theme. After this class is initialized, you should also make sure to set its
- /// `delegate` and `hostViewController` properties.
- /// - Parameters:
- /// - customerContext: The customer context the payment context will use to fetch
- /// and modify its Stripe customer. - seealso: STPCustomerContext.h
- /// - configuration: The configuration for the payment context to use. This
- /// lets you set your Stripe publishable API key, required billing address fields, etc.
- /// - seealso: STPPaymentConfiguration.h
- /// - theme: The theme describing the visual appearance of all UI
- /// that the payment context automatically creates for you. - seealso: STPTheme.h
- /// - Returns: the newly-instantiated payment context
- @objc
- public convenience init(
- customerContext: STPCustomerContext,
- configuration: STPPaymentConfiguration,
- theme: STPTheme
- ) {
- self.init(
- apiAdapter: customerContext,
- configuration: configuration,
- theme: theme
- )
- }
-
- /// Note: Instead of providing your own backend API adapter, we recommend using
- /// `STPCustomerContext`, which will manage retrieving and updating a
- /// Stripe customer for you. - seealso: STPCustomerContext.h
- /// This is a convenience initializer; it is equivalent to calling
- /// `init(apiAdapter:apiAdapter configuration:STPPaymentConfiguration.shared theme:STPTheme.defaultTheme)`.
- @objc
- public convenience init(
- apiAdapter: STPBackendAPIAdapter
- ) {
- self.init(
- apiAdapter: apiAdapter,
- configuration: STPPaymentConfiguration.shared,
- theme: STPTheme.defaultTheme
- )
- }
-
- /// Note: Instead of providing your own backend API adapter, we recommend using
- /// `STPCustomerContext`, which will manage retrieving and updating a
- /// Stripe customer for you. - seealso: STPCustomerContext.h
- /// Initializes a new Payment Context with the provided API adapter and configuration.
- /// After this class is initialized, you should also make sure to set its `delegate`
- /// and `hostViewController` properties.
- /// - Parameters:
- /// - apiAdapter: The API adapter the payment context will use to fetch and
- /// modify its contents. You need to make a class conforming to this protocol that
- /// talks to your server. - seealso: STPBackendAPIAdapter.h
- /// - configuration: The configuration for the payment context to use. This lets
- /// you set your Stripe publishable API key, required billing address fields, etc.
- /// - seealso: STPPaymentConfiguration.h
- /// - theme: The theme describing the visual appearance of all UI that
- /// the payment context automatically creates for you. - seealso: STPTheme.h
- /// - Returns: the newly-instantiated payment context
- @objc
- public init(
- apiAdapter: STPBackendAPIAdapter,
- configuration: STPPaymentConfiguration,
- theme: STPTheme
- ) {
- STPAnalyticsClient.sharedClient.addClass(toProductUsageIfNecessary: STPPaymentContext.self)
- AnalyticsHelper.shared.generateSessionID()
- self.configuration = configuration
- self.apiAdapter = apiAdapter
- self.theme = theme
- paymentCurrency = "USD"
- paymentCountry = "US"
- apiClient = STPAPIClient.shared
- modalPresentationStyle = .fullScreen
- state = STPPaymentContextState.none
- super.init()
- retryLoading()
- }
-
- /// Note: Instead of providing your own backend API adapter, we recommend using
- /// `STPCustomerContext`, which will manage retrieving and updating a
- /// Stripe customer for you. - seealso: STPCustomerContext.h
- /// The API adapter the payment context will use to fetch and modify its contents.
- /// You need to make a class conforming to this protocol that talks to your server.
- /// - seealso: STPBackendAPIAdapter.h
- @objc public private(set) var apiAdapter: STPBackendAPIAdapter
- /// The configuration for the payment context to use internally. - seealso: STPPaymentConfiguration.h
- @objc public private(set) var configuration: STPPaymentConfiguration
- /// The visual appearance that will be used by any views that the context generates. - seealso: STPTheme.h
- @objc public private(set) var theme: STPTheme
-
- private var _prefilledInformation: STPUserInformation?
- /// If you've already collected some information from your user, you can set it here and it'll be automatically filled out when possible/appropriate in any UI that the payment context creates.
- @objc public var prefilledInformation: STPUserInformation? {
- get {
- _prefilledInformation
- }
- set(prefilledInformation) {
- _prefilledInformation = prefilledInformation
- if prefilledInformation?.shippingAddress != nil && shippingAddress == nil {
- shippingAddress = prefilledInformation?.shippingAddress
- shippingAddressNeedsVerification = true
- }
- }
- }
-
- private weak var _hostViewController: UIViewController?
- /// The view controller that any additional UI will be presented on. If you have a "checkout view controller" in your app, that should be used as the host view controller.
- @objc public weak var hostViewController: UIViewController? {
- get {
- _hostViewController
- }
- set(hostViewController) {
- assert(
- _hostViewController == nil,
- "You cannot change the hostViewController on an STPPaymentContext after it's already been set."
- )
- _hostViewController = hostViewController
- if hostViewController is UINavigationController {
- originalTopViewController =
- (hostViewController as? UINavigationController)?.topViewController
- }
- if let hostViewController = hostViewController {
- artificiallyRetain(hostViewController)
- }
- }
- }
-
- private weak var _delegate: STPPaymentContextDelegate?
- /// This delegate will be notified when the payment context's contents change. - seealso: STPPaymentContextDelegate
- @objc public weak var delegate: STPPaymentContextDelegate? {
- get {
- _delegate
- }
- set(delegate) {
- _delegate = delegate
- DispatchQueue.main.async(execute: {
- self.delegate?.paymentContextDidChange(self)
- })
- }
- }
- /// Whether or not the payment context is currently loading information from the network.
-
- @objc public var loading: Bool {
- return !(loadingPromise?.completed)!
- }
- /// @note This is no longer recommended as of v18.3.0 - the SDK automatically saves the Stripe ID of the last selected
- /// payment method using NSUserDefaults and displays it as the default pre-selected option. You can override this behavior
- /// by setting this property.
- /// The Stripe ID of a payment method to display as the default pre-selected option.
- /// @note Set this property immediately after initializing STPPaymentContext, or call `retryLoading` afterwards.
- @objc public var defaultPaymentMethod: String?
-
- private var _selectedPaymentOption: STPPaymentOption?
- /// The user's currently selected payment option. May be nil.
- @objc public private(set) var selectedPaymentOption: STPPaymentOption? {
- get {
- _selectedPaymentOption
- }
- set {
- if let newValue = newValue, let paymentOptions = self.paymentOptions {
- if !paymentOptions.contains(where: { (option) -> Bool in
- newValue.isEqual(option)
- }) {
- if newValue.isReusable {
- self.paymentOptions = paymentOptions + [newValue]
- }
- }
- }
- if !(_selectedPaymentOption?.isEqual(newValue) ?? false) {
- _selectedPaymentOption = newValue
- stpDispatchToMainThreadIfNecessary({
- self.delegate?.paymentContextDidChange(self)
- })
- }
-
- }
- }
-
- private var _paymentOptions: [STPPaymentOption]?
- /// The available payment options the user can choose between. May be nil.
- @objc public private(set) var paymentOptions: [STPPaymentOption]? {
- get {
- _paymentOptions
- }
- set {
- _paymentOptions = newValue?.sorted(by: { (obj1, obj2) -> Bool in
- let applePayKlass = STPApplePayPaymentOption.self
- let paymentMethodKlass = STPPaymentMethod.self
- if obj1.isKind(of: applePayKlass) {
- return true
- } else if obj2.isKind(of: applePayKlass) {
- return false
- }
- if obj1.isKind(of: paymentMethodKlass) && obj2.isKind(of: paymentMethodKlass) {
- return (obj1.label.compare(obj2.label) == .orderedAscending)
- }
- return false
- })
- }
- }
-
- /// The user's currently selected shipping method. May be nil.
- @objc public internal(set) var selectedShippingMethod: PKShippingMethod?
-
- private var _shippingMethods: [PKShippingMethod]?
- /// An array of STPShippingMethod objects that describe the supported shipping methods. May be nil.
- @objc public private(set) var shippingMethods: [PKShippingMethod]? {
- get {
- _shippingMethods
- }
- set {
- _shippingMethods = newValue
- if let shippingMethods = newValue,
- let selectedShippingMethod = self.selectedShippingMethod
- {
- if shippingMethods.count == 0 {
- self.selectedShippingMethod = nil
- } else if shippingMethods.contains(selectedShippingMethod) {
- self.selectedShippingMethod = shippingMethods.first
- }
- }
- }
- }
-
- /// The user's shipping address. May be nil.
- /// If you've already collected a shipping address from your user, you may
- /// prefill it by setting a shippingAddress in PaymentContext's prefilledInformation.
- /// When your user enters a new shipping address, PaymentContext will save it to
- /// the current customer object. When PaymentContext loads, if you haven't
- /// manually set a prefilled value, any shipping information saved on the customer
- /// will be used to prefill the shipping address form. Note that because your
- /// customer's email may not be the same as the email provided with their shipping
- /// info, PaymentContext will not prefill the shipping form's email using your
- /// customer's email.
- /// You should not rely on the shipping information stored on the Stripe customer
- /// for order fulfillment, as your user may change this information if they make
- /// multiple purchases. We recommend adding shipping information when you create
- /// a charge (which can also help prevent fraud), or saving it to your own
- /// database. https://stripe.com/docs/api/payment_intents/create#create_payment_intent-shipping
- /// Note: by default, your user will still be prompted to verify a prefilled
- /// shipping address. To change this behavior, you can set
- /// `verifyPrefilledShippingAddress` to NO in your `STPPaymentConfiguration`.
- @objc public private(set) var shippingAddress: STPAddress?
- /// The amount of money you're requesting from the user, in the smallest currency
- /// unit for the selected currency. For example, to indicate $10 USD, use 1000
- /// (i.e. 1000 cents). For more information, see https://stripe.com/docs/api/payment_intents/create#create_payment_intent-amount
- /// @note This value must be present and greater than zero in order for Apple Pay
- /// to be automatically enabled.
- /// @note You should only set either this or `paymentSummaryItems`, not both.
- /// The other will be automatically calculated on demand using your `paymentCurrency`.
-
- @objc public var paymentAmount: Int {
- get {
- return paymentAmountModel.paymentAmount(
- withCurrency: paymentCurrency,
- shippingMethod: selectedShippingMethod
- )
- }
- set(paymentAmount) {
- paymentAmountModel = STPPaymentContextAmountModel(amount: paymentAmount)
- }
- }
- /// The three-letter currency code for the currency of the payment (i.e. USD, GBP,
- /// JPY, etc). Defaults to "USD".
- /// @note Changing this property may change the return value of `paymentAmount`
- /// or `paymentSummaryItems` (whichever one you didn't directly set yourself).
- @objc public var paymentCurrency: String
- /// The two-letter country code for the country where the payment will be processed.
- /// You should set this to the country your Stripe account is in. Defaults to "US".
- /// @note Changing this property will change the `countryCode` of your Apple Pay
- /// payment requests.
- /// - seealso: PKPaymentRequest for more information.
- @objc public var paymentCountry: String
- /// If you support Apple Pay, you can optionally set the PKPaymentSummaryItems
- /// you want to display here instead of using `paymentAmount`. Note that the
- /// grand total (the amount of the last summary item) must be greater than zero.
- /// If not set, a single summary item will be automatically generated using
- /// `paymentAmount` and your configuration's `companyName`.
- /// - seealso: PKPaymentRequest for more information
- /// @note You should only set either this or `paymentAmount`, not both.
- /// The other will be automatically calculated on demand using your `paymentCurrency.`
-
- @objc public var paymentSummaryItems: [PKPaymentSummaryItem] {
- get {
- return paymentAmountModel.paymentSummaryItems(
- withCurrency: paymentCurrency,
- companyName: configuration.companyName,
- shippingMethod: selectedShippingMethod
- ) ?? []
- }
- set(paymentSummaryItems) {
- paymentAmountModel = STPPaymentContextAmountModel(
- paymentSummaryItems: paymentSummaryItems
- )
- }
- }
-
- /// A value that indicates whether Apple Pay Later is available for a transaction.
- /// Defaults to enabled.
- /// - Seealso: This property is mirrors `PKPaymentRequest.applePayLaterAvailability`
-#if compiler(>=5.9)
- @available(macOS 14.0, iOS 17.0, *)
- @objc public var applePayLaterAvailability: PKApplePayLaterAvailability {
- // Stored properties cannot be marked potentially unavailable with '@available', so do this workaround instead
- get {
- return _applePayLaterAvailability as! PKApplePayLaterAvailability
- }
- set {
- _applePayLaterAvailability = newValue
-
- }
- }
- private lazy var _applePayLaterAvailability: Any? = {
- if #available(macOS 14.0, iOS 17.0, *) {
- return PKApplePayLaterAvailability.available
- }
- return nil
- }()
-#endif
- /// The presentation style used for all view controllers presented modally by the context.
- /// Since custom transition styles are not supported, you should set this to either
- /// `UIModalPresentationFullScreen`, `UIModalPresentationPageSheet`, or `UIModalPresentationFormSheet`.
- /// The default value is `UIModalPresentationFullScreen`.
- @objc public var modalPresentationStyle: UIModalPresentationStyle = .fullScreen
- /// The mode to use when displaying the title of the navigation bar in all view
- /// controllers presented by the context. The default value is `automatic`,
- /// which causes the title to use the same styling as the previously displayed
- /// navigation item (if the view controller is pushed onto the `hostViewController`).
- /// If the `prefersLargeTitles` property of the `hostViewController`'s navigation bar
- /// is false, this property has no effect and the navigation item's title is always
- /// displayed as a small title.
- /// If the view controller is presented modally, `automatic` and
- /// `never` always result in a navigation bar with a small title.
- @objc public var largeTitleDisplayMode = UINavigationItem.LargeTitleDisplayMode.automatic
- /// A view that will be placed as the footer of the payment options selection
- /// view controller.
- /// When the footer view needs to be resized, it will be sent a
- /// `sizeThatFits:` call. The view should respond correctly to this method in order
- /// to be sized and positioned properly.
- @objc public var paymentOptionsViewControllerFooterView: UIView?
- /// A view that will be placed as the footer of the add card view controller.
- /// When the footer view needs to be resized, it will be sent a
- /// `sizeThatFits:` call. The view should respond correctly to this method in order
- /// to be sized and positioned properly.
- @objc public var addCardViewControllerFooterView: UIView?
- /// The API Client to use to make requests.
- /// Defaults to STPAPIClient.shared
- public var apiClient: STPAPIClient = .shared {
- didSet {
- analyticsLogger.apiClient = apiClient
- }
- }
- internal let analyticsLogger: AnalyticsLogger = .init(product: STPPaymentContext.self)
- internal var loadingStartDate: Date?
-
- /// If `paymentContext:didFailToLoadWithError:` is called on your delegate, you
- /// can in turn call this method to try loading again (if that hasn't been called,
- /// calling this will do nothing). If retrying in turn fails, `paymentContext:didFailToLoadWithError:`
- /// will be called again (and you can again call this to keep retrying, etc).
- @objc
- public func retryLoading() {
- let loadingStartDate = Date()
- self.loadingStartDate = loadingStartDate
- analyticsLogger.logLoadStarted()
- // Clear any cached customer object and attached payment methods before refetching
- if apiAdapter is STPCustomerContext {
- let customerContext = apiAdapter as? STPCustomerContext
- customerContext?.clearCache()
- }
- weak var weakSelf = self
- loadingPromise = STPPromise.init().onSuccess({ tuple in
- guard let strongSelf = weakSelf else {
- return
- }
- strongSelf.analyticsLogger.logLoadSucceeded(loadStartDate: loadingStartDate, defaultPaymentOption: tuple.selectedPaymentOption)
- strongSelf.paymentOptions = tuple.paymentOptions
- strongSelf.selectedPaymentOption = tuple.selectedPaymentOption
- }).onFailure({ error in
- guard let strongSelf = weakSelf else {
- return
- }
- strongSelf.analyticsLogger.logLoadFailed(loadStartDate: loadingStartDate, error: error)
- if strongSelf.hostViewController != nil {
- if strongSelf.paymentOptionsViewController != nil
- && strongSelf.paymentOptionsViewController?.viewIfLoaded?.window != nil
- {
- if let paymentOptionsViewController1 = strongSelf.paymentOptionsViewController {
- strongSelf.appropriatelyDismiss(paymentOptionsViewController1) {
- strongSelf.delegate?.paymentContext(
- strongSelf,
- didFailToLoadWithError: error
- )
- }
- }
- } else {
- strongSelf.delegate?.paymentContext(strongSelf, didFailToLoadWithError: error)
- }
- }
- })
- apiAdapter.retrieveCustomer({ customer, retrieveCustomerError in
- stpDispatchToMainThreadIfNecessary({
- guard let strongSelf = weakSelf else {
- return
- }
- if let retrieveCustomerError = retrieveCustomerError {
- strongSelf.loadingPromise?.fail(retrieveCustomerError)
- return
- }
- if strongSelf.shippingAddress == nil && customer?.shippingAddress != nil {
- strongSelf.shippingAddress = customer?.shippingAddress
- strongSelf.shippingAddressNeedsVerification = true
- }
-
- strongSelf.apiAdapter.listPaymentMethodsForCustomer(completion: {
- paymentMethods,
- error in
- guard let strongSelf2 = weakSelf else {
- return
- }
- stpDispatchToMainThreadIfNecessary({
- if let error = error {
- strongSelf2.loadingPromise?.fail(error)
- return
- }
-
- if self.defaultPaymentMethod == nil
- && (strongSelf2.apiAdapter is STPCustomerContext)
- {
- // Retrieve the last selected payment method saved by STPCustomerContext
- (strongSelf2.apiAdapter as? STPCustomerContext)?
- .retrieveLastSelectedPaymentMethodIDForCustomer(completion: {
- paymentMethodID,
- _ in
- guard let strongSelf3 = weakSelf else {
- return
- }
- if let paymentMethods = paymentMethods {
- let paymentTuple = STPPaymentOptionTuple(
- filteredForUIWith: paymentMethods,
- selectedPaymentMethod: paymentMethodID,
- configuration: strongSelf3.configuration
- )
- strongSelf3.loadingPromise?.succeed(paymentTuple)
- } else {
- strongSelf3.loadingPromise?.fail(
- STPErrorCode.invalidRequestError as! Error
- )
- }
- })
- } else {
- if let paymentMethods = paymentMethods {
- let paymentTuple = STPPaymentOptionTuple(
- filteredForUIWith: paymentMethods,
- selectedPaymentMethod: self.defaultPaymentMethod,
- configuration: strongSelf2.configuration
- )
- strongSelf2.loadingPromise?.succeed(paymentTuple)
- }
- }
- })
- })
- })
- })
- }
-
- /// This creates, configures, and appropriately presents an `STPPaymentOptionsViewController`
- /// on top of the payment context's `hostViewController`. It'll be dismissed automatically
- /// when the user is done selecting their payment method.
- /// @note This method will do nothing if it is called while STPPaymentContext is
- /// already showing a view controller or in the middle of requesting a payment.
- @objc
- public func presentPaymentOptionsViewController() {
- presentPaymentOptionsViewController(withNewState: .showingRequestedViewController)
- }
-
- /// This creates, configures, and appropriately pushes an `STPPaymentOptionsViewController`
- /// onto the navigation stack of the context's `hostViewController`. It'll be popped
- /// automatically when the user is done selecting their payment method.
- /// @note This method will do nothing if it is called while STPPaymentContext is
- /// already showing a view controller or in the middle of requesting a payment.
- @objc
- public func pushPaymentOptionsViewController() {
- assert(
- hostViewController != nil && hostViewController?.viewIfLoaded?.window != nil,
- "hostViewController must not be nil on STPPaymentContext when calling pushPaymentOptionsViewController on it. Next time, set the hostViewController property first!"
- )
- var navigationController: UINavigationController?
- if hostViewController is UINavigationController {
- navigationController = hostViewController as? UINavigationController
- } else {
- navigationController = hostViewController?.navigationController
- }
- assert(
- navigationController != nil,
- "The payment context's hostViewController is not a navigation controller, or is not contained in one. Either make sure it is inside a navigation controller before calling pushPaymentOptionsViewController, or call presentPaymentOptionsViewController instead."
- )
- if state == STPPaymentContextState.none {
- state = .showingRequestedViewController
-
- let paymentOptionsViewController = STPPaymentOptionsViewController(paymentContext: self)
- self.paymentOptionsViewController = paymentOptionsViewController
- paymentOptionsViewController.prefilledInformation = prefilledInformation
- paymentOptionsViewController.defaultPaymentMethod = defaultPaymentMethod
- paymentOptionsViewController.paymentOptionsViewControllerFooterView =
- paymentOptionsViewControllerFooterView
- paymentOptionsViewController.addCardViewControllerFooterView =
- addCardViewControllerFooterView
- paymentOptionsViewController.navigationItem.largeTitleDisplayMode =
- largeTitleDisplayMode
-
- navigationController?.pushViewController(
- paymentOptionsViewController,
- animated: transitionAnimationsEnabled()
- )
- }
- }
-
- /// This creates, configures, and appropriately presents a view controller for
- /// collecting shipping address and shipping method on top of the payment context's
- /// `hostViewController`. It'll be dismissed automatically when the user is done
- /// entering their shipping info.
- /// @note This method will do nothing if it is called while STPPaymentContext is
- /// already showing a view controller or in the middle of requesting a payment.
- @objc
- public func presentShippingViewController() {
- presentShippingViewController(withNewState: .showingRequestedViewController)
- }
-
- /// This creates, configures, and appropriately pushes a view controller for
- /// collecting shipping address and shipping method onto the navigation stack of
- /// the context's `hostViewController`. It'll be popped automatically when the
- /// user is done entering their shipping info.
- /// @note This method will do nothing if it is called while STPPaymentContext is
- /// already showing a view controller, or in the middle of requesting a payment.
- @objc
- public func pushShippingViewController() {
- assert(
- hostViewController != nil && hostViewController?.viewIfLoaded?.window != nil,
- "hostViewController must not be nil on STPPaymentContext when calling pushShippingViewController on it. Next time, set the hostViewController property first!"
- )
- var navigationController: UINavigationController?
- if hostViewController is UINavigationController {
- navigationController = hostViewController as? UINavigationController
- } else {
- navigationController = hostViewController?.navigationController
- }
- assert(
- navigationController != nil,
- "The payment context's hostViewController is not a navigation controller, or is not contained in one. Either make sure it is inside a navigation controller before calling pushShippingInfoViewController, or call presentShippingInfoViewController instead."
- )
- if state == STPPaymentContextState.none {
- state = .showingRequestedViewController
-
- let addressViewController = STPShippingAddressViewController(paymentContext: self)
- addressViewController.navigationItem.largeTitleDisplayMode = largeTitleDisplayMode
- navigationController?.pushViewController(
- addressViewController,
- animated: transitionAnimationsEnabled()
- )
- }
- }
-
- /// Requests payment from the user. This may need to present some supplemental UI
- /// to the user, in which case it will be presented on the payment context's
- /// `hostViewController`. For instance, if they've selected Apple Pay as their
- /// payment method, calling this method will show the payment sheet. If the user
- /// has a card on file, this will use that without presenting any additional UI.
- /// After this is called, the `paymentContext:didCreatePaymentResult:completion:`
- /// and `paymentContext:didFinishWithStatus:error:` methods will be called on the
- /// context's `delegate`.
- /// @note This method will do nothing if it is called while STPPaymentContext is
- /// already showing a view controller, or in the middle of requesting a payment.
- @objc
- public func requestPayment() {
- weak var weakSelf = self
- loadingPromise?.onSuccess({ _ in
- guard let strongSelf = weakSelf else {
- return
- }
-
- if strongSelf.state != STPPaymentContextState.none {
- return
- }
-
- if strongSelf.selectedPaymentOption == nil {
- strongSelf.presentPaymentOptionsViewController(withNewState: .requestingPayment)
- } else if strongSelf.requestPaymentShouldPresentShippingViewController() {
- strongSelf.presentShippingViewController(withNewState: .requestingPayment)
- } else if let selectedPaymentOption = strongSelf.selectedPaymentOption,
- (selectedPaymentOption is STPPaymentMethod || selectedPaymentOption is STPPaymentMethodParams)
- {
- strongSelf.state = .requestingPayment
- let result = STPPaymentResult(paymentOption: strongSelf.selectedPaymentOption!)
- strongSelf.delegate?.paymentContext(self, didCreatePaymentResult: result) { status, error in
- // Note `selectedPaymentOption` is always an `STPPaymentMethod` for cards
- strongSelf.analyticsLogger.logPayment(status: status, loadStartDate: strongSelf.loadingStartDate, paymentOption: selectedPaymentOption, error: error)
- stpDispatchToMainThreadIfNecessary({
- strongSelf.didFinish(with: status, error: error)
- })
- }
- } else if let selectedPaymentOption = strongSelf.selectedPaymentOption, selectedPaymentOption is STPApplePayPaymentOption {
- assert(
- strongSelf.hostViewController != nil,
- "hostViewController must not be nil on STPPaymentContext. Next time, set the hostViewController property first!"
- )
- strongSelf.state = .requestingPayment
- let paymentRequest = strongSelf.buildPaymentRequest()
- let shippingAddressHandler: STPShippingAddressSelectionBlock = {
- shippingAddress,
- completion in
- // Apple Pay always returns a partial address here, so we won't
- // update self.shippingAddress or self.shippingMethods
- if strongSelf.delegate?.responds(
- to: #selector(
- STPPaymentContextDelegate.paymentContext(
- _:
- didUpdateShippingAddress:
- completion:
- ))
- )
- ?? false
- {
- strongSelf.delegate?.paymentContext?(
- strongSelf,
- didUpdateShippingAddress: shippingAddress
- ) { status, _, shippingMethods, _ in
- completion(
- status,
- shippingMethods ?? [],
- strongSelf.paymentSummaryItems
- )
- }
- } else {
- completion(
- .valid,
- strongSelf.shippingMethods ?? [],
- strongSelf.paymentSummaryItems
- )
- }
- }
- let shippingMethodHandler: STPShippingMethodSelectionBlock = {
- shippingMethod,
- completion in
- strongSelf.selectedShippingMethod = shippingMethod
- strongSelf.delegate?.paymentContextDidChange(strongSelf)
- completion(self.paymentSummaryItems)
- }
- let paymentHandler: STPPaymentAuthorizationBlock = { payment in
- strongSelf.selectedShippingMethod = payment.shippingMethod
- if let shippingContact = payment.shippingContact {
- strongSelf.shippingAddress = STPAddress(pkContact: shippingContact)
- }
- strongSelf.shippingAddressNeedsVerification = false
- strongSelf.delegate?.paymentContextDidChange(strongSelf)
- if strongSelf.apiAdapter is STPCustomerContext {
- let customerContext = strongSelf.apiAdapter as? STPCustomerContext
- if let shippingAddress1 = strongSelf.shippingAddress {
- customerContext?.updateCustomer(
- withShippingAddress: shippingAddress1,
- completion: nil
- )
- }
- }
- }
- let applePayPaymentMethodHandler: STPApplePayPaymentMethodHandlerBlock = {
- paymentMethod,
- completion in
- strongSelf.apiAdapter.attachPaymentMethod(toCustomer: paymentMethod) {
- attachPaymentMethodError in
- stpDispatchToMainThreadIfNecessary({
- if attachPaymentMethodError != nil {
- completion(.error, attachPaymentMethodError)
- } else {
- let result = STPPaymentResult(paymentOption: paymentMethod)
- strongSelf.delegate?.paymentContext(
- strongSelf,
- didCreatePaymentResult: result
- ) {
- status,
- error in
- // for Apple Pay, the didFinishWithStatus callback is fired later when Apple Pay VC finishes
- completion(status, error)
- }
- }
- })
- }
- }
- if let paymentRequest = paymentRequest {
- strongSelf.applePayVC = PKPaymentAuthorizationViewController.stp_controller(
- with: paymentRequest,
- apiClient: strongSelf.apiClient,
- onShippingAddressSelection: shippingAddressHandler,
- onShippingMethodSelection: shippingMethodHandler,
- onPaymentAuthorization: paymentHandler,
- onPaymentMethodCreation: applePayPaymentMethodHandler,
- onFinish: { status, error in
- strongSelf.analyticsLogger.logPayment(status: status, loadStartDate: strongSelf.loadingStartDate, paymentOption: selectedPaymentOption, error: error)
- if strongSelf.applePayVC?.presentingViewController != nil {
- strongSelf.hostViewController?.dismiss(
- animated: strongSelf.transitionAnimationsEnabled()
- ) {
- strongSelf.didFinish(with: status, error: error)
- }
- } else {
- strongSelf.didFinish(with: status, error: error)
- }
- strongSelf.applePayVC = nil
- }
- )
- }
- if let applePayVC1 = strongSelf.applePayVC {
- strongSelf.hostViewController?.present(
- applePayVC1,
- animated: strongSelf.transitionAnimationsEnabled()
- )
- }
- }
- }).onFailure({ error in
- guard let strongSelf = weakSelf else {
- return
- }
- strongSelf.didFinish(with: .error, error: error)
- })
- }
- private var loadingPromise: STPPromise?
- private weak var paymentOptionsViewController: STPPaymentOptionsViewController?
- private var state: STPPaymentContextState = .none
- private var paymentAmountModel = STPPaymentContextAmountModel(amount: 0)
- private var shippingAddressNeedsVerification = false
- // If hostViewController was set to a nav controller, the original VC on top of the stack
- private weak var originalTopViewController: UIViewController?
- private var applePayVC: PKPaymentAuthorizationViewController?
-
- // Disable transition animations in tests
- func transitionAnimationsEnabled() -> Bool {
- return NSClassFromString("XCTest") == nil
- }
-
- var currentValuePromise: STPPromise {
- weak var weakSelf = self
- return
- (loadingPromise?.map({ _ in
- guard let strongSelf = weakSelf, let paymentOptions = strongSelf.paymentOptions
- else {
- return STPPaymentOptionTuple()
- }
- return STPPaymentOptionTuple(
- paymentOptions: paymentOptions,
- selectedPaymentOption: strongSelf.selectedPaymentOption
- )
- }))!
- }
-
- func remove(_ paymentOptionToRemove: STPPaymentOption?) {
- // Remove payment method from cached representation
- var paymentOptions = self.paymentOptions
- paymentOptions?.removeAll { $0 as AnyObject === paymentOptionToRemove as AnyObject }
- self.paymentOptions = paymentOptions
-
- // Elect new selected payment method if needed
- if let selectedPaymentOption = selectedPaymentOption,
- selectedPaymentOption.isEqual(paymentOptionToRemove)
- {
- self.selectedPaymentOption = self.paymentOptions?.first
- }
- }
-
- // MARK: - Payment Methods
-
- func presentPaymentOptionsViewController(withNewState state: STPPaymentContextState) {
- assert(
- hostViewController != nil && hostViewController?.viewIfLoaded?.window != nil,
- "hostViewController must not be nil on STPPaymentContext when calling pushPaymentOptionsViewController on it. Next time, set the hostViewController property first!"
- )
- if self.state == STPPaymentContextState.none {
- self.state = state
- let paymentOptionsViewController = STPPaymentOptionsViewController(paymentContext: self)
- self.paymentOptionsViewController = paymentOptionsViewController
- paymentOptionsViewController.prefilledInformation = prefilledInformation
- paymentOptionsViewController.defaultPaymentMethod = defaultPaymentMethod
- paymentOptionsViewController.paymentOptionsViewControllerFooterView =
- paymentOptionsViewControllerFooterView
- paymentOptionsViewController.addCardViewControllerFooterView =
- addCardViewControllerFooterView
- paymentOptionsViewController.navigationItem.largeTitleDisplayMode =
- largeTitleDisplayMode
-
- let navigationController = UINavigationController(
- rootViewController: paymentOptionsViewController
- )
- navigationController.navigationBar.stp_theme = theme
- navigationController.navigationBar.prefersLargeTitles = true
- navigationController.modalPresentationStyle = modalPresentationStyle
- hostViewController?.present(
- navigationController,
- animated: transitionAnimationsEnabled()
- )
- }
- }
-
- @objc
- public func paymentOptionsViewController(
- _ paymentOptionsViewController: STPPaymentOptionsViewController,
- didSelect paymentOption: STPPaymentOption
- ) {
- selectedPaymentOption = paymentOption
- }
-
- @objc
- public func paymentOptionsViewControllerDidFinish(
- _ paymentOptionsViewController: STPPaymentOptionsViewController
- ) {
- appropriatelyDismiss(paymentOptionsViewController) {
- if self.state == .requestingPayment {
- self.state = STPPaymentContextState.none
- self.requestPayment()
- } else {
- self.state = STPPaymentContextState.none
- }
- }
- }
-
- @objc
- public func paymentOptionsViewControllerDidCancel(
- _ paymentOptionsViewController: STPPaymentOptionsViewController
- ) {
- appropriatelyDismiss(paymentOptionsViewController) {
- if self.state == .requestingPayment {
- self.didFinish(
- with: .userCancellation,
- error: nil
- )
- } else {
- self.state = STPPaymentContextState.none
- }
- }
- }
-
- @objc
- public func paymentOptionsViewController(
- _ paymentOptionsViewController: STPPaymentOptionsViewController,
- didFailToLoadWithError error: Error
- ) {
- // we'll handle this ourselves when the loading promise fails.
- }
-
- @objc(appropriatelyDismissPaymentOptionsViewController:completion:) func appropriatelyDismiss(
- _ viewController: STPPaymentOptionsViewController,
- completion: @escaping STPVoidBlock
- ) {
- if viewController.stp_isAtRootOfNavigationController() {
- // if we're the root of the navigation controller, we've been presented modally.
- viewController.presentingViewController?.dismiss(
- animated: transitionAnimationsEnabled()
- ) {
- self.paymentOptionsViewController = nil
- completion()
- }
- } else {
- // otherwise, we've been pushed onto the stack.
- var destinationViewController = hostViewController
- // If hostViewController is a nav controller, pop to the original VC on top of the stack.
- if hostViewController is UINavigationController {
- destinationViewController = originalTopViewController
- }
- viewController.navigationController?.stp_pop(
- to: destinationViewController,
- animated: transitionAnimationsEnabled()
- ) {
- self.paymentOptionsViewController = nil
- completion()
- }
- }
- }
-
- // MARK: - Shipping Info
-
- func presentShippingViewController(withNewState state: STPPaymentContextState) {
- assert(
- hostViewController != nil && hostViewController?.viewIfLoaded?.window != nil,
- "hostViewController must not be nil on STPPaymentContext when calling presentShippingViewController on it. Next time, set the hostViewController property first!"
- )
-
- if self.state == STPPaymentContextState.none {
- self.state = state
-
- let addressViewController = STPShippingAddressViewController(paymentContext: self)
- addressViewController.navigationItem.largeTitleDisplayMode = largeTitleDisplayMode
- let navigationController = UINavigationController(
- rootViewController: addressViewController
- )
- navigationController.navigationBar.stp_theme = theme
- navigationController.navigationBar.prefersLargeTitles = true
- navigationController.modalPresentationStyle = modalPresentationStyle
- hostViewController?.present(
- navigationController,
- animated: transitionAnimationsEnabled()
- )
- }
- }
-
- @objc
- public func shippingAddressViewControllerDidCancel(
- _ addressViewController: STPShippingAddressViewController
- ) {
- appropriatelyDismiss(addressViewController) {
- if self.state == .requestingPayment {
- self.didFinish(
- with: .userCancellation,
- error: nil
- )
- } else {
- self.state = STPPaymentContextState.none
- }
- }
- }
-
- @objc
- public func shippingAddressViewController(
- _ addressViewController: STPShippingAddressViewController,
- didEnter address: STPAddress,
- completion: @escaping STPShippingMethodsCompletionBlock
- ) {
- if delegate?.responds(
- to: #selector(
- STPPaymentContextDelegate.paymentContext(_:didUpdateShippingAddress:completion:))
- )
- ?? false
- {
- delegate?.paymentContext?(self, didUpdateShippingAddress: address) {
- status,
- shippingValidationError,
- shippingMethods,
- selectedMethod in
- self.shippingMethods = shippingMethods
- completion(status, shippingValidationError, shippingMethods, selectedMethod)
- }
- } else {
- completion(.valid, nil, nil, nil)
- }
- }
-
- @objc
- public func shippingAddressViewController(
- _ addressViewController: STPShippingAddressViewController,
- didFinishWith address: STPAddress,
- shippingMethod method: PKShippingMethod?
- ) {
- shippingAddress = address
- shippingAddressNeedsVerification = false
- selectedShippingMethod = method
- delegate?.paymentContextDidChange(self)
- if apiAdapter.responds(
- to: #selector(STPCustomerContext.updateCustomer(withShippingAddress:completion:))
- ) {
- if let shippingAddress = shippingAddress {
- apiAdapter.updateCustomer?(withShippingAddress: shippingAddress, completion: nil)
- }
- }
- appropriatelyDismiss(addressViewController) {
- if self.state == .requestingPayment {
- self.state = STPPaymentContextState.none
- self.requestPayment()
- } else {
- self.state = STPPaymentContextState.none
- }
- }
- }
-
- @objc(appropriatelyDismissViewController:completion:) func appropriatelyDismiss(
- _ viewController: UIViewController,
- completion: @escaping STPVoidBlock
- ) {
- if viewController.stp_isAtRootOfNavigationController() {
- // if we're the root of the navigation controller, we've been presented modally.
- viewController.presentingViewController?.dismiss(
- animated: transitionAnimationsEnabled()
- ) {
- completion()
- }
- } else {
- // otherwise, we've been pushed onto the stack.
- var destinationViewController = hostViewController
- // If hostViewController is a nav controller, pop to the original VC on top of the stack.
- if hostViewController is UINavigationController {
- destinationViewController = originalTopViewController
- }
- viewController.navigationController?.stp_pop(
- to: destinationViewController,
- animated: transitionAnimationsEnabled()
- ) {
- completion()
- }
- }
- }
-
- // MARK: - Request Payment
- func requestPaymentShouldPresentShippingViewController() -> Bool {
- let shippingAddressRequired = (configuration.requiredShippingAddressFields?.count ?? 0) > 0
- var shippingAddressIncomplete: Bool?
- if let requiredShippingAddressFields1 = configuration.requiredShippingAddressFields {
- shippingAddressIncomplete =
- !(shippingAddress?.containsRequiredShippingAddressFields(
- requiredShippingAddressFields1
- )
- ?? false)
- }
- let shippingMethodRequired =
- configuration.shippingType == .shipping
- && delegate?.responds(
- to: #selector(
- STPPaymentContextDelegate.paymentContext(_:didUpdateShippingAddress:completion:)
- )
- )
- ?? false
- && selectedShippingMethod == nil
- let verificationRequired =
- configuration.verifyPrefilledShippingAddress && shippingAddressNeedsVerification
- // true if STPShippingVC should be presented to collect or verify a shipping address
- let shouldPresentShippingAddress =
- shippingAddressRequired && (shippingAddressIncomplete ?? false || verificationRequired)
- // this handles a corner case where STPShippingVC should be presented because:
- // - shipping address has been pre-filled
- // - no verification is required, but the user still needs to enter a shipping method
- let shouldPresentShippingMethods =
- shippingAddressRequired && !(shippingAddressIncomplete ?? false)
- && !verificationRequired
- && shippingMethodRequired
- return shouldPresentShippingAddress || shouldPresentShippingMethods
- }
-
- func didFinish(
- with status: STPPaymentStatus,
- error: Error?
- ) {
- state = STPPaymentContextState.none
- delegate?.paymentContext(
- self,
- didFinishWith: status,
- error: error
- )
- }
-
- func buildPaymentRequest() -> PKPaymentRequest? {
- guard let appleMerchantIdentifier = configuration.appleMerchantIdentifier, paymentAmount > 0
- else {
- return nil
- }
- let paymentRequest = StripeAPI.paymentRequest(
- withMerchantIdentifier: appleMerchantIdentifier,
- country: paymentCountry,
- currency: paymentCurrency
- )
-
-#if compiler(>=5.9)
- if #available(macOS 14.0, iOS 17.0, *) {
- paymentRequest.applePayLaterAvailability = applePayLaterAvailability._convertedToSwiftValue()
- }
-#endif
-
- let summaryItems = paymentSummaryItems
- paymentRequest.paymentSummaryItems = summaryItems
-
- let requiredFields = STPAddress.applePayContactFields(
- from: configuration.requiredBillingAddressFields
- )
- paymentRequest.requiredBillingContactFields = requiredFields
-
- var shippingRequiredFields: Set?
- if let requiredShippingAddressFields1 = configuration.requiredShippingAddressFields {
- shippingRequiredFields = STPAddress.pkContactFields(
- fromStripeContactFields: requiredShippingAddressFields1
- )
- }
- if let shippingRequiredFields = shippingRequiredFields {
- paymentRequest.requiredShippingContactFields = shippingRequiredFields
- }
-
- paymentRequest.currencyCode = paymentCurrency.uppercased()
- if let selectedShippingMethod = selectedShippingMethod {
- var orderedShippingMethods = shippingMethods
- orderedShippingMethods?.removeAll {
- $0 as AnyObject === selectedShippingMethod as AnyObject
- }
- orderedShippingMethods?.insert(selectedShippingMethod, at: 0)
- paymentRequest.shippingMethods = orderedShippingMethods
- } else {
- paymentRequest.shippingMethods = shippingMethods
- }
-
- paymentRequest.shippingType = STPPaymentContext.pkShippingType(configuration.shippingType)
-
- if let shippingAddress = shippingAddress {
- paymentRequest.shippingContact = shippingAddress.pkContactValue()
- }
- return paymentRequest
- }
-
- class func pkShippingType(_ shippingType: STPShippingType) -> PKShippingType {
- switch shippingType {
- case .shipping:
- return .shipping
- case .delivery:
- return .delivery
- @unknown default:
- fatalError()
- }
- }
-
- func artificiallyRetain(_ host: NSObject) {
- objc_setAssociatedObject(
- host,
- UnsafeRawPointer(&kSTPPaymentCoordinatorAssociatedObjectKey),
- self,
- .OBJC_ASSOCIATION_RETAIN_NONATOMIC
- )
- }
-
- // MARK: - STPAuthenticationContext
- @objc
- public func authenticationPresentingViewController() -> UIViewController {
- return hostViewController!
- }
-
- @objc
- public func prepare(forPresentation completion: @escaping STPVoidBlock) {
- if applePayVC != nil && applePayVC?.presentingViewController != nil {
- hostViewController?.dismiss(
- animated: transitionAnimationsEnabled()
- ) {
- completion()
- }
- } else {
- completion()
- }
- }
-}
-
-/// Implement `STPPaymentContextDelegate` to get notified when a payment context changes, finishes, encounters errors, etc. In practice, if your app has a "checkout screen view controller", that is a good candidate to implement this protocol.
-@objc public protocol STPPaymentContextDelegate: NSObjectProtocol {
- /// Called when the payment context encounters an error when fetching its initial set of data. A few ways to handle this are:
- /// - If you're showing the user a checkout page, dismiss the checkout page when this is called and present the error to the user.
- /// - Present the error to the user using a `UIAlertController` with two buttons: Retry and Cancel. If they cancel, dismiss your UI. If they Retry, call `retryLoading` on the payment context.
- /// To make it harder to get your UI into a bad state, this won't be called until the context's `hostViewController` has finished appearing.
- /// - Parameters:
- /// - paymentContext: the payment context that encountered the error
- /// - error: the error that was encountered
- func paymentContext(_ paymentContext: STPPaymentContext, didFailToLoadWithError error: Error)
- /// This is called every time the contents of the payment context change. When this is called, you should update your app's UI to reflect the current state of the payment context. For example, if you have a checkout page with a "selected payment method" row, you should update its payment method with `paymentContext.selectedPaymentOption.label`. If that checkout page has a "buy" button, you should enable/disable it depending on the result of `paymentContext.isReadyForPayment`.
- /// - Parameter paymentContext: the payment context that changed
- func paymentContextDidChange(_ paymentContext: STPPaymentContext)
- /// Inside this method, you should make a call to your backend API to make a PaymentIntent with that Customer + payment method, and invoke the `completion` block when that is done.
- /// - Parameters:
- /// - paymentContext: The context that succeeded
- /// - paymentResult: Information associated with the payment that you can pass to your server. You should go to your backend API with this payment result and use the PaymentIntent API to complete the payment. See https://stripe.com/docs/mobile/ios/basic#submit-payment-intents Once that's done call the `completion` block with any error that occurred (or none, if the payment succeeded). - seealso: STPPaymentResult.h
- /// - completion: Call this block when you're done creating a payment intent (or subscription, etc) on your backend. If it succeeded, call `completion(STPPaymentStatusSuccess, nil)`. If it failed with an error, call `completion(STPPaymentStatusError, error)`. If the user canceled, call `completion(STPPaymentStatusUserCancellation, nil)`.
- func paymentContext(
- _ paymentContext: STPPaymentContext,
- didCreatePaymentResult paymentResult: STPPaymentResult,
- completion: @escaping STPPaymentStatusBlock
- )
- /// This is invoked by an `STPPaymentContext` when it is finished. This will be called after the payment is done and all necessary UI has been dismissed. You should inspect the returned `status` and behave appropriately. For example: if it's `STPPaymentStatusSuccess`, show the user a receipt. If it's `STPPaymentStatusError`, inform the user of the error. If it's `STPPaymentStatusUserCancellation`, do nothing.
- /// - Parameters:
- /// - paymentContext: The payment context that finished
- /// - status: The status of the payment - `STPPaymentStatusSuccess` if it succeeded, `STPPaymentStatusError` if it failed with an error (in which case the `error` parameter will be non-nil), `STPPaymentStatusUserCancellation` if the user canceled the payment.
- /// - error: An error that occurred, if any.
- func paymentContext(
- _ paymentContext: STPPaymentContext,
- didFinishWith status: STPPaymentStatus,
- error: Error?
- )
-
- /// Inside this method, you should verify that you can ship to the given address.
- /// You should call the completion block with the results of your validation
- /// and the available shipping methods for the given address. If you don't implement
- /// this method, the user won't be prompted to select a shipping method and all
- /// addresses will be valid. If you call the completion block with nil or an
- /// empty array of shipping methods, the user won't be prompted to select a
- /// shipping method.
- /// @note If a user updates their shipping address within the Apple Pay dialog,
- /// this address will be anonymized. For example, in the US, it will only include the
- /// city, state, and zip code. The payment context will have the user's complete
- /// shipping address by the time `paymentContext:didFinishWithStatus:error` is
- /// called.
- /// - Parameters:
- /// - paymentContext: The context that updated its shipping address
- /// - address: The current shipping address
- /// - completion: Call this block when you're done validating the shipping
- /// address and calculating available shipping methods. If you call the completion
- /// block with nil or an empty array of shipping methods, the user won't be prompted
- /// to select a shipping method.
- @objc optional func paymentContext(
- _ paymentContext: STPPaymentContext,
- didUpdateShippingAddress address: STPAddress,
- completion: @escaping STPShippingMethodsCompletionBlock
- )
-}
-
-/// The current state of the payment context
-/// - STPPaymentContextStateNone: No view controllers are currently being shown. The payment may or may not have already been completed
-/// - STPPaymentContextStateShowingRequestedViewController: The view controller that you requested the context show is being shown (via the push or present payment methods or shipping view controller methods)
-/// - STPPaymentContextStateRequestingPayment: The payment context is in the middle of requesting payment. It may be showing some other UI or view controller if more information is necessary to complete the payment.
-enum STPPaymentContextState: Int {
- case none
- case showingRequestedViewController
- case requestingPayment
-}
-
-private var kSTPPaymentCoordinatorAssociatedObjectKey = 0
-
-/// :nodoc:
-@_spi(STP) extension STPPaymentContext: STPAnalyticsProtocol {
- @_spi(STP) public static var stp_analyticsIdentifier = "STPPaymentContext"
-}
-
-#if compiler(>=5.9)
-@available(macOS 14.0, iOS 17.0, *)
-extension PKApplePayLaterAvailability {
- func _convertedToSwiftValue() -> PKPaymentRequest.ApplePayLaterAvailability {
- switch self {
- case .available:
- return .available
- case .unavailableItemIneligible:
- return .unavailable(.itemIneligible)
- case .unavailableRecurringTransaction:
- return .unavailable(.recurringTransaction)
- @unknown default:
- fatalError()
- }
- }
-}
-#endif
diff --git a/Stripe/StripeiOS/Source/STPPaymentContextAmountModel.swift b/Stripe/StripeiOS/Source/STPPaymentContextAmountModel.swift
deleted file mode 100644
index c79c13e2041..00000000000
--- a/Stripe/StripeiOS/Source/STPPaymentContextAmountModel.swift
+++ /dev/null
@@ -1,96 +0,0 @@
-//
-// STPPaymentContextAmountModel.swift
-// StripeiOS
-//
-// Created by Brian Dorfman on 8/16/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-import Foundation
-import PassKit
-@_spi(STP) import StripePayments
-
-/// Internal model for STPPaymentContext's `paymentAmount` and
-/// `paymentSummaryItems` properties.
-class STPPaymentContextAmountModel: NSObject {
- private var paymentAmount = 0
- private var paymentSummaryItems: [PKPaymentSummaryItem]?
-
- init(
- amount paymentAmount: Int
- ) {
- super.init()
- self.paymentAmount = paymentAmount
- paymentSummaryItems = nil
- }
-
- init(
- paymentSummaryItems: [PKPaymentSummaryItem]?
- ) {
- super.init()
- paymentAmount = 0
- self.paymentSummaryItems = paymentSummaryItems
- }
-
- func paymentAmount(withCurrency currency: String?, shippingMethod: PKShippingMethod?) -> Int {
- let shippingAmount =
- ((shippingMethod != nil)
- ? shippingMethod?.amount.stp_amount(withCurrency: currency) : 0) ?? 0
- if paymentSummaryItems == nil {
- return paymentAmount + shippingAmount
- } else {
- let lastItem = paymentSummaryItems?.last
- return (lastItem?.amount.stp_amount(withCurrency: currency) ?? 0) + shippingAmount
- }
- }
-
- func paymentSummaryItems(
- withCurrency currency: String?,
- companyName: String?,
- shippingMethod: PKShippingMethod?
- ) -> [PKPaymentSummaryItem]? {
- var shippingItem: PKPaymentSummaryItem?
- if let shippingMethod = shippingMethod {
- shippingItem = PKPaymentSummaryItem(
- label: shippingMethod.label,
- amount: shippingMethod.amount
- )
- }
- if paymentSummaryItems == nil {
- let shippingAmount = shippingMethod?.amount.stp_amount(withCurrency: currency) ?? 0
- let total = NSDecimalNumber.stp_decimalNumber(
- withAmount: paymentAmount + shippingAmount,
- currency: currency
- )
- let totalItem = PKPaymentSummaryItem(label: companyName ?? "", amount: total)
- var items = [totalItem]
- if let shippingItem = shippingItem {
- items.insert(shippingItem, at: 0)
- }
- return items.compactMap { $0 }
- } else {
- if (paymentSummaryItems?.count ?? 0) > 0 && shippingItem != nil {
- var items = paymentSummaryItems
- let origTotalItem = items?.last
- var newTotal: NSDecimalNumber?
- if let amount1 = shippingItem?.amount {
- newTotal = origTotalItem?.amount.adding(amount1)
- }
- var totalItem: PKPaymentSummaryItem?
- if let newTotal = newTotal {
- totalItem = PKPaymentSummaryItem(
- label: origTotalItem?.label ?? "",
- amount: newTotal
- )
- }
- items?.removeLast()
- if let items = items {
- return items + [shippingItem, totalItem].compactMap { $0 }
- }
- return nil
- } else {
- return paymentSummaryItems
- }
- }
- }
-}
diff --git a/Stripe/StripeiOS/Source/STPPaymentOptionTableViewCell.swift b/Stripe/StripeiOS/Source/STPPaymentOptionTableViewCell.swift
deleted file mode 100644
index 9d6bb81ccbe..00000000000
--- a/Stripe/StripeiOS/Source/STPPaymentOptionTableViewCell.swift
+++ /dev/null
@@ -1,326 +0,0 @@
-//
-// STPPaymentOptionTableViewCell.swift
-// StripeiOS
-//
-// Created by Ben Guo on 8/30/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-@_spi(STP) import StripeCore
-@_spi(STP) import StripePaymentsUI
-import UIKit
-
-class STPPaymentOptionTableViewCell: UITableViewCell {
- @objc(configureForNewCardRowWithTheme:) func configureForNewCardRow(with theme: STPTheme) {
- paymentOption = nil
- self.theme = theme
-
- backgroundColor = theme.secondaryBackgroundColor
-
- // Left icon
- leftIcon.image = STPLegacyImageLibrary.addIcon()
- leftIcon.tintColor = theme.accentColor
-
- // Title label
- titleLabel.font = theme.font
- titleLabel.textColor = theme.accentColor
- titleLabel.text = STPLocalizedString("Add New Card…", "Button to add a new credit card.")
-
- // Checkmark icon
- checkmarkIcon.isHidden = true
-
- setNeedsLayout()
- }
-
- @objc(configureWithPaymentOption:theme:selected:) func configure(
- with paymentOption: STPPaymentOption?,
- theme: STPTheme,
- selected: Bool
- ) {
- self.paymentOption = paymentOption
- self.theme = theme
-
- backgroundColor = theme.secondaryBackgroundColor
-
- // Left icon
- leftIcon.image = paymentOption?.templateImage
- leftIcon.tintColor = primaryColorForPaymentOption(withSelected: selected)
-
- // Title label
- titleLabel.font = theme.font
- titleLabel.attributedText = buildAttributedString(with: paymentOption, selected: selected)
-
- // Checkmark icon
- checkmarkIcon.tintColor = theme.accentColor
- checkmarkIcon.isHidden = !selected
-
- // Accessibility
- if selected {
- accessibilityTraits.insert(.selected)
- } else {
- accessibilityTraits.remove(.selected)
- }
-
- setNeedsLayout()
- }
-
- @objc(configureForFPXRowWithTheme:) func configureForFPXRow(with theme: STPTheme) {
- paymentOption = nil
- self.theme = theme
-
- backgroundColor = theme.secondaryBackgroundColor
-
- // Left icon
- leftIcon.image = STPImageLibrary.bankIcon()
- leftIcon.tintColor = primaryColorForPaymentOption(withSelected: false)
-
- // Title label
- titleLabel.font = theme.font
- titleLabel.textColor = self.theme.primaryForegroundColor
- titleLabel.text = STPLocalizedString(
- "Online Banking (FPX)",
- "Button to pay with a Bank Account (using FPX)."
- )
-
- // Checkmark icon
- checkmarkIcon.isHidden = true
- accessoryType = .disclosureIndicator
- setNeedsLayout()
- }
-
- private var paymentOption: STPPaymentOption?
- private var theme: STPTheme = .defaultTheme
- private var leftIcon = UIImageView()
- private var titleLabel = UILabel()
- private var checkmarkIcon = UIImageView(image: STPLegacyImageLibrary.checkmarkIcon())
-
- override init(
- style: UITableViewCell.CellStyle,
- reuseIdentifier: String?
- ) {
- super.init(style: style, reuseIdentifier: reuseIdentifier)
- // Left icon
- leftIcon.translatesAutoresizingMaskIntoConstraints = false
- contentView.addSubview(leftIcon)
-
- // Title label
- titleLabel.translatesAutoresizingMaskIntoConstraints = false
- contentView.addSubview(titleLabel)
-
- // Checkmark icon
- checkmarkIcon.translatesAutoresizingMaskIntoConstraints = false
- contentView.addSubview(checkmarkIcon)
-
- NSLayoutConstraint.activate(
- [
- self.leftIcon.centerXAnchor.constraint(
- equalTo: contentView.leadingAnchor,
- constant: kPadding + 0.5 * kDefaultIconWidth
- ),
- self.leftIcon.centerYAnchor.constraint(
- lessThanOrEqualTo: contentView.centerYAnchor
- ),
- self.checkmarkIcon.widthAnchor.constraint(equalToConstant: kCheckmarkWidth),
- self.checkmarkIcon.heightAnchor.constraint(
- equalTo: self.checkmarkIcon.widthAnchor,
- multiplier: 1.0
- ),
- self.checkmarkIcon.centerXAnchor.constraint(
- equalTo: contentView.trailingAnchor,
- constant: -kPadding
- ),
- self.checkmarkIcon.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
- // Constrain label to leadingAnchor with the default
- // icon width so that the text always aligns vertically
- // even if the icond widths differ
- self.titleLabel.leadingAnchor.constraint(
- equalTo: contentView.leadingAnchor,
- constant: 2.0 * kPadding + kDefaultIconWidth
- ),
- self.titleLabel.trailingAnchor.constraint(
- equalTo: self.checkmarkIcon.leadingAnchor,
- constant: -kPadding
- ),
- self.titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
- ])
- accessibilityTraits.insert(.button)
- isAccessibilityElement = true
- }
-
- func primaryColorForPaymentOption(withSelected selected: Bool) -> UIColor {
- let fadedColor: UIColor = UIColor(dynamicProvider: { _ in
- return self.theme.primaryForegroundColor.withAlphaComponent(0.6)
- })
-
- return (selected ? theme.accentColor : fadedColor)
- }
-
- func buildAttributedString(
- with paymentOption: STPPaymentOption?,
- selected: Bool
- )
- -> NSAttributedString
- {
- if let paymentOption = paymentOption as? STPCard {
- return buildAttributedString(with: paymentOption, selected: selected)
- } else if let source = paymentOption as? STPSource {
- if source.type == .card && source.cardDetails != nil {
- return buildAttributedString(withCardSource: source, selected: selected)
- }
- } else if let paymentMethod = paymentOption as? STPPaymentMethod {
- if paymentMethod.type == .card && paymentMethod.card != nil {
- return buildAttributedString(
- withCardPaymentMethod: paymentMethod,
- selected: selected
- )
- }
- if paymentMethod.type == .FPX && paymentMethod.fpx != nil {
- return buildAttributedString(
- with: STPFPXBank.brandFrom(paymentMethod.fpx?.bankIdentifierCode),
- selected: selected
- )
- }
- } else if paymentOption is STPApplePayPaymentOption {
- let label = String.Localized.apple_pay
- let primaryColor = primaryColorForPaymentOption(withSelected: selected)
- return NSAttributedString(
- string: label,
- attributes: [
- NSAttributedString.Key.foregroundColor: primaryColor
- ]
- )
- } else if let paymentMethodParams = paymentOption as? STPPaymentMethodParams {
- if paymentMethodParams.type == .card && paymentMethodParams.card != nil {
- return buildAttributedString(
- withCardPaymentMethodParams: paymentMethodParams,
- selected: selected
- )
- }
- if paymentMethodParams.type == .FPX && paymentMethodParams.fpx != nil {
- return buildAttributedString(
- with: paymentMethodParams.fpx?.bank ?? STPFPXBankBrand.unknown,
- selected: selected
- )
- }
- }
-
- // Unrecognized payment method
- return NSAttributedString(string: "")
- }
-
- func buildAttributedString(with card: STPCard, selected: Bool) -> NSAttributedString {
- return buildAttributedString(
- with: card.brand,
- last4: card.last4,
- selected: selected
- )
- }
-
- func buildAttributedString(withCardSource card: STPSource, selected: Bool) -> NSAttributedString
- {
- return buildAttributedString(
- with: card.cardDetails?.brand ?? .unknown,
- last4: card.cardDetails?.last4 ?? "",
- selected: selected
- )
- }
-
- func buildAttributedString(
- withCardPaymentMethod paymentMethod: STPPaymentMethod,
- selected: Bool
- )
- -> NSAttributedString
- {
- return buildAttributedString(
- with: paymentMethod.card?.preferredDisplayBrand ?? .unknown,
- last4: paymentMethod.card?.last4 ?? "",
- selected: selected
- )
- }
-
- func buildAttributedString(
- withCardPaymentMethodParams paymentMethodParams: STPPaymentMethodParams,
- selected: Bool
- ) -> NSAttributedString {
- let brand = paymentMethodParams.card?.preferredDisplayBrand ?? .unknown
- return buildAttributedString(
- with: brand,
- last4: paymentMethodParams.card?.last4 ?? "",
- selected: selected
- )
- }
-
- func buildAttributedString(
- with bankBrand: STPFPXBankBrand,
- selected: Bool
- )
- -> NSAttributedString
- {
- let label = (STPFPXBank.stringFrom(bankBrand) ?? "") + " (FPX)"
- let primaryColor = primaryColorForPaymentOption(withSelected: selected)
- return NSAttributedString(
- string: label,
- attributes: [
- NSAttributedString.Key.foregroundColor: primaryColor
- ]
- )
- }
-
- func buildAttributedString(
- with brand: STPCardBrand,
- last4: String,
- selected: Bool
- ) -> NSAttributedString {
- let format = String.Localized.card_brand_ending_in_last_4
- let brandString = STPCard.string(from: brand)
- let label = String(format: format, brandString, last4)
-
- let primaryColor = selected ? theme.accentColor : theme.primaryForegroundColor
-
- let secondaryColor = UIColor(dynamicProvider: { _ in
- return primaryColor.withAlphaComponent(0.6)
- })
-
- let attributes: [NSAttributedString.Key: Any] = [
- NSAttributedString.Key.foregroundColor: secondaryColor,
- NSAttributedString.Key.font: self.theme.font,
- ]
-
- let attributedString = NSMutableAttributedString(
- string: label,
- attributes: attributes as [NSAttributedString.Key: Any]
- )
- attributedString.addAttribute(
- .foregroundColor,
- value: primaryColor,
- range: (label as NSString).range(of: brandString)
- )
- attributedString.addAttribute(
- .foregroundColor,
- value: primaryColor,
- range: (label as NSString).range(of: last4)
- )
- attributedString.addAttribute(
- .font,
- value: theme.emphasisFont,
- range: (label as NSString).range(of: brandString)
- )
- attributedString.addAttribute(
- .font,
- value: theme.emphasisFont,
- range: (label as NSString).range(of: last4)
- )
-
- return attributedString
- }
-
- required init?(
- coder aDecoder: NSCoder
- ) {
- super.init(coder: aDecoder)
- }
-}
-
-private let kDefaultIconWidth: CGFloat = 26.0
-private let kPadding: CGFloat = 15.0
-private let kCheckmarkWidth: CGFloat = 14.0
diff --git a/Stripe/StripeiOS/Source/STPPaymentOptionTuple.swift b/Stripe/StripeiOS/Source/STPPaymentOptionTuple.swift
deleted file mode 100644
index 6084d1329a1..00000000000
--- a/Stripe/StripeiOS/Source/STPPaymentOptionTuple.swift
+++ /dev/null
@@ -1,88 +0,0 @@
-//
-// STPPaymentOptionTuple.swift
-// StripeiOS
-//
-// Created by Jack Flintermann on 5/17/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-import Foundation
-
-class STPPaymentOptionTuple: NSObject {
- @objc
- public convenience init(
- paymentOptions: [STPPaymentOption],
- selectedPaymentOption: STPPaymentOption?
- ) {
- self.init()
- self.paymentOptions = paymentOptions
- self.selectedPaymentOption = selectedPaymentOption
- }
-
- @objc
- public convenience init(
- paymentOptions: [STPPaymentOption],
- selectedPaymentOption: STPPaymentOption?,
- addApplePayOption applePayEnabled: Bool,
- addFPXOption fpxEnabled: Bool
- ) {
- var mutablePaymentOptions = paymentOptions
- weak var selected = selectedPaymentOption
-
- if applePayEnabled {
- let applePay = STPApplePayPaymentOption()
- mutablePaymentOptions.append(applePay)
-
- if selected == nil {
- selected = applePay
- }
- }
-
- if fpxEnabled {
- let fpx = STPPaymentMethodFPXParams()
- let fpxPaymentOption = STPPaymentMethodParams(
- fpx: fpx,
- billingDetails: nil,
- metadata: nil
- )
- mutablePaymentOptions.append(fpxPaymentOption)
- }
-
- self.init(
- paymentOptions: mutablePaymentOptions,
- selectedPaymentOption: selected
- )
- }
-
- /// Returns a tuple for the given array of STPPaymentMethod, filtered to only include the
- /// the types supported by STPPaymentContext/STPPaymentOptionsViewController and adding
- /// Apple Pay as a method if appropriate.
- /// - Returns: A new tuple ready to be used by the SDK's UI elements
- @objc(tupleFilteredForUIWithPaymentMethods:selectedPaymentMethod:configuration:)
- public convenience init(
- filteredForUIWith paymentMethods: [STPPaymentMethod],
- selectedPaymentMethod selectedPaymentMethodID: String?,
- configuration: STPPaymentConfiguration
- ) {
- var paymentOptions: [STPPaymentOption] = []
- var selectedPaymentMethod: STPPaymentMethod?
- for paymentMethod in paymentMethods {
- if paymentMethod.type == .card {
- paymentOptions.append(paymentMethod)
- if paymentMethod.stripeId == selectedPaymentMethodID {
- selectedPaymentMethod = paymentMethod
- }
- }
- }
-
- self.init(
- paymentOptions: paymentOptions,
- selectedPaymentOption: selectedPaymentMethod,
- addApplePayOption: configuration.applePayEnabled,
- addFPXOption: configuration.fpxEnabled
- )
- }
-
- private(set) weak var selectedPaymentOption: STPPaymentOption?
- private(set) var paymentOptions: [STPPaymentOption] = []
-}
diff --git a/Stripe/StripeiOS/Source/STPPaymentOptionsInternalViewController.swift b/Stripe/StripeiOS/Source/STPPaymentOptionsInternalViewController.swift
deleted file mode 100644
index 10bd0d4848b..00000000000
--- a/Stripe/StripeiOS/Source/STPPaymentOptionsInternalViewController.swift
+++ /dev/null
@@ -1,593 +0,0 @@
-//
-// STPPaymentOptionsInternalViewController.swift
-// StripeiOS
-//
-// Created by Jack Flintermann on 6/9/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-import Foundation
-@_spi(STP) import StripeCore
-import UIKit
-
-@objc protocol STPPaymentOptionsInternalViewControllerDelegate: AnyObject {
- func internalViewControllerDidSelect(_ paymentOption: STPPaymentOption?)
- func internalViewControllerDidDelete(_ paymentOption: STPPaymentOption?)
- func internalViewControllerDidCreatePaymentOption(
- _ paymentOption: STPPaymentOption?,
- completion: @escaping STPErrorBlock
- )
- func internalViewControllerDidCancel()
-}
-
-class STPPaymentOptionsInternalViewController: STPCoreTableViewController, UITableViewDataSource,
- UITableViewDelegate, STPAddCardViewControllerDelegate, STPBankSelectionViewControllerDelegate
-{
- init(
- configuration: STPPaymentConfiguration,
- customerContext: STPCustomerContext?,
- analyticsLogger: STPPaymentContext.AnalyticsLogger,
- apiClient: STPAPIClient,
- theme: STPTheme,
- prefilledInformation: STPUserInformation?,
- shippingAddress: STPAddress?,
- paymentOptionTuple tuple: STPPaymentOptionTuple,
- delegate: STPPaymentOptionsInternalViewControllerDelegate?
- ) {
- self.analyticsLogger = analyticsLogger
- super.init(theme: theme)
- self.configuration = configuration
- // This parameter may be a custom API adapter, and not a CustomerContext.
- apiAdapter = customerContext
- self.apiClient = apiClient
- self.prefilledInformation = prefilledInformation
- self.shippingAddress = shippingAddress
- paymentOptions = tuple.paymentOptions
- selectedPaymentOption = tuple.selectedPaymentOption
- self.delegate = delegate
-
- title = STPLocalizedString("Payment Method", "Title for Payment Method screen")
- }
-
- func update(with tuple: STPPaymentOptionTuple) {
- if let selectedPaymentOption = selectedPaymentOption,
- selectedPaymentOption.isEqual(tuple.selectedPaymentOption)
- {
- return
- }
-
- paymentOptions = tuple.paymentOptions
- selectedPaymentOption = tuple.selectedPaymentOption
-
- // Reload card list section
- let sections = NSMutableIndexSet(index: PaymentOptionSectionCardList)
- tableView?.reloadSections(sections as IndexSet, with: .automatic)
- }
-
- private var _customFooterView: UIView?
- internal let analyticsLogger: STPPaymentContext.AnalyticsLogger
- var customFooterView: UIView? {
- get {
- _customFooterView
- }
- set(footerView) {
- _customFooterView = footerView
- _didSetCustomFooterView()
- }
- }
- func _didSetCustomFooterView() {
- if isViewLoaded {
- if let size = _customFooterView?.sizeThatFits(
- CGSize(width: view.bounds.size.width, height: CGFloat.greatestFiniteMagnitude)
- ) {
- _customFooterView?.frame = CGRect(
- x: 0,
- y: 0,
- width: size.width,
- height: size.height
- )
- }
-
- tableView?.tableFooterView = _customFooterView
- }
- }
-
- var addCardViewControllerCustomFooterView: UIView?
- var prefilledInformation: STPUserInformation?
- private var configuration: STPPaymentConfiguration?
- private var apiAdapter: STPBackendAPIAdapter?
- private var shippingAddress: STPAddress?
- private var paymentOptions: [STPPaymentOption]?
- private var apiClient: STPAPIClient = .shared
- private var selectedPaymentOption: STPPaymentOption?
- private weak var delegate: STPPaymentOptionsInternalViewControllerDelegate?
- private var cardImageView: UIImageView?
-
- override func createAndSetupViews() {
- super.createAndSetupViews()
-
- // Table view
- tableView?.register(
- STPPaymentOptionTableViewCell.self,
- forCellReuseIdentifier: PaymentOptionCellReuseIdentifier
- )
-
- tableView?.dataSource = self
- tableView?.delegate = self
- tableView?.reloadData()
-
- // Table header view
- let cardImageView = UIImageView(image: STPLegacyImageLibrary.largeCardFrontImage())
- cardImageView.contentMode = .center
- cardImageView.frame = CGRect(
- x: 0.0,
- y: 0.0,
- width: view.bounds.size.width,
- height: cardImageView.bounds.size.height + (57.0 * 2.0)
- )
- cardImageView.image = STPLegacyImageLibrary.largeCardFrontImage()
- cardImageView.tintColor = theme.accentColor
- self.cardImageView = cardImageView
-
- tableView?.tableHeaderView = cardImageView
-
- // Table view editing state
- tableView?.setEditing(false, animated: false)
- reloadRightBarButtonItem(
- withTableViewIsEditing: tableView?.isEditing ?? false,
- animated: false
- )
-
- stp_navigationItemProxy?.leftBarButtonItem?.accessibilityIdentifier =
- "PaymentOptionsViewControllerCancelButtonIdentifier"
- // re-set the custom footer view if it was added before we loaded
- _didSetCustomFooterView()
- }
-
- override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
- analyticsLogger.logPaymentOptionsScreenAppeared()
- }
-
- override func viewDidLayoutSubviews() {
- super.viewDidLayoutSubviews()
-
- // Resetting it re-calculates the size based on new view width
- // UITableView requires us to call setter again to actually pick up frame
- // change on footers
- if tableView?.tableFooterView != nil {
- customFooterView = tableView?.tableFooterView
- }
- }
-
- func reloadRightBarButtonItem(withTableViewIsEditing tableViewIsEditing: Bool, animated: Bool) {
- var barButtonItem: UIBarButtonItem?
-
- if !tableViewIsEditing {
- if isAnyPaymentOptionDetachable() {
- // Show edit button
- barButtonItem = UIBarButtonItem(
- barButtonSystemItem: .edit,
- target: self,
- action: #selector(handleEditButtonTapped(_:))
- )
- } else {
- // Show no button
- barButtonItem = nil
- }
- } else {
- // Show done button
- barButtonItem = UIBarButtonItem(
- barButtonSystemItem: .done,
- target: self,
- action: #selector(handleDoneButtonTapped(_:))
- )
- }
-
- barButtonItem?.stp_setTheme(theme)
-
- stp_navigationItemProxy?.setRightBarButton(barButtonItem, animated: animated)
- }
-
- func isAnyPaymentOptionDetachable() -> Bool {
- for paymentOption in cardPaymentOptions() {
- if isPaymentOptionDetachable(paymentOption) {
- return true
- }
- }
-
- return false
- }
-
- func isPaymentOptionDetachable(_ paymentOption: STPPaymentOption?) -> Bool {
- if !(configuration?.canDeletePaymentOptions ?? false) {
- // Feature is disabled
- return false
- }
-
- if apiAdapter == nil {
- // Cannot detach payment methods without customer context
- return false
- }
-
- if !(apiAdapter?.responds(
- to: #selector(STPCustomerContext.detachPaymentMethod(fromCustomer:completion:))
- )
- ?? false)
- {
- // Cannot detach payment methods if customerContext is an apiAdapter
- // that doesn't implement detachPaymentMethod
- return false
- }
-
- if paymentOption == nil {
- // Cannot detach non-existent payment method
- return false
- }
-
- if !(paymentOption is STPPaymentMethod) {
- // Cannot detach non-payment method
- return false
- }
-
- // Payment method can be deleted from customer
- return true
- }
-
- func cardPaymentOptions() -> [STPPaymentOption] {
- guard let paymentOptions = paymentOptions else {
- return []
- }
-
- return paymentOptions.filter({ (o) -> Bool in
- if o is STPPaymentMethodParams {
- let paymentMethodParams = o as? STPPaymentMethodParams
- if paymentMethodParams?.type != .card {
- return false
- }
- }
- return true
- })
- }
-
- func apmPaymentOptions() -> [STPPaymentOption] {
- guard let paymentOptions = paymentOptions else {
- return []
- }
- return paymentOptions.filter({ (o) -> Bool in
- if (o) is STPPaymentMethodParams {
- let paymentMethodParams = o as? STPPaymentMethodParams
- if paymentMethodParams?.type == .FPX {
- // Add other APMs as we gain support for them in Basic Integration
- return true
- }
- }
- return false
- })
- }
-
- // MARK: - Button Handlers
- @objc override func handleCancelTapped(_ sender: Any?) {
- delegate?.internalViewControllerDidCancel()
- }
-
- @objc func handleEditButtonTapped(_ sender: Any?) {
- tableView?.setEditing(true, animated: true)
- reloadRightBarButtonItem(
- withTableViewIsEditing: tableView?.isEditing ?? false,
- animated: true
- )
- }
-
- @objc func handleDoneButtonTapped(_ sender: Any?) {
- _endTableViewEditing()
- reloadRightBarButtonItem(
- withTableViewIsEditing: tableView?.isEditing ?? false,
- animated: true
- )
- }
-
- func _endTableViewEditing() {
- tableView?.setEditing(false, animated: true)
- }
-
- // MARK: - UITableViewDataSource
- func numberOfSections(in tableView: UITableView) -> Int {
- if apmPaymentOptions().count > 0 {
- return 3
- } else {
- return 2
- }
- }
-
- func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- if section == PaymentOptionSectionCardList {
- return cardPaymentOptions().count
- }
-
- if section == PaymentOptionSectionAddCard {
- return 1
- }
-
- if section == PaymentOptionSectionAPM {
- return apmPaymentOptions().count
- }
-
- return 0
- }
-
- func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- let cell =
- tableView.dequeueReusableCell(
- withIdentifier: PaymentOptionCellReuseIdentifier,
- for: indexPath
- )
- as? STPPaymentOptionTableViewCell
-
- if indexPath.section == PaymentOptionSectionCardList {
- weak var paymentOption =
- cardPaymentOptions().stp_boundSafeObject(at: indexPath.row)
- let selected = paymentOption!.isEqual(selectedPaymentOption)
-
- cell?.configure(with: paymentOption!, theme: theme, selected: selected)
- } else if indexPath.section == PaymentOptionSectionAddCard {
- cell?.configureForNewCardRow(with: theme)
- cell?.accessibilityIdentifier = "PaymentOptionsTableViewAddNewCardButtonIdentifier"
- } else if indexPath.section == PaymentOptionSectionAPM {
- weak var paymentOption =
- apmPaymentOptions().stp_boundSafeObject(at: indexPath.row)
- if paymentOption is STPPaymentMethodParams {
- let paymentMethodParams = paymentOption as? STPPaymentMethodParams
- if paymentMethodParams?.type == .FPX {
- cell?.configureForFPXRow(with: theme)
- cell?.accessibilityIdentifier = "PaymentOptionsTableViewFPXButtonIdentifier"
- }
- }
- }
-
- return cell!
- }
-
- func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
- if indexPath.section == PaymentOptionSectionCardList {
- weak var paymentOption =
- cardPaymentOptions().stp_boundSafeObject(at: indexPath.row)
-
- if isPaymentOptionDetachable(paymentOption) {
- return true
- }
- }
-
- return false
- }
-
- func tableView(
- _ tableView: UITableView,
- commit editingStyle: UITableViewCell.EditingStyle,
- forRowAt indexPath: IndexPath
- ) {
- if indexPath.section == PaymentOptionSectionCardList {
- if editingStyle != .delete {
- // Showed the user a non-delete option when we shouldn't have
- tableView.reloadData()
- return
- }
-
- if !(indexPath.row < cardPaymentOptions().count) {
- // Data source and table view out of sync for some reason
- tableView.reloadData()
- return
- }
-
- weak var paymentOptionToDelete =
- cardPaymentOptions().stp_boundSafeObject(at: indexPath.row)
-
- if !isPaymentOptionDetachable(paymentOptionToDelete) {
- // Showed the user a delete option for a payment method when we shouldn't have
- tableView.reloadData()
- return
- }
-
- let paymentMethod = paymentOptionToDelete as? STPPaymentMethod
-
- // Kickoff request to delete payment method from customer
- if let paymentMethod = paymentMethod {
- apiAdapter?.detachPaymentMethod?(fromCustomer: paymentMethod, completion: nil)
- }
-
- // Optimistically remove payment method from data source
- var paymentOptions = self.paymentOptions
- paymentOptions?.removeAll { $0 as AnyObject === paymentOptionToDelete as AnyObject }
- self.paymentOptions = paymentOptions
-
- // Perform deletion animation for single row
- tableView.deleteRows(at: [indexPath], with: .automatic)
-
- var tableViewIsEditing = tableView.isEditing
- if !isAnyPaymentOptionDetachable() {
- // we deleted the last available payment option, stop editing
- // (but delay to next runloop because calling tableView setEditing:animated:
- // in this function is not allowed)
- DispatchQueue.main.async(execute: {
- self._endTableViewEditing()
- })
- // manually set the value passed to reloadRightBarButtonItemWithTableViewIsEditing
- // below
- tableViewIsEditing = false
- }
-
- // Reload right bar button item text
- reloadRightBarButtonItem(withTableViewIsEditing: tableViewIsEditing, animated: true)
-
- // Notify delegate
- delegate?.internalViewControllerDidDelete(paymentOptionToDelete)
- }
- }
-
- // MARK: - UITableViewDelegate
- func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- if indexPath.section == PaymentOptionSectionCardList {
- // Update data source
- weak var paymentOption =
- cardPaymentOptions().stp_boundSafeObject(at: indexPath.row)
- selectedPaymentOption = paymentOption
-
- // Perform selection animation
- tableView.reloadSections(
- NSIndexSet(index: PaymentOptionSectionCardList) as IndexSet,
- with: .fade
- )
-
- // Notify delegate
- delegate?.internalViewControllerDidSelect(paymentOption)
- } else if indexPath.section == PaymentOptionSectionAddCard {
- var paymentCardViewController: STPAddCardViewController?
- if let configuration = configuration {
- paymentCardViewController = STPAddCardViewController(
- configuration: configuration,
- theme: theme
- )
- }
- paymentCardViewController?.analyticsLogger = analyticsLogger
- paymentCardViewController?.apiClient = apiClient
- paymentCardViewController?.delegate = self
- paymentCardViewController?.prefilledInformation = prefilledInformation
- paymentCardViewController?.shippingAddress = shippingAddress
- paymentCardViewController?.customFooterView = addCardViewControllerCustomFooterView
-
- if let paymentCardViewController = paymentCardViewController {
- navigationController?.pushViewController(paymentCardViewController, animated: true)
- }
- } else if indexPath.section == PaymentOptionSectionAPM {
- weak var paymentOption =
- apmPaymentOptions().stp_boundSafeObject(at: indexPath.row)
- if paymentOption is STPPaymentMethodParams {
- if let paymentMethodParams = paymentOption as? STPPaymentMethodParams,
- paymentMethodParams.type == .FPX
- {
- var bankSelectionViewController: STPBankSelectionViewController?
- if let configuration = configuration {
- bankSelectionViewController = STPBankSelectionViewController(
- bankMethod: .FPX,
- configuration: configuration,
- theme: theme
- )
- }
- bankSelectionViewController?.apiClient = apiClient
- bankSelectionViewController?.delegate = self
-
- if let bankSelectionViewController = bankSelectionViewController {
- navigationController?.pushViewController(
- bankSelectionViewController,
- animated: true
- )
- }
- }
- }
- }
-
- tableView.deselectRow(at: indexPath, animated: true)
- }
-
- func tableView(
- _ tableView: UITableView,
- willDisplay cell: UITableViewCell,
- forRowAt indexPath: IndexPath
- ) {
- let isTopRow = indexPath.row == 0
- let isBottomRow =
- self.tableView(tableView, numberOfRowsInSection: indexPath.section) - 1 == indexPath.row
-
- cell.stp_setBorderColor(theme.tertiaryBackgroundColor)
- cell.stp_setTopBorderHidden(!isTopRow)
- cell.stp_setBottomBorderHidden(!isBottomRow)
- cell.stp_setFakeSeparatorColor(theme.quaternaryBackgroundColor)
- cell.stp_setFakeSeparatorLeftInset(15.0)
- }
-
- func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
- if self.tableView(tableView, numberOfRowsInSection: section) == 0 {
- return 0.01
- }
-
- return 27.0
- }
-
- override func tableView(
- _ tableView: UITableView,
- heightForHeaderInSection section: Int
- )
- -> CGFloat
- {
- return 0.01
- }
-
- func tableView(
- _ tableView: UITableView,
- editingStyleForRowAt indexPath: IndexPath
- )
- -> UITableViewCell.EditingStyle
- {
- if indexPath.section == PaymentOptionSectionCardList {
- return .delete
- }
-
- return .none
- }
-
- func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
- reloadRightBarButtonItem(withTableViewIsEditing: true, animated: true)
- }
-
- func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
- reloadRightBarButtonItem(withTableViewIsEditing: tableView.isEditing, animated: true)
- }
-
- // MARK: - STPAddCardViewControllerDelegate
- func addCardViewControllerDidCancel(_ addCardViewController: STPAddCardViewController) {
- navigationController?.popViewController(animated: true)
- }
-
- @objc func addCardViewController(
- _ addCardViewController: STPAddCardViewController,
- didCreatePaymentMethod paymentMethod: STPPaymentMethod,
- completion: @escaping STPErrorBlock
- ) {
- delegate?.internalViewControllerDidCreatePaymentOption(
- paymentMethod,
- completion: completion
- )
- }
-
- @objc func bankSelectionViewController(
- _ bankViewController: STPBankSelectionViewController,
- didCreatePaymentMethodParams paymentMethodParams: STPPaymentMethodParams
- ) {
- delegate?.internalViewControllerDidCreatePaymentOption(paymentMethodParams) { _ in
- }
- }
-
- required init?(
- coder aDecoder: NSCoder
- ) {
- fatalError("init(coder:) has not been implemented")
- }
-
- required init(
- nibName nibNameOrNil: String?,
- bundle nibBundleOrNil: Bundle?
- ) {
- fatalError("init(nibName:bundle:) has not been implemented")
- }
-
- required init(
- theme: STPTheme?
- ) {
- fatalError("init(theme:) has not been implemented")
- }
-}
-
-private let PaymentOptionCellReuseIdentifier = "PaymentOptionCellReuseIdentifier"
-private let PaymentOptionSectionCardList = 0
-private let PaymentOptionSectionAddCard = 1
-private let PaymentOptionSectionAPM = 2
diff --git a/Stripe/StripeiOS/Source/STPPaymentOptionsViewController.swift b/Stripe/StripeiOS/Source/STPPaymentOptionsViewController.swift
deleted file mode 100644
index aa392eae4cc..00000000000
--- a/Stripe/StripeiOS/Source/STPPaymentOptionsViewController.swift
+++ /dev/null
@@ -1,662 +0,0 @@
-//
-// STPPaymentOptionsViewController.swift
-// StripeiOS
-//
-// Created by Jack Flintermann on 1/12/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-@_spi(STP) import StripeCore
-@_spi(STP) import StripePaymentsUI
-import UIKit
-
-/// This view controller presents a list of payment method options to the user,
-/// which they can select between. They can also add credit cards to the list.
-/// It must be displayed inside a `UINavigationController`, so you can either
-/// create a `UINavigationController` with an `STPPaymentOptionsViewController`
-/// as the `rootViewController` and then present the `UINavigationController`,
-/// or push a new `STPPaymentOptionsViewController` onto an existing
-/// `UINavigationController`'s stack. You can also have `STPPaymentContext` do this
-/// for you automatically, by calling `presentPaymentOptionsViewController`
-/// or `pushPaymentOptionsViewController` on it.
-public class STPPaymentOptionsViewController: STPCoreViewController,
- STPPaymentOptionsInternalViewControllerDelegate, STPAddCardViewControllerDelegate
-{
-
- /// The delegate for the view controller.
- /// The delegate receives callbacks when the user selects a method or cancels,
- /// and is responsible for dismissing the payments methods view controller when
- /// it is finished.
- @objc private(set) weak var delegate: STPPaymentOptionsViewControllerDelegate?
-
- /// Creates a new payment methods view controller.
- /// - Parameter paymentContext: A payment context to power the view controller's view.
- /// The payment context will in turn use its backend API adapter to fetch the
- /// information it needs from your application.
- /// - Returns: an initialized view controller.
- @objc(initWithPaymentContext:)
- public convenience init(
- paymentContext: STPPaymentContext
- ) {
- self.init(
- configuration: paymentContext.configuration,
- apiAdapter: paymentContext.apiAdapter,
- apiClient: paymentContext.apiClient,
- analyticsLogger: paymentContext.analyticsLogger,
- loadingPromise: paymentContext.currentValuePromise,
- theme: paymentContext.theme,
- shippingAddress: paymentContext.shippingAddress,
- delegate: paymentContext
- )
- }
-
- init(
- configuration: STPPaymentConfiguration?,
- apiAdapter: STPBackendAPIAdapter,
- apiClient: STPAPIClient?,
- analyticsLogger: STPPaymentContext.AnalyticsLogger,
- loadingPromise: STPPromise?,
- theme: STPTheme?,
- shippingAddress: STPAddress?,
- delegate: STPPaymentOptionsViewControllerDelegate
- ) {
- self.apiAdapter = apiAdapter
- self.analyticsLogger = analyticsLogger
- super.init(theme: theme)
- commonInit(
- configuration: configuration,
- apiAdapter: apiAdapter,
- apiClient: apiClient,
- loadingPromise: loadingPromise,
- shippingAddress: shippingAddress,
- delegate: delegate
- )
- }
-
- func commonInit(
- configuration: STPPaymentConfiguration?,
- apiAdapter: STPBackendAPIAdapter,
- apiClient: STPAPIClient?,
- loadingPromise: STPPromise?,
- shippingAddress: STPAddress?,
- delegate: STPPaymentOptionsViewControllerDelegate
- ) {
- STPAnalyticsClient.sharedClient.addClass(
- toProductUsageIfNecessary: STPPaymentOptionsViewController.self
- )
-
- self.configuration = configuration
- self.apiClient = apiClient ?? .shared
- self.shippingAddress = shippingAddress
- self.apiAdapter = apiAdapter
- self.loadingPromise = loadingPromise
- self.delegate = delegate
-
- navigationItem.title = STPLocalizedString(
- "Loading…",
- "Title for screen when data is still loading from the network."
- )
-
- weak var weakSelf = self
- loadingPromise?.onSuccess({ tuple in
- guard let strongSelf = weakSelf else {
- return
- }
- var `internal`: UIViewController?
- if (tuple.paymentOptions.count) > 0 {
- let customerContext = strongSelf.apiAdapter as? STPCustomerContext
-
- var payMethodsInternal: STPPaymentOptionsInternalViewController?
- if let configuration1 = strongSelf.configuration {
- payMethodsInternal = STPPaymentOptionsInternalViewController(
- configuration: configuration1,
- customerContext: customerContext,
- analyticsLogger: strongSelf.analyticsLogger,
- apiClient: strongSelf.apiClient,
- theme: strongSelf.theme,
- prefilledInformation: strongSelf.prefilledInformation,
- shippingAddress: strongSelf.shippingAddress,
- paymentOptionTuple: tuple,
- delegate: strongSelf
- )
- }
- if strongSelf.paymentOptionsViewControllerFooterView != nil {
- payMethodsInternal?.customFooterView =
- strongSelf.paymentOptionsViewControllerFooterView
- }
- if strongSelf.addCardViewControllerFooterView != nil {
- payMethodsInternal?.addCardViewControllerCustomFooterView =
- strongSelf.addCardViewControllerFooterView
- }
- `internal` = payMethodsInternal
- } else {
- var addCardViewController: STPAddCardViewController?
- if let configuration1 = strongSelf.configuration {
- addCardViewController = STPAddCardViewController(
- configuration: configuration1,
- theme: strongSelf.theme
- )
- }
- addCardViewController?.analyticsLogger = strongSelf.analyticsLogger
- addCardViewController?.apiClient = strongSelf.apiClient
- addCardViewController?.delegate = strongSelf
- addCardViewController?.prefilledInformation = strongSelf.prefilledInformation
- addCardViewController?.shippingAddress = strongSelf.shippingAddress
- `internal` = addCardViewController
-
- if strongSelf.addCardViewControllerFooterView != nil {
- addCardViewController?.customFooterView =
- strongSelf.addCardViewControllerFooterView
- }
- }
-
- `internal`?.stp_navigationItemProxy = strongSelf.navigationItem
- if let controller = `internal` {
- strongSelf.addChild(controller)
- }
- `internal`?.view.alpha = 0
- if let view = `internal`?.view, let activityIndicator1 = strongSelf.activityIndicator {
- strongSelf.view.insertSubview(view, belowSubview: activityIndicator1)
- }
- if let view = `internal`?.view {
- strongSelf.view.addSubview(view)
- }
- `internal`?.view.frame = strongSelf.view.bounds
- `internal`?.didMove(toParent: strongSelf)
- UIView.animate(
- withDuration: 0.2,
- animations: {
- strongSelf.activityIndicator?.alpha = 0
- `internal`?.view.alpha = 1
- }
- ) { _ in
- strongSelf.activityIndicator?.animating = false
- }
- strongSelf.navigationItem.setRightBarButton(
- `internal`?.stp_navigationItemProxy?.rightBarButtonItem,
- animated: true
- )
- strongSelf.internalViewController = `internal`
- })
- }
-
- /// Initializes a new payment methods view controller without using a
- /// payment context.
- /// - Parameters:
- /// - configuration: The configuration to use to determine what types of
- /// payment method to offer your user. - seealso: STPPaymentConfiguration.h
- /// - theme: The theme to inform the appearance of the UI.
- /// - customerContext: The customer context the view controller will use to
- /// fetch and modify its Stripe customer
- /// - delegate: A delegate that will be notified when the payment
- /// methods view controller's selection changes.
- /// - Returns: an initialized view controller.
- @objc(initWithConfiguration:theme:customerContext:delegate:)
- public convenience init(
- configuration: STPPaymentConfiguration,
- theme: STPTheme,
- customerContext: STPCustomerContext,
- delegate: STPPaymentOptionsViewControllerDelegate
- ) {
- self.init(
- configuration: configuration,
- theme: theme,
- apiAdapter: customerContext,
- delegate: delegate
- )
- }
-
- /// Note: Instead of providing your own backend API adapter, we recommend using
- /// `STPCustomerContext`, which will manage retrieving and updating a
- /// Stripe customer for you. - seealso: STPCustomerContext.h
- /// Initializes a new payment methods view controller without using
- /// a payment context.
- /// - Parameters:
- /// - configuration: The configuration to use to determine what types of
- /// payment method to offer your user.
- /// - theme: The theme to inform the appearance of the UI.
- /// - apiAdapter: The API adapter to use to retrieve a customer's stored
- /// payment methods and save new ones.
- /// - delegate: A delegate that will be notified when the payment methods
- /// view controller's selection changes.
- @objc(initWithConfiguration:theme:apiAdapter:delegate:)
- public init(
- configuration: STPPaymentConfiguration,
- theme: STPTheme,
- apiAdapter: STPBackendAPIAdapter,
- delegate: STPPaymentOptionsViewControllerDelegate
- ) {
- self.apiAdapter = apiAdapter
- super.init(theme: theme)
- let promise = retrievePaymentMethods(with: configuration, apiAdapter: apiAdapter)
-
- commonInit(
- configuration: configuration,
- apiAdapter: apiAdapter,
- apiClient: STPAPIClient.shared,
- loadingPromise: promise,
- shippingAddress: nil,
- delegate: delegate
- )
- }
-
- /// If you've already collected some information from your user, you can set it
- /// here and it'll be automatically filled out when possible/appropriate in any UI
- /// that the payment context creates.
- @objc public var prefilledInformation: STPUserInformation? {
- didSet {
- if let payMethodsInternal = internalViewController as? STPPaymentOptionsInternalViewController {
- payMethodsInternal.prefilledInformation = prefilledInformation
- } else if let payMethodsInternal = internalViewController as? STPAddCardViewController {
- payMethodsInternal.prefilledInformation = prefilledInformation
- }
- }
- }
- /// @note This is no longer recommended as of v18.3.0 - the SDK automatically saves the Stripe ID of the last selected
- /// payment method using NSUserDefaults and displays it as the default pre-selected option. You can override this behavior
- /// by setting this property.
- /// The Stripe ID of a payment method to display as the default pre-selected option.
- /// @note Setting this after the view controller's view has loaded has no effect.
- @objc public var defaultPaymentMethod: String?
- /// A view that will be placed as the footer of the view controller when it is
- /// showing a list of saved payment methods to select from.
- /// When the footer view needs to be resized, it will be sent a
- /// `sizeThatFits:` call. The view should respond correctly to this method in order
- /// to be sized and positioned properly.
- @objc public var paymentOptionsViewControllerFooterView: UIView? {
- didSet {
- if let payMethodsInternal = internalViewController as? STPPaymentOptionsInternalViewController {
- payMethodsInternal.customFooterView = paymentOptionsViewControllerFooterView
- }
- }
- }
-
- /// A view that will be placed as the footer of the view controller when it is
- /// showing the add card view.
- /// When the footer view needs to be resized, it will be sent a
- /// `sizeThatFits:` call. The view should respond correctly to this method in order
- /// to be sized and positioned properly.
- @objc public var addCardViewControllerFooterView: UIView? {
- didSet {
- if let payMethodsInternal = internalViewController as? STPPaymentOptionsInternalViewController {
- payMethodsInternal.addCardViewControllerCustomFooterView = addCardViewControllerFooterView
- } else if let payMethodsInternal = internalViewController as? STPAddCardViewController {
- payMethodsInternal.customFooterView = addCardViewControllerFooterView
- }
- }
- }
-
- /// The API Client to use to make requests.
- /// Defaults to STPAPIClient.shared
- public var apiClient: STPAPIClient = .shared
-
- /// If you're pushing `STPPaymentOptionsViewController` onto an existing
- /// `UINavigationController`'s stack, you should use this method to dismiss it,
- /// since it may have pushed an additional add card view controller onto the
- /// navigation controller's stack.
- /// - Parameter completion: The callback to run after the view controller is dismissed.
- /// You may specify nil for this parameter.
- @objc(dismissWithCompletion:)
- public func dismiss(withCompletion completion: STPVoidBlock?) {
- if stp_isAtRootOfNavigationController() {
- presentingViewController?.dismiss(animated: true, completion: completion)
- } else {
- var previous = navigationController?.viewControllers.first
- for viewController in navigationController?.viewControllers ?? [] {
- if viewController == self {
- break
- }
- previous = viewController
- }
- navigationController?.stp_pop(
- to: previous,
- animated: true,
- completion: completion ?? {}
- )
- }
- }
-
- /// Use one of the initializers declared in this interface.
- @available(
- *,
- unavailable,
- message: "Use one of the initializers declared in this interface instead."
- )
- @objc public required init(
- theme: STPTheme?
- ) {
- fatalError("init(theme:) has not been implemented")
- }
-
- /// Use one of the initializers declared in this interface.
- @available(
- *,
- unavailable,
- message: "Use one of the initializers declared in this interface instead."
- )
- @objc public required init(
- nibName nibNameOrNil: String?,
- bundle nibBundleOrNil: Bundle?
- ) {
- fatalError("init(nibName:bundle:) has not been implemented")
- }
-
- /// Use one of the initializers declared in this interface.
- @available(
- *,
- unavailable,
- message: "Use one of the initializers declared in this interface instead."
- )
- @objc public required init?(
- coder aDecoder: NSCoder
- ) {
- fatalError("init(coder:) has not been implemented")
- }
-
- private var configuration: STPPaymentConfiguration?
- private var shippingAddress: STPAddress?
- private var apiAdapter: STPBackendAPIAdapter
- var loadingPromise: STPPromise?
- private var activityIndicator: STPPaymentActivityIndicatorView?
- internal var internalViewController: UIViewController?
- // Should be overwritten if this class is used by STPPaymentContext
- internal var analyticsLogger: STPPaymentContext.AnalyticsLogger = .init(product: STPPaymentOptionsViewController.self)
-
- func retrievePaymentMethods(
- with configuration: STPPaymentConfiguration,
- apiAdapter: STPBackendAPIAdapter?
- ) -> STPPromise {
- let promise = STPPromise()
- apiAdapter?.listPaymentMethodsForCustomer(completion: { paymentMethods, error in
- // We don't use stpDispatchToMainThreadIfNecessary here because we want this completion block to always be called asynchronously, so that users can set self.defaultPaymentMethod in time.
- DispatchQueue.main.async(execute: {
- if let error = error {
- promise.fail(error)
- } else {
- let defaultPaymentMethod = self.defaultPaymentMethod
- if defaultPaymentMethod == nil && (apiAdapter is STPCustomerContext) {
- // Retrieve the last selected payment method saved by STPCustomerContext
- (apiAdapter as? STPCustomerContext)?
- .retrieveLastSelectedPaymentMethodIDForCustomer(
- completion: { paymentMethodID, _ in
- var paymentTuple: STPPaymentOptionTuple?
- if let paymentMethods = paymentMethods {
- paymentTuple = STPPaymentOptionTuple.init(
- filteredForUIWith: paymentMethods,
- selectedPaymentMethod: paymentMethodID,
- configuration: configuration
- )
- }
- promise.succeed(paymentTuple!)
- })
- }
- var paymentTuple: STPPaymentOptionTuple?
- if let paymentMethods = paymentMethods {
- paymentTuple = STPPaymentOptionTuple.init(
- filteredForUIWith: paymentMethods,
- selectedPaymentMethod: defaultPaymentMethod,
- configuration: configuration
- )
- }
- promise.succeed(paymentTuple!)
- }
- })
- })
- return promise
- }
-
- override func createAndSetupViews() {
- super.createAndSetupViews()
-
- let activityIndicator = STPPaymentActivityIndicatorView()
- activityIndicator.animating = true
- view.addSubview(activityIndicator)
- self.activityIndicator = activityIndicator
- }
-
- /// :nodoc:
- @objc
- public override func viewDidLayoutSubviews() {
- super.viewDidLayoutSubviews()
- let centerX = (view.frame.size.width - (activityIndicator?.frame.size.width ?? 0.0)) / 2
- let centerY = (view.frame.size.height - (activityIndicator?.frame.size.height ?? 0.0)) / 2
- activityIndicator?.frame = CGRect(
- x: centerX,
- y: centerY,
- width: activityIndicator?.frame.size.width ?? 0.0,
- height: activityIndicator?.frame.size.height ?? 0.0
- )
- internalViewController?.view.frame = view.bounds
- }
-
- /// :nodoc:
- @objc
- public override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
- weak var weakSelf = self
- loadingPromise?.onSuccess({ tuple in
- let strongSelf = weakSelf
- if strongSelf == nil {
- return
- }
-
- if tuple.selectedPaymentOption != nil {
- if strongSelf?.delegate?.responds(
- to: #selector(
- STPPaymentOptionsViewControllerDelegate.paymentOptionsViewController(
- _:
- didSelect:
- ))
- )
- ?? false
- {
- if let strongSelf = strongSelf,
- let selectedPaymentOption = tuple.selectedPaymentOption
- {
- strongSelf.delegate?.paymentOptionsViewController?(
- strongSelf,
- didSelect: selectedPaymentOption
- )
- }
- }
- }
- }).onFailure({ error in
- let strongSelf = weakSelf
- if strongSelf == nil {
- return
- }
-
- if let strongSelf = strongSelf {
- strongSelf.delegate?.paymentOptionsViewController(
- strongSelf,
- didFailToLoadWithError: error
- )
- }
- })
- }
-
- @objc override func updateAppearance() {
- super.updateAppearance()
-
- activityIndicator?.tintColor = theme.accentColor
- }
-
- func finish(with paymentOption: STPPaymentOption?) {
- let isReusablePaymentMethod =
- (paymentOption is STPPaymentMethod)
- && (paymentOption as? STPPaymentMethod)?.isReusable ?? false
-
- if apiAdapter is STPCustomerContext {
- if isReusablePaymentMethod {
- // Save the payment method
- let paymentMethod = paymentOption as? STPPaymentMethod
- (apiAdapter as? STPCustomerContext)?.saveLastSelectedPaymentMethodID(
- forCustomer: paymentMethod?.stripeId ?? "",
- completion: nil
- )
- } else {
- // The customer selected something else (like Apple Pay)
- (apiAdapter as? STPCustomerContext)?.saveLastSelectedPaymentMethodID(
- forCustomer: nil,
- completion: nil
- )
- }
- }
-
- if delegate?.responds(
- to: #selector(
- STPPaymentOptionsViewControllerDelegate.paymentOptionsViewController(_:didSelect:))
- )
- ?? false
- {
- if let paymentOption = paymentOption {
- delegate?.paymentOptionsViewController?(self, didSelect: paymentOption)
- }
- }
- delegate?.paymentOptionsViewControllerDidFinish(self)
- }
-
- func internalViewControllerDidSelect(_ paymentOption: STPPaymentOption?) {
- finish(with: paymentOption)
- }
-
- func internalViewControllerDidDelete(_ paymentOption: STPPaymentOption?) {
- if delegate is STPPaymentContext {
- // Notify payment context to update its copy of payment methods
- if let paymentContext = delegate as? STPPaymentContext,
- let paymentOption = paymentOption
- {
- paymentContext.remove(paymentOption)
- }
- }
- }
-
- func internalViewControllerDidCreatePaymentOption(
- _ paymentOption: STPPaymentOption?,
- completion: @escaping STPErrorBlock
- ) {
- if !(paymentOption?.isReusable ?? false) {
- // Don't save a non-reusable payment option
- finish(with: paymentOption)
- return
- }
- let paymentMethod = paymentOption as? STPPaymentMethod
- if let paymentMethod = paymentMethod {
- apiAdapter.attachPaymentMethod(toCustomer: paymentMethod) { error in
- stpDispatchToMainThreadIfNecessary({
- completion(error)
- if error == nil {
- var promise: STPPromise?
- if let configuration = self.configuration {
- promise = self.retrievePaymentMethods(
- with: configuration,
- apiAdapter: self.apiAdapter
- )
- }
- weak var weakSelf = self
- promise?.onSuccess({ tuple in
- let strongSelf = weakSelf
- if strongSelf == nil {
- return
- }
- let paymentTuple = STPPaymentOptionTuple(
- paymentOptions: tuple.paymentOptions,
- selectedPaymentOption: paymentMethod
- )
- if strongSelf?.internalViewController
- is STPPaymentOptionsInternalViewController
- {
- let paymentOptionsVC =
- strongSelf?.internalViewController
- as? STPPaymentOptionsInternalViewController
- paymentOptionsVC?.update(with: paymentTuple)
- }
- })
- self.finish(with: paymentMethod)
- }
- })
- }
- }
- }
-
- func internalViewControllerDidCancel() {
- delegate?.paymentOptionsViewControllerDidCancel(self)
- }
-
- @objc override func handleCancelTapped(_ sender: Any?) {
- delegate?.paymentOptionsViewControllerDidCancel(self)
- }
-
- @objc
- public func addCardViewControllerDidCancel(
- _ addCardViewController: STPAddCardViewController
- ) {
- // Add card is only our direct delegate if there are no other payment methods possible
- // and we skipped directly to this screen. In this case, a cancel from it is the same as a cancel to us.
- delegate?.paymentOptionsViewControllerDidCancel(self)
- }
-
- @objc
- public func addCardViewController(
- _ addCardViewController: STPAddCardViewController,
- didCreatePaymentMethod paymentMethod: STPPaymentMethod,
- completion: @escaping STPErrorBlock
- ) {
- internalViewControllerDidCreatePaymentOption(paymentMethod, completion: completion)
- }
-}
-
-// MARK: - STPPaymentOptionsViewControllerDelegate
-
-/// An `STPPaymentOptionsViewControllerDelegate` responds when a user selects a
-/// payment option from (or cancels) an `STPPaymentOptionsViewController`. In both
-/// of these instances, you should dismiss the view controller (either by popping
-/// it off the navigation stack, or dismissing it).
-@objc public protocol STPPaymentOptionsViewControllerDelegate: NSObjectProtocol {
- /// This is called when the view controller encounters an error fetching the user's
- /// payment options from its API adapter. You should dismiss the view controller
- /// when this is called.
- /// - Parameters:
- /// - paymentOptionsViewController: the view controller in question
- /// - error: the error that occurred
- func paymentOptionsViewController(
- _ paymentOptionsViewController: STPPaymentOptionsViewController,
- didFailToLoadWithError error: Error
- )
- /// This is called when the user selects or adds a payment method, so it will often
- /// be called immediately after calling `paymentOptionsViewController:didSelectPaymentOption:`.
- /// You should dismiss the view controller when this is called.
- /// - Parameter paymentOptionsViewController: the view controller that has finished
- func paymentOptionsViewControllerDidFinish(
- _ paymentOptionsViewController: STPPaymentOptionsViewController
- )
- /// This is called when the user taps "cancel".
- /// You should dismiss the view controller when this is called.
- /// - Parameter paymentOptionsViewController: the view controller that has finished
- func paymentOptionsViewControllerDidCancel(
- _ paymentOptionsViewController: STPPaymentOptionsViewController
- )
-
- /// This is called when the user either makes a selection, or adds a new card.
- /// This will be triggered after the view controller loads with the user's current
- /// selection (if they have one) and then subsequently when they change their
- /// choice. You should use this callback to update any necessary UI in your app
- /// that displays the user's currently selected payment method. You should *not*
- /// dismiss the view controller at this point, instead do this in
- /// `paymentOptionsViewControllerDidFinish:`. `STPPaymentOptionsViewController`
- /// will also call the necessary methods on your API adapter, so you don't need to
- /// call them directly during this method.
- /// - Parameters:
- /// - paymentOptionsViewController: the view controller in question
- /// - paymentOption: the selected payment method
- @objc(paymentOptionsViewController:didSelectPaymentOption:)
- optional func paymentOptionsViewController(
- _ paymentOptionsViewController: STPPaymentOptionsViewController,
- didSelect paymentOption: STPPaymentOption
- )
-}
-
-/// :nodoc:
-@_spi(STP) extension STPPaymentOptionsViewController: STPAnalyticsProtocol {
- @_spi(STP) public static var stp_analyticsIdentifier = "STPPaymentOptionsViewController"
-}
diff --git a/Stripe/StripeiOS/Source/STPSectionHeaderView.swift b/Stripe/StripeiOS/Source/STPSectionHeaderView.swift
deleted file mode 100644
index 2b45b2af198..00000000000
--- a/Stripe/StripeiOS/Source/STPSectionHeaderView.swift
+++ /dev/null
@@ -1,161 +0,0 @@
-//
-// STPSectionHeaderView.swift
-// StripeiOS
-//
-// Created by Ben Guo on 1/3/17.
-// Copyright © 2017 Stripe, Inc. All rights reserved.
-//
-
-import UIKit
-
-class STPSectionHeaderView: UIView {
- private var _theme: STPTheme = STPTheme.defaultTheme
- var theme: STPTheme {
- get {
- _theme
- }
- set(theme) {
- _theme = theme
- updateAppearance()
- }
- }
-
- private var _title: String?
- var title: String? {
- get {
- _title
- }
- set(title) {
- _title = title
- if let title = title {
- let style = NSMutableParagraphStyle()
- style.firstLineHeadIndent = 15
- style.headIndent = style.firstLineHeadIndent
- let attributes = [
- NSAttributedString.Key.paragraphStyle: style
- ]
- label?.attributedText = NSAttributedString(
- string: title,
- attributes: attributes
- )
- } else {
- label?.attributedText = nil
- }
- setNeedsLayout()
- }
- }
- weak var button: UIButton?
-
- private var _buttonHidden = false
- var buttonHidden: Bool {
- get {
- _buttonHidden
- }
- set(buttonHidden) {
- _buttonHidden = buttonHidden
- button?.alpha = buttonHidden ? 0 : 1
- }
- }
- private weak var label: UILabel?
- private let buttonInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 15)
-
- override init(
- frame: CGRect
- ) {
- super.init(frame: frame)
- let label = UILabel()
- label.numberOfLines = 0
- label.lineBreakMode = .byWordWrapping
- label.accessibilityTraits.insert(.header)
- addSubview(label)
- self.label = label
- let button = UIButton(type: .system)
- button.contentHorizontalAlignment = .right
- button.titleLabel?.numberOfLines = 0
- button.titleLabel?.lineBreakMode = .byWordWrapping
- button.titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 15)
- button.contentEdgeInsets = .zero
- addSubview(button)
- self.button = button
- backgroundColor = UIColor.clear
- updateAppearance()
- }
-
- @objc func updateAppearance() {
- label?.font = theme.smallFont
- label?.textColor = theme.secondaryForegroundColor
- button?.titleLabel?.font = theme.smallFont
- button?.tintColor = theme.accentColor
- }
-
- override func layoutSubviews() {
- super.layoutSubviews()
- let bounds = stp_boundsWithHorizontalSafeAreaInsets()
- if buttonHidden {
- label?.frame = bounds
- } else {
- let halfWidth = bounds.size.width / 2
- let heightThatFits = self.heightThatFits(bounds.size)
- label?.frame = CGRect(
- x: bounds.origin.x,
- y: bounds.origin.y,
- width: halfWidth,
- height: heightThatFits
- )
- button?.frame = CGRect(
- x: bounds.origin.x + halfWidth,
- y: bounds.origin.y,
- width: halfWidth,
- height: heightThatFits
- )
- }
- }
-
- func heightThatFits(_ size: CGSize) -> CGFloat {
- let labelPadding: CGFloat = 16
- if buttonHidden {
- let labelHeight = label?.sizeThatFits(size).height ?? 0.0
- return labelHeight + labelPadding
- } else {
- let halfSize = CGSize(width: size.width / 2, height: size.height)
- let labelHeight = (label?.sizeThatFits(halfSize).height ?? 0.0) + labelPadding
- let buttonHeight = height(
- forButtonText: button?.titleLabel?.text,
- width: halfSize.width
- )
- return CGFloat(max(buttonHeight, labelHeight))
- }
- }
-
- private func height(forButtonText text: String?, width: CGFloat) -> CGFloat {
- let insets = buttonInsets
- let textSize = CGSize(
- width: width - insets.left - insets.right,
- height: CGFloat.greatestFiniteMagnitude
- )
- var attributes: [NSAttributedString.Key: Any]?
- if let font1 = button?.titleLabel?.font {
- attributes = [
- NSAttributedString.Key.font: font1
- ]
- }
- let buttonSize =
- text?.boundingRect(
- with: textSize,
- options: .usesLineFragmentOrigin,
- attributes: attributes,
- context: nil
- ).size ?? .zero
- return buttonSize.height + insets.top + insets.bottom
- }
-
- override func sizeThatFits(_ size: CGSize) -> CGSize {
- return CGSize(width: size.width, height: heightThatFits(size))
- }
-
- required init?(
- coder aDecoder: NSCoder
- ) {
- super.init(coder: aDecoder)
- }
-}
diff --git a/Stripe/StripeiOS/Source/STPShippingAddressViewController.swift b/Stripe/StripeiOS/Source/STPShippingAddressViewController.swift
deleted file mode 100644
index ac63189dcee..00000000000
--- a/Stripe/StripeiOS/Source/STPShippingAddressViewController.swift
+++ /dev/null
@@ -1,674 +0,0 @@
-//
-// STPShippingAddressViewController.swift
-// StripeiOS
-//
-// Created by Ben Guo on 8/29/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-import PassKit
-@_spi(STP) import StripeCore
-@_spi(STP) import StripePaymentsUI
-@_spi(STP) import StripeUICore
-import UIKit
-
-/// This view controller contains a shipping address collection form. It renders a right bar button item that submits the form, so it must be shown inside a `UINavigationController`. Depending on your configuration's shippingType, the view controller may present a shipping method selection form after the user enters an address.
-public class STPShippingAddressViewController: STPCoreTableViewController {
-
- /// A convenience initializer; equivalent to calling `init(configuration: STPPaymentConfiguration.shared theme: STPTheme.defaultTheme currency:"" shippingAddress:nil selectedShippingMethod:nil prefilledInformation:nil)`.
- @objc
- public convenience init() {
- self.init(
- configuration: STPPaymentConfiguration.shared,
- theme: STPTheme.defaultTheme,
- currency: "",
- shippingAddress: nil,
- selectedShippingMethod: nil,
- prefilledInformation: nil
- )
- }
-
- /// Initializes a new `STPShippingAddressViewController` with the given payment context and sets the payment context as its delegate.
- /// - Parameter paymentContext: The payment context to use.
- @objc(initWithPaymentContext:)
- public convenience init(
- paymentContext: STPPaymentContext
- ) {
- STPAnalyticsClient.sharedClient.addClass(
- toProductUsageIfNecessary: STPShippingAddressViewController.self
- )
-
- var billingAddress: STPAddress?
- weak var paymentOption = paymentContext.selectedPaymentOption
- if paymentOption is STPCard {
- let card = paymentOption as? STPCard
- billingAddress = card?.address
- } else if paymentOption is STPPaymentMethod {
- let paymentMethod = paymentOption as? STPPaymentMethod
- if let billingDetails1 = paymentMethod?.billingDetails {
- billingAddress = STPAddress(paymentMethodBillingDetails: billingDetails1)
- }
- }
- var prefilledInformation: STPUserInformation?
- if paymentContext.prefilledInformation != nil {
- prefilledInformation = paymentContext.prefilledInformation
- } else {
- prefilledInformation = STPUserInformation()
- }
- prefilledInformation?.billingAddress = billingAddress
- self.init(
- configuration: paymentContext.configuration,
- theme: paymentContext.theme,
- currency: paymentContext.paymentCurrency,
- shippingAddress: paymentContext.shippingAddress,
- selectedShippingMethod: paymentContext.selectedShippingMethod,
- prefilledInformation: prefilledInformation
- )
-
- self.delegate = paymentContext
- }
-
- /// Initializes a new `STPShippingAddressCardViewController` with the provided parameters.
- /// - Parameters:
- /// - configuration: The configuration to use (this determines the required shipping address fields and shipping type). - seealso: STPPaymentConfiguration
- /// - theme: The theme to use to inform the view controller's visual appearance. - seealso: STPTheme
- /// - currency: The currency to use when displaying amounts for shipping methods. The default is USD.
- /// - shippingAddress: If set, the shipping address view controller will be pre-filled with this address. - seealso: STPAddress
- /// - selectedShippingMethod: If set, the shipping methods view controller will use this method as the selected shipping method. If `selectedShippingMethod` is nil, the first shipping method in the array of methods returned by your delegate will be selected.
- /// - prefilledInformation: If set, the shipping address view controller will be pre-filled with this information. - seealso: STPUserInformation
- @objc(
- initWithConfiguration:
- theme:
- currency:
- shippingAddress:
- selectedShippingMethod:
- prefilledInformation:
- )
- public init(
- configuration: STPPaymentConfiguration,
- theme: STPTheme,
- currency: String?,
- shippingAddress: STPAddress?,
- selectedShippingMethod: PKShippingMethod?,
- prefilledInformation: STPUserInformation?
- ) {
- STPAnalyticsClient.sharedClient.addClass(
- toProductUsageIfNecessary: STPShippingAddressViewController.self
- )
- addressViewModel = STPAddressViewModel(
- requiredBillingFields: configuration.requiredBillingAddressFields,
- availableCountries: configuration.availableCountries
- )
- super.init(theme: theme)
- assert(
- (configuration.requiredShippingAddressFields?.count ?? 0) > 0,
- "`requiredShippingAddressFields` must not be empty when initializing an STPShippingAddressViewController."
- )
- self.configuration = configuration
- self.currency = currency
- self.selectedShippingMethod = selectedShippingMethod
- billingAddress = prefilledInformation?.billingAddress
- hasUsedBillingAddress = false
- addressViewModel = STPAddressViewModel(
- requiredShippingFields: configuration.requiredShippingAddressFields ?? [],
- availableCountries: configuration.availableCountries
- )
- addressViewModel.delegate = self
- if let shippingAddress = shippingAddress {
- addressViewModel.address = shippingAddress
- } else if prefilledInformation?.shippingAddress != nil {
- addressViewModel.address = prefilledInformation?.shippingAddress ?? STPAddress()
- }
- title = title(for: self.configuration?.shippingType ?? .shipping)
- }
-
- /// The view controller's delegate. This must be set before showing the view controller in order for it to work properly. - seealso: STPShippingAddressViewControllerDelegate
- @objc public weak var delegate: STPShippingAddressViewControllerDelegate?
-
- /// If you're pushing `STPShippingAddressViewController` onto an existing `UINavigationController`'s stack, you should use this method to dismiss it, since it may have pushed an additional shipping method view controller onto the navigation controller's stack.
- /// - Parameter completion: The callback to run after the view controller is dismissed. You may specify nil for this parameter.
- @objc(dismissWithCompletion:)
- public func dismiss(withCompletion completion: STPVoidBlock?) {
- if stp_isAtRootOfNavigationController() {
- presentingViewController?.dismiss(animated: true, completion: completion ?? {})
- } else {
- var previous = navigationController?.viewControllers.first
- for viewController in navigationController?.viewControllers ?? [] {
- if viewController == self {
- break
- }
- previous = viewController
- }
- navigationController?.stp_pop(
- to: previous,
- animated: true,
- completion: completion ?? {}
- )
- }
- }
-
- /// Use one of the initializers declared in this interface.
- @available(
- *,
- unavailable,
- message: "Use one of the initializers declared in this interface instead."
- )
- @objc public required init(
- theme: STPTheme?
- ) {
- let configuration = STPPaymentConfiguration.shared
- addressViewModel = STPAddressViewModel(
- requiredBillingFields: configuration.requiredBillingAddressFields,
- availableCountries: configuration.availableCountries
- )
-
- super.init(theme: theme)
- }
-
- /// Use one of the initializers declared in this interface.
- @available(
- *,
- unavailable,
- message: "Use one of the initializers declared in this interface instead."
- )
- @objc public required init(
- nibName nibNameOrNil: String?,
- bundle nibBundleOrNil: Bundle?
- ) {
- let configuration = STPPaymentConfiguration.shared
- addressViewModel = STPAddressViewModel(
- requiredBillingFields: configuration.requiredBillingAddressFields,
- availableCountries: configuration.availableCountries
- )
- super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
- }
-
- /// Use one of the initializers declared in this interface.
- @available(
- *,
- unavailable,
- message: "Use one of the initializers declared in this interface instead."
- )
- required init?(
- coder aDecoder: NSCoder
- ) {
- let configuration = STPPaymentConfiguration.shared
- addressViewModel = STPAddressViewModel(
- requiredBillingFields: configuration.requiredBillingAddressFields,
- availableCountries: configuration.availableCountries
- )
- super.init(coder: aDecoder)
- }
-
- private var configuration: STPPaymentConfiguration?
- private var currency: String?
- private var selectedShippingMethod: PKShippingMethod?
- private weak var imageView: UIImageView?
- private var nextItem: UIBarButtonItem?
-
- private var _loading = false
- private var loading: Bool {
- get {
- _loading
- }
- set(loading) {
- if loading == _loading {
- return
- }
- _loading = loading
- stp_navigationItemProxy?.setHidesBackButton(loading, animated: true)
- stp_navigationItemProxy?.leftBarButtonItem?.isEnabled = !loading
- activityIndicator?.animating = loading
- if loading {
- tableView?.endEditing(true)
- var loadingItem: UIBarButtonItem?
- if let activityIndicator = activityIndicator {
- loadingItem = UIBarButtonItem(customView: activityIndicator)
- }
- stp_navigationItemProxy?.setRightBarButton(loadingItem, animated: true)
- } else {
- stp_navigationItemProxy?.setRightBarButton(nextItem, animated: true)
- }
- for cell in addressViewModel.addressCells {
- cell.isUserInteractionEnabled = !loading
- UIView.animate(
- withDuration: 0.1,
- animations: {
- cell.alpha = loading ? 0.7 : 1.0
- }
- )
- }
- }
- }
- private var activityIndicator: STPPaymentActivityIndicatorView?
- internal var addressViewModel: STPAddressViewModel
- private var billingAddress: STPAddress?
- private var hasUsedBillingAddress = false
- private var addressHeaderView: STPSectionHeaderView?
-
- override func createAndSetupViews() {
- super.createAndSetupViews()
-
- var nextItem: UIBarButtonItem?
- switch configuration?.shippingType {
- case .shipping:
- nextItem = UIBarButtonItem(
- title: STPLocalizedString("Next", "Button to move to the next text entry field"),
- style: .done,
- target: self,
- action: #selector(next(_:))
- )
- case .delivery, .none, .some:
- nextItem = UIBarButtonItem(
- barButtonSystemItem: .done,
- target: self,
- action: #selector(next(_:))
- )
- }
- self.nextItem = nextItem
- stp_navigationItemProxy?.rightBarButtonItem = nextItem
- stp_navigationItemProxy?.rightBarButtonItem?.isEnabled = false
- stp_navigationItemProxy?.rightBarButtonItem?.accessibilityIdentifier =
- "ShippingViewControllerNextButtonIdentifier"
-
- let imageView = UIImageView(image: STPLegacyImageLibrary.largeShippingImage())
- imageView.contentMode = .center
- imageView.frame = CGRect(
- x: 0,
- y: 0,
- width: view.bounds.size.width,
- height: imageView.bounds.size.height + (57 * 2)
- )
- self.imageView = imageView
- tableView?.tableHeaderView = imageView
-
- activityIndicator = STPPaymentActivityIndicatorView(
- frame: CGRect(x: 0, y: 0, width: 20.0, height: 20.0)
- )
-
- tableView?.dataSource = self
- tableView?.delegate = self
- tableView?.reloadData()
- view.addGestureRecognizer(
- UITapGestureRecognizer(
- target: self,
- action: #selector(NSMutableAttributedString.endEditing)
- )
- )
-
- let headerView = STPSectionHeaderView()
- headerView.theme = theme
- if let shippingType1 = configuration?.shippingType {
- headerView.title = headerTitle(for: shippingType1)
- }
- headerView.button?.setTitle(
- STPLocalizedString(
- "Use Billing",
- "Button to fill shipping address from billing address."
- ),
- for: .normal
- )
- headerView.button?.addTarget(
- self,
- action: #selector(useBillingAddress(_:)),
- for: .touchUpInside
- )
- headerView.button?.accessibilityIdentifier = "ShippingAddressViewControllerUseBillingButton"
- var buttonVisible = false
- if let requiredFields = configuration?.requiredShippingAddressFields {
- let needsAddress =
- requiredFields.contains(.postalAddress) && !(addressViewModel.isValid)
- buttonVisible =
- needsAddress
- && billingAddress?.containsContent(forShippingAddressFields: requiredFields)
- ?? false
- && !hasUsedBillingAddress
- }
- headerView.button?.alpha = buttonVisible ? 1 : 0
- headerView.setNeedsLayout()
- addressHeaderView = headerView
-
- updateDoneButton()
- }
-
- @objc func endEditing() {
- view.endEditing(false)
- }
-
- @objc override func updateAppearance() {
- super.updateAppearance()
- let navBarTheme = navigationController?.navigationBar.stp_theme ?? theme
- nextItem?.stp_setTheme(navBarTheme)
-
- tableView?.allowsSelection = false
-
- imageView?.tintColor = theme.accentColor
- activityIndicator?.tintColor = theme.accentColor
- for cell in addressViewModel.addressCells {
- cell.theme = theme
- }
- addressHeaderView?.theme = theme
- }
-
- /// :nodoc:
- @objc
- public override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
- stp_beginObservingKeyboardAndInsettingScrollView(
- tableView,
- onChange: nil
- )
- firstEmptyField()?.becomeFirstResponder()
- }
-
- func firstEmptyField() -> UIResponder? {
- for cell in addressViewModel.addressCells {
- if (cell.contents?.count ?? 0) == 0 {
- return cell
- }
- }
- return nil
- }
-
- @objc override func handleCancelTapped(_ sender: Any?) {
- delegate?.shippingAddressViewControllerDidCancel(self)
- }
-
- @objc func next(_ sender: Any?) {
- let address = addressViewModel.address
- switch configuration?.shippingType {
- case .shipping:
- loading = true
- delegate?.shippingAddressViewController(self, didEnter: address) {
- status,
- shippingValidationError,
- shippingMethods,
- selectedShippingMethod in
- self.loading = false
- if status == .valid {
- if (shippingMethods?.count ?? 0) > 0 {
- var nextViewController: STPShippingMethodsViewController?
- if let shippingMethods = shippingMethods,
- let selectedShippingMethod = selectedShippingMethod
- {
- nextViewController = STPShippingMethodsViewController(
- shippingMethods: shippingMethods,
- selectedShippingMethod: selectedShippingMethod,
- currency: self.currency ?? "",
- theme: self.theme
- )
- }
- nextViewController?.delegate = self
- if let nextViewController = nextViewController {
- self.navigationController?.pushViewController(
- nextViewController,
- animated: true
- )
- }
- } else {
- self.delegate?.shippingAddressViewController(
- self,
- didFinishWith: address,
- shippingMethod: nil
- )
- }
- } else {
- self.handleShippingValidationError(shippingValidationError)
- }
- }
- case .delivery, .none, .some:
- delegate?.shippingAddressViewController(
- self,
- didFinishWith: address,
- shippingMethod: nil
- )
- }
- }
-
- func updateDoneButton() {
- stp_navigationItemProxy?.rightBarButtonItem?.isEnabled = addressViewModel.isValid
- }
-
- func handleShippingValidationError(_ error: Error?) {
- firstEmptyField()?.becomeFirstResponder()
- var title = STPLocalizedString("Invalid Shipping Address", "Shipping form error message")
- var message: String?
- if let error = error {
- title = error.localizedDescription
- message = (error as NSError).localizedFailureReason
- }
- let alertController = UIAlertController(
- title: title,
- message: message,
- preferredStyle: .alert
- )
- alertController.addAction(
- UIAlertAction(
- title: String.Localized.ok,
- style: .cancel,
- handler: nil
- )
- )
- present(alertController, animated: true)
- }
-
- /// :nodoc:
- @objc
- public override func tableView(
- _ tableView: UITableView,
- heightForHeaderInSection section: Int
- ) -> CGFloat {
- let size = addressHeaderView?.sizeThatFits(
- CGSize(width: view.bounds.size.width, height: CGFloat.greatestFiniteMagnitude)
- )
- return size?.height ?? 0.0
- }
-
- @objc func useBillingAddress(_ sender: UIButton) {
- guard let billingAddress = billingAddress else {
- return
- }
- tableView?.beginUpdates()
- addressViewModel.address = billingAddress
- hasUsedBillingAddress = true
- firstEmptyField()?.becomeFirstResponder()
- UIView.animate(
- withDuration: 0.2,
- animations: {
- self.addressHeaderView?.buttonHidden = true
- }
- )
- tableView?.endUpdates()
- }
-
- func title(for type: STPShippingType) -> String {
- if let shippingAddressFields = configuration?.requiredShippingAddressFields,
- shippingAddressFields.contains(.postalAddress)
- {
- switch type {
- case .shipping:
- return STPLocalizedString("Shipping", "Title for shipping info form")
- case .delivery:
- return STPLocalizedString("Delivery", "Title for delivery info form")
- }
- } else {
- return STPLocalizedString("Contact", "Title for contact info form")
- }
- }
-
- func headerTitle(for type: STPShippingType) -> String {
- if let shippingAddressFields = configuration?.requiredShippingAddressFields,
- shippingAddressFields.contains(.postalAddress)
- {
- switch type {
- case .shipping:
- return String.Localized.shipping_address
- case .delivery:
- return STPLocalizedString(
- "Delivery Address",
- "Title for delivery address entry section"
- )
- }
- } else {
- return STPLocalizedString("Contact", "Title for contact info form")
- }
- }
-}
-
-/// An `STPShippingAddressViewControllerDelegate` is notified when an `STPShippingAddressViewController` receives an address, completes with an address, or is cancelled.
-@objc public protocol STPShippingAddressViewControllerDelegate: NSObjectProtocol {
- /// Called when the user cancels entering a shipping address. You should dismiss (or pop) the view controller at this point.
- /// - Parameter addressViewController: the view controller that has been cancelled
- func shippingAddressViewControllerDidCancel(
- _ addressViewController: STPShippingAddressViewController
- )
- /// This is called when the user enters a shipping address and taps next. You
- /// should validate the address and determine what shipping methods are available,
- /// and call the `completion` block when finished. If an error occurrs, call
- /// the `completion` block with the error. Otherwise, call the `completion`
- /// block with a nil error and an array of available shipping methods. If you don't
- /// need to collect a shipping method, you may pass an empty array or nil.
- /// - Parameters:
- /// - addressViewController: the view controller where the address was entered
- /// - address: the address that was entered. - seealso: STPAddress
- /// - completion: call this callback when you're done validating the address and determining available shipping methods.
-
- @objc(shippingAddressViewController:didEnterAddress:completion:)
- func shippingAddressViewController(
- _ addressViewController: STPShippingAddressViewController,
- didEnter address: STPAddress,
- completion: @escaping STPShippingMethodsCompletionBlock
- )
- /// This is called when the user selects a shipping method. If no shipping methods are given, or if the shipping type doesn't require a shipping method, this will be called after the user has a shipping address and your validation has succeeded. After updating your app with the user's shipping info, you should dismiss (or pop) the view controller. Note that if `shippingMethod` is non-nil, there will be an additional shipping methods view controller on the navigation controller's stack.
- /// - Parameters:
- /// - addressViewController: the view controller where the address was entered
- /// - address: the address that was entered. - seealso: STPAddress
- /// - method: the shipping method that was selected.
- @objc(shippingAddressViewController:didFinishWithAddress:shippingMethod:)
- func shippingAddressViewController(
- _ addressViewController: STPShippingAddressViewController,
- didFinishWith address: STPAddress,
- shippingMethod method: PKShippingMethod?
- )
-}
-
-extension STPShippingAddressViewController:
- STPAddressViewModelDelegate, UITableViewDelegate, UITableViewDataSource,
- STPShippingMethodsViewControllerDelegate
-{
-
- // MARK: - UITableView
- /// :nodoc:
- @objc
- public func numberOfSections(in tableView: UITableView) -> Int {
- return 1
- }
-
- /// :nodoc:
- @objc
- public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return addressViewModel.addressCells.count
- }
-
- /// :nodoc:
- @objc
- public func tableView(
- _ tableView: UITableView,
- cellForRowAt indexPath: IndexPath
- ) -> UITableViewCell {
- let cell =
- addressViewModel.addressCells.stp_boundSafeObject(at: indexPath.row)
- cell?.backgroundColor = theme.secondaryBackgroundColor
- cell?.contentView.backgroundColor = UIColor.clear
- return cell!
- }
-
- /// :nodoc:
- @objc
- public func tableView(
- _ tableView: UITableView,
- willDisplay cell: UITableViewCell,
- forRowAt indexPath: IndexPath
- ) {
- let topRow = indexPath.row == 0
- let bottomRow = tableView.numberOfRows(inSection: indexPath.section) - 1 == indexPath.row
- cell.stp_setBorderColor(theme.tertiaryBackgroundColor)
- cell.stp_setTopBorderHidden(!topRow)
- cell.stp_setBottomBorderHidden(!bottomRow)
- cell.stp_setFakeSeparatorColor(theme.quaternaryBackgroundColor)
- cell.stp_setFakeSeparatorLeftInset(15.0)
- }
-
- /// :nodoc:
- @objc
- public func tableView(
- _ tableView: UITableView,
- heightForFooterInSection section: Int
- )
- -> CGFloat
- {
- return 0.01
- }
-
- /// :nodoc:
- @objc
- public func tableView(
- _ tableView: UITableView,
- viewForFooterInSection section: Int
- )
- -> UIView?
- {
- return UIView()
- }
-
- /// :nodoc:
- @objc
- public func tableView(
- _ tableView: UITableView,
- viewForHeaderInSection section: Int
- )
- -> UIView?
- {
- return addressHeaderView
- }
-
- // MARK: - STPShippingMethodsViewControllerDelegate
- func shippingMethodsViewController(
- _ methodsViewController: STPShippingMethodsViewController,
- didFinishWith method: PKShippingMethod
- ) {
- delegate?.shippingAddressViewController(
- self,
- didFinishWith: addressViewModel.address,
- shippingMethod: method
- )
- }
-
- // MARK: - STPAddressViewModelDelegate
- func addressViewModel(_ addressViewModel: STPAddressViewModel, addedCellAt index: Int) {
- let indexPath = IndexPath(row: index, section: 0)
- tableView?.insertRows(at: [indexPath], with: .automatic)
- }
-
- func addressViewModel(_ addressViewModel: STPAddressViewModel, removedCellAt index: Int) {
- let indexPath = IndexPath(row: index, section: 0)
- tableView?.deleteRows(at: [indexPath], with: .automatic)
- }
-
- func addressViewModelDidChange(_ addressViewModel: STPAddressViewModel) {
- updateDoneButton()
- }
-
- func addressViewModelWillUpdate(_ addressViewModel: STPAddressViewModel) {
- tableView?.beginUpdates()
- }
-
- func addressViewModelDidUpdate(_ addressViewModel: STPAddressViewModel) {
- tableView?.endUpdates()
- }
-}
-
-/// :nodoc:
-@_spi(STP) extension STPShippingAddressViewController: STPAnalyticsProtocol {
- @_spi(STP) public static var stp_analyticsIdentifier = "STPShippingAddressViewController"
-}
diff --git a/Stripe/StripeiOS/Source/STPShippingMethodTableViewCell.swift b/Stripe/StripeiOS/Source/STPShippingMethodTableViewCell.swift
deleted file mode 100644
index f40c493c022..00000000000
--- a/Stripe/StripeiOS/Source/STPShippingMethodTableViewCell.swift
+++ /dev/null
@@ -1,147 +0,0 @@
-//
-// STPShippingMethodTableViewCell.swift
-// StripeiOS
-//
-// Created by Ben Guo on 8/30/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-import PassKit
-@_spi(STP) import StripePayments
-import UIKit
-
-class STPShippingMethodTableViewCell: UITableViewCell {
- private var _theme: STPTheme?
- var theme: STPTheme? {
- get {
- _theme
- }
- set(theme) {
- _theme = theme
- updateAppearance()
- }
- }
-
- func setShippingMethod(_ method: PKShippingMethod, currency: String) {
- shippingMethod = method
- titleLabel?.text = method.label
- subtitleLabel?.text = method.detail
- var localeInfo = [
- NSLocale.Key.currencyCode.rawValue: currency
- ]
- localeInfo[NSLocale.Key.languageCode.rawValue] = NSLocale.preferredLanguages.first ?? ""
- let localeID = NSLocale.localeIdentifier(fromComponents: localeInfo)
- let locale = NSLocale(localeIdentifier: localeID)
- numberFormatter?.locale = locale as Locale
- let amount = method.amount.stp_amount(withCurrency: currency)
- if amount == 0 {
- amountLabel?.text = STPLocalizedString("Free", "Label for free shipping method")
- } else {
- let number = NSDecimalNumber.stp_decimalNumber(
- withAmount: amount,
- currency: currency
- )
- amountLabel?.text = numberFormatter?.string(from: number)
- }
- setNeedsLayout()
- }
-
- private weak var titleLabel: UILabel?
- private weak var subtitleLabel: UILabel?
- private weak var amountLabel: UILabel?
- private weak var checkmarkIcon: UIImageView?
- private var shippingMethod: PKShippingMethod?
- private var numberFormatter: NumberFormatter?
-
- override init(
- style: UITableViewCell.CellStyle,
- reuseIdentifier: String?
- ) {
- super.init(style: style, reuseIdentifier: reuseIdentifier)
- theme = STPTheme()
- let titleLabel = UILabel()
- self.titleLabel = titleLabel
- let subtitleLabel = UILabel()
- self.subtitleLabel = subtitleLabel
- let amountLabel = UILabel()
- self.amountLabel = amountLabel
- let checkmarkIcon = UIImageView(image: STPLegacyImageLibrary.checkmarkIcon())
- self.checkmarkIcon = checkmarkIcon
- let formatter = NumberFormatter()
- formatter.numberStyle = .currency
- formatter.usesGroupingSeparator = true
- numberFormatter = formatter
- contentView.addSubview(titleLabel)
- contentView.addSubview(subtitleLabel)
- contentView.addSubview(amountLabel)
- contentView.addSubview(checkmarkIcon)
- updateAppearance()
- }
-
- override var isSelected: Bool {
- get {
- return super.isSelected
- }
- set(selected) {
- super.isSelected = selected
- updateAppearance()
- }
- }
-
- @objc func updateAppearance() {
- contentView.backgroundColor = theme?.secondaryBackgroundColor
- backgroundColor = UIColor.clear
- titleLabel?.font = theme?.font
- subtitleLabel?.font = theme?.smallFont
- amountLabel?.font = theme?.font
- titleLabel?.textColor = isSelected ? theme?.accentColor : theme?.primaryForegroundColor
- amountLabel?.textColor = titleLabel?.textColor
- var subduedAccentColor: UIColor?
- if #available(iOS 13.0, *) {
- subduedAccentColor = UIColor(dynamicProvider: { _ in
- return self.theme?.accentColor.withAlphaComponent(0.6) ?? UIColor.clear
- })
- } else {
- subduedAccentColor = theme?.accentColor.withAlphaComponent(0.6)
- }
- subtitleLabel?.textColor = isSelected ? subduedAccentColor : theme?.secondaryForegroundColor
- checkmarkIcon?.tintColor = theme?.accentColor
- checkmarkIcon?.isHidden = !isSelected
- }
-
- override func layoutSubviews() {
- super.layoutSubviews()
- let midY = bounds.midY
- checkmarkIcon?.frame = CGRect(x: 0, y: 0, width: 14, height: 14)
- checkmarkIcon?.center = CGPoint(
- x: bounds.width - 15 - (checkmarkIcon?.bounds.midX ?? 0.0),
- y: midY
- )
- amountLabel?.sizeToFit()
- amountLabel?.center = CGPoint(
- x: (checkmarkIcon?.frame.minX ?? 0.0) - 15 - (amountLabel?.bounds.midX ?? 0.0),
- y: midY
- )
- let labelWidth = (amountLabel?.frame.minX ?? 0.0) - 30
- titleLabel?.sizeToFit()
- titleLabel?.frame = CGRect(
- x: 15,
- y: 8,
- width: labelWidth,
- height: titleLabel?.frame.size.height ?? 0.0
- )
- subtitleLabel?.sizeToFit()
- subtitleLabel?.frame = CGRect(
- x: 15,
- y: bounds.size.height - 8 - (subtitleLabel?.frame.size.height ?? 0.0),
- width: labelWidth,
- height: subtitleLabel?.frame.size.height ?? 0.0
- )
- }
-
- required init?(
- coder aDecoder: NSCoder
- ) {
- super.init(coder: aDecoder)
- }
-}
diff --git a/Stripe/StripeiOS/Source/STPShippingMethodsViewController.swift b/Stripe/StripeiOS/Source/STPShippingMethodsViewController.swift
deleted file mode 100644
index 8d915855419..00000000000
--- a/Stripe/StripeiOS/Source/STPShippingMethodsViewController.swift
+++ /dev/null
@@ -1,211 +0,0 @@
-//
-// STPShippingMethodsViewController.swift
-// StripeiOS
-//
-// Created by Ben Guo on 8/29/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-import PassKit
-@_spi(STP) import StripeCore
-import UIKit
-
-class STPShippingMethodsViewController: STPCoreTableViewController, UITableViewDataSource,
- UITableViewDelegate
-{
- init(
- shippingMethods methods: [PKShippingMethod],
- selectedShippingMethod selectedMethod: PKShippingMethod,
- currency: String,
- theme: STPTheme
- ) {
- super.init(theme: theme)
- shippingMethods = methods
- if (methods.firstIndex(of: selectedMethod) ?? NSNotFound) != NSNotFound {
- selectedShippingMethod = selectedMethod
- } else {
- selectedShippingMethod = methods.stp_boundSafeObject(at: 0)
- }
-
- self.currency = currency
- title = STPLocalizedString("Shipping", "Title for shipping info form")
- }
-
- weak var delegate: STPShippingMethodsViewControllerDelegate?
- private var shippingMethods: [PKShippingMethod]?
- private var selectedShippingMethod: PKShippingMethod?
- private var currency: String?
- private weak var imageView: UIImageView?
- private var doneItem: UIBarButtonItem?
- private var headerView: STPSectionHeaderView?
-
- override func createAndSetupViews() {
- super.createAndSetupViews()
-
- tableView?.register(
- STPShippingMethodTableViewCell.self,
- forCellReuseIdentifier: STPShippingMethodCellReuseIdentifier
- )
-
- let doneItem = UIBarButtonItem(
- barButtonSystemItem: .done,
- target: self,
- action: #selector(done(_:))
- )
- self.doneItem = doneItem
- stp_navigationItemProxy?.rightBarButtonItem = doneItem
- stp_navigationItemProxy?.rightBarButtonItem?.accessibilityIdentifier =
- "ShippingMethodsViewControllerDoneButtonIdentifier"
-
- let imageView = UIImageView(image: STPLegacyImageLibrary.largeShippingImage())
- imageView.contentMode = .center
- imageView.frame = CGRect(
- x: 0,
- y: 0,
- width: view.bounds.size.width,
- height: imageView.bounds.size.height + (57 * 2)
- )
- self.imageView = imageView
-
- tableView?.tableHeaderView = imageView
- tableView?.dataSource = self
- tableView?.delegate = self
- tableView?.reloadData()
-
- let headerView = STPSectionHeaderView()
- headerView.theme = theme
- headerView.buttonHidden = true
- headerView.title = STPLocalizedString("Shipping Method", "Label for shipping method form")
- headerView.setNeedsLayout()
- self.headerView = headerView
- }
-
- @objc override func updateAppearance() {
- super.updateAppearance()
-
- let navBarTheme = navigationController?.navigationBar.stp_theme ?? theme
- doneItem?.stp_setTheme(navBarTheme)
-
- imageView?.tintColor = theme.accentColor
- for cell in tableView?.visibleCells ?? [] {
- let shippingCell = cell as? STPShippingMethodTableViewCell
- shippingCell?.theme = theme
- }
- }
-
- @objc func done(_ sender: Any?) {
- if let selectedShippingMethod = selectedShippingMethod {
- delegate?.shippingMethodsViewController(self, didFinishWith: selectedShippingMethod)
- }
- }
-
- override func useSystemBackButton() -> Bool {
- return true
- }
-
- // MARK: - UITableView
- func numberOfSections(in tableView: UITableView) -> Int {
- return 1
- }
-
- func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return shippingMethods?.count ?? 0
- }
-
- func tableView(
- _ tableView: UITableView,
- cellForRowAt indexPath: IndexPath
- ) -> UITableViewCell {
- let cell =
- tableView.dequeueReusableCell(
- withIdentifier: STPShippingMethodCellReuseIdentifier,
- for: indexPath
- )
- as? STPShippingMethodTableViewCell
- let method =
- shippingMethods?.stp_boundSafeObject(at: indexPath.row)
- cell?.theme = theme
- if let method = method {
- cell?.setShippingMethod(method, currency: currency ?? "")
- }
- cell?.isSelected = method?.identifier == selectedShippingMethod?.identifier
- return cell!
- }
-
- func tableView(
- _ tableView: UITableView,
- willDisplay cell: UITableViewCell,
- forRowAt indexPath: IndexPath
- ) {
- let topRow = indexPath.row == 0
- let bottomRow =
- self.tableView(tableView, numberOfRowsInSection: indexPath.section) - 1 == indexPath.row
- cell.stp_setBorderColor(theme.tertiaryBackgroundColor)
- cell.stp_setTopBorderHidden(!topRow)
- cell.stp_setBottomBorderHidden(!bottomRow)
- cell.stp_setFakeSeparatorColor(theme.quaternaryBackgroundColor)
- cell.stp_setFakeSeparatorLeftInset(15.0)
- }
-
- func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
- return 57
- }
-
- func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
- return 27.0
- }
-
- override func tableView(
- _ tableView: UITableView,
- heightForHeaderInSection section: Int
- )
- -> CGFloat
- {
- let size = headerView?.sizeThatFits(
- CGSize(width: view.bounds.size.width, height: CGFloat.greatestFiniteMagnitude)
- )
- return size?.height ?? 0.0
- }
-
- func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
- return headerView
- }
-
- func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- tableView.deselectRow(at: indexPath, animated: true)
- selectedShippingMethod =
- shippingMethods?.stp_boundSafeObject(at: indexPath.row)
- tableView.reloadSections(
- NSIndexSet(index: indexPath.section) as IndexSet,
- with: .fade
- )
- }
-
- required init?(
- coder aDecoder: NSCoder
- ) {
- super.init(coder: aDecoder)
- }
-
- required init(
- nibName nibNameOrNil: String?,
- bundle nibBundleOrNil: Bundle?
- ) {
- fatalError("init(nibName:bundle:) has not been implemented")
- }
-
- required init(
- theme: STPTheme?
- ) {
- fatalError("init(theme:) has not been implemented")
- }
-}
-
-@objc protocol STPShippingMethodsViewControllerDelegate: NSObjectProtocol {
- func shippingMethodsViewController(
- _ methodsViewController: STPShippingMethodsViewController,
- didFinishWith method: PKShippingMethod
- )
-}
-
-private let STPShippingMethodCellReuseIdentifier = "STPShippingMethodCellReuseIdentifier"
diff --git a/Stripe/StripeiOS/Source/STPUserInformation.swift b/Stripe/StripeiOS/Source/STPUserInformation.swift
deleted file mode 100644
index 5e6eb2cdb9d..00000000000
--- a/Stripe/StripeiOS/Source/STPUserInformation.swift
+++ /dev/null
@@ -1,42 +0,0 @@
-//
-// STPUserInformation.swift
-// StripeiOS
-//
-// Created by Jack Flintermann on 6/15/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-import Foundation
-
-/// You can use this class to specify information that you've already collected
-/// from your user. You can then set the `prefilledInformation` property on
-/// `STPPaymentContext`, `STPAddCardViewController`, etc and it will pre-fill
-/// this information whenever possible.
-public class STPUserInformation: NSObject, NSCopying {
- /// The user's billing address. When set, the add card form will be filled with
- /// this address. The user will also have the option to fill their shipping address
- /// using this address.
- /// @note Set this using `setBillingAddressWithBillingDetails:` to use the billing
- /// details from an `STPPaymentMethod` or `STPPaymentMethodParams` instance.
- @objc public var billingAddress: STPAddress?
- /// The user's shipping address. When set, the shipping address form will be filled
- /// with this address. The user will also have the option to fill their billing
- /// address using this address.
- @objc public var shippingAddress: STPAddress?
-
- /// A convenience method to populate `billingAddress` with a PaymentMethod's billing details.
- /// @note Calling this overwrites the value of `billingAddress`.
- @objc(setBillingAddressWithBillingDetails:)
- public func setBillingAddress(with billingDetails: STPPaymentMethodBillingDetails) {
- billingAddress = STPAddress(paymentMethodBillingDetails: billingDetails)
- }
-
- /// :nodoc:
- @objc
- public func copy(with zone: NSZone? = nil) -> Any {
- let copy = STPUserInformation()
- copy.billingAddress = billingAddress
- copy.shippingAddress = shippingAddress
- return copy
- }
-}
diff --git a/Stripe/StripeiOSTests/STPAPIClientTest.swift b/Stripe/StripeiOSTests/STPAPIClientTest.swift
index 656381cf113..c8731fdf981 100644
--- a/Stripe/StripeiOSTests/STPAPIClientTest.swift
+++ b/Stripe/StripeiOSTests/STPAPIClientTest.swift
@@ -115,10 +115,10 @@ class STPAPIClientTest: XCTestCase {
STPAnalyticsClient.sharedClient.addClass(toProductUsageIfNecessary: MockUAUsageClass.self)
var params: [String: Any] = [:]
params = STPAPIClient.paramsAddingPaymentUserAgent(params)
- XCTAssertEqual(params["payment_user_agent"] as! String, "stripe-ios/\(StripeAPIConfiguration.STPSDKVersion); variant.legacy; MockUAUsageClass")
+ XCTAssertEqual(params["payment_user_agent"] as! String, "stripe-ios/\(StripeAPIConfiguration.STPSDKVersion); variant.paymentsheet; MockUAUsageClass")
params = STPAPIClient.paramsAddingPaymentUserAgent(params, additionalValues: ["foo"])
- XCTAssertEqual(params["payment_user_agent"] as! String, "stripe-ios/\(StripeAPIConfiguration.STPSDKVersion); variant.legacy; MockUAUsageClass; foo")
+ XCTAssertEqual(params["payment_user_agent"] as! String, "stripe-ios/\(StripeAPIConfiguration.STPSDKVersion); variant.paymentsheet; MockUAUsageClass; foo")
}
func testSetAppInfo() {
diff --git a/Stripe/StripeiOSTests/STPAddCardViewControllerLocalizationSnapshotTests.swift b/Stripe/StripeiOSTests/STPAddCardViewControllerLocalizationSnapshotTests.swift
deleted file mode 100644
index 9ebaf74d8a0..00000000000
--- a/Stripe/StripeiOSTests/STPAddCardViewControllerLocalizationSnapshotTests.swift
+++ /dev/null
@@ -1,101 +0,0 @@
-//
-// STPAddCardViewControllerLocalizationSnapshotTests.swift
-// StripeiOS Tests
-//
-// Created by Brian Dorfman on 10/17/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-import iOSSnapshotTestCase
-import StripeCoreTestUtils
-
-@testable@_spi(STP) import Stripe
-@testable@_spi(STP) import StripeCore
-@testable@_spi(STP) import StripePayments
-@testable@_spi(STP) import StripePaymentSheet
-@testable@_spi(STP) import StripePaymentsUI
-
-class STPAddCardViewControllerLocalizationSnapshotTests: STPSnapshotTestCase {
- func performSnapshotTest(forLanguage language: String?, delivery: Bool) {
- let config = STPPaymentConfiguration()
- config.companyName = "Test Company"
- config.requiredBillingAddressFields = .full
- config.shippingType = delivery ? .delivery : .shipping
- config.cardScanningEnabled = true
- STPLocalizationUtils.overrideLanguage(to: language)
-
- let addCardVC = STPAddCardViewController(
- configuration: config,
- theme: STPTheme.defaultTheme
- )
- addCardVC.shippingAddress = STPAddress()
- addCardVC.shippingAddress?.line1 = "1" // trigger "use shipping address" button
-
- let viewToTest = stp_preparedAndSizedViewForSnapshotTest(from: addCardVC)!
-
- if delivery {
- addCardVC.addressViewModel.addressFieldTableViewCountryCode = "INVALID"
- STPSnapshotVerifyView(viewToTest, identifier: "delivery")
- } else {
- // This method rejects nil or empty country codes to stop strange looking behavior
- // when scrolling to the top "unset" position in the picker, so put in
- // an invalid country code instead to test seeing the "Country" placeholder
- addCardVC.addressViewModel.addressFieldTableViewCountryCode = "INVALID"
- STPSnapshotVerifyView(viewToTest, identifier: "no_country")
-
- addCardVC.addressViewModel.addressFieldTableViewCountryCode = "US"
- STPSnapshotVerifyView(viewToTest, identifier: "US")
-
- addCardVC.addressViewModel.addressFieldTableViewCountryCode = "GB"
- STPSnapshotVerifyView(viewToTest, identifier: "GB")
-
- addCardVC.addressViewModel.addressFieldTableViewCountryCode = "CA"
- STPSnapshotVerifyView(viewToTest, identifier: "CA")
-
- addCardVC.addressViewModel.addressFieldTableViewCountryCode = "MX"
- STPSnapshotVerifyView(viewToTest, identifier: "MX")
- }
-
- STPLocalizationUtils.overrideLanguage(to: nil)
- }
-
- func testGerman() {
- performSnapshotTest(forLanguage: "de", delivery: false)
- performSnapshotTest(forLanguage: "de", delivery: true)
- }
-
- func testEnglish() {
- performSnapshotTest(forLanguage: "en", delivery: false)
- performSnapshotTest(forLanguage: "en", delivery: true)
- }
-
- func testSpanish() {
- performSnapshotTest(forLanguage: "es", delivery: false)
- performSnapshotTest(forLanguage: "es", delivery: true)
- }
-
- func testFrench() {
- performSnapshotTest(forLanguage: "fr", delivery: false)
- performSnapshotTest(forLanguage: "fr", delivery: true)
- }
-
- func testItalian() {
- performSnapshotTest(forLanguage: "it", delivery: false)
- performSnapshotTest(forLanguage: "it", delivery: true)
- }
-
- func testJapanese() {
- performSnapshotTest(forLanguage: "ja", delivery: false)
- performSnapshotTest(forLanguage: "ja", delivery: true)
- }
-
- func testDutch() {
- performSnapshotTest(forLanguage: "nl", delivery: false)
- performSnapshotTest(forLanguage: "nl", delivery: true)
- }
-
- func testChinese() {
- performSnapshotTest(forLanguage: "zh-Hans", delivery: false)
- performSnapshotTest(forLanguage: "zh-Hans", delivery: true)
- }
-}
diff --git a/Stripe/StripeiOSTests/STPAddCardViewControllerTest.swift b/Stripe/StripeiOSTests/STPAddCardViewControllerTest.swift
deleted file mode 100644
index 16b50807c90..00000000000
--- a/Stripe/StripeiOSTests/STPAddCardViewControllerTest.swift
+++ /dev/null
@@ -1,290 +0,0 @@
-//
-// STPAddCardViewControllerTest.swift
-// StripeiOS Tests
-//
-// Created by Ben Guo on 7/5/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-import OHHTTPStubs
-import OHHTTPStubsSwift
-import StripeCoreTestUtils
-
-@testable@_spi(STP) import Stripe
-@testable@_spi(STP) import StripeCore
-@testable@_spi(STP) import StripePayments
-@testable@_spi(STP) import StripePaymentSheet
-@testable@_spi(STP) import StripePaymentsUI
-
-class MockDelegate: NSObject, STPAddCardViewControllerDelegate {
- func addCardViewControllerDidCancel(_ addCardViewController: STPAddCardViewController) {
-
- }
-
- var addCardViewControllerDidCreatePaymentMethodBlock:
- (STPAddCardViewController, STPPaymentMethod, STPErrorBlock) -> Void = { _, _, _ in }
- func addCardViewController(
- _ addCardViewController: STPAddCardViewController,
- didCreatePaymentMethod paymentMethod: STPPaymentMethod,
- completion: @escaping STPErrorBlock
- ) {
- addCardViewControllerDidCreatePaymentMethodBlock(
- addCardViewController,
- paymentMethod,
- completion
- )
- }
-}
-
-class STPAddCardViewControllerTest: APIStubbedTestCase {
-
- func paymentMethodAPIFilter(
- expectedCardParams: STPPaymentMethodCardParams,
- urlRequest: URLRequest
- ) -> Bool {
- if urlRequest.url?.absoluteString.contains("payment_methods") ?? false {
- let cardNumber = urlRequest.queryItems?.first(where: { item in
- item.name == "card[number]"
- })
- XCTAssertEqual(cardNumber!.value, expectedCardParams.number)
- return true
- }
- return false
- }
-
- func buildAddCardViewController() -> STPAddCardViewController? {
- let config = STPPaymentConfiguration()
- let theme = STPTheme.defaultTheme
- let vc = STPAddCardViewController(
- configuration: config,
- theme: theme
- )
- XCTAssertNotNil(vc.view)
- return vc
- }
-
- func testPrefilledBillingAddress_removeAddress() {
- let config = STPPaymentConfiguration()
- config.requiredBillingAddressFields = .postalCode
- let sut = STPAddCardViewController(
- configuration: config,
- theme: STPTheme.defaultTheme
- )
- let address = STPAddress()
- address.name = "John Smith Doe"
- address.phone = "8885551212"
- address.email = "foo@example.com"
- address.line1 = "55 John St"
- address.city = "Harare"
- address.postalCode = "10002"
- // Zimbabwe does not require zip codes, while the default locale for tests (US) does
- address.country = "ZW"
- // Sanity checks
- XCTAssertFalse(STPPostalCodeValidator.postalCodeIsRequired(forCountryCode: "ZW"))
- XCTAssertTrue(STPPostalCodeValidator.postalCodeIsRequired(forCountryCode: "US"))
-
- let prefilledInfo = STPUserInformation()
- prefilledInfo.billingAddress = address
- sut.prefilledInformation = prefilledInfo
-
- XCTAssertNoThrow(sut.loadView())
- XCTAssertNoThrow(sut.viewDidLoad())
- }
-
- func testPrefilledBillingAddress_viewDidLoadHappensBeforeSettingAddress() {
- let config = STPPaymentConfiguration()
- config.requiredBillingAddressFields = .full
- let sut = STPAddCardViewController(
- configuration: config,
- theme: STPTheme.defaultTheme
- )
- XCTAssertNoThrow(sut.loadView())
- XCTAssertNoThrow(sut.viewDidLoad())
-
- let address = STPAddress()
- address.name = "John Smith Doe"
- address.line1 = "55 John St"
- address.city = "Harare"
- address.postalCode = "10002"
-
- let prefilledInfo = STPUserInformation()
- prefilledInfo.billingAddress = address
- sut.prefilledInformation = prefilledInfo
-
- let nameCell = sut.addressViewModel.addressCells.first { $0.type == .name }!
- XCTAssertEqual( nameCell.contents, "John Smith Doe")
-
- let line1Cell = sut.addressViewModel.addressCells.first { $0.type == .line1 }!
- XCTAssertEqual( line1Cell.contents, "55 John St")
-
- let cityCell = sut.addressViewModel.addressCells.first { $0.type == .city }!
- XCTAssertEqual( cityCell.contents, "Harare")
-
- let zipCell = sut.addressViewModel.addressCells.first { $0.type == .zip }!
- XCTAssertEqual( zipCell.contents, "10002")
- }
-
- func testPrefilledBillingAddress_addAddress() {
- // Zimbabwe does not require zip codes, while the default locale for tests (US) does
- NSLocale.stp_withLocale(as: NSLocale(localeIdentifier: "en_ZW")) {
- // Sanity checks
- XCTAssertFalse(STPPostalCodeValidator.postalCodeIsRequired(forCountryCode: "ZW"))
- XCTAssertTrue(STPPostalCodeValidator.postalCodeIsRequired(forCountryCode: "US"))
- let config = STPPaymentConfiguration()
- config.requiredBillingAddressFields = .postalCode
- let sut = STPAddCardViewController(
- configuration: config,
- theme: STPTheme.defaultTheme
- )
- let address = STPAddress()
- address.name = "John Smith Doe"
- address.phone = "8885551212"
- address.email = "foo@example.com"
- address.line1 = "55 John St"
- address.city = "New York"
- address.state = "NY"
- address.postalCode = "10002"
- address.country = "US"
-
- let prefilledInfo = STPUserInformation()
- prefilledInfo.billingAddress = address
- sut.prefilledInformation = prefilledInfo
-
- XCTAssertNoThrow(sut.loadView())
- XCTAssertNoThrow(sut.viewDidLoad())
- }
- }
-
- func testNextWithCreatePaymentMethodError() {
- let sut = buildAddCardViewController()!
- let expectedCardParams = STPFixtures.paymentMethodCardParams()
- sut.paymentCell?.paymentField!.perform(NSSelectorFromString("setCardParams:"), with: expectedCardParams)
-
- let exp = expectation(description: "createPaymentMethodWithCard network request")
- stub { urlRequest in
- return self.paymentMethodAPIFilter(
- expectedCardParams: expectedCardParams,
- urlRequest: urlRequest
- )
- } response: { _ in
- XCTAssertTrue(sut.loading)
- let paymentMethod = ["error": "intentionally_invalid"]
- defer {
- exp.fulfill()
- }
- return HTTPStubsResponse(jsonObject: paymentMethod, statusCode: 200, headers: nil)
- }
- sut.apiClient = stubbedAPIClient()
- // tap next button
- let nextButton = sut.navigationItem.rightBarButtonItem
- _ = nextButton?.target?.perform(nextButton?.action, with: nextButton)
-
- waitForExpectations(timeout: 2, handler: nil)
-
- // It takes a few more spins on the runloop before we get a response from
- // the HTTP stubs, so we'll wait 0.5 seconds before checking the loading indicator.
- let loadExp = expectation(description: "loading has stopped")
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
- XCTAssertFalse(sut.loading)
- loadExp.fulfill()
- }
- waitForExpectations(timeout: 1, handler: nil)
- }
-
- func testNextWithCreatePaymentMethodSuccessAndDidCreatePaymentMethodError() {
- let sut = buildAddCardViewController()!
- let createPaymentMethodExp = expectation(description: "createPaymentMethodWithCard")
-
- let expectedCardParams = STPFixtures.paymentMethodCardParams()
- let expectedPaymentMethod = STPFixtures.paymentMethod()
- let expectedPaymentMethodData = STPFixtures.paymentMethodJSON()
-
- stub { urlRequest in
- return self.paymentMethodAPIFilter(
- expectedCardParams: expectedCardParams,
- urlRequest: urlRequest
- )
- } response: { _ in
- XCTAssertTrue(sut.loading)
- defer {
- createPaymentMethodExp.fulfill()
- }
- return HTTPStubsResponse(
- jsonObject: expectedPaymentMethodData,
- statusCode: 200,
- headers: nil
- )
- }
-
- let mockDelegate = MockDelegate()
- sut.apiClient = stubbedAPIClient()
- sut.delegate = mockDelegate
- sut.paymentCell?.paymentField!.perform(NSSelectorFromString("setCardParams:"), with: expectedCardParams)
-
- let didCreatePaymentMethodExp = expectation(description: "didCreatePaymentMethod")
-
- mockDelegate.addCardViewControllerDidCreatePaymentMethodBlock = {
- (_, paymentMethod, completion) in
- XCTAssertTrue(sut.loading)
- let error = NSError.stp_genericFailedToParseResponseError()
- XCTAssertEqual(paymentMethod.stripeId, expectedPaymentMethod.stripeId)
- completion(error)
- XCTAssertFalse(sut.loading)
- didCreatePaymentMethodExp.fulfill()
- }
-
- // tap next button
- let nextButton = sut.navigationItem.rightBarButtonItem
- _ = nextButton?.target?.perform(nextButton?.action, with: nextButton)
-
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- func testNextWithCreateTokenSuccessAndDidCreateTokenSuccess() {
- let sut = buildAddCardViewController()!
-
- let createPaymentMethodExp = expectation(description: "createPaymentMethodWithCard")
-
- let expectedCardParams = STPFixtures.paymentMethodCardParams()
- let expectedPaymentMethod = STPFixtures.paymentMethod()
- let expectedPaymentMethodData = STPFixtures.paymentMethodJSON()
-
- stub { urlRequest in
- return self.paymentMethodAPIFilter(
- expectedCardParams: expectedCardParams,
- urlRequest: urlRequest
- )
- } response: { _ in
- XCTAssertTrue(sut.loading)
- defer {
- createPaymentMethodExp.fulfill()
- }
- return HTTPStubsResponse(
- jsonObject: expectedPaymentMethodData,
- statusCode: 200,
- headers: nil
- )
- }
-
- let mockDelegate = MockDelegate()
- sut.apiClient = stubbedAPIClient()
- sut.delegate = mockDelegate
- sut.paymentCell?.paymentField!.perform(NSSelectorFromString("setCardParams:"), with: expectedCardParams)
-
- let didCreatePaymentMethodExp = expectation(description: "didCreatePaymentMethod")
- mockDelegate.addCardViewControllerDidCreatePaymentMethodBlock = {
- (_, paymentMethod, completion) in
- XCTAssertTrue(sut.loading)
- XCTAssertEqual(paymentMethod.stripeId, expectedPaymentMethod.stripeId)
- completion(nil)
- XCTAssertFalse(sut.loading)
- didCreatePaymentMethodExp.fulfill()
- }
-
- // tap next button
- let nextButton = sut.navigationItem.rightBarButtonItem
- _ = nextButton?.target?.perform(nextButton?.action, with: nextButton)
-
- waitForExpectations(timeout: 2, handler: nil)
- }
-}
diff --git a/Stripe/StripeiOSTests/STPAddressViewModelTest.swift b/Stripe/StripeiOSTests/STPAddressViewModelTest.swift
deleted file mode 100644
index 429368e28d7..00000000000
--- a/Stripe/StripeiOSTests/STPAddressViewModelTest.swift
+++ /dev/null
@@ -1,205 +0,0 @@
-//
-// STPAddressViewModelTest.swift
-// StripeiOS Tests
-//
-// Created by Ben Guo on 10/21/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-@testable@_spi(STP) import Stripe
-@testable@_spi(STP) import StripeCore
-@testable@_spi(STP) import StripePayments
-@testable@_spi(STP) import StripePaymentSheet
-@testable@_spi(STP) import StripePaymentsUI
-
-class STPAddressViewModelTest: XCTestCase {
- func testInitWithRequiredBillingFields() {
- var sut = STPAddressViewModel(requiredBillingFields: .none, availableCountries: nil)
- XCTAssertTrue(sut.addressCells.count == 0)
- XCTAssertTrue(sut.isValid)
-
- sut = STPAddressViewModel(requiredBillingFields: .postalCode, availableCountries: nil)
- XCTAssertTrue(sut.addressCells.count == 0)
-
- sut = STPAddressViewModel(requiredBillingFields: .full, availableCountries: nil)
- XCTAssertTrue(sut.addressCells.count == 7)
- let types: [STPAddressFieldType] = [
- .name,
- .line1,
- .line2,
- .country,
- .zip,
- .city,
- .state,
- ]
- for i in 0..(),
- availableCountries: nil
- )
- XCTAssertTrue(sut.addressCells.count == 0)
-
- sut = STPAddressViewModel(
- requiredShippingFields: Set([.name]),
- availableCountries: nil
- )
- XCTAssertTrue(sut.addressCells.count == 1)
- let cell1 = sut.addressCells[0]
- XCTAssertEqual(cell1.type, .name)
-
- sut = STPAddressViewModel(
- requiredShippingFields: Set([.name, .emailAddress]),
- availableCountries: nil
- )
- XCTAssertTrue(sut.addressCells.count == 2)
- var types: [STPAddressFieldType] = [.name, .email]
- for i in 0..([
- .postalAddress, .emailAddress, .phoneNumber,
- ]),
- availableCountries: nil
- )
- XCTAssertTrue(sut.addressCells.count == 9)
- types = [
- .email,
- .name,
- .line1,
- .line2,
- .country,
- .zip,
- .city,
- .state,
- .phone,
- ]
- for i in 0..([
- .postalAddress,
- .emailAddress,
- .phoneNumber,
- ]),
- availableCountries: nil
- )
- sut.addressCells[0].contents = "foo@example.com"
- sut.addressCells[1].contents = "John Smith"
- sut.addressCells[2].contents = "55 John St"
- sut.addressCells[3].contents = "#3B"
- sut.addressCells[4].contents = "US"
- sut.addressCells[5].contents = "10002"
- sut.addressCells[6].contents = "New York"
- sut.addressCells[7].contents = "NY"
- sut.addressCells[8].contents = "555-555-5555"
-
- XCTAssertEqual(sut.address.email, "foo@example.com")
- XCTAssertEqual(sut.address.name, "John Smith")
- XCTAssertEqual(sut.address.line1, "55 John St")
- XCTAssertEqual(sut.address.line2, "#3B")
- XCTAssertEqual(sut.address.city, "New York")
- XCTAssertEqual(sut.address.state, "NY")
- XCTAssertEqual(sut.address.postalCode, "10002")
- XCTAssertEqual(sut.address.country, "US")
- XCTAssertEqual(sut.address.phone, "555-555-5555")
- }
-
- func testSetAddress() {
- let address = STPAddress()
- address.email = "foo@example.com"
- address.name = "John Smith"
- address.line1 = "55 John St"
- address.line2 = "#3B"
- address.city = "New York"
- address.state = "NY"
- address.postalCode = "10002"
- address.country = "US"
- address.phone = "555-555-5555"
-
- let sut = STPAddressViewModel(
- requiredShippingFields: Set([
- .postalAddress,
- .emailAddress,
- .phoneNumber,
- ]),
- availableCountries: nil
- )
- sut.address = address
- XCTAssertEqual(sut.addressCells[0].contents, "foo@example.com")
- XCTAssertEqual(sut.addressCells[1].contents, "John Smith")
- XCTAssertEqual(sut.addressCells[2].contents, "55 John St")
- XCTAssertEqual(sut.addressCells[3].contents, "#3B")
- XCTAssertEqual(sut.addressCells[4].contents, "US")
- XCTAssertEqual(sut.addressCells[4].textField.text, "United States")
- XCTAssertEqual(sut.addressCells[5].contents, "10002")
- XCTAssertEqual(sut.addressCells[6].contents, "New York")
- XCTAssertEqual(sut.addressCells[7].contents, "NY")
- XCTAssertEqual(sut.addressCells[8].contents, "555-555-5555")
- }
-
- func testIsValid_Zip() {
- let sut = STPAddressViewModel(requiredBillingFields: .postalCode, availableCountries: nil)
-
- let address = STPAddress()
-
- address.country = "US"
- sut.address = address
- // The AddressViewModel shouldn't request any information when requesting ZIPs.
- XCTAssertEqual(sut.addressCells.count, 0)
-
- address.postalCode = "94016"
- sut.address = address
- XCTAssertTrue(sut.isValid)
-
- address.country = "MO" // in Macao, postalCode is optional
- address.postalCode = nil
- sut.address = address
- XCTAssertEqual(sut.addressCells.count, 0)
- XCTAssertTrue(sut.isValid, "in Macao, postalCode is optional, valid without one")
- }
-
- func testIsValid_Full() {
- let sut = STPAddressViewModel(requiredBillingFields: .full, availableCountries: nil)
- XCTAssertFalse(sut.isValid)
- sut.addressCells[0].contents = "John Smith"
- sut.addressCells[1].contents = "55 John St"
- sut.addressCells[2].contents = "#3B"
- XCTAssertFalse(sut.isValid)
- sut.addressCells[3].contents = "10002"
- sut.addressCells[4].contents = "New York"
- sut.addressCells[5].contents = "NY"
- sut.addressCells[6].contents = "US"
- XCTAssertTrue(sut.isValid)
- }
-
- func testIsValid_Name() {
- let sut = STPAddressViewModel(requiredBillingFields: .name, availableCountries: nil)
-
- let address = STPAddress()
-
- address.name = ""
- sut.address = address
- XCTAssertEqual(sut.addressCells.count, 1)
- XCTAssertFalse(sut.isValid)
-
- address.name = "Jane Doe"
- sut.address = address
- XCTAssertEqual(sut.addressCells.count, 1)
- XCTAssertTrue(sut.isValid)
- }
-}
diff --git a/Stripe/StripeiOSTests/STPAnalyticsClientPaymentsTest.swift b/Stripe/StripeiOSTests/STPAnalyticsClientPaymentsTest.swift
index 5ab6e8b508d..5718b68e695 100644
--- a/Stripe/StripeiOSTests/STPAnalyticsClientPaymentsTest.swift
+++ b/Stripe/StripeiOSTests/STPAnalyticsClientPaymentsTest.swift
@@ -32,10 +32,6 @@ class STPAnalyticsClientPaymentsTest: XCTestCase {
client.addAdditionalInfo("how are you?")
XCTAssertEqual(client.additionalInfo(), ["hello", "how are you?", "i'm additional info"])
-
- // Clear it
- client.clearAdditionalInfo()
- XCTAssertEqual(client.additionalInfo(), [])
}
func testPayloadFromAnalytic() throws {
@@ -125,80 +121,17 @@ class STPAnalyticsClientPaymentsTest: XCTestCase {
)
}
- func testPaymentContextAddsUsage() {
- let keyManager = STPEphemeralKeyManager(
- keyProvider: MockKeyProvider(),
- apiVersion: "1",
- performsEagerFetching: false
- )
- let apiClient = STPAPIClient()
- let customerContext = STPCustomerContext.init(keyManager: keyManager, apiClient: apiClient)
- let paymentContext = STPPaymentContext(customerContext: customerContext)
- XCTAssertTrue(STPAnalyticsClient.sharedClient.productUsage.contains("STPCustomerContext"))
- XCTAssertEqual(paymentContext.analyticsLogger.product, "STPPaymentContext")
- }
-
func testApplePayContextAddsUsage() {
_ = STPApplePayContext(paymentRequest: STPFixtures.applePayRequest(), delegate: nil)
XCTAssertTrue(STPAnalyticsClient.sharedClient.productUsage.contains("STPApplePayContext"))
}
- func testCustomerContextAddsUsage() {
- let keyManager = STPEphemeralKeyManager(
- keyProvider: MockKeyProvider(),
- apiVersion: "1",
- performsEagerFetching: false
- )
- let apiClient = STPAPIClient()
- _ = STPCustomerContext(keyManager: keyManager, apiClient: apiClient)
- XCTAssertTrue(STPAnalyticsClient.sharedClient.productUsage.contains("STPCustomerContext"))
- }
-
- func testAddCardVCAddsUsage() {
- let addCardVC = STPAddCardViewController()
- XCTAssertTrue(
- STPAnalyticsClient.sharedClient.productUsage.contains("STPAddCardViewController")
- )
- XCTAssertEqual(addCardVC.analyticsLogger.product, "STPAddCardViewController")
- }
-
- func testPaymentOptionsVCAddsUsage() {
- let customerContext = Testing_StaticCustomerContext.init(
- customer: STPFixtures.customerWithCardTokenAndSourceSources(),
- paymentMethods: []
- )
- let delegate = MockSTPPaymentOptionsViewControllerDelegate()
- let paymentOptionsVC = STPPaymentOptionsViewController(configuration: .shared, theme: .defaultTheme, customerContext: customerContext, delegate: delegate)
- XCTAssertTrue(
- STPAnalyticsClient.sharedClient.productUsage.contains("STPPaymentOptionsViewController")
- )
- XCTAssertEqual(paymentOptionsVC.analyticsLogger.product, "STPPaymentOptionsViewController")
- }
-
func testBankSelectionVCAddsUsage() {
_ = STPBankSelectionViewController()
XCTAssertTrue(
STPAnalyticsClient.sharedClient.productUsage.contains("STPBankSelectionViewController")
)
}
-
- func testShippingVCAddsUsage() {
- let config = STPPaymentConfiguration()
- config.requiredShippingAddressFields = [STPContactField.postalAddress]
- _ = STPShippingAddressViewController(
- configuration: config,
- theme: .defaultTheme,
- currency: nil,
- shippingAddress: nil,
- selectedShippingMethod: nil,
- prefilledInformation: nil
- )
- XCTAssertTrue(
- STPAnalyticsClient.sharedClient.productUsage.contains(
- "STPShippingAddressViewController"
- )
- )
- }
}
// MARK: - Helpers
@@ -234,14 +167,3 @@ private struct MockAnalyticsClass1: STPAnalyticsProtocol {
private struct MockAnalyticsClass2: STPAnalyticsProtocol {
static let stp_analyticsIdentifier = "MockAnalyticsClass2"
}
-
-private class MockKeyProvider: NSObject, STPCustomerEphemeralKeyProvider {
- func createCustomerKey(
- withAPIVersion apiVersion: String,
- completion: @escaping STPJSONResponseCompletionBlock
- ) {
- guard apiVersion == "1" else { return }
-
- completion(nil, NSError.stp_genericConnectionError())
- }
-}
diff --git a/Stripe/StripeiOSTests/STPBlocks.h b/Stripe/StripeiOSTests/STPBlocks.h
index d9be6f7231b..24431b7a532 100644
--- a/Stripe/StripeiOSTests/STPBlocks.h
+++ b/Stripe/StripeiOSTests/STPBlocks.h
@@ -152,17 +152,6 @@ typedef void (^STPPaymentMethodCompletionBlock)(STPPaymentMethod * __nullable pa
*/
typedef void (^STPPaymentMethodsCompletionBlock)(NSArray *__nullable paymentMethods, NSError * __nullable error);
-/**
- A callback to be run with a validation result and shipping methods for a
- shipping address.
-
- @param status An enum representing whether the shipping address is valid.
- @param shippingValidationError If the shipping address is invalid, an error describing the issue with the address. If no error is given and the address is invalid, the default error message will be used.
- @param shippingMethods The shipping methods available for the address.
- @param selectedShippingMethod The default selected shipping method for the address.
- */
-typedef void (^STPShippingMethodsCompletionBlock)(STPShippingStatus status, NSError * __nullable shippingValidationError, NSArray* __nullable shippingMethods, PKShippingMethod * __nullable selectedShippingMethod);
-
/**
A callback to be run with a file response from the Stripe API.
diff --git a/Stripe/StripeiOSTests/STPCustomerContextTest.swift b/Stripe/StripeiOSTests/STPCustomerContextTest.swift
deleted file mode 100644
index f622ac96ce8..00000000000
--- a/Stripe/StripeiOSTests/STPCustomerContextTest.swift
+++ /dev/null
@@ -1,671 +0,0 @@
-//
-// STPCustomerContextTest.swift
-// StripeiOS Tests
-//
-// Created by David Estes on 9/20/21.
-// Copyright © 2021 Stripe, Inc. All rights reserved.
-//
-
-import Foundation
-import OHHTTPStubs
-import OHHTTPStubsSwift
-import StripeCoreTestUtils
-
-@testable@_spi(STP) import Stripe
-@testable@_spi(STP) import StripeCore
-@testable@_spi(STP) import StripePayments
-
-class MockEphemeralKeyManager: STPEphemeralKeyManagerProtocol {
- var ephemeralKey: STPEphemeralKey?
- var error: Error?
-
- init(
- key: STPEphemeralKey?,
- error: Error?
- ) {
- self.ephemeralKey = key
- self.error = error
- }
-
- func getOrCreateKey(_ completion: @escaping STPEphemeralKeyCompletionBlock) {
- completion(ephemeralKey, error)
- }
-}
-
-class STPCustomerContextTests: APIStubbedTestCase {
- func stubRetrieveCustomers(
- key: STPEphemeralKey,
- returningCustomerJSON: [AnyHashable: Any],
- expectedCount: Int,
- apiClient: STPAPIClient
- ) {
- let exp = expectation(description: "retrieveCustomer")
- exp.expectedFulfillmentCount = expectedCount
-
- stub { urlRequest in
- return urlRequest.url?.absoluteString.contains("/customers") ?? false
- && urlRequest.httpMethod == "GET"
- } response: { _ in
- DispatchQueue.main.async {
- // Fulfill after response is sent
- exp.fulfill()
- }
- return HTTPStubsResponse(
- jsonObject: returningCustomerJSON,
- statusCode: 200,
- headers: nil
- )
- }
- }
-
- func stubListPaymentMethods(
- key: STPEphemeralKey,
- paymentMethodJSONs: [[AnyHashable: Any]],
- expectedCount: Int,
- apiClient: STPAPIClient
- ) {
- let exp = expectation(description: "listPaymentMethod")
- exp.expectedFulfillmentCount = expectedCount
- stub { urlRequest in
- if urlRequest.url?.absoluteString.contains("/payment_methods") ?? false
- && urlRequest.httpMethod == "GET"
- {
- // Check to make sure we pass the ephemeral key correctly
- let keyFromHeader = urlRequest.allHTTPHeaderFields!["Authorization"]?
- .replacingOccurrences(of: "Bearer ", with: "")
- XCTAssertEqual(keyFromHeader, key.secret)
- return true
- }
- return false
- } response: { _ in
- let paymentMethodsJSON = """
- {
- "object": "list",
- "url": "/v1/payment_methods",
- "has_more": false,
- "data": [
- ]
- }
- """
- var pmList =
- try! JSONSerialization.jsonObject(
- with: paymentMethodsJSON.data(using: .utf8)!,
- options: []
- ) as! [AnyHashable: Any]
- pmList["data"] = paymentMethodJSONs
- DispatchQueue.main.async {
- // Fulfill after response is sent
- exp.fulfill()
- }
- return HTTPStubsResponse(jsonObject: pmList, statusCode: 200, headers: nil)
- }
- }
-
- func testGetOrCreateKeyErrorForwardedToRetrieveCustomer() {
- let exp = expectation(description: "retrieveCustomer")
- let expectedError = NSError(domain: "test", code: 123, userInfo: nil)
- let apiClient = stubbedAPIClient()
- stub { urlRequest in
- return urlRequest.url?.absoluteString.contains("/customers") ?? false
- } response: { _ in
- XCTFail("Retrieve customer should not be called")
- return HTTPStubsResponse(error: NSError(domain: "test", code: 100, userInfo: nil))
- }
- let ekm = MockEphemeralKeyManager(key: nil, error: expectedError)
- let sut = STPCustomerContext(keyManager: ekm, apiClient: apiClient)
- sut.retrieveCustomer { customer, error in
- XCTAssertNil(customer)
- XCTAssertEqual((error as NSError?)?.domain, expectedError.domain)
- exp.fulfill()
- }
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- func testInitRetrievesResourceKeyAndCustomerAndPaymentMethods() {
- let customerKey = STPFixtures.ephemeralKey()
- let expectedCustomerJSON = STPFixtures.customerWithSingleCardTokenSourceJSON()
- let apiClient = stubbedAPIClient()
- stubRetrieveCustomers(
- key: customerKey,
- returningCustomerJSON: expectedCustomerJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
- stubListPaymentMethods(
- key: customerKey,
- paymentMethodJSONs: [],
- expectedCount: 1,
- apiClient: apiClient
- )
-
- let ekm = MockEphemeralKeyManager(key: customerKey, error: nil)
- let sut = STPCustomerContext(keyManager: ekm, apiClient: apiClient)
- XCTAssertNotNil(sut)
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- func testRetrieveCustomerUsesCachedCustomerIfNotExpired() {
- let customerKey = STPFixtures.ephemeralKey()
- let expectedCustomer = STPFixtures.customerWithSingleCardTokenSource()
- let expectedCustomerJSON = STPFixtures.customerWithSingleCardTokenSourceJSON()
- let apiClient = stubbedAPIClient()
-
- // apiClient.retrieveCustomer should be called once, when the context is initialized.
- // When sut.retrieveCustomer is called below, the cached customer will be used.
- stubRetrieveCustomers(
- key: customerKey,
- returningCustomerJSON: expectedCustomerJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
- stubListPaymentMethods(
- key: customerKey,
- paymentMethodJSONs: [],
- expectedCount: 1,
- apiClient: apiClient
- )
- let ekm = MockEphemeralKeyManager(key: customerKey, error: nil)
- let sut = STPCustomerContext(keyManager: ekm, apiClient: apiClient)
- // Give the mocked API request a little time to complete and cache the customer, then check cache
- waitForExpectations(timeout: 2, handler: nil)
- let exp2 = expectation(description: "retrieveCustomer again")
- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + ohhttpDelay) {
- sut.retrieveCustomer { customer, _ in
- XCTAssertEqual(customer!.stripeID, expectedCustomer.stripeID)
- exp2.fulfill()
- }
- }
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- func testRetrieveCustomerDoesNotUseCachedCustomerIfExpired() {
- let customerKey = STPFixtures.ephemeralKey()
- let expectedCustomer = STPFixtures.customerWithSingleCardTokenSource()
- let expectedCustomerJSON = STPFixtures.customerWithSingleCardTokenSourceJSON()
- let apiClient = stubbedAPIClient()
-
- // apiClient.retrieveCustomer should be called twice:
- // - when the context is initialized,
- // - when sut.retrieveCustomer is called below, as the cached customer has expired.
- stubRetrieveCustomers(
- key: customerKey,
- returningCustomerJSON: expectedCustomerJSON,
- expectedCount: 2,
- apiClient: apiClient
- )
- stubListPaymentMethods(
- key: customerKey,
- paymentMethodJSONs: [],
- expectedCount: 1,
- apiClient: apiClient
- )
-
- let ekm = MockEphemeralKeyManager(key: customerKey, error: nil)
- let sut = STPCustomerContext(keyManager: ekm, apiClient: apiClient)
- // Give the mocked API request a little time to complete and cache the customer, then reset and check cache
- let exp2 = expectation(description: "retrieveCustomer again")
- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + ohhttpDelay) {
- sut.customerRetrievedDate = Date(timeIntervalSinceNow: -70)
- sut.retrieveCustomer { customer, _ in
- XCTAssertEqual(customer!.stripeID, expectedCustomer.stripeID)
- exp2.fulfill()
- }
- }
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- func testRetrieveCustomerDoesNotUseCachedCustomerAfterClearingCache() {
- let customerKey = STPFixtures.ephemeralKey()
- let expectedCustomer = STPFixtures.customerWithSingleCardTokenSource()
- let expectedCustomerJSON = STPFixtures.customerWithSingleCardTokenSourceJSON()
- let apiClient = stubbedAPIClient()
-
- // apiClient.retrieveCustomer should be called twice:
- // - when the context is initialized,
- // - when sut.retrieveCustomer is called below, as the cached customer has been cleared.
- stubRetrieveCustomers(
- key: customerKey,
- returningCustomerJSON: expectedCustomerJSON,
- expectedCount: 2,
- apiClient: apiClient
- )
- stubListPaymentMethods(
- key: customerKey,
- paymentMethodJSONs: [],
- expectedCount: 1,
- apiClient: apiClient
- )
-
- let ekm = MockEphemeralKeyManager(key: customerKey, error: nil)
- let sut = STPCustomerContext(keyManager: ekm, apiClient: apiClient)
- // Give the mocked API request a little time to complete and cache the customer, then reset and check cache
- let exp2 = expectation(description: "retrieveCustomer again")
- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + ohhttpDelay) {
- sut.clearCache()
- sut.retrieveCustomer { customer, _ in
- XCTAssertEqual(customer!.stripeID, expectedCustomer.stripeID)
- exp2.fulfill()
- }
- }
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- func testRetrievePaymentMethodsUsesCacheIfNotExpired() {
- let customerKey = STPFixtures.ephemeralKey()
- let expectedCustomerJSON = STPFixtures.customerWithSingleCardTokenSourceJSON()
- let expectedPaymentMethods = [STPFixtures.paymentMethod()]
- let expectedPaymentMethodsJSON = [STPFixtures.paymentMethodJSON()]
- let apiClient = stubbedAPIClient()
-
- // apiClient.listPaymentMethods should be called once, when the context is initialized.
- // When sut.listPaymentMethods is called below, the cached list will be used.
- stubRetrieveCustomers(
- key: customerKey,
- returningCustomerJSON: expectedCustomerJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
- stubListPaymentMethods(
- key: customerKey,
- paymentMethodJSONs: expectedPaymentMethodsJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
-
- let ekm = MockEphemeralKeyManager(key: customerKey, error: nil)
- let sut = STPCustomerContext(keyManager: ekm, apiClient: apiClient)
- // Give the mocked API request a little time to complete and cache the customer, then check cache
- let exp2 = expectation(description: "listPaymentMethods")
- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + ohhttpDelay) {
- sut.listPaymentMethodsForCustomer { paymentMethods, _ in
- XCTAssertEqual(paymentMethods!.count, expectedPaymentMethods.count)
- exp2.fulfill()
- }
- }
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- func testRetrievePaymentMethodsDoesNotUseCacheIfExpired() {
- let customerKey = STPFixtures.ephemeralKey()
- let expectedCustomerJSON = STPFixtures.customerWithSingleCardTokenSourceJSON()
- let expectedPaymentMethods = [STPFixtures.paymentMethod()]
- let expectedPaymentMethodsJSON = [STPFixtures.paymentMethodJSON()]
- let apiClient = stubbedAPIClient()
-
- // apiClient.listPaymentMethods should be called twice:
- // - when the context is initialized,
- // - when sut.listPaymentMethods is called below, as the cached list has expired.
- stubRetrieveCustomers(
- key: customerKey,
- returningCustomerJSON: expectedCustomerJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
- stubListPaymentMethods(
- key: customerKey,
- paymentMethodJSONs: expectedPaymentMethodsJSON,
- expectedCount: 2,
- apiClient: apiClient
- )
-
- let ekm = MockEphemeralKeyManager(key: customerKey, error: nil)
- let sut = STPCustomerContext(keyManager: ekm, apiClient: apiClient)
- // Give the mocked API request a little time to complete and cache the customer, then check cache
- let exp2 = expectation(description: "listPaymentMethods")
- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + ohhttpDelay) {
- sut.paymentMethodsRetrievedDate = Date(timeIntervalSinceNow: -70)
- sut.listPaymentMethodsForCustomer { paymentMethods, _ in
- XCTAssertEqual(paymentMethods!.count, expectedPaymentMethods.count)
- exp2.fulfill()
- }
- }
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- func testRetrievePaymentMethodsDoesNotUseCacheAfterClearingCache() {
- let customerKey = STPFixtures.ephemeralKey()
- let expectedCustomerJSON = STPFixtures.customerWithSingleCardTokenSourceJSON()
- let expectedPaymentMethods = [STPFixtures.paymentMethod()]
- let expectedPaymentMethodsJSON = [STPFixtures.paymentMethodJSON()]
- let apiClient = stubbedAPIClient()
-
- // apiClient.listPaymentMethods should be called twice:
- // - when the context is initialized,
- // - when sut.listPaymentMethods is called below, as the cached list has been cleared
- stubRetrieveCustomers(
- key: customerKey,
- returningCustomerJSON: expectedCustomerJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
- stubListPaymentMethods(
- key: customerKey,
- paymentMethodJSONs: expectedPaymentMethodsJSON,
- expectedCount: 2,
- apiClient: apiClient
- )
-
- let ekm = MockEphemeralKeyManager(key: customerKey, error: nil)
- let sut = STPCustomerContext(keyManager: ekm, apiClient: apiClient)
- // Give the mocked API request a little time to complete and cache the customer, then check cache
- let exp2 = expectation(description: "listPaymentMethods")
- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + ohhttpDelay) {
- sut.clearCache()
- sut.listPaymentMethodsForCustomer { paymentMethods, _ in
- XCTAssertEqual(paymentMethods!.count, expectedPaymentMethods.count)
- exp2.fulfill()
- }
- }
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- func testSetCustomerShippingCallsAPIClientCorrectly() {
- let address = STPFixtures.address()
- let customerKey = STPFixtures.ephemeralKey()
- let expectedCustomerJSON = STPFixtures.customerWithSingleCardTokenSourceJSON()
- let apiClient = stubbedAPIClient()
-
- stubRetrieveCustomers(
- key: customerKey,
- returningCustomerJSON: expectedCustomerJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
- stubListPaymentMethods(
- key: customerKey,
- paymentMethodJSONs: [],
- expectedCount: 1,
- apiClient: apiClient
- )
-
- let exp = expectation(description: "updateCustomer")
- stub { urlRequest in
- if urlRequest.url?.absoluteString.contains("/customers") ?? false
- && urlRequest.httpMethod == "POST"
- {
- let state = urlRequest.queryItems?.first(where: { item in
- item.name == "shipping[address][state]"
- })!
- XCTAssertEqual(state?.value, address.state)
- return true
- }
- return false
- } response: { _ in
- exp.fulfill()
- return HTTPStubsResponse(
- jsonObject: expectedCustomerJSON,
- statusCode: 200,
- headers: nil
- )
- }
-
- let ekm = MockEphemeralKeyManager(key: customerKey, error: nil)
- let sut = STPCustomerContext(keyManager: ekm, apiClient: apiClient)
- let exp2 = expectation(description: "updateCustomerWithShipping")
- sut.updateCustomer(withShippingAddress: address) { error in
- XCTAssertNil(error)
- exp2.fulfill()
- }
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- func testAttachPaymentMethodCallsAPIClientCorrectly() {
- let customerKey = STPFixtures.ephemeralKey()
- let expectedCustomerJSON = STPFixtures.customerWithSingleCardTokenSourceJSON()
- let apiClient = stubbedAPIClient()
- let expectedPaymentMethod = STPFixtures.paymentMethod()
- let expectedPaymentMethodJSON = STPFixtures.paymentMethodJSON()
- let expectedPaymentMethods = [STPFixtures.paymentMethod()]
-
- stubRetrieveCustomers(
- key: customerKey,
- returningCustomerJSON: expectedCustomerJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
- stubListPaymentMethods(
- key: customerKey,
- paymentMethodJSONs: [],
- expectedCount: 1,
- apiClient: apiClient
- )
-
- let exp = expectation(description: "payment method attach")
- // We're attaching 2 payment methods:
- exp.expectedFulfillmentCount = 2
- stub { urlRequest in
- if urlRequest.url?.absoluteString.contains("/payment_method") ?? false
- && urlRequest.httpMethod == "POST"
- {
- return true
- }
- return false
- } response: { _ in
- exp.fulfill()
- return HTTPStubsResponse(
- jsonObject: expectedPaymentMethodJSON,
- statusCode: 200,
- headers: nil
- )
- }
-
- let ekm = MockEphemeralKeyManager(key: customerKey, error: nil)
- let sut = STPCustomerContext(keyManager: ekm, apiClient: apiClient)
- let exp2 = expectation(description: "CustomerContext attachPaymentMethod")
- sut.attachPaymentMethod(toCustomer: expectedPaymentMethods.first!) { error in
- XCTAssertNil(error)
- exp2.fulfill()
- }
-
- let exp3 = expectation(description: "CustomerContext attachPaymentMethod with ID")
- sut.attachPaymentMethodToCustomer(paymentMethodId: expectedPaymentMethod.stripeId) {
- error in
- XCTAssertNil(error)
- exp3.fulfill()
- }
-
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- func testDetachPaymentMethodCallsAPIClientCorrectly() {
- let customerKey = STPFixtures.ephemeralKey()
- let expectedCustomerJSON = STPFixtures.customerWithSingleCardTokenSourceJSON()
- let apiClient = stubbedAPIClient()
- let expectedPaymentMethod = STPFixtures.paymentMethod()
- let expectedPaymentMethodJSON = STPFixtures.paymentMethodJSON()
- let expectedPaymentMethods = [STPFixtures.paymentMethod()]
-
- stubRetrieveCustomers(
- key: customerKey,
- returningCustomerJSON: expectedCustomerJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
- stubListPaymentMethods(
- key: customerKey,
- paymentMethodJSONs: [],
- expectedCount: 1,
- apiClient: apiClient
- )
-
- let exp = expectation(description: "payment method detach")
- // We're detaching 2 payment methods:
- exp.expectedFulfillmentCount = 2
- stub { urlRequest in
- if urlRequest.url?.absoluteString.contains("/payment_method") ?? false
- && urlRequest.httpMethod == "POST"
- {
- return true
- }
- return false
- } response: { _ in
- exp.fulfill()
- return HTTPStubsResponse(
- jsonObject: expectedPaymentMethodJSON,
- statusCode: 200,
- headers: nil
- )
- }
-
- let ekm = MockEphemeralKeyManager(key: customerKey, error: nil)
- let sut = STPCustomerContext(keyManager: ekm, apiClient: apiClient)
- let exp2 = expectation(description: "CustomerContext detachPaymentMethod")
- sut.detachPaymentMethod(fromCustomer: expectedPaymentMethods.first!) { error in
- XCTAssertNil(error)
- exp2.fulfill()
- }
-
- let exp3 = expectation(description: "CustomerContext detachPaymentMethod with ID")
- sut.detachPaymentMethodFromCustomer(paymentMethodId: expectedPaymentMethod.stripeId) {
- error in
- XCTAssertNil(error)
- exp3.fulfill()
- }
-
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- func testFiltersApplePayPaymentMethodsByDefault() {
- let customerKey = STPFixtures.ephemeralKey()
- let expectedCustomerJSON = STPFixtures.customerWithSingleCardTokenSourceJSON()
- let expectedPaymentMethodsJSON = [
- STPFixtures.paymentMethodJSON(), STPFixtures.applePayPaymentMethodJSON(),
- ]
- let apiClient = stubbedAPIClient()
-
- stubRetrieveCustomers(
- key: customerKey,
- returningCustomerJSON: expectedCustomerJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
- stubListPaymentMethods(
- key: customerKey,
- paymentMethodJSONs: expectedPaymentMethodsJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
-
- let ekm = MockEphemeralKeyManager(key: customerKey, error: nil)
- let sut = STPCustomerContext(keyManager: ekm, apiClient: apiClient)
- // Give the mocked API request a little time to complete and cache the customer, then check cache
- let exp2 = expectation(description: "listPaymentMethods")
- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + ohhttpDelay) {
- sut.listPaymentMethodsForCustomer { paymentMethods, _ in
- // Apple Pay should be filtered out
- XCTAssertEqual(paymentMethods!.count, 1)
- exp2.fulfill()
- }
- }
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- func testIncludesApplePayPaymentMethods() {
- let customerKey = STPFixtures.ephemeralKey()
- let expectedCustomerJSON = STPFixtures.customerWithSingleCardTokenSourceJSON()
- let expectedPaymentMethodsJSON = [
- STPFixtures.paymentMethodJSON(), STPFixtures.applePayPaymentMethodJSON(),
- ]
- let apiClient = stubbedAPIClient()
-
- stubRetrieveCustomers(
- key: customerKey,
- returningCustomerJSON: expectedCustomerJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
- stubListPaymentMethods(
- key: customerKey,
- paymentMethodJSONs: expectedPaymentMethodsJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
-
- let ekm = MockEphemeralKeyManager(key: customerKey, error: nil)
- let sut = STPCustomerContext(keyManager: ekm, apiClient: apiClient)
- sut.includeApplePayPaymentMethods = true
- // Give the mocked API request a little time to complete and cache the customer, then check cache
- let exp2 = expectation(description: "listPaymentMethods")
- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + ohhttpDelay) {
- sut.listPaymentMethodsForCustomer { paymentMethods, _ in
- // Apple Pay should be included
- XCTAssertEqual(paymentMethods!.count, 2)
- exp2.fulfill()
- }
- }
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- func testFiltersApplePaySourcesByDefault() {
- let customerKey = STPFixtures.ephemeralKey()
- let expectedCustomerJSON = STPFixtures.customerWithCardAndApplePaySourcesJSON()
- let expectedPaymentMethodsJSON = [
- STPFixtures.paymentMethodJSON(), STPFixtures.applePayPaymentMethodJSON(),
- ]
- let apiClient = stubbedAPIClient()
-
- stubRetrieveCustomers(
- key: customerKey,
- returningCustomerJSON: expectedCustomerJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
- stubListPaymentMethods(
- key: customerKey,
- paymentMethodJSONs: expectedPaymentMethodsJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
-
- let ekm = MockEphemeralKeyManager(key: customerKey, error: nil)
- let sut = STPCustomerContext(keyManager: ekm, apiClient: apiClient)
- // Give the mocked API request a little time to complete and cache the customer, then check cache
- let exp = expectation(description: "retrieveCustomer")
- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + ohhttpDelay) {
- sut.retrieveCustomer { customer, _ in
- // Apple Pay should be filtered out
- XCTAssertEqual(customer!.sources.count, 1)
- exp.fulfill()
- }
- }
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- func testIncludeApplePaySources() {
- let customerKey = STPFixtures.ephemeralKey()
- let expectedCustomerJSON = STPFixtures.customerWithCardAndApplePaySourcesJSON()
- let expectedPaymentMethodsJSON = [
- STPFixtures.paymentMethodJSON(), STPFixtures.applePayPaymentMethodJSON(),
- ]
- let apiClient = stubbedAPIClient()
-
- stubRetrieveCustomers(
- key: customerKey,
- returningCustomerJSON: expectedCustomerJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
- stubListPaymentMethods(
- key: customerKey,
- paymentMethodJSONs: expectedPaymentMethodsJSON,
- expectedCount: 1,
- apiClient: apiClient
- )
-
- let ekm = MockEphemeralKeyManager(key: customerKey, error: nil)
- let sut = STPCustomerContext(keyManager: ekm, apiClient: apiClient)
- sut.includeApplePayPaymentMethods = true
- // Give the mocked API request a little time to complete and cache the customer, then check cache
- let exp = expectation(description: "retrieveCustomer")
- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + ohhttpDelay) {
- sut.retrieveCustomer { customer, _ in
- // Apple Pay should be filtered out
- XCTAssertEqual(customer!.sources.count, 2)
- exp.fulfill()
- }
- }
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- let ohhttpDelay = 0.1
-}
diff --git a/Stripe/StripeiOSTests/STPEphemeralKeyTest.swift b/Stripe/StripeiOSTests/STPEphemeralKeyTest.swift
index b424c92adac..da37c4de265 100644
--- a/Stripe/StripeiOSTests/STPEphemeralKeyTest.swift
+++ b/Stripe/StripeiOSTests/STPEphemeralKeyTest.swift
@@ -27,6 +27,5 @@ class STPEphemeralKeyTest: XCTestCase {
Date(timeIntervalSince1970: TimeInterval((json["expires"] as! NSNumber).doubleValue))
)
XCTAssertEqual(key.livemode, (json["livemode"] as! NSNumber).boolValue)
- XCTAssertEqual(key.customerID, "cus_123")
}
}
diff --git a/Stripe/StripeiOSTests/STPMocks.h b/Stripe/StripeiOSTests/STPMocks.h
index 1b782caa656..1b066a680ca 100644
--- a/Stripe/StripeiOSTests/STPMocks.h
+++ b/Stripe/StripeiOSTests/STPMocks.h
@@ -12,17 +12,6 @@
@interface STPMocks : NSObject
-/**
- A stateless customer context that always retrieves the same customer object.
- */
-+ (STPCustomerContext *)staticCustomerContext;
-
-/**
- A static customer context that always retrieves the given customer and the given payment methods.
- Selecting a default source and attaching a source have no effect.
- */
-+ (STPCustomerContext *)staticCustomerContextWithCustomer:(STPCustomer *)customer paymentMethods:(NSArray *)paymentMethods;
-
/**
A PaymentConfiguration object with a fake publishable key and a fake apple
merchant identifier that ignores the true value of [StripeAPI deviceSupportsApplePay]
diff --git a/Stripe/StripeiOSTests/STPMocks.m b/Stripe/StripeiOSTests/STPMocks.m
index 63787637994..37cfe4c810f 100644
--- a/Stripe/StripeiOSTests/STPMocks.m
+++ b/Stripe/StripeiOSTests/STPMocks.m
@@ -26,15 +26,6 @@ - (BOOL)stpmock_applePayEnabled;
@implementation STPMocks
-+ (STPCustomerContext *)staticCustomerContext {
- return [self staticCustomerContextWithCustomer:[STPFixtures customerWithSingleCardTokenSource]
- paymentMethods:@[[STPFixtures paymentMethod]]];
-}
-
-+ (STPCustomerContext *)staticCustomerContextWithCustomer:(STPCustomer *)customer paymentMethods:(NSArray *)paymentMethods {
- return [[Testing_StaticCustomerContext_Objc alloc] initWithCustomer:customer paymentMethods:paymentMethods];
-}
-
+ (STPPaymentConfiguration *)paymentConfigurationWithApplePaySupportingDevice {
STPPaymentConfiguration *config = [STPPaymentConfiguration new];
config.appleMerchantIdentifier = @"fake_apple_merchant_id";
diff --git a/Stripe/StripeiOSTests/STPPaymentContextApplePayTest.swift b/Stripe/StripeiOSTests/STPPaymentContextApplePayTest.swift
deleted file mode 100644
index 1e317ef3d38..00000000000
--- a/Stripe/StripeiOSTests/STPPaymentContextApplePayTest.swift
+++ /dev/null
@@ -1,204 +0,0 @@
-//
-// STPPaymentContextApplePayTest.swift
-// StripeiOS Tests
-//
-// Created by Brian Dorfman on 8/1/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-@testable@_spi(STP) import Stripe
-@testable@_spi(STP) import StripeCore
-@testable@_spi(STP) import StripePayments
-@testable@_spi(STP) import StripePaymentSheet
-@testable@_spi(STP) import StripePaymentsUI
-
-/// These tests cover STPPaymentContext's Apple Pay specific behavior:
-/// - building a PKPaymentRequest
-/// - determining paymentSummaryItems
-class STPPaymentContextApplePayTest: XCTestCase {
- func buildPaymentContext() -> STPPaymentContext {
- let config = STPPaymentConfiguration()
- config.appleMerchantIdentifier = "fake_merchant_id"
- let theme = STPTheme.defaultTheme
- let customerContext = Testing_StaticCustomerContext()
- let paymentContext = STPPaymentContext(
- customerContext: customerContext,
- configuration: config,
- theme: theme
- )
- return paymentContext
- }
-
- // MARK: - buildPaymentRequest
- func testBuildPaymentRequest_totalAmount() {
- let context = buildPaymentContext()
- context.paymentAmount = 150
- let request = context.buildPaymentRequest()
-
- XCTAssertTrue(
- (request?.paymentSummaryItems.last?.amount == NSDecimalNumber(string: "1.50")),
- "PKPayment total is not equal to STPPaymentContext amount"
- )
- }
-
- func testBuildPaymentRequest_USDDefault() {
- let context = buildPaymentContext()
- context.paymentAmount = 100
- let request = context.buildPaymentRequest()
-
- XCTAssertTrue(
- (request?.currencyCode == "USD"),
- "Default PKPaymentRequest currency code is not USD"
- )
- }
-
- func testBuildPaymentRequest_currency() {
- let context = buildPaymentContext()
- context.paymentAmount = 100
- context.paymentCurrency = "GBP"
- let request = context.buildPaymentRequest()
-
- XCTAssertTrue(
- (request?.currencyCode == "GBP"),
- "PKPaymentRequest currency code is not equal to STPPaymentContext currency"
- )
- }
-
- func testBuildPaymentRequest_uppercaseCurrency() {
- let context = buildPaymentContext()
- context.paymentAmount = 100
- context.paymentCurrency = "eur"
- let request = context.buildPaymentRequest()
-
- XCTAssertTrue(
- (request?.currencyCode == "EUR"),
- "PKPaymentRequest currency code is not uppercased"
- )
- }
-
- func testSummaryItems() -> [PKPaymentSummaryItem]? {
- return [
- PKPaymentSummaryItem(
- label: "First item",
- amount: NSDecimalNumber(mantissa: 20, exponent: 0, isNegative: false)
- ),
- PKPaymentSummaryItem(
- label: "Second item",
- amount: NSDecimalNumber(mantissa: 90, exponent: 0, isNegative: false)
- ),
- PKPaymentSummaryItem(
- label: "Discount",
- amount: NSDecimalNumber(mantissa: 10, exponent: 0, isNegative: true)
- ),
- PKPaymentSummaryItem(
- label: "Total",
- amount: NSDecimalNumber(mantissa: 100, exponent: 0, isNegative: false)
- ),
- ]
- }
-
- func testBuildPaymentRequest_summaryItems() {
- let context = buildPaymentContext()
- context.paymentSummaryItems = testSummaryItems()!
- let request = context.buildPaymentRequest()
-
- XCTAssertTrue((request?.paymentSummaryItems == context.paymentSummaryItems))
- }
-
- // MARK: - paymentSummaryItems
- func testSetPaymentAmount_generateSummaryItems() {
- let context = buildPaymentContext()
- context.paymentAmount = 10000
- context.paymentCurrency = "USD"
- let itemTotalAmount = context.paymentSummaryItems.last?.amount
- let correctTotalAmount = NSDecimalNumber.stp_decimalNumber(
- withAmount: context.paymentAmount,
- currency: context.paymentCurrency
- )
-
- XCTAssertTrue((itemTotalAmount == correctTotalAmount))
- }
-
- func testSetPaymentAmount_generateSummaryItemsShippingMethod() {
- let context = buildPaymentContext()
- context.paymentAmount = 100
- context.configuration.companyName = "Foo Company"
- let method = PKShippingMethod()
- method.amount = NSDecimalNumber(string: "5.99")
- method.label = "FedEx"
- method.detail = "foo"
- method.identifier = "123"
- context.selectedShippingMethod = method
-
- let items = context.paymentSummaryItems
- XCTAssertEqual(Int(items.count), 2)
- let item1 = items[0]
- XCTAssertEqual(item1.label, "FedEx")
- XCTAssertEqual(item1.amount, NSDecimalNumber(string: "5.99"))
- let item2 = items[1]
- XCTAssertEqual(item2.label, "Foo Company")
- XCTAssertEqual(item2.amount, NSDecimalNumber(string: "6.99"))
- }
-
- func testSummaryItemsToSummaryItems_shippingMethod() {
- let context = buildPaymentContext()
- let item1 = PKPaymentSummaryItem()
- item1.amount = NSDecimalNumber(string: "1.00")
- item1.label = "foo"
- let item2 = PKPaymentSummaryItem()
- item2.amount = NSDecimalNumber(string: "9.00")
- item2.label = "bar"
- let item3 = PKPaymentSummaryItem()
- item3.amount = NSDecimalNumber(string: "10.00")
- item3.label = "baz"
- context.paymentSummaryItems = [item1, item2, item3]
- let method = PKShippingMethod()
- method.amount = NSDecimalNumber(string: "5.99")
- method.label = "FedEx"
- method.detail = "foo"
- method.identifier = "123"
- context.selectedShippingMethod = method
-
- let items = context.paymentSummaryItems
- XCTAssertEqual(Int(items.count), 4)
- let resultItem1 = items[0]
- XCTAssertEqual(resultItem1.label, "foo")
- XCTAssertEqual(resultItem1.amount, NSDecimalNumber(string: "1.00"))
- let resultItem2 = items[1]
- XCTAssertEqual(resultItem2.label, "bar")
- XCTAssertEqual(resultItem2.amount, NSDecimalNumber(string: "9.00"))
- let resultItem3 = items[2]
- XCTAssertEqual(resultItem3.label, "FedEx")
- XCTAssertEqual(resultItem3.amount, NSDecimalNumber(string: "5.99"))
- let resultItem4 = items[3]
- XCTAssertEqual(resultItem4.label, "baz")
- XCTAssertEqual(resultItem4.amount, NSDecimalNumber(string: "15.99"))
- }
-
- func testAmountToAmount_shippingMethod_usd() {
- let context = buildPaymentContext()
- context.paymentAmount = 100
- let method = PKShippingMethod()
- method.amount = NSDecimalNumber(string: "5.99")
- method.label = "FedEx"
- method.detail = "foo"
- method.identifier = "123"
- context.selectedShippingMethod = method
- let amount = context.paymentAmount
- XCTAssertEqual(amount, 699)
- }
-
- func testSummaryItems_generateAmountDecimalCurrency() {
- let context = buildPaymentContext()
- context.paymentSummaryItems = testSummaryItems()!
- context.paymentCurrency = "USD"
- XCTAssertTrue(context.paymentAmount == 10000)
- }
-
- func testSummaryItems_generateAmountNoDecimalCurrency() {
- let context = buildPaymentContext()
- context.paymentSummaryItems = testSummaryItems()!
- context.paymentCurrency = "JPY"
- XCTAssertTrue(context.paymentAmount == 100)
- }
-}
diff --git a/Stripe/StripeiOSTests/STPPaymentContextSnapshotTests.swift b/Stripe/StripeiOSTests/STPPaymentContextSnapshotTests.swift
deleted file mode 100644
index 319e9b9673c..00000000000
--- a/Stripe/StripeiOSTests/STPPaymentContextSnapshotTests.swift
+++ /dev/null
@@ -1,84 +0,0 @@
-// Converted to Swift 5.8.1 by Swiftify v5.8.28463 - https://swiftify.com/
-//
-// STPPaymentContextSnapshotTests.m
-// StripeiOS Tests
-//
-// Created by Ben Guo on 12/13/17.
-// Copyright © 2017 Stripe, Inc. All rights reserved.
-//
-
-import iOSSnapshotTestCaseCore
-import StripeCoreTestUtils
-
-class STPPaymentContextSnapshotTests: STPSnapshotTestCase {
- var customerContext: STPCustomerContext?
- var config: STPPaymentConfiguration?
- var hostViewController: UINavigationController?
- var paymentContext: STPPaymentContext?
-
- override func setUp() {
- super.setUp()
- let config = STPPaymentConfiguration()
- config.companyName = "Test Company"
- config.requiredBillingAddressFields = .full
- config.shippingType = .shipping
- self.config = config
- let customerContext = Testing_StaticCustomerContext_Objc.init(customer: STPFixtures.customerWithCardTokenAndSourceSources(), paymentMethods: [STPFixtures.paymentMethod(), STPFixtures.paymentMethod()])
- self.customerContext = customerContext
-
- let viewController = UIViewController()
- hostViewController = stp_navigationControllerForSnapshotTest(withRootVC: viewController)
- }
-
- func buildPaymentContext() {
- let context = STPPaymentContext(customerContext: customerContext!)
- context.hostViewController = hostViewController
- context.configuration.requiredShippingAddressFields = Set([STPContactField.emailAddress])
- paymentContext = context
- }
-
- func testPushPaymentOptionsSmallTitle() {
- buildPaymentContext()
-
- hostViewController?.navigationBar.prefersLargeTitles = false
- paymentContext?.largeTitleDisplayMode = UINavigationItem.LargeTitleDisplayMode.automatic
- paymentContext?.pushPaymentOptionsViewController()
- let view = stp_preparedAndSizedViewForSnapshotTest(from: hostViewController)!
- STPSnapshotVerifyView(view, identifier: nil)
- }
-
- // This test renders at a slightly larger size half the time.
- // We're deprecating Basic Integration soon, and we've spent enough time on this,
- // so these tests are being disabled for now.
- // - (void)testPushPaymentOptionsLargeTitle {
- // [self buildPaymentContext];
- //
- // self.hostViewController.navigationBar.prefersLargeTitles = YES;
- // self.paymentContext.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAutomatic;
- // [self.paymentContext pushPaymentOptionsViewController];
- // UIView *view = [self stp_preparedAndSizedViewForSnapshotTestFromNavigationController:self.hostViewController];
- // STPSnapshotVerifyView(view, nil);
- // }
-
- func testPushShippingAddressSmallTitle() {
- buildPaymentContext()
-
- hostViewController?.navigationBar.prefersLargeTitles = false
- paymentContext?.largeTitleDisplayMode = UINavigationItem.LargeTitleDisplayMode.automatic
- paymentContext?.pushShippingViewController()
- let view = stp_preparedAndSizedViewForSnapshotTest(from: hostViewController)!
- STPSnapshotVerifyView(view, identifier: nil)
- }
- // This test renders at a slightly larger size half the time.
- // We're deprecating Basic Integration soon, and we've spent enough time on this,
- // so these tests are being disabled for now.
- // - (void)testPushShippingAddressLargeTitle {
- // [self buildPaymentContext];
- //
- // self.hostViewController.navigationBar.prefersLargeTitles = YES;
- // self.paymentContext.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAutomatic;
- // [self.paymentContext pushShippingViewController];
- // UIView *view = [self stp_preparedAndSizedViewForSnapshotTestFromNavigationController:self.hostViewController];
- // STPSnapshotVerifyView(view, nil);
- // }
-}
diff --git a/Stripe/StripeiOSTests/STPPaymentOptionsViewControllerLocalizationSnapshotTests.swift b/Stripe/StripeiOSTests/STPPaymentOptionsViewControllerLocalizationSnapshotTests.swift
deleted file mode 100644
index 0853069526d..00000000000
--- a/Stripe/StripeiOSTests/STPPaymentOptionsViewControllerLocalizationSnapshotTests.swift
+++ /dev/null
@@ -1,102 +0,0 @@
-//
-// STPPaymentOptionsViewControllerLocalizationSnapshotTests.swift
-// StripeiOS Tests
-//
-// Created by Brian Dorfman on 10/17/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-import iOSSnapshotTestCase
-import StripeCoreTestUtils
-
-@testable@_spi(STP) import Stripe
-@testable@_spi(STP) import StripeCore
-@testable@_spi(STP) import StripePayments
-@testable@_spi(STP) import StripePaymentSheet
-@testable@_spi(STP) import StripePaymentsUI
-
-class MockSTPPaymentOptionsViewControllerDelegate: NSObject, STPPaymentOptionsViewControllerDelegate
-{
- func paymentOptionsViewController(
- _ paymentOptionsViewController: STPPaymentOptionsViewController,
- didFailToLoadWithError error: Error
- ) {
- }
-
- func paymentOptionsViewControllerDidFinish(
- _ paymentOptionsViewController: STPPaymentOptionsViewController
- ) {
- }
-
- func paymentOptionsViewControllerDidCancel(
- _ paymentOptionsViewController: STPPaymentOptionsViewController
- ) {
- }
-
-}
-
-class STPPaymentOptionsViewControllerLocalizationSnapshotTests: STPSnapshotTestCase {
-
- func performSnapshotTest(forLanguage language: String?) {
- let config = STPPaymentConfiguration()
- config.companyName = "Test Company"
- config.requiredBillingAddressFields = .full
- let theme = STPTheme.defaultTheme
- let paymentMethods = [STPFixtures.paymentMethod(), STPFixtures.paymentMethod()]
- let customerContext = Testing_StaticCustomerContext.init(
- customer: STPFixtures.customerWithCardTokenAndSourceSources(),
- paymentMethods: paymentMethods
- )
- let delegate = MockSTPPaymentOptionsViewControllerDelegate()
- STPLocalizationUtils.overrideLanguage(to: language)
- let paymentOptionsVC = STPPaymentOptionsViewController(
- configuration: config,
- theme: theme,
- customerContext: customerContext,
- delegate: delegate
- )
- let didLoadExpectation = expectation(description: "VC did load")
-
- paymentOptionsVC.loadingPromise?.onSuccess({ (_) in
- didLoadExpectation.fulfill()
- })
- wait(for: [didLoadExpectation].compactMap { $0 }, timeout: 2)
-
- let viewToTest = stp_preparedAndSizedViewForSnapshotTest(from: paymentOptionsVC)!
-
- STPSnapshotVerifyView(viewToTest, identifier: nil)
- STPLocalizationUtils.overrideLanguage(to: nil)
- }
-
- func testGerman() {
- performSnapshotTest(forLanguage: "de")
- }
-
- func testEnglish() {
- performSnapshotTest(forLanguage: "en")
- }
-
- func testSpanish() {
- performSnapshotTest(forLanguage: "es")
- }
-
- func testFrench() {
- performSnapshotTest(forLanguage: "fr")
- }
-
- func testItalian() {
- performSnapshotTest(forLanguage: "it")
- }
-
- func testJapanese() {
- performSnapshotTest(forLanguage: "ja")
- }
-
- func testDutch() {
- performSnapshotTest(forLanguage: "nl")
- }
-
- func testChinese() {
- performSnapshotTest(forLanguage: "zh-Hans")
- }
-}
diff --git a/Stripe/StripeiOSTests/STPPaymentOptionsViewControllerTest.swift b/Stripe/StripeiOSTests/STPPaymentOptionsViewControllerTest.swift
deleted file mode 100644
index 0555fa9799a..00000000000
--- a/Stripe/StripeiOSTests/STPPaymentOptionsViewControllerTest.swift
+++ /dev/null
@@ -1,351 +0,0 @@
-//
-// STPPaymentOptionsViewControllerTest.swift
-// StripeiOS Tests
-//
-// Created by Brian Dorfman on 10/10/17.
-// Copyright © 2017 Stripe, Inc. All rights reserved.
-//
-
-import OCMock
-
-@testable@_spi(STP) import Stripe
-@testable@_spi(STP) import StripeCore
-@testable@_spi(STP) import StripePayments
-@testable@_spi(STP) import StripePaymentSheet
-@testable@_spi(STP) import StripePaymentsUI
-
-class STPPaymentOptionsViewControllerTest: XCTestCase {
- class MockSTPPaymentOptionsViewControllerDelegate: NSObject,
- STPPaymentOptionsViewControllerDelegate
- {
- var didFail = false
- func paymentOptionsViewController(
- _ paymentOptionsViewController: STPPaymentOptionsViewController,
- didFailToLoadWithError error: Error
- ) {
- didFail = true
- }
-
- var didFinish = false
- func paymentOptionsViewControllerDidFinish(
- _ paymentOptionsViewController: STPPaymentOptionsViewController
- ) {
- didFinish = true
- }
-
- var didCancel = false
- func paymentOptionsViewControllerDidCancel(
- _ paymentOptionsViewController: STPPaymentOptionsViewController
- ) {
- didCancel = true
- }
-
- var didSelect = false
- func paymentOptionsViewController(
- _ paymentOptionsViewController: STPPaymentOptionsViewController,
- didSelect paymentOption: STPPaymentOption
- ) {
- didSelect = true
- }
- }
-
- func buildViewController(
- with customer: STPCustomer,
- paymentMethods: [STPPaymentMethod],
- configuration config: STPPaymentConfiguration,
- delegate: STPPaymentOptionsViewControllerDelegate
- ) -> STPPaymentOptionsViewController {
- let mockCustomerContext = Testing_StaticCustomerContext(
- customer: customer,
- paymentMethods: paymentMethods
- )
- return buildViewController(
- with: mockCustomerContext,
- configuration: config,
- delegate: delegate
- )
- }
-
- func buildViewController(
- with customerContext: STPCustomerContext,
- configuration config: STPPaymentConfiguration,
- delegate: STPPaymentOptionsViewControllerDelegate
- ) -> STPPaymentOptionsViewController {
- let theme = STPTheme.defaultTheme
- let vc = STPPaymentOptionsViewController(
- configuration: config,
- theme: theme,
- customerContext: customerContext,
- delegate: delegate
- )
- let didLoadExpectation = expectation(description: "VC did load")
- vc.loadingPromise?.onSuccess({ (_) in
- didLoadExpectation.fulfill()
- })
-
- wait(for: [didLoadExpectation], timeout: 2)
-
- return vc
- }
-
- /// When the customer has no sources, and card is the sole available payment
- /// method, STPAddCardViewController should be shown.
- func testInitWithNoSourcesAndConfigWithUseSourcesOffAndCardAvailable() {
- let customer = STPFixtures.customerWithNoSources()
- let config = STPPaymentConfiguration()
- config.applePayEnabled = false
- let delegate = MockSTPPaymentOptionsViewControllerDelegate()
- let sut = buildViewController(
- with: customer,
- paymentMethods: [],
- configuration: config,
- delegate: delegate
- )
- XCTAssertTrue((sut.internalViewController is STPAddCardViewController))
- }
-
- /// When the customer has a single card token source and the available payment methods
- /// are card and apple pay, STPPaymentOptionsInternalVC should be shown.
- func testInitWithSingleCardTokenSourceAndCardAvailable() {
- let customer = STPFixtures.customerWithSingleCardTokenSource()
- let paymentMethods = [STPFixtures.paymentMethod()]
- let config = STPPaymentConfiguration()
- let delegate = MockSTPPaymentOptionsViewControllerDelegate()
- let sut = buildViewController(
- with: customer,
- paymentMethods: paymentMethods.compactMap { $0 },
- configuration: config,
- delegate: delegate
- )
- XCTAssertTrue((sut.internalViewController is STPPaymentOptionsInternalViewController))
- }
-
- /// When the customer has a single card source source and the available payment methods
- /// are card only, STPPaymentOptionsInternalVC should be shown.
- func testInitWithSingleCardSourceSourceAndCardAvailable() {
- let customer = STPFixtures.customerWithSingleCardSourceSource()
- let paymentMethods = [STPFixtures.paymentMethod()]
- let config = STPPaymentConfiguration()
- config.applePayEnabled = false
- let delegate = MockSTPPaymentOptionsViewControllerDelegate()
- let sut = buildViewController(
- with: customer,
- paymentMethods: paymentMethods.compactMap { $0 },
- configuration: config,
- delegate: delegate
- )
- XCTAssertTrue((sut.internalViewController is STPPaymentOptionsInternalViewController))
- }
-
- /// Tapping cancel in an internal AddCard view controller should result in a call to
- /// didCancel:
- func testAddCardCancelForwardsToDelegate() {
- let customer = STPFixtures.customerWithNoSources()
- let config = STPPaymentConfiguration()
- let delegate = MockSTPPaymentOptionsViewControllerDelegate()
- let sut = buildViewController(
- with: customer,
- paymentMethods: [],
- configuration: config,
- delegate: delegate
- )
- XCTAssertTrue((sut.internalViewController is STPAddCardViewController))
- let cancelButton = sut.internalViewController?.navigationItem.leftBarButtonItem
- _ = cancelButton?.target?.perform(cancelButton?.action, with: cancelButton)
-
- XCTAssertTrue(delegate.didCancel)
- }
-
- /// Tapping cancel in an internal PaymentOptionsInternal view controller should
- /// result in a call to didCancel:
- func testInternalCancelForwardsToDelegate() {
- let customer = STPFixtures.customerWithSingleCardTokenSource()
- let paymentMethods = [STPFixtures.paymentMethod()]
- let config = STPPaymentConfiguration()
- let delegate = MockSTPPaymentOptionsViewControllerDelegate()
- let sut = buildViewController(
- with: customer,
- paymentMethods: paymentMethods.compactMap { $0 },
- configuration: config,
- delegate: delegate
- )
- XCTAssertTrue((sut.internalViewController is STPPaymentOptionsInternalViewController))
- let cancelButton = sut.internalViewController?.navigationItem.leftBarButtonItem
- _ = cancelButton?.target?.perform(cancelButton?.action, with: cancelButton)
-
- XCTAssertTrue(delegate.didCancel)
- }
-
- /// When an AddCard view controller creates a card payment method, it should be attached to the
- /// customer and the correct delegate methods should be called.
- func testAddCardAttachesToCustomerAndFinishes() {
- let config = STPPaymentConfiguration()
- let customer = STPFixtures.customerWithNoSources()
- let mockCustomerContext = Testing_StaticCustomerContext(
- customer: customer,
- paymentMethods: []
- )
- let delegate = MockSTPPaymentOptionsViewControllerDelegate()
- let sut = buildViewController(
- with: mockCustomerContext,
- configuration: config,
- delegate: delegate
- )
- XCTAssertNotNil(sut.view)
- XCTAssertTrue((sut.internalViewController is STPAddCardViewController))
-
- let internalVC = sut.internalViewController as? STPAddCardViewController
- let exp = expectation(description: "completion")
- let expectedPaymentMethod = STPFixtures.paymentMethod()
- internalVC?.delegate?.addCardViewController(
- internalVC!,
- didCreatePaymentMethod: expectedPaymentMethod
- ) { error in
- XCTAssertNil(error)
- exp.fulfill()
- }
-
- let _: ((Any?) -> Bool)? = { obj in
- let paymentMethod = obj as? STPPaymentMethod
- return paymentMethod?.stripeId == expectedPaymentMethod.stripeId
- }
- XCTAssertTrue(mockCustomerContext.didAttach)
- XCTAssertTrue(delegate.didSelect)
- XCTAssertTrue(delegate.didFinish)
- waitForExpectations(timeout: 2, handler: nil)
- }
-
- // Tests for race condition where the promise for fetching payment methods
- // finishes in the context of intializing the sut, and `addCardViewControllerFooterView`
- // is set directly after init, while internalViewController is `STPAddCardViewController`
- func testSetAfterInit_addCardViewControllerFooterView_STPAddCardViewController() {
- let customer = STPFixtures.customerWithNoSources()
- let config = STPPaymentConfiguration()
- config.applePayEnabled = false
- let delegate = MockSTPPaymentOptionsViewControllerDelegate()
- let sut = buildViewController(
- with: customer,
- paymentMethods: [],
- configuration: config,
- delegate: delegate
- )
- sut.addCardViewControllerFooterView = UIView()
- guard let payMethodsInternal = sut.internalViewController as? STPAddCardViewController else {
- XCTFail()
- return
- }
- XCTAssertNotNil(payMethodsInternal.customFooterView)
- }
-
- // Tests for race condition where the promise for fetching payment methods
- // finishes in the context of intializing the sut, and the `paymentOptionsViewControllerFooterView`
- // is set directly after init, while internalViewController is `STPPaymentOptionsInternalViewController`
- func testSetAfterInit_paymentOptionsViewControllerFooterView_STPPaymentOptionsInternalViewController() {
- let customer = STPFixtures.customerWithSingleCardTokenSource()
- let paymentMethods = [STPFixtures.paymentMethod()]
- let config = STPPaymentConfiguration()
- let delegate = MockSTPPaymentOptionsViewControllerDelegate()
- let sut = buildViewController(
- with: customer,
- paymentMethods: paymentMethods.compactMap { $0 },
- configuration: config,
- delegate: delegate
- )
- sut.paymentOptionsViewControllerFooterView = UIView()
- guard let payMethodsInternal = sut.internalViewController as? STPPaymentOptionsInternalViewController else {
- XCTFail()
- return
- }
-#if compiler(>=5.7)
- XCTAssertNotNil(payMethodsInternal.customFooterView)
-#endif
- }
-
- // Tests for race condition where the promise for fetching payment methods
- // finishes in the context of init the sut, and the `addCardViewControllerFooterView`
- // is set directly after init, while internalViewController is `STPPaymentOptionsInternalViewController`
- func testSetAfterInit_addCardViewControllerFooterView_STPPaymentOptionsInternalViewController() {
- let customer = STPFixtures.customerWithSingleCardTokenSource()
- let paymentMethods = [STPFixtures.paymentMethod()]
- let config = STPPaymentConfiguration()
- let delegate = MockSTPPaymentOptionsViewControllerDelegate()
- let sut = buildViewController(
- with: customer,
- paymentMethods: paymentMethods.compactMap { $0 },
- configuration: config,
- delegate: delegate
- )
- sut.addCardViewControllerFooterView = UIView()
- guard let payMethodsInternal = sut.internalViewController as? STPPaymentOptionsInternalViewController else {
- XCTFail()
- return
- }
-#if compiler(>=5.7)
- XCTAssertNotNil(payMethodsInternal.addCardViewControllerCustomFooterView)
-#endif
- }
-
- // Tests for race condition where the promise for fetching payment methods
- // finishes in the context of init the sut, and the `prefilledInformation`
- // is set directly after init, while internalViewController is `STPPaymentOptionsInternalViewController`
- func testSetAfterInit_prefilledInformation_STPPaymentOptionsInternalViewController() {
- let customer = STPFixtures.customerWithSingleCardTokenSource()
- let paymentMethods = [STPFixtures.paymentMethod()]
- let config = STPPaymentConfiguration()
- let delegate = MockSTPPaymentOptionsViewControllerDelegate()
- let sut = buildViewController(
- with: customer,
- paymentMethods: paymentMethods.compactMap { $0 },
- configuration: config,
- delegate: delegate
- )
- let userInformation = STPUserInformation()
- let address = STPAddress()
- address.name = "John Doe"
- address.line1 = "123 Main"
- address.city = "Seattle"
- address.state = "Washington"
- address.postalCode = "98104"
- address.phone = "2065551234"
- userInformation.billingAddress = address
- sut.prefilledInformation = userInformation
- guard let payMethodsInternal = sut.internalViewController as? STPPaymentOptionsInternalViewController else {
- XCTFail()
- return
- }
-#if compiler(>=5.7)
- XCTAssertNotNil(payMethodsInternal.prefilledInformation)
-#endif
- }
-
- // Tests for race condition where the promise for fetching payment methods
- // finishes in the context of init the sut, and the `prefilledInformation`
- // is set directly after init, while internalViewController is `STPAddCardViewController`
- func testSetAfterInit_prefilledInformation_STPAddCardViewController() {
- let customer = STPFixtures.customerWithNoSources()
- let config = STPPaymentConfiguration()
- config.applePayEnabled = false
- let delegate = MockSTPPaymentOptionsViewControllerDelegate()
- let sut = buildViewController(
- with: customer,
- paymentMethods: [],
- configuration: config,
- delegate: delegate
- )
- let userInformation = STPUserInformation()
- let address = STPAddress()
- address.name = "John Doe"
- address.line1 = "123 Main"
- address.city = "Seattle"
- address.state = "Washington"
- address.postalCode = "98104"
- address.phone = "2065551234"
- userInformation.billingAddress = address
- sut.prefilledInformation = userInformation
- guard let payMethodsInternal = sut.internalViewController as? STPAddCardViewController else {
- XCTFail()
- return
- }
- XCTAssertNotNil(payMethodsInternal.prefilledInformation)
- }
-}
diff --git a/Stripe/StripeiOSTests/STPShippingAddressViewControllerLocalizationSnapshotTests.swift b/Stripe/StripeiOSTests/STPShippingAddressViewControllerLocalizationSnapshotTests.swift
deleted file mode 100644
index 1760b7a0d41..00000000000
--- a/Stripe/StripeiOSTests/STPShippingAddressViewControllerLocalizationSnapshotTests.swift
+++ /dev/null
@@ -1,113 +0,0 @@
-//
-// STPShippingAddressViewControllerLocalizationSnapshotTests.swift
-// StripeiOS Tests
-//
-// Created by Ben Guo on 11/3/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-import iOSSnapshotTestCase
-import StripeCoreTestUtils
-
-@testable@_spi(STP) import Stripe
-@testable@_spi(STP) import StripeCore
-@testable@_spi(STP) import StripePayments
-@testable@_spi(STP) import StripePaymentSheet
-@testable@_spi(STP) import StripePaymentsUI
-
-class STPShippingAddressViewControllerLocalizationSnapshotTests: STPSnapshotTestCase {
-
- func performSnapshotTest(
- forLanguage language: String?,
- shippingType: STPShippingType,
- contact: Bool
- ) {
- var identifier = (shippingType == .shipping) ? "shipping" : "delivery"
- let config = STPPaymentConfiguration()
- config.companyName = "Test Company"
- config.requiredShippingAddressFields = Set([
- .postalAddress,
- .emailAddress,
- .phoneNumber,
- .name,
- ])
- if contact {
- config.requiredShippingAddressFields = Set([.emailAddress])
- identifier = "contact"
- }
- config.shippingType = shippingType
-
- STPLocalizationUtils.overrideLanguage(to: language)
- let info = STPUserInformation()
- info.billingAddress = STPAddress()
- info.billingAddress!.email = "@" // trigger "use billing address" button
-
- let shippingVC = STPShippingAddressViewController(
- configuration: config,
- theme: STPTheme.defaultTheme,
- currency: nil,
- shippingAddress: nil,
- selectedShippingMethod: nil,
- prefilledInformation: info
- )
-
- /// This method rejects nil or empty country codes to stop strange looking behavior
- /// when scrolling to the top "unset" position in the picker, so put in
- /// an invalid country code instead to test seeing the "Country" placeholder
- shippingVC.addressViewModel.addressFieldTableViewCountryCode = "INVALID"
-
- let viewToTest = stp_preparedAndSizedViewForSnapshotTest(from: shippingVC)!
-
- STPSnapshotVerifyView(viewToTest, identifier: identifier)
-
- STPLocalizationUtils.overrideLanguage(to: nil)
- }
-
- func testGerman() {
- performSnapshotTest(forLanguage: "de", shippingType: .shipping, contact: false)
- performSnapshotTest(forLanguage: "de", shippingType: .shipping, contact: true)
- performSnapshotTest(forLanguage: "de", shippingType: .delivery, contact: false)
- }
-
- func testEnglish() {
- performSnapshotTest(forLanguage: "en", shippingType: .shipping, contact: false)
- performSnapshotTest(forLanguage: "en", shippingType: .shipping, contact: true)
- performSnapshotTest(forLanguage: "en", shippingType: .delivery, contact: false)
- }
-
- func testSpanish() {
- performSnapshotTest(forLanguage: "es", shippingType: .shipping, contact: false)
- performSnapshotTest(forLanguage: "es", shippingType: .shipping, contact: true)
- performSnapshotTest(forLanguage: "es", shippingType: .delivery, contact: false)
- }
-
- func testFrench() {
- performSnapshotTest(forLanguage: "fr", shippingType: .shipping, contact: false)
- performSnapshotTest(forLanguage: "fr", shippingType: .shipping, contact: true)
- performSnapshotTest(forLanguage: "fr", shippingType: .delivery, contact: false)
- }
-
- func testItalian() {
- performSnapshotTest(forLanguage: "it", shippingType: .shipping, contact: false)
- performSnapshotTest(forLanguage: "it", shippingType: .shipping, contact: true)
- performSnapshotTest(forLanguage: "it", shippingType: .delivery, contact: false)
- }
-
- func testJapanese() {
- performSnapshotTest(forLanguage: "ja", shippingType: .shipping, contact: false)
- performSnapshotTest(forLanguage: "ja", shippingType: .shipping, contact: true)
- performSnapshotTest(forLanguage: "ja", shippingType: .delivery, contact: false)
- }
-
- func testDutch() {
- performSnapshotTest(forLanguage: "nl", shippingType: .shipping, contact: false)
- performSnapshotTest(forLanguage: "nl", shippingType: .shipping, contact: true)
- performSnapshotTest(forLanguage: "nl", shippingType: .delivery, contact: false)
- }
-
- func testChinese() {
- performSnapshotTest(forLanguage: "zh-Hans", shippingType: .shipping, contact: false)
- performSnapshotTest(forLanguage: "zh-Hans", shippingType: .shipping, contact: true)
- performSnapshotTest(forLanguage: "zh-Hans", shippingType: .delivery, contact: false)
- }
-}
diff --git a/Stripe/StripeiOSTests/STPShippingAddressViewControllerTest.swift b/Stripe/StripeiOSTests/STPShippingAddressViewControllerTest.swift
deleted file mode 100644
index cc473049d34..00000000000
--- a/Stripe/StripeiOSTests/STPShippingAddressViewControllerTest.swift
+++ /dev/null
@@ -1,114 +0,0 @@
-//
-// STPShippingAddressViewControllerTest.swift
-// StripeiOS Tests
-//
-// Created by Cameron Sabol on 8/7/18.
-// Copyright © 2018 Stripe, Inc. All rights reserved.
-//
-
-import Stripe
-
-@testable@_spi(STP) import Stripe
-@testable@_spi(STP) import StripeCore
-@testable@_spi(STP) import StripePayments
-@testable@_spi(STP) import StripePaymentSheet
-@testable@_spi(STP) import StripePaymentsUI
-
-class STPShippingAddressViewControllerTest: XCTestCase {
- func testPrefilledBillingAddress_removeAddress() {
- let config = STPPaymentConfiguration()
- config.requiredShippingAddressFields = Set([.postalAddress])
-
- let address = STPAddress()
- address.name = "John Smith Doe"
- address.phone = "8885551212"
- address.email = "foo@example.com"
- address.line1 = "55 John St"
- address.city = "Harare"
- address.postalCode = "10002"
- // Zimbabwe does not require zip codes, while the default locale for tests (US) does
- address.country = "ZW"
- // Sanity checks
- XCTAssertFalse(STPPostalCodeValidator.postalCodeIsRequired(forCountryCode: "ZW"))
- XCTAssertTrue(STPPostalCodeValidator.postalCodeIsRequired(forCountryCode: "US"))
-
- let sut = STPShippingAddressViewController(
- configuration: config,
- theme: STPTheme.defaultTheme,
- currency: nil,
- shippingAddress: address,
- selectedShippingMethod: nil,
- prefilledInformation: nil
- )
-
- XCTAssertNoThrow(sut.loadView())
- XCTAssertNoThrow(sut.viewDidLoad())
- }
-
- func testPrefilledBillingAddress_addAddressWithLimitedCountries() {
- // Zimbabwe does not require zip codes, while the default locale for tests (US) does
- NSLocale.stp_withLocale(as: NSLocale(localeIdentifier: "en_ZW")) {
- // Sanity checks
- XCTAssertFalse(STPPostalCodeValidator.postalCodeIsRequired(forCountryCode: "ZW"))
- XCTAssertTrue(STPPostalCodeValidator.postalCodeIsRequired(forCountryCode: "US"))
- let config = STPPaymentConfiguration()
- config.requiredShippingAddressFields = Set([.postalAddress])
- config.availableCountries = Set(["CA", "BT"])
-
- let address = STPAddress()
- address.name = "John Smith Doe"
- address.phone = "8885551212"
- address.email = "foo@example.com"
- address.line1 = "55 John St"
- address.city = "New York"
- address.state = "NY"
- address.postalCode = "10002"
- address.country = "US"
-
- let sut = STPShippingAddressViewController(
- configuration: config,
- theme: STPTheme.defaultTheme,
- currency: nil,
- shippingAddress: address,
- selectedShippingMethod: nil,
- prefilledInformation: nil
- )
-
- XCTAssertNoThrow(sut.loadView())
- XCTAssertNoThrow(sut.viewDidLoad())
- }
- }
-
- func testPrefilledBillingAddress_addAddress() {
- // Zimbabwe does not require zip codes, while the default locale for tests (US) does
- NSLocale.stp_withLocale(as: NSLocale(localeIdentifier: "en_ZW")) {
- // Sanity checks
- XCTAssertFalse(STPPostalCodeValidator.postalCodeIsRequired(forCountryCode: "ZW"))
- XCTAssertTrue(STPPostalCodeValidator.postalCodeIsRequired(forCountryCode: "US"))
- let config = STPPaymentConfiguration()
- config.requiredShippingAddressFields = Set([.postalAddress])
-
- let address = STPAddress()
- address.name = "John Smith Doe"
- address.phone = "8885551212"
- address.email = "foo@example.com"
- address.line1 = "55 John St"
- address.city = "New York"
- address.state = "NY"
- address.postalCode = "10002"
- address.country = "US"
-
- let sut = STPShippingAddressViewController(
- configuration: config,
- theme: STPTheme.defaultTheme,
- currency: nil,
- shippingAddress: address,
- selectedShippingMethod: nil,
- prefilledInformation: nil
- )
-
- XCTAssertNoThrow(sut.loadView())
- XCTAssertNoThrow(sut.viewDidLoad())
- }
- }
-}
diff --git a/Stripe/StripeiOSTests/STPShippingMethodsViewControllerLocalizationSnapshotTests.swift b/Stripe/StripeiOSTests/STPShippingMethodsViewControllerLocalizationSnapshotTests.swift
deleted file mode 100644
index f9b256b34ee..00000000000
--- a/Stripe/StripeiOSTests/STPShippingMethodsViewControllerLocalizationSnapshotTests.swift
+++ /dev/null
@@ -1,76 +0,0 @@
-//
-// STPShippingMethodsViewControllerLocalizationSnapshotTests.swift
-// StripeiOS Tests
-//
-// Created by Ben Guo on 11/3/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-import iOSSnapshotTestCase
-import StripeCoreTestUtils
-
-@testable@_spi(STP) import Stripe
-@testable@_spi(STP) import StripeCore
-@testable@_spi(STP) import StripePayments
-@testable@_spi(STP) import StripePaymentSheet
-@testable@_spi(STP) import StripePaymentsUI
-
-class STPShippingMethodsViewControllerLocalizationSnapshotTests: STPSnapshotTestCase {
-
- func performSnapshotTest(forLanguage language: String?) {
- STPLocalizationUtils.overrideLanguage(to: language)
-
- let method1 = PKShippingMethod()
- method1.label = "UPS Ground"
- method1.detail = "Arrives in 3-5 days"
- method1.amount = NSDecimalNumber(string: "0.00")
- method1.identifier = "ups_ground"
- let method2 = PKShippingMethod()
- method2.label = "FedEx"
- method2.detail = "Arrives tomorrow"
- method2.amount = NSDecimalNumber(string: "5.99")
- method2.identifier = "fedex"
-
- let shippingVC = STPShippingMethodsViewController(
- shippingMethods: [method1, method2],
- selectedShippingMethod: method1,
- currency: "usd",
- theme: STPTheme.defaultTheme
- )
- let viewToTest = stp_preparedAndSizedViewForSnapshotTest(from: shippingVC)!
- STPSnapshotVerifyView(viewToTest, identifier: nil)
- STPLocalizationUtils.overrideLanguage(to: nil)
- }
-
- func testGerman() {
- performSnapshotTest(forLanguage: "de")
- }
-
- func testEnglish() {
- performSnapshotTest(forLanguage: "en")
- }
-
- func testSpanish() {
- performSnapshotTest(forLanguage: "es")
- }
-
- func testFrench() {
- performSnapshotTest(forLanguage: "fr")
- }
-
- func testItalian() {
- performSnapshotTest(forLanguage: "it")
- }
-
- func testJapanese() {
- performSnapshotTest(forLanguage: "ja")
- }
-
- func testDutch() {
- performSnapshotTest(forLanguage: "nl")
- }
-
- func testChinese() {
- performSnapshotTest(forLanguage: "zh-Hans")
- }
-}
diff --git a/Stripe/StripeiOSTests/STPSwiftFixtures.swift b/Stripe/StripeiOSTests/STPSwiftFixtures.swift
index f522531e350..1005db9ba80 100644
--- a/Stripe/StripeiOSTests/STPSwiftFixtures.swift
+++ b/Stripe/StripeiOSTests/STPSwiftFixtures.swift
@@ -9,7 +9,6 @@
import Foundation
@testable@_spi(STP) import Stripe
-@testable@_spi(STP) import StripeCore
@testable@_spi(STP) import StripePayments
@testable@_spi(STP) import StripePaymentSheet
@testable@_spi(STP) import StripePaymentsUI
@@ -33,75 +32,3 @@ extension STPFixtures {
return .decodedObject(fromAPIResponse: response)!
}
}
-
-class MockEphemeralKeyProvider: NSObject, STPCustomerEphemeralKeyProvider {
- func createCustomerKey(
- withAPIVersion apiVersion: String,
- completion: @escaping STPJSONResponseCompletionBlock
- ) {
- completion(STPFixtures.ephemeralKey().allResponseFields, nil)
- }
-}
-
-@objcMembers
-@objc class Testing_StaticCustomerContext_Objc: Testing_StaticCustomerContext {
-
-}
-
-@objcMembers
-class Testing_StaticCustomerContext: STPCustomerContext {
- var customer: STPCustomer
- var paymentMethods: [STPPaymentMethod]
- convenience init() {
- let customer = STPFixtures.customerWithSingleCardTokenSource()
- let paymentMethods = [STPFixtures.paymentMethod()].compactMap { $0 }
- self.init(
- customer: customer,
- paymentMethods: paymentMethods
- )
- }
- init(
- customer: STPCustomer,
- paymentMethods: [STPPaymentMethod]
- ) {
- self.customer = customer
- self.paymentMethods = paymentMethods
- super.init(
- keyManager: STPEphemeralKeyManager(
- keyProvider: MockEphemeralKeyProvider(),
- apiVersion: "1",
- performsEagerFetching: false
- ),
- apiClient: STPAPIClient.shared
- )
- }
-
- override func retrieveCustomer(_ completion: STPCustomerCompletionBlock?) {
- if let completion = completion {
- completion(customer, nil)
- }
- }
-
- override func listPaymentMethodsForCustomer(completion: STPPaymentMethodsCompletionBlock?) {
- if let completion = completion {
- completion(paymentMethods, nil)
- }
- }
-
- var didAttach = false
- override func attachPaymentMethod(
- toCustomer paymentMethod: STPPaymentMethod,
- completion: STPErrorBlock?
- ) {
- didAttach = true
- if let completion = completion {
- completion(nil)
- }
- }
-
- override func retrieveLastSelectedPaymentMethodIDForCustomer(
- completion: @escaping (String?, Error?) -> Void
- ) {
- completion(nil, nil)
- }
-}
diff --git a/Stripe/StripeiOSTests/UINavigationBar+StripeTest.m b/Stripe/StripeiOSTests/UINavigationBar+StripeTest.m
deleted file mode 100644
index bc5a2bd8679..00000000000
--- a/Stripe/StripeiOSTests/UINavigationBar+StripeTest.m
+++ /dev/null
@@ -1,52 +0,0 @@
-//
-// UINavigationBar+StripeTest.m
-// Stripe
-//
-// Created by Brian Dorfman on 12/9/16.
-// Copyright © 2016 Stripe, Inc. All rights reserved.
-//
-
-#import
-#import
-@import Stripe;
-#import "STPMocks.h"
-@import StripePaymentsObjcTestUtils;
-
-@interface UINavigationBar_StripeTest : XCTestCase
-
-@end
-
-@implementation UINavigationBar_StripeTest
-
-- (STPPaymentOptionsViewController *)buildPaymentOptionsViewController {
- id customerContext = [STPMocks staticCustomerContext];
- STPPaymentConfiguration *config = [STPPaymentConfiguration new];
- STPTheme *theme = [STPTheme defaultTheme];
- id delegate = OCMProtocolMock(@protocol(STPPaymentOptionsViewControllerDelegate));
- STPPaymentOptionsViewController *paymentOptionsVC = [[STPPaymentOptionsViewController alloc] initWithConfiguration:config
- theme:theme
- customerContext:customerContext
- delegate:delegate];
- return paymentOptionsVC;
-}
-
-- (void)testVCUsesNavigationBarColor {
- STPPaymentOptionsViewController *paymentOptionsVC = [self buildPaymentOptionsViewController];
- STPTheme *navTheme = [STPTheme new];
- navTheme.accentColor = [UIColor purpleColor];
-
- UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:paymentOptionsVC];
- navController.navigationBar.stp_theme = navTheme;
- __unused UIView *view = paymentOptionsVC.view;
- XCTAssertEqualObjects(paymentOptionsVC.navigationItem.leftBarButtonItem.tintColor, [UIColor purpleColor]);
-}
-
-- (void)testVCDoesNotUseNavigationBarColor {
- STPPaymentOptionsViewController *paymentOptionsVC = [self buildPaymentOptionsViewController];
- __unused UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:paymentOptionsVC];
- __unused UIView *view = paymentOptionsVC.view;
- XCTAssertEqualObjects(paymentOptionsVC.navigationItem.leftBarButtonItem.tintColor, [STPTheme defaultTheme].accentColor);
-}
-
-
-@end
diff --git a/Stripe3DS2/Stripe3DS2.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/Stripe3DS2/Stripe3DS2.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index a8bfb542134..10ffe531e9b 100644
--- a/Stripe3DS2/Stripe3DS2.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Stripe3DS2/Stripe3DS2.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,14 +9,32 @@
isShown
orderHint
- 12
+ 8
Stripe3DS2DemoUI.xcscheme_^#shared#^_
isShown
orderHint
- 13
+ 9
+
+
+ SuppressBuildableAutocreation
+
+ 57AEC53510AE0DC0539730F3
+
+ primary
+
+
+ 7DA168BC86CE957505FA091B
+
+ primary
+
+
+ F2D50FA32F27498F56CD08DD
+
+ primary
+
diff --git a/StripeApplePay/StripeApplePay.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/StripeApplePay/StripeApplePay.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index dd8fc035d06..96739d542c0 100644
--- a/StripeApplePay/StripeApplePay.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/StripeApplePay/StripeApplePay.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,20 @@
isShown
orderHint
- 14
+ 10
+
+
+ SuppressBuildableAutocreation
+
+ 747C9FE1743C0B4444303569
+
+ primary
+
+
+ ACAFA21CF224F80EFAEFDC2F
+
+ primary
+
diff --git a/StripeCameraCore/StripeCameraCore.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/StripeCameraCore/StripeCameraCore.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index 62a025f50a1..13b94c5b781 100644
--- a/StripeCameraCore/StripeCameraCore.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/StripeCameraCore/StripeCameraCore.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,20 @@
isShown
orderHint
- 15
+ 11
+
+
+ SuppressBuildableAutocreation
+
+ B50782C89D54809DFFCF80D0
+
+ primary
+
+
+ F3DED9AB60FDAB786A737384
+
+ primary
+
diff --git a/StripeCardScan/StripeCardScan.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/StripeCardScan/StripeCardScan.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index d16e898423e..0d6594e8818 100644
--- a/StripeCardScan/StripeCardScan.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/StripeCardScan/StripeCardScan.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,20 @@
isShown
orderHint
- 16
+ 12
+
+
+ SuppressBuildableAutocreation
+
+ 03CF79975A56288F02F20E52
+
+ primary
+
+
+ DCC6FFCFBE0B51B33F4CBB26
+
+ primary
+
diff --git a/StripeCore/StripeCore.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/StripeCore/StripeCore.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index 2020b015f1d..95e51da799b 100644
--- a/StripeCore/StripeCore.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/StripeCore/StripeCore.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,20 @@
isShown
orderHint
- 17
+ 13
+
+
+ SuppressBuildableAutocreation
+
+ 03B50F59824D1D18AA073D57
+
+ primary
+
+
+ 7877B31445857B119EA45445
+
+ primary
+
diff --git a/StripeCore/StripeCore/Source/Analytics/STPAnalyticsClient.swift b/StripeCore/StripeCore/Source/Analytics/STPAnalyticsClient.swift
index 39460383163..313a66e07f8 100644
--- a/StripeCore/StripeCore/Source/Analytics/STPAnalyticsClient.swift
+++ b/StripeCore/StripeCore/Source/Analytics/STPAnalyticsClient.swift
@@ -62,10 +62,6 @@ import UIKit
_ = additionalInfoSet.insert(info)
}
- public func clearAdditionalInfo() {
- additionalInfoSet.removeAll()
- }
-
public static var isSimulatorOrTest: Bool {
#if targetEnvironment(simulator)
return true
diff --git a/StripeCore/StripeCore/Source/Helpers/PaymentsSDKVariant.swift b/StripeCore/StripeCore/Source/Helpers/PaymentsSDKVariant.swift
index 630920aa145..7a2d110a179 100644
--- a/StripeCore/StripeCore/Source/Helpers/PaymentsSDKVariant.swift
+++ b/StripeCore/StripeCore/Source/Helpers/PaymentsSDKVariant.swift
@@ -9,10 +9,6 @@ import Foundation
@_spi(STP) public class PaymentsSDKVariant {
@_spi(STP) public static let variant: String = {
- if NSClassFromString("STPPaymentContext") != nil {
- // This is the full legacy Payments SDK, including Basic Integration
- return "legacy"
- }
if NSClassFromString("STP_Internal_PaymentSheetViewController") != nil {
// This is the PaymentSheet SDK
return "paymentsheet"
diff --git a/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index e690478a4a5..5d9c4f4e950 100644
--- a/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/StripeFinancialConnections/StripeFinancialConnections.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,20 @@
isShown
orderHint
- 18
+ 14
+
+
+ SuppressBuildableAutocreation
+
+ 44C90013B7C82C80A2F69956
+
+ primary
+
+
+ DF72D31B68363878FC1604CF
+
+ primary
+
diff --git a/StripeIdentity/StripeIdentity.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/StripeIdentity/StripeIdentity.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index 8307d5cf2b6..c226f31f45e 100644
--- a/StripeIdentity/StripeIdentity.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/StripeIdentity/StripeIdentity.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,20 @@
isShown
orderHint
- 19
+ 15
+
+
+ SuppressBuildableAutocreation
+
+ 5883CCEAA4C73E1DB1F0D3A5
+
+ primary
+
+
+ F251015845B21C036CFBC636
+
+ primary
+
diff --git a/StripePaymentSheet/StripePaymentSheet.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/StripePaymentSheet/StripePaymentSheet.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index a97177c1d1d..3a0b3b5e678 100644
--- a/StripePaymentSheet/StripePaymentSheet.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/StripePaymentSheet/StripePaymentSheet.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,20 @@
isShown
orderHint
- 20
+ 16
+
+
+ SuppressBuildableAutocreation
+
+ 9A93AB1D98A0A1EC6ADEA65D
+
+ primary
+
+
+ A74928FD0171C3213676E29C
+
+ primary
+
diff --git a/StripePayments/StripePayments.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/StripePayments/StripePayments.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index 7e55261b2c8..1f09a1ab996 100644
--- a/StripePayments/StripePayments.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/StripePayments/StripePayments.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,20 @@
isShown
orderHint
- 21
+ 17
+
+
+ SuppressBuildableAutocreation
+
+ 0A35A66495209E60ADD254A3
+
+ primary
+
+
+ 82D53FD79169DC5CBF54660A
+
+ primary
+
diff --git a/StripePaymentsUI/StripePaymentsUI.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/StripePaymentsUI/StripePaymentsUI.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index efeb18d87a0..f247141de55 100644
--- a/StripePaymentsUI/StripePaymentsUI.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/StripePaymentsUI/StripePaymentsUI.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,20 @@
isShown
orderHint
- 22
+ 18
+
+
+ SuppressBuildableAutocreation
+
+ C159BFB17E834CD30378F9F3
+
+ primary
+
+
+ CFDEE20ADA9F0099259938AF
+
+ primary
+
diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPPromise.swift b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPPromise.swift
index 4fbfc0450a3..7e221a26d3a 100644
--- a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPPromise.swift
+++ b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/STPPromise.swift
@@ -16,8 +16,6 @@ import Foundation
@_spi(STP) public typealias STPPromiseCompletionBlock = (T?, Error?) -> Void
- @_spi(STP) public typealias STPPromiseMapBlock = (T) -> Any?
-
@_spi(STP) public typealias STPPromiseFlatMapBlock = (T) -> STPPromise
@_spi(STP) public var completed: Bool {
@@ -111,16 +109,6 @@ import Foundation
})
}
- @discardableResult @_spi(STP) public func map(_ callback: @escaping STPPromiseMapBlock) -> STPPromise {
- let wrapper = STPPromise.init()
- onSuccess({ value in
- wrapper.succeed(callback(value) as! T)
- }).onFailure({ error in
- wrapper.fail(error)
- })
- return wrapper
- }
-
@discardableResult func flatMap(_ callback: @escaping STPPromiseFlatMapBlock) -> STPPromise {
let wrapper = STPPromise.init()
onSuccess({ value in
diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/String+Localized.swift b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/String+Localized.swift
index 8574aab5b8c..036ed04b4e0 100644
--- a/StripePaymentsUI/StripePaymentsUI/Source/Helpers/String+Localized.swift
+++ b/StripePaymentsUI/StripePaymentsUI/Source/Helpers/String+Localized.swift
@@ -52,10 +52,6 @@ extension String.Localized {
STPLocalizedString("Shipping Address", "Title for shipping address entry section")
}
- @_spi(STP) public static var billing_address: String {
- STPLocalizedString("Billing Address", "Title for billing address entry section")
- }
-
@_spi(STP) public static var billing_address_lowercase: String {
STPLocalizedString("Billing address", "Billing address section title for card form entry.")
}
diff --git a/StripeUICore/StripeUICore.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/StripeUICore/StripeUICore.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index 53df2f856d5..647a2dcf5d3 100644
--- a/StripeUICore/StripeUICore.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/StripeUICore/StripeUICore.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,20 @@
isShown
orderHint
- 23
+ 19
+
+
+ SuppressBuildableAutocreation
+
+ A4B3F8AEF10396425E1A79D0
+
+ primary
+
+
+ DE3C3F3D3BB67DD660A44B1E
+
+ primary
+
diff --git a/Testers/IntegrationTester/IntegrationTester.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist b/Testers/IntegrationTester/IntegrationTester.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
index 1e4b100c93b..5c713c87b3d 100644
--- a/Testers/IntegrationTester/IntegrationTester.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/Testers/IntegrationTester/IntegrationTester.xcodeproj/xcuserdata/porter.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,7 @@
isShown
orderHint
- 9
+ 5