diff --git a/Example/Basic Integration/CheckoutViewController.swift b/Example/Basic Integration/CheckoutViewController.swift index 39d6f5cd732..c5caa08057e 100644 --- a/Example/Basic Integration/CheckoutViewController.swift +++ b/Example/Basic Integration/CheckoutViewController.swift @@ -84,6 +84,7 @@ class CheckoutViewController: UIViewController { config.requiredShippingAddressFields = settings.requiredShippingAddressFields config.shippingType = settings.shippingType config.additionalPaymentOptions = settings.additionalPaymentOptions + config.cardScanningEnabled = true self.country = settings.country self.paymentCurrency = settings.currency diff --git a/Example/UI Examples/BrowseViewController.swift b/Example/UI Examples/BrowseViewController.swift index 9921ef3eee8..d8e6d8e8e33 100644 --- a/Example/UI Examples/BrowseViewController.swift +++ b/Example/UI Examples/BrowseViewController.swift @@ -93,6 +93,7 @@ class BrowseViewController: UITableViewController, STPAddCardViewControllerDeleg present(navigationController, animated: true, completion: nil) case .STPAddCardViewController: let config = STPPaymentConfiguration() + config.cardScanningEnabled = true let viewController = STPAddCardViewController(configuration: config, theme: theme) viewController.apiClient = MockAPIClient() viewController.delegate = self @@ -101,6 +102,7 @@ class BrowseViewController: UITableViewController, STPAddCardViewControllerDeleg present(navigationController, animated: true, completion: nil) case .STPAddCardViewControllerWithAddress: let config = STPPaymentConfiguration() + config.cardScanningEnabled = true config.requiredBillingAddressFields = .full let viewController = STPAddCardViewController(configuration: config, theme: theme) viewController.apiClient = MockAPIClient() @@ -113,6 +115,7 @@ class BrowseViewController: UITableViewController, STPAddCardViewControllerDeleg config.additionalPaymentOptions = [.default, .FPX] config.requiredBillingAddressFields = .none config.appleMerchantIdentifier = "dummy-merchant-id" + config.cardScanningEnabled = true let viewController = STPPaymentOptionsViewController(configuration: config, theme: theme, customerContext: self.customerContext, @@ -126,6 +129,7 @@ class BrowseViewController: UITableViewController, STPAddCardViewControllerDeleg config.additionalPaymentOptions = .default config.requiredBillingAddressFields = .none config.appleMerchantIdentifier = "dummy-merchant-id" + config.cardScanningEnabled = true let viewController = STPPaymentOptionsViewController(configuration: config, theme: theme, customerContext: self.customerContext, diff --git a/Example/UI Examples/Info.plist b/Example/UI Examples/Info.plist index 4222ac2dd31..3783cf6d5d8 100644 --- a/Example/UI Examples/Info.plist +++ b/Example/UI Examples/Info.plist @@ -22,6 +22,8 @@ UILaunchStoryboardName LaunchScreen + NSCameraUsageDescription + Granting access to your camera will allow you to scan payment cards. UIRequiredDeviceCapabilities armv7 diff --git a/LocalizationTester/ViewController.m b/LocalizationTester/ViewController.m index 8ea30e35f28..0a942584b97 100644 --- a/LocalizationTester/ViewController.m +++ b/LocalizationTester/ViewController.m @@ -156,7 +156,6 @@ - (void)tableView:(__unused UITableView *)tableView didSelectRowAtIndexPath:(__u STPPaymentConfiguration *configuration = [[STPPaymentConfiguration alloc] init]; configuration.requiredBillingAddressFields = STPBillingAddressFieldsFull; STPAddCardViewController *addCardVC = [[STPAddCardViewController alloc] initWithConfiguration:configuration theme:[STPTheme defaultTheme]]; - addCardVC.alwaysShowScanCardButton = YES; addCardVC.alwaysEnableDoneButton = YES; addCardVC.delegate = self; vc = addCardVC; diff --git a/README.md b/README.md index 3f89941cd96..35c9bf49e78 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Table of contents * [Getting Started](#getting-started) * [Integration](#integration) * [Examples](#examples) - * [Card IO](#card-io) + * [Card scanning](#card-scanning-beta) * [Contributing](#contributing) * [Migrating](#migrating-from-older-versions) @@ -51,7 +51,7 @@ You can use these individually, or take all of the prebuilt UI in one flow by fo From left to right: [STPAddCardViewController](https://stripe.dev/stripe-ios/docs/Classes/STPAddCardViewController.html), [STPPaymentOptionsViewController](https://stripe.dev/stripe-ios/docs/Classes/STPPaymentOptionsViewController.html), [STPShippingAddressViewController](https://stripe.dev/stripe-ios/docs/Classes/STPShippingAddressViewController.html) -**Card Scanning**: We support card scanning capabilities using card.io. See our [Card IO](#card-io) section. +**Card scanning**: We support card scanning on iOS 13 and higher. See our [Card scanning](#card-scanning-beta) section. ## Releases @@ -89,11 +89,13 @@ Check out [stripe-samples](https://github.com/stripe-samples/) for more, includi - [Accepting a card payment](https://github.com/stripe-samples/card-payment-charges-api) (Charges API) -## Card IO +## Card scanning (Beta) -To add card scanning capabilities to our prebuilt UI components, [install card.io](https://github.com/card-io/card.io-iOS-SDK#setup) alongside our SDK. You'll also need to set `NSCameraUsageDescription` in your application's plist, and provide a reason for accessing the camera (e.g. "To scan cards"). +To add card scanning capabilities to our prebuilt UI components, set the `cardScanningEnabled` option on your `STPPaymentConfiguration`. You'll also need to set `NSCameraUsageDescription` in your application's plist, and provide a reason for accessing the camera (e.g. "To scan cards"). Card scanning is supported on devices with iOS 13 or higher. -Demo this in our [Basic Integration example app](https://github.com/stripe/stripe-ios/tree/v19.4.0/Example/Basic&20Integration) by running `./install_cardio.rb`, which will download and install card.io in the project. Now, when you run the example app on a device, you'll see a "Scan Card" button when adding a new card. +Demo this in our [Basic Integration example app](https://github.com/stripe/stripe-ios/tree/v19.4.0/Example/Basic%20Integration). When you run the example app on a device, you'll see a "Scan Card" button when adding a new card. + +This feature is currently in beta. Please file bugs on our [GitHub issues page](https://github.com/stripe/stripe-ios/issues). ## Contributing diff --git a/Stripe.xcodeproj/project.pbxproj b/Stripe.xcodeproj/project.pbxproj index fcf6383699b..f85ba9955df 100644 --- a/Stripe.xcodeproj/project.pbxproj +++ b/Stripe.xcodeproj/project.pbxproj @@ -366,6 +366,18 @@ 0731329D2277ABF40019CE3F /* STPPinManagementService.h in Headers */ = {isa = PBXBuildFile; fileRef = 0731328D2277A3F60019CE3F /* STPPinManagementService.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0731329E2277ABF60019CE3F /* STPPinManagementService.m in Sources */ = {isa = PBXBuildFile; fileRef = 0731328E2277A3F60019CE3F /* STPPinManagementService.m */; }; 0731329F227CFE410019CE3F /* STPIssuingCardPin.h in Headers */ = {isa = PBXBuildFile; fileRef = 073132932277A72D0019CE3F /* STPIssuingCardPin.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 311A475624EB1D2300576D92 /* STPCardScanner.h in Headers */ = {isa = PBXBuildFile; fileRef = 311A475424EB1D2300576D92 /* STPCardScanner.h */; }; + 311A475724EB1D2300576D92 /* STPCardScanner.h in Headers */ = {isa = PBXBuildFile; fileRef = 311A475424EB1D2300576D92 /* STPCardScanner.h */; }; + 311A475824EB1D2300576D92 /* STPCardScanner.m in Sources */ = {isa = PBXBuildFile; fileRef = 311A475524EB1D2300576D92 /* STPCardScanner.m */; }; + 311A475924EB1D2300576D92 /* STPCardScanner.m in Sources */ = {isa = PBXBuildFile; fileRef = 311A475524EB1D2300576D92 /* STPCardScanner.m */; }; + 311A475C24EB21AE00576D92 /* STPCameraView.h in Headers */ = {isa = PBXBuildFile; fileRef = 311A475A24EB21AE00576D92 /* STPCameraView.h */; }; + 311A475D24EB21AE00576D92 /* STPCameraView.h in Headers */ = {isa = PBXBuildFile; fileRef = 311A475A24EB21AE00576D92 /* STPCameraView.h */; }; + 311A475E24EB21AE00576D92 /* STPCameraView.m in Sources */ = {isa = PBXBuildFile; fileRef = 311A475B24EB21AE00576D92 /* STPCameraView.m */; }; + 311A475F24EB21AE00576D92 /* STPCameraView.m in Sources */ = {isa = PBXBuildFile; fileRef = 311A475B24EB21AE00576D92 /* STPCameraView.m */; }; + 311A476224EB27D000576D92 /* STPCardScannerTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 311A476024EB27D000576D92 /* STPCardScannerTableViewCell.h */; }; + 311A476324EB27D000576D92 /* STPCardScannerTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 311A476024EB27D000576D92 /* STPCardScannerTableViewCell.h */; }; + 311A476424EB27D000576D92 /* STPCardScannerTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 311A476124EB27D000576D92 /* STPCardScannerTableViewCell.m */; }; + 311A476524EB27D000576D92 /* STPCardScannerTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 311A476124EB27D000576D92 /* STPCardScannerTableViewCell.m */; }; 3142E72722FCC19800DDA097 /* STPFPXBankBrand.h in Headers */ = {isa = PBXBuildFile; fileRef = 31F5B33322FCAC4000A71C64 /* STPFPXBankBrand.h */; settings = {ATTRIBUTES = (Public, ); }; }; 314B6A532384A713001FE708 /* STPKlarnaLineItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 314B6A512384A713001FE708 /* STPKlarnaLineItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; 314B6A542384A713001FE708 /* STPKlarnaLineItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 314B6A512384A713001FE708 /* STPKlarnaLineItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1190,10 +1202,6 @@ C1717DB11CC00ED60009CF4A /* STPAddress.h in Headers */ = {isa = PBXBuildFile; fileRef = C1080F471CBECF7B007B2D89 /* STPAddress.h */; settings = {ATTRIBUTES = (Public, ); }; }; C175B7941FE834A3009F5A0E /* STPCustomer+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C175B7931FE834A3009F5A0E /* STPCustomer+Private.h */; }; C175B7951FE834A3009F5A0E /* STPCustomer+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C175B7931FE834A3009F5A0E /* STPCustomer+Private.h */; }; - C1785F5C1EC60B5E00E9CFAC /* STPCardIOProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = C1785F5A1EC60B5E00E9CFAC /* STPCardIOProxy.h */; }; - C1785F5D1EC60B5E00E9CFAC /* STPCardIOProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = C1785F5A1EC60B5E00E9CFAC /* STPCardIOProxy.h */; }; - C1785F5E1EC60B5E00E9CFAC /* STPCardIOProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = C1785F5B1EC60B5E00E9CFAC /* STPCardIOProxy.m */; }; - C1785F5F1EC60B5E00E9CFAC /* STPCardIOProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = C1785F5B1EC60B5E00E9CFAC /* STPCardIOProxy.m */; }; C17A030D1CBEE7A2006C819F /* STPAddressFieldTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C17A030B1CBEE7A2006C819F /* STPAddressFieldTableViewCell.h */; }; C17A030E1CBEE7A2006C819F /* STPAddressFieldTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C17A030C1CBEE7A2006C819F /* STPAddressFieldTableViewCell.m */; }; C17D24EE1E37DBAC005CB188 /* STPSourceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C17D24ED1E37DBAC005CB188 /* STPSourceTest.m */; }; @@ -1775,6 +1783,12 @@ 073132942277A72D0019CE3F /* STPIssuingCardPin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPIssuingCardPin.m; sourceTree = ""; }; 0731329A2277AA200019CE3F /* STPPinManagementServiceFunctionalTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPPinManagementServiceFunctionalTest.m; sourceTree = ""; }; 11C74B9B164043050071C2CA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 311A475424EB1D2300576D92 /* STPCardScanner.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STPCardScanner.h; sourceTree = ""; }; + 311A475524EB1D2300576D92 /* STPCardScanner.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPCardScanner.m; sourceTree = ""; }; + 311A475A24EB21AE00576D92 /* STPCameraView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STPCameraView.h; sourceTree = ""; }; + 311A475B24EB21AE00576D92 /* STPCameraView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPCameraView.m; sourceTree = ""; }; + 311A476024EB27D000576D92 /* STPCardScannerTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = STPCardScannerTableViewCell.h; sourceTree = ""; }; + 311A476124EB27D000576D92 /* STPCardScannerTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPCardScannerTableViewCell.m; sourceTree = ""; }; 3142E72922FCC26500DDA097 /* stp_bank_fpx_hong_leong_bank@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "stp_bank_fpx_hong_leong_bank@2x.png"; sourceTree = ""; }; 3142E72A22FCC26600DDA097 /* stp_bank_fpx_public_bank@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "stp_bank_fpx_public_bank@2x.png"; sourceTree = ""; }; 3142E72C22FCC26600DDA097 /* stp_bank_fpx_maybank2e@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "stp_bank_fpx_maybank2e@2x.png"; sourceTree = ""; }; @@ -2315,8 +2329,6 @@ C15B02721EA176090026E606 /* StripeErrorTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StripeErrorTest.m; sourceTree = ""; }; C16F66AA1CA21BAC006A21B5 /* STPFormTextFieldTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPFormTextFieldTest.m; sourceTree = ""; }; C175B7931FE834A3009F5A0E /* STPCustomer+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "STPCustomer+Private.h"; sourceTree = ""; }; - C1785F5A1EC60B5E00E9CFAC /* STPCardIOProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STPCardIOProxy.h; sourceTree = ""; }; - C1785F5B1EC60B5E00E9CFAC /* STPCardIOProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPCardIOProxy.m; sourceTree = ""; }; C17A030B1CBEE7A2006C819F /* STPAddressFieldTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STPAddressFieldTableViewCell.h; sourceTree = ""; }; C17A030C1CBEE7A2006C819F /* STPAddressFieldTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPAddressFieldTableViewCell.m; sourceTree = ""; }; C17D24ED1E37DBAC005CB188 /* STPSourceTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPSourceTest.m; sourceTree = ""; }; @@ -3611,6 +3623,10 @@ F1728CEC1EAAA2C3002E0C29 /* UI Components */ = { isa = PBXGroup; children = ( + 311A475A24EB21AE00576D92 /* STPCameraView.h */, + 311A475B24EB21AE00576D92 /* STPCameraView.m */, + 311A475424EB1D2300576D92 /* STPCardScanner.h */, + 311A475524EB1D2300576D92 /* STPCardScanner.m */, 04BC29A21CD8697900318357 /* STPTheme.h */, 04BC29A31CD8697900318357 /* STPTheme.m */, 04B31DF71D11AC6400EF1631 /* STPUserInformation.h */, @@ -3669,8 +3685,6 @@ 366658C32411934900D00354 /* STPBSBNumberValidator.m */, F12829D81D7747E4008B10D6 /* STPBundleLocator.h */, F12829D91D7747E4008B10D6 /* STPBundleLocator.m */, - C1785F5A1EC60B5E00E9CFAC /* STPCardIOProxy.h */, - C1785F5B1EC60B5E00E9CFAC /* STPCardIOProxy.m */, 3691EB6F2119111A008C49E1 /* STPCardValidator+Private.h */, 3691EB702119111A008C49E1 /* STPCardValidator+Private.m */, 04FCFA171BD59A8C00297732 /* STPCategoryLoader.h */, @@ -3949,6 +3963,8 @@ C17A030C1CBEE7A2006C819F /* STPAddressFieldTableViewCell.m */, 31B9609522FE20DF00DC6FD9 /* STPBankSelectionTableViewCell.h */, 31B9609422FE20DE00DC6FD9 /* STPBankSelectionTableViewCell.m */, + 311A476024EB27D000576D92 /* STPCardScannerTableViewCell.h */, + 311A476124EB27D000576D92 /* STPCardScannerTableViewCell.m */, C1363BB51D7633D800EB82B4 /* STPPaymentOptionTableViewCell.h */, C1363BB61D7633D800EB82B4 /* STPPaymentOptionTableViewCell.m */, 04B31DFD1D131D9000EF1631 /* STPPaymentCardTextFieldCell.h */, @@ -3991,6 +4007,7 @@ 04F94DA11D229F12004FC826 /* STPAddressFieldTableViewCell.h in Headers */, B613DD3822C5452600C7603F /* STPSetupIntentEnums.h in Headers */, B6027BC32230ABAE0025DB29 /* STPPaymentMethodCardParams.h in Headers */, + 311A475724EB1D2300576D92 /* STPCardScanner.h in Headers */, B69CFB462236F8E3001E9885 /* STPPaymentMethodCardPresent.h in Headers */, B61D4B922457671F001AEBEF /* STPPaymentIntentShippingDetails.h in Headers */, 319A609C22E9186B00AACF66 /* STDSThreeDSProtocolVersion.h in Headers */, @@ -4022,7 +4039,6 @@ F1D3A2661EBA5BAE0095BFA9 /* STPPaymentCardTextField+Private.h in Headers */, F1DEB8911E2052150066B8E8 /* STPCoreScrollViewController.h in Headers */, 36B6CB9D235A3CF300331C38 /* STPMandateCustomerAcceptanceParams.h in Headers */, - C1785F5D1EC60B5E00E9CFAC /* STPCardIOProxy.h in Headers */, B6E2F309222F442E0001FED4 /* STPPaymentMethodCardChecks.h in Headers */, 319A608D22E9186B00AACF66 /* STDSFooterCustomization.h in Headers */, C1363BB81D7633D800EB82B4 /* STPPaymentOptionTableViewCell.h in Headers */, @@ -4050,12 +4066,14 @@ 36854421242977DA000B0B4B /* STPMultiFormTextField.h in Headers */, 04F94DCB1D22A229004FC826 /* UIView+Stripe_FirstResponder.h in Headers */, 319A609122E9186B00AACF66 /* STDSJSONEncoder.h in Headers */, + 311A476324EB27D000576D92 /* STPCardScannerTableViewCell.h in Headers */, 319A608222E9186B00AACF66 /* STDSAlreadyInitializedException.h in Headers */, 04F94DD11D22A239004FC826 /* STPPromise.h in Headers */, 0413CB2F233FECD5006429EA /* STPPushProvisioningDetails.h in Headers */, C1BD9B351E3940C400CEE925 /* STPSourceVerification.h in Headers */, 36E582FD22B456760044F82C /* STPAuthenticationContext.h in Headers */, F1A0197D1EA5733200354301 /* STPSourceParams+Private.h in Headers */, + 311A475D24EB21AE00576D92 /* STPCameraView.h in Headers */, 04633B161CD45222009D4FB5 /* STPAPIClient+ApplePay.h in Headers */, 0413CB2B233FECD5006429EA /* STPPushProvisioningContext.h in Headers */, 0438EF2E1B7416BB00D506CC /* STPFormTextField.h in Headers */, @@ -4294,6 +4312,7 @@ 36B6CB6A23551A0200331C38 /* STPPaymentMethodSEPADebitParams.h in Headers */, B690DDEC222F01BF000B902D /* STPPaymentMethodBillingDetails.h in Headers */, 04695AD91C77F9EF00E08063 /* STPDelegateProxy.h in Headers */, + 311A475C24EB21AE00576D92 /* STPCameraView.h in Headers */, 31F5B33522FCAC4000A71C64 /* STPFPXBankBrand.h in Headers */, B613DD3222C536C900C7603F /* STPSetupIntent.h in Headers */, 0433EB491BD06313003912B4 /* NSDictionary+Stripe.h in Headers */, @@ -4351,7 +4370,6 @@ 04CDB5121A5F30A700B854EE /* STPToken.h in Headers */, 8BD87B921EFB1C1E00269C2B /* STPSourceVerification+Private.h in Headers */, B69CABB4246DC2AC0081B1EF /* STPPaymentMethodAlipayParams.h in Headers */, - C1785F5C1EC60B5E00E9CFAC /* STPCardIOProxy.h in Headers */, 049952CF1BCF13510088C703 /* STPAPIRequest.h in Headers */, C15993381D8808680047950D /* STPShippingMethodTableViewCell.h in Headers */, 049A3FB21CC9FEFC00F57DE7 /* UIToolbar+Stripe_InputAccessory.h in Headers */, @@ -4418,6 +4436,7 @@ 315CB8D722E7D96100E612A3 /* STDSJSONEncoder.h in Headers */, 448895A82452452600F7D0C2 /* STPPaymentMethodPrzelewy24Params.h in Headers */, 31F5A50B22F0EFB10033663B /* STPPaymentMethodFPX.h in Headers */, + 311A476224EB27D000576D92 /* STPCardScannerTableViewCell.h in Headers */, 3626617F23C908BA00B13AE0 /* STPConfirmPaymentMethodOptions.h in Headers */, 315CB8D822E7D96100E612A3 /* STDSChallengeParameters.h in Headers */, 315CB8D922E7D96100E612A3 /* STDSJSONDecodable.h in Headers */, @@ -4471,6 +4490,7 @@ 3604006F22C18C78004CF80B /* STPThreeDSFooterCustomization.h in Headers */, 0438EF381B7416BB00D506CC /* STPPaymentCardTextFieldViewModel.h in Headers */, 04CDE5BC1BC1F21500548833 /* STPCardParams.h in Headers */, + 311A475624EB1D2300576D92 /* STPCardScanner.h in Headers */, B699BC9324577FED009107F9 /* STPPaymentIntentShippingDetailsAddressParams.h in Headers */, C19D098F1EAEAE4000A4AB3E /* STPTelemetryClient.h in Headers */, B3BDCAD620EEF5EC0034F7F5 /* STPPaymentIntentParams.h in Headers */, @@ -5332,7 +5352,6 @@ 3619624C244FA66D0025D60B /* STPPaymentMethodGiropayParams.m in Sources */, B604CF2422C56E9B00A23CC4 /* STPIntentActionRedirectToURL.m in Sources */, F19491E51E60DD72001E1FC2 /* STPSourceSEPADebitDetails.m in Sources */, - C1785F5F1EC60B5E00E9CFAC /* STPCardIOProxy.m in Sources */, 0438EF311B7416BB00D506CC /* STPFormTextField.m in Sources */, 04B31DF51D09F0A800EF1631 /* UIViewController+Stripe_NavigationItemProxy.m in Sources */, B6472192246CC1D20098DE24 /* STPConfirmAlipayOptions.m in Sources */, @@ -5360,6 +5379,7 @@ B640DB1522C58E82003C8810 /* STPSetupIntentConfirmParams.m in Sources */, 04A4C38C1C4F25F900B3B290 /* NSArray+Stripe.m in Sources */, 04F94DCE1D22A232004FC826 /* UIViewController+Stripe_KeyboardAvoiding.m in Sources */, + 311A475924EB1D2300576D92 /* STPCardScanner.m in Sources */, 36E582FB22B4566A0044F82C /* STPPaymentHandler.m in Sources */, B69CABB7246DC2AC0081B1EF /* STPPaymentMethodAlipayParams.m in Sources */, 04B31E021D131D9000EF1631 /* STPPaymentCardTextFieldCell.m in Sources */, @@ -5445,6 +5465,7 @@ C18410791EC2529400178149 /* STPEphemeralKeyManager.m in Sources */, 0731329E2277ABF60019CE3F /* STPPinManagementService.m in Sources */, 04F94DB41D229F71004FC826 /* STPPaymentActivityIndicatorView.m in Sources */, + 311A476524EB27D000576D92 /* STPCardScannerTableViewCell.m in Sources */, 448895AC2452456800F7D0C2 /* STPPaymentMethodPrzelewy24Params.m in Sources */, C1BD9B251E393FFE00CEE925 /* STPSourceReceiver.m in Sources */, B6B5FC44222F4C0200440249 /* STPPaymentMethodThreeDSecureUsage.m in Sources */, @@ -5483,6 +5504,7 @@ 04BC29A11CD8412000318357 /* STPPaymentContext.m in Sources */, 314B6A562384A713001FE708 /* STPKlarnaLineItem.m in Sources */, 04CDE5C71BC20AF800548833 /* STPBankAccountParams.m in Sources */, + 311A475F24EB21AE00576D92 /* STPCameraView.m in Sources */, 314B6A5C2384ABF9001FE708 /* STPSourceKlarnaDetails.m in Sources */, C1363BBA1D7633D800EB82B4 /* STPPaymentOptionTableViewCell.m in Sources */, ); @@ -5547,6 +5569,7 @@ F12829DC1D7747E4008B10D6 /* STPBundleLocator.m in Sources */, 04BC29BE1CDD535700318357 /* STPSwitchTableViewCell.m in Sources */, 314B6A5B2384ABF9001FE708 /* STPSourceKlarnaDetails.m in Sources */, + 311A475E24EB21AE00576D92 /* STPCameraView.m in Sources */, F1DEB88C1E2047CA0066B8E8 /* STPCoreTableViewController.m in Sources */, 44BDCFD3245A278F007EE6D5 /* STPPaymentMethodBancontact.m in Sources */, 04E39F6B1CED48D500AF3B96 /* UIBarButtonItem+Stripe.m in Sources */, @@ -5609,6 +5632,7 @@ 049880FE1CED5A2300EA4FFD /* STPPaymentConfiguration.m in Sources */, 046FE9A21CE55D1D00DA6A7B /* STPPaymentActivityIndicatorView.m in Sources */, 31B9609622FE20DF00DC6FD9 /* STPBankSelectionTableViewCell.m in Sources */, + 311A476424EB27D000576D92 /* STPCardScannerTableViewCell.m in Sources */, 04E7D4BF233FF29100FC8C02 /* STPFakeAddPaymentPassViewController.m in Sources */, B621F061223465EE002141B7 /* STPPaymentMethodCardWalletVisaCheckout.m in Sources */, 045D71221CEFA57000F6CD65 /* UIViewController+Stripe_Promises.m in Sources */, @@ -5630,7 +5654,6 @@ 314F9CC2235E66920059E2F6 /* STPFPXBankStatusResponse.m in Sources */, 073132972277A72D0019CE3F /* STPIssuingCardPin.m in Sources */, 04695ADC1C77F9EF00E08063 /* STPPhoneNumberValidator.m in Sources */, - C1785F5E1EC60B5E00E9CFAC /* STPCardIOProxy.m in Sources */, 04CDB5141A5F30A700B854EE /* STPToken.m in Sources */, 44BDCFD9245A2BFA007EE6D5 /* STPPaymentMethodBancontactParams.m in Sources */, B6A46F7822FCDB81001991B2 /* STPPaymentIntentLastPaymentError.m in Sources */, @@ -5672,6 +5695,7 @@ 04695AD41C77F9DB00E08063 /* NSString+Stripe.m in Sources */, F15232261EA9303800D65C67 /* STPURLCallbackHandler.m in Sources */, B68D52E422A739AA00D4E8BA /* STPSourceWeChatPayDetails.m in Sources */, + 311A475824EB1D2300576D92 /* STPCardScanner.m in Sources */, F1BEB2FF1F3508BB0043F48C /* NSError+Stripe.m in Sources */, B699BC9524577FED009107F9 /* STPPaymentIntentShippingDetailsAddressParams.m in Sources */, 049952D01BCF13510088C703 /* STPAPIRequest.m in Sources */, diff --git a/Stripe/PublicHeaders/STPPaymentConfiguration.h b/Stripe/PublicHeaders/STPPaymentConfiguration.h index 22718a0b5b7..7d18023ed9e 100644 --- a/Stripe/PublicHeaders/STPPaymentConfiguration.h +++ b/Stripe/PublicHeaders/STPPaymentConfiguration.h @@ -114,6 +114,21 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign, readwrite) BOOL canDeletePaymentOptions; +/** + Determines whether STPAddCardViewController allows the user to + scan cards using the camera on devices running iOS 13 or later. + + To use this feature, you must also set the `NSCameraUsageDescription` + value in your app's Info.plist. + + @note This feature is currently in beta. Please file bugs at + https://github.com/stripe/stripe-ios/issues + + The default value is NO. The default may be changed to YES in a future + version of the Stripe SDK. + */ +@property (nonatomic, assign, readwrite) BOOL cardScanningEnabled; + #pragma mark - Deprecated /** diff --git a/Stripe/Resources/Localizations/en.lproj/Localizable.strings b/Stripe/Resources/Localizations/en.lproj/Localizable.strings index 4982e97c727..f163b59bf25 100644 --- a/Stripe/Resources/Localizations/en.lproj/Localizable.strings +++ b/Stripe/Resources/Localizations/en.lproj/Localizable.strings @@ -199,6 +199,9 @@ /* Error when 3DS2 authentication timed out. */ "Timed out authenticating your payment method -- try again" = "Timed out authenticating your payment method -- try again"; +/* Error when the user hasn't allowed the current app to access the camera when scanning a payment card. 'Settings' is the localized name of the iOS Settings app. */ +"To scan your card, you'll need to allow access to your camera in Settings." = "To scan your card, you'll need to allow access to your camera in Settings."; + /* Default missing source type label */ "Unknown" = "Unknown"; diff --git a/Stripe/STPAddCardViewController+Private.h b/Stripe/STPAddCardViewController+Private.h index 85a5758e454..4c379600922 100644 --- a/Stripe/STPAddCardViewController+Private.h +++ b/Stripe/STPAddCardViewController+Private.h @@ -14,7 +14,6 @@ @interface STPAddCardViewController (Private) @property (nonatomic) STPAddress *shippingAddress; -@property (nonatomic) BOOL alwaysShowScanCardButton; @property (nonatomic) BOOL alwaysEnableDoneButton; - (void)commonInitWithConfiguration:(STPPaymentConfiguration *)configuration; diff --git a/Stripe/STPAddCardViewController.m b/Stripe/STPAddCardViewController.m index 427974be287..acfe1e62988 100644 --- a/Stripe/STPAddCardViewController.m +++ b/Stripe/STPAddCardViewController.m @@ -12,7 +12,9 @@ #import "STPAddressFieldTableViewCell.h" #import "STPAddressViewModel.h" #import "STPAnalyticsClient.h" -#import "STPCardIOProxy.h" +#import "STPCameraView.h" +#import "STPCardScanner.h" +#import "STPCardScannerTableViewCell.h" #import "STPCardValidator.h" #import "STPColorUtils.h" #import "STPCoreTableViewController+Private.h" @@ -41,12 +43,11 @@ @interface STPAddCardViewController ()< STPAddressViewModelDelegate, - STPCardIOProxyDelegate, + STPCardScannerDelegate, STPPaymentCardTextFieldDelegate, UITableViewDelegate, UITableViewDataSource> -@property (nonatomic) BOOL alwaysShowScanCardButton; @property (nonatomic) BOOL alwaysEnableDoneButton; @property (nonatomic) STPPaymentConfiguration *configuration; @property (nonatomic) STPAddress *shippingAddress; @@ -54,7 +55,9 @@ @interface STPAddCardViewController ()< @property (nonatomic, weak) UIImageView *cardImageView; @property (nonatomic) UIBarButtonItem *doneItem; @property (nonatomic) STPSectionHeaderView *cardHeaderView; -@property (nonatomic) STPCardIOProxy *cardIOProxy; +@property (nonatomic) STPCardScanner *cardScanner API_AVAILABLE(ios(13.0)); +@property (nonatomic) STPCardScannerTableViewCell *scannerCell; +@property (nonatomic) BOOL isScanning; @property (nonatomic) STPSectionHeaderView *addressHeaderView; @property (nonatomic) STPPaymentCardTextFieldCell *paymentCell; @property (nonatomic) BOOL loading; @@ -63,13 +66,17 @@ @interface STPAddCardViewController ()< @property (nonatomic) STPAddressViewModel *addressViewModel; @property (nonatomic) UIToolbar *inputAccessoryToolbar; @property (nonatomic) BOOL lookupSucceeded; + +@property (nonatomic) NSTimer *scannerCompleteAnimationTimer; + @end static NSString *const STPPaymentCardCellReuseIdentifier = @"STPPaymentCardCellReuseIdentifier"; typedef NS_ENUM(NSUInteger, STPPaymentCardSection) { STPPaymentCardNumberSection = 0, - STPPaymentCardBillingAddressSection = 1, + STPPaymentCardScannerSection = 1, + STPPaymentCardBillingAddressSection = 2, }; @implementation STPAddCardViewController @@ -100,6 +107,11 @@ - (void)commonInitWithConfiguration:(STPPaymentConfiguration *)configuration { self.title = STPLocalizedString(@"Add a Card", @"Title for Add a Card view"); } +- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath +{ + return 44.0; +} + - (void)createAndSetupViews { [super createAndSetupViews]; @@ -186,11 +198,20 @@ - (void)viewDidLayoutSubviews { } - (void)setUpCardScanningIfAvailable { - if ([STPCardIOProxy isCardIOAvailable] || self.alwaysShowScanCardButton) { - self.cardIOProxy = [[STPCardIOProxy alloc] initWithDelegate:self]; + if (@available(iOS 13.0, *)) { + if (![STPCardScanner cardScanningAvailable] || !self.configuration.cardScanningEnabled) { + return; + } + STPCardScannerTableViewCell *scannerCell = [[STPCardScannerTableViewCell alloc] init]; + _scannerCell = scannerCell; + + STPCardScanner *cardScanner = [[STPCardScanner alloc] initWithDelegate:self]; + cardScanner.cameraView = scannerCell.cameraView; + _cardScanner = cardScanner; + self.cardHeaderView.buttonHidden = NO; [self.cardHeaderView.button setTitle:STPLocalizedString(@"Scan Card", @"Text for button to scan a credit card") forState:UIControlStateNormal]; - [self.cardHeaderView.button addTarget:self action:@selector(presentCardIO) forControlEvents:UIControlEventTouchUpInside]; + [self.cardHeaderView.button addTarget:self action:@selector(scanCard) forControlEvents:UIControlEventTouchUpInside]; [self.cardHeaderView setNeedsLayout]; } } @@ -202,8 +223,33 @@ - (void)setAlwaysEnableDoneButton:(BOOL)alwaysEnableDoneButton { } } -- (void)presentCardIO { - [self.cardIOProxy presentCardIOFromViewController:self]; +- (void)setIsScanning:(BOOL)isScanning { + if (_isScanning == isScanning) { + return; + } + _isScanning = isScanning; + + self.cardHeaderView.button.enabled = !isScanning; + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:STPPaymentCardScannerSection]; + [self.tableView beginUpdates]; + if (isScanning) { + [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; + } else { + [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; + } + [self.tableView endUpdates]; + if (isScanning) { + [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES]; + } + [self updateInputAccessoryVisiblity]; +} + +- (void)scanCard { + if (@available(iOS 13.0, *)) { + [self.view endEditing:YES]; + self.isScanning = YES; + [self.cardScanner start]; + } } - (void)endEditing { @@ -225,7 +271,6 @@ - (void)updateAppearance { self.paymentCell.theme = self.theme; self.cardHeaderView.theme = self.theme; self.addressHeaderView.theme = self.theme; - for (STPAddressFieldTableViewCell *cell in self.addressViewModel.addressCells) { cell.theme = self.theme; } @@ -270,6 +315,7 @@ - (UIResponder *)firstEmptyField { if (self.paymentCell.isEmpty) { return self.paymentCell; } + for (STPAddressFieldTableViewCell *cell in self.addressViewModel.addressCells) { if (cell.contents.length == 0) { return cell; @@ -409,6 +455,12 @@ - (void)paymentCardTextFieldDidEndEditingCVC:(STPPaymentCardTextField *)textFiel } completion:nil]; } +- (void)paymentCardTextFieldDidBeginEditing:(nonnull STPPaymentCardTextField *)textField { + if (@available(iOS 13.0, *)) { + [[self cardScanner] stop]; + } +} + #pragma mark - STPAddressViewModelDelegate - (void)addressViewModel:(__unused STPAddressViewModel *)addressViewModel addedCellAtIndex:(NSUInteger)index { @@ -438,12 +490,14 @@ - (void)addressViewModelDidUpdate:(__unused STPAddressViewModel *)addressViewMod #pragma mark - UITableView - (NSInteger)numberOfSectionsInTableView:(__unused UITableView *)tableView { - return 2; + return 3; } - (NSInteger)tableView:(__unused UITableView *)tableView numberOfRowsInSection:(NSInteger)section { if (section == STPPaymentCardNumberSection) { return 1; + } else if (section == STPPaymentCardScannerSection) { + return _isScanning ? 1 : 0; } else if (section == STPPaymentCardBillingAddressSection) { return self.addressViewModel.addressCells.count; } @@ -457,6 +511,9 @@ - (UITableViewCell *)tableView:(__unused UITableView *)tableView case STPPaymentCardNumberSection: cell = self.paymentCell; break; + case STPPaymentCardScannerSection: + cell = self.scannerCell; + break; case STPPaymentCardBillingAddressSection: cell = [self.addressViewModel.addressCells stp_boundSafeObjectAtIndex:indexPath.row]; break; @@ -492,6 +549,8 @@ - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSIntege return [self.cardHeaderView sizeThatFits:fittingSize].height; } else if (section == STPPaymentCardBillingAddressSection && numberOfRows != 0) { return [self.addressHeaderView sizeThatFits:fittingSize].height; + } else if (section == STPPaymentCardScannerSection) { + return 0.01f; } else if (numberOfRows != 0) { return tableView.sectionHeaderHeight; } @@ -526,11 +585,57 @@ - (void)useShippingAddress:(__unused UIButton *)sender { [self.tableView endUpdates]; } -#pragma mark - STPCardIOProxyDelegate +#pragma mark - STPCardScanner -- (void)cardIOProxy:(__unused STPCardIOProxy *)proxy didFinishWithCardParams:(STPPaymentMethodCardParams *)cardParams { +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator +{ + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + if (@available(iOS 13.0, *)) { + UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation]; + if (UIDeviceOrientationIsPortrait(orientation) || UIDeviceOrientationIsLandscape(orientation)) { + _cardScanner.deviceOrientation = orientation; + } + if (_isScanning) { + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:STPPaymentCardScannerSection]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES]; + }); + } + } +} + +- (void)cardScanner:(__unused STPCardScanner *)scanner didFinishWithCardParams:(nullable STPPaymentMethodCardParams *)cardParams error:(nullable NSError *)error API_AVAILABLE(ios(13)){ + if (error) { + [self handleError:error]; + } + + static NSTimeInterval const kSTPCardScanAnimationTime = 0.04; if (cardParams) { - self.paymentCell.paymentField.cardParams = cardParams; + self.view.userInteractionEnabled = NO; + self.paymentCell.paymentField.inputView = [[UIView alloc] init]; + __block NSUInteger i = 0; + self.scannerCompleteAnimationTimer = [NSTimer scheduledTimerWithTimeInterval:kSTPCardScanAnimationTime repeats:YES block:^(NSTimer * _Nonnull timer) { + i++; + STPPaymentMethodCardParams *newParams = [[STPPaymentMethodCardParams alloc] init]; + if (i < [cardParams.number length]) { + newParams.number = [cardParams.number substringToIndex:i]; + } else { + newParams.number = cardParams.number; + } + self.paymentCell.paymentField.cardParams = newParams; + if (i > [cardParams.number length]) { + self.paymentCell.paymentField.cardParams = cardParams; + self.isScanning = NO; + 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.userInteractionEnabled = YES; + } + }]; + } else { + self.isScanning = NO; } } diff --git a/Stripe/STPAnalyticsClient.h b/Stripe/STPAnalyticsClient.h index 6978e2366d9..eb05dbfbe45 100644 --- a/Stripe/STPAnalyticsClient.h +++ b/Stripe/STPAnalyticsClient.h @@ -72,4 +72,8 @@ intentID:(NSString *)intentID errorDictionary:(NSDictionary *)errorDictionary; +- (void)logCardScanSucceededWithDuration:(NSTimeInterval)duration; + +- (void)logCardScanCancelledWithDuration:(NSTimeInterval)duration; + @end diff --git a/Stripe/STPAnalyticsClient.m b/Stripe/STPAnalyticsClient.m index 50d80569e2e..d3ae04aa36e 100644 --- a/Stripe/STPAnalyticsClient.m +++ b/Stripe/STPAnalyticsClient.m @@ -16,7 +16,7 @@ #import "STPAddCardViewController+Private.h" #import "STPAddCardViewController.h" #import "STPCard.h" -#import "STPCardIOProxy.h" +#import "STPCardScanner.h" #import "STPFormEncodable.h" #import "STPPaymentCardTextField.h" #import "STPPaymentCardTextField+Private.h" @@ -321,6 +321,28 @@ - (void)log3DS2ChallengeFlowCompletedWithConfiguration:(STPPaymentConfiguration [self logPayload:payload]; } +- (void)logCardScanSucceededWithDuration:(NSTimeInterval)duration { + NSMutableDictionary *payload = [self.class commonPayload]; + [payload addEntriesFromDictionary:@{ + @"event": @"stripeios.cardscan_success", + @"duration": @(round(duration)), + @"additional_info": [self additionalInfo], + }]; + [payload addEntriesFromDictionary:[self productUsageDictionary]]; + [self logPayload:payload]; +} + +- (void)logCardScanCancelledWithDuration:(NSTimeInterval)duration { + NSMutableDictionary *payload = [self.class commonPayload]; + [payload addEntriesFromDictionary:@{ + @"event": @"stripeios.cardscan_cancel", + @"duration": @(round(duration)), + @"additional_info": [self additionalInfo], + }]; + [payload addEntriesFromDictionary:[self productUsageDictionary]]; + [self logPayload:payload]; +} + #pragma mark - Helpers + (NSMutableDictionary *)commonPayload { @@ -340,7 +362,12 @@ + (NSMutableDictionary *)commonPayload { payload[@"app_name"] = [NSBundle stp_applicationName]; payload[@"app_version"] = [NSBundle stp_applicationVersion]; payload[@"apple_pay_enabled"] = @([Stripe deviceSupportsApplePay]); - payload[@"ocr_type"] = [STPCardIOProxy isCardIOAvailable] ? @"card_io" : @"none"; + payload[@"ocr_type"] = @"none"; + if (@available(iOS 13.0, *)) { + if([[STPAnalyticsClient sharedClient].productUsage containsObject:NSStringFromClass([STPCardScanner class])]) { + payload[@"ocr_type"] = @"stripe"; + } + } return payload; } diff --git a/Stripe/STPCameraView.h b/Stripe/STPCameraView.h new file mode 100644 index 00000000000..27f61cd5d7d --- /dev/null +++ b/Stripe/STPCameraView.h @@ -0,0 +1,22 @@ +// +// STPCameraView.h +// Stripe +// +// Created by David Estes on 8/17/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +#import +NS_ASSUME_NONNULL_BEGIN + +@class AVCaptureSession, AVCaptureVideoPreviewLayer; + +@interface STPCameraView : UIView + +@property (nonatomic, strong, nullable) AVCaptureSession *captureSession; +@property (nonatomic, readonly) AVCaptureVideoPreviewLayer *videoPreviewLayer; +- (void)playSnapshotAnimation; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe/STPCameraView.m b/Stripe/STPCameraView.m new file mode 100644 index 00000000000..522c71ad4dd --- /dev/null +++ b/Stripe/STPCameraView.m @@ -0,0 +1,62 @@ +// +// STPCameraView.m +// Stripe +// +// Created by David Estes on 8/17/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +#import "STPCameraView.h" +#import + +@implementation STPCameraView { + CALayer *_flashLayer; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + _flashLayer = [[CALayer alloc] init]; + [self.layer addSublayer:_flashLayer]; + _flashLayer.masksToBounds = YES; + _flashLayer.backgroundColor = [[UIColor blackColor] CGColor]; + _flashLayer.opacity = 0.0; + self.layer.masksToBounds = YES; + self.videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; + return self; +} + +- (AVCaptureSession *)captureSession { + return [self.videoPreviewLayer session]; +} + +- (void)setCaptureSession:(AVCaptureSession *)captureSession { + return [self.videoPreviewLayer setSession:captureSession]; +} + +- (void)playSnapshotAnimation { + [CATransaction begin]; + [CATransaction setValue:(id)kCFBooleanTrue + forKey:kCATransactionDisableActions]; + _flashLayer.frame = CGRectMake(0, 0, self.layer.bounds.size.width, self.layer.bounds.size.height); + _flashLayer.opacity = 1.0; + [CATransaction commit]; + dispatch_async(dispatch_get_main_queue(), ^{ + CABasicAnimation* fadeAnim = [CABasicAnimation animationWithKeyPath:@"opacity"]; + fadeAnim.fromValue = [NSNumber numberWithFloat:1.0]; + fadeAnim.toValue = [NSNumber numberWithFloat:0.0]; + fadeAnim.duration = 1.0; + [self->_flashLayer addAnimation:fadeAnim forKey:@"opacity"]; + self->_flashLayer.opacity = 0.0; + }); +} + ++ (Class)layerClass { + return [AVCaptureVideoPreviewLayer class]; +} + +- (AVCaptureVideoPreviewLayer *)videoPreviewLayer { + return (AVCaptureVideoPreviewLayer *)self.layer; +} + +@end diff --git a/Stripe/STPCardIOProxy.h b/Stripe/STPCardIOProxy.h deleted file mode 100644 index 86f566a3022..00000000000 --- a/Stripe/STPCardIOProxy.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// STPCardIOAdapter.h -// Stripe -// -// Created by Ben Guo on 5/12/17. -// Copyright © 2017 Stripe, Inc. All rights reserved. -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class STPCardIOProxy, STPPaymentMethodCardParams; - -@protocol STPCardIOProxyDelegate -- (void)cardIOProxy:(STPCardIOProxy *)proxy didFinishWithCardParams:(STPPaymentMethodCardParams *)cardParams; -@end - -@interface STPCardIOProxy : NSObject - -+ (BOOL)isCardIOAvailable; -- (instancetype)init __attribute__((unavailable("Use initWithDelegate"))); -- (instancetype)initWithDelegate:(id)delegate; -- (void)presentCardIOFromViewController:(UIViewController *)viewController; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Stripe/STPCardIOProxy.m b/Stripe/STPCardIOProxy.m deleted file mode 100644 index 970e6c8a198..00000000000 --- a/Stripe/STPCardIOProxy.m +++ /dev/null @@ -1,131 +0,0 @@ -// -// STPCardIOProxy.m -// Stripe -// -// Created by Ben Guo on 5/12/17. -// Copyright © 2017 Stripe, Inc. All rights reserved. -// - -#import "STPCardIOProxy.h" - -#import "FauxPasAnnotations.h" -#import "STPPaymentMethodCardParams.h" -#import "STPAnalyticsClient.h" - -@protocol STPClassProxy -+ (Class)proxiedClass; -+ (BOOL)proxiedClassExists; -@end - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wincomplete-implementation" -@interface STPCardIOUtilitiesProxy : NSObject -+ (BOOL)canReadCardWithCamera; -@end - -@implementation STPCardIOUtilitiesProxy -+ (Class)proxiedClass { - return NSClassFromString(@"CardIOUtilities"); -} -+ (BOOL)proxiedClassExists { - Class proxiedClass = [self proxiedClass]; - return proxiedClass && [proxiedClass respondsToSelector:@selector(canReadCardWithCamera)]; -} -@end - -@interface STPCardIOCreditCardInfoProxy : NSObject -@property (nonatomic, strong) NSString *cardNumber; -@property (nonatomic, assign, readwrite) NSUInteger expiryMonth; -@property (nonatomic, assign, readwrite) NSUInteger expiryYear; -@property (nonatomic, copy, readwrite) NSString *cvv; -@end - -@implementation STPCardIOCreditCardInfoProxy -+ (Class)proxiedClass { - return NSClassFromString(@"CardIOCreditCardInfo"); -} -+ (BOOL)proxiedClassExists { - Class proxiedClass = [self proxiedClass]; - return proxiedClass - && [proxiedClass instancesRespondToSelector:@selector(cardNumber)] - && [proxiedClass instancesRespondToSelector:@selector(expiryMonth)] - && [proxiedClass instancesRespondToSelector:@selector(expiryYear)] - && [proxiedClass instancesRespondToSelector:@selector(cvv)]; -} -@end - -@interface STPCardIOPaymentViewControllerProxy : UIViewController -+ (id)initWithPaymentDelegate:id; -@property (nonatomic, assign, readwrite) BOOL hideCardIOLogo; -@property (nonatomic, assign, readwrite) BOOL disableManualEntryButtons; -@property (nonatomic, assign, readwrite) CGFloat scannedImageDuration; -@end - -@implementation STPCardIOPaymentViewControllerProxy -+ (Class)proxiedClass { - return NSClassFromString(@"CardIOPaymentViewController"); -} -+ (BOOL)proxiedClassExists { - Class proxiedClass = [self proxiedClass]; - return proxiedClass - && [proxiedClass instancesRespondToSelector:@selector(initWithPaymentDelegate:)] - && [proxiedClass instancesRespondToSelector:@selector(setHideCardIOLogo:)] - && [proxiedClass instancesRespondToSelector:@selector(setDisableManualEntryButtons:)] - && [proxiedClass instancesRespondToSelector:@selector(setScannedImageDuration:)]; -} -@end -#pragma clang diagnostic pop - -@interface STPCardIOProxy () -@property (nonatomic, weak) iddelegate; -@end - -@implementation STPCardIOProxy - -+ (BOOL)isCardIOAvailable { -#if TARGET_OS_SIMULATOR - return NO; -#else - if ([STPCardIOPaymentViewControllerProxy proxiedClassExists] - && [STPCardIOCreditCardInfoProxy proxiedClassExists] - && [STPCardIOUtilitiesProxy proxiedClassExists]) { - return [[STPCardIOUtilitiesProxy proxiedClass] canReadCardWithCamera]; - } - return NO; -#endif -} - -- (instancetype)initWithDelegate:(id)delegate { - self = [super init]; - if (self) { - _delegate = delegate; - } - return self; -} - -- (void)presentCardIOFromViewController:(UIViewController *)viewController { - STPCardIOPaymentViewControllerProxy *cardIOViewController = [[[STPCardIOPaymentViewControllerProxy proxiedClass] alloc] initWithPaymentDelegate:self]; - cardIOViewController.hideCardIOLogo = YES; - cardIOViewController.disableManualEntryButtons = YES; - cardIOViewController.scannedImageDuration = 0; - [viewController presentViewController:cardIOViewController animated:YES completion:nil]; -} - -- (void)userDidCancelPaymentViewController:(UIViewController *)scanViewController { FAUXPAS_IGNORED_ON_LINE(UnusedMethod) - [scanViewController dismissViewControllerAnimated:YES completion:nil]; - [[STPAnalyticsClient sharedClient] addAdditionalInfo:@"cardio_canceled"]; -} - -- (void)userDidProvideCreditCardInfo:(STPCardIOCreditCardInfoProxy *)info inPaymentViewController:(UIViewController *)scanViewController { FAUXPAS_IGNORED_ON_LINE(UnusedMethod) - [scanViewController dismissViewControllerAnimated:YES completion:^{ - STPPaymentMethodCardParams *cardParams = [STPPaymentMethodCardParams new]; - cardParams.number = info.cardNumber; - cardParams.expMonth = @(info.expiryMonth); - cardParams.expYear = @(info.expiryYear); - cardParams.cvc = info.cvv; - [self.delegate cardIOProxy:self didFinishWithCardParams:cardParams]; - [[STPAnalyticsClient sharedClient] addAdditionalInfo:@"cardio_used"]; - }]; -} - -@end diff --git a/Stripe/STPCardScanner.h b/Stripe/STPCardScanner.h new file mode 100644 index 00000000000..12ff3df70ac --- /dev/null +++ b/Stripe/STPCardScanner.h @@ -0,0 +1,44 @@ +// +// STPCardScanner.h +// Stripe +// +// Created by David Estes on 8/17/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +#import +#import "STPCameraView.h" + +NS_ASSUME_NONNULL_BEGIN + +@class STPCardScanner, STPPaymentMethodCardParams; + +extern NSString *const STPCardScannerErrorDomain; + +typedef NS_ENUM(NSInteger, STPCardScannerError) { + /** + Camera not available. + */ + STPCardScannerErrorCameraNotAvailable, +}; + +API_AVAILABLE(ios(13.0)) +@protocol STPCardScannerDelegate +- (void)cardScanner:(STPCardScanner *)scanner didFinishWithCardParams:(nullable STPPaymentMethodCardParams *)cardParams error:(nullable NSError *)error; +@end + +API_AVAILABLE(ios(13.0)) +@interface STPCardScanner : NSObject + ++ (BOOL)cardScanningAvailable; + +@property (nonatomic, weak) STPCameraView *cameraView; +@property (atomic) UIDeviceOrientation deviceOrientation; + +- (instancetype)init __attribute__((unavailable("Use initWithDelegate"))); +- (instancetype)initWithDelegate:(id)delegate; +- (void)start; +- (void)stop; + +@end +NS_ASSUME_NONNULL_END diff --git a/Stripe/STPCardScanner.m b/Stripe/STPCardScanner.m new file mode 100644 index 00000000000..fc865ebbc0d --- /dev/null +++ b/Stripe/STPCardScanner.m @@ -0,0 +1,418 @@ +// +// STPCardScanner.m +// Stripe +// +// Created by David Estes on 8/17/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +#import "STPCardScanner.h" +#import "STPAnalyticsClient.h" + +#import +#import + +#import "STPCardValidator+Private.h" +#import "STPPaymentMethodCardParams.h" +#import "STPStringUtils.h" +#import "STPLocalizationUtils.h" +#import "StripeError.h" + +// The number of successful scans required for both card number and expiration date before returning a result. +static const NSUInteger kSTPCardScanningMinimumValidScans = 2; +// If no expiration date is found, we'll return a result after this many successful scans. +static const NSUInteger kSTPCardScanningMaxValidScans = 3; +// Once one successful scan is found, we'll stop scanning after this many seconds. +static const NSTimeInterval kSTPCardScanningTimeout = 1.0; + +NSString * const STPCardScannerErrorDomain = @"STPCardScannerErrorDomain"; + +@interface STPCardScanner () +@property (nonatomic, weak) iddelegate; +@property (nonatomic, strong) AVCaptureDevice *captureDevice; + +@property (nonatomic, strong) AVCaptureSession *captureSession; +@property (nonatomic, strong, readwrite) dispatch_queue_t captureSessionQueue; + +@property (nonatomic, strong) AVCaptureVideoDataOutput *videoDataOutput; +@property (nonatomic, strong) dispatch_queue_t videoDataOutputQueue; + +@property (nonatomic, strong) VNRecognizeTextRequest *textRequest; + +@property (atomic) BOOL isScanning; +@property (atomic) BOOL didTimeout; +@property (atomic) BOOL timeoutStarted; + +@property (atomic) UIDeviceOrientation _stp_deviceOrientation; +@property (atomic) AVCaptureVideoOrientation videoOrientation; +@property (atomic) CGImagePropertyOrientation textOrientation; + +@property (nonatomic) CGRect regionOfInterest; + +@property (nonatomic) NSCountedSet *detectedNumbers; +@property (nonatomic) NSCountedSet *detectedExpirations; + +@property (nonatomic) NSDate *startTime; + +@end + +@implementation STPCardScanner + +#pragma mark Public + ++ (BOOL)cardScanningAvailable { + // Always allow in tests: + if (NSClassFromString(@"XCTest") != nil) { + return YES; + } + + // iOS will kill the app if it tries to request the camera without an NSCameraUsageDescription + static BOOL cameraHasUsageDescription = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + if ([[[NSBundle mainBundle] infoDictionary] objectForKey:@"NSCameraUsageDescription"] != nil) { + cameraHasUsageDescription = YES; + } + }); + return cameraHasUsageDescription; +} + ++ (NSError *)stp_cardScanningError { + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey: STPLocalizedString(@"To scan your card, you'll need to allow access to your camera in Settings.", @"Error when the user hasn't allowed the current app to access the camera when scanning a payment card. 'Settings' is the localized name of the iOS Settings app."), + STPErrorMessageKey: @"The camera couldn't be used." + }; + return [[NSError alloc] initWithDomain:STPCardScannerErrorDomain code:STPCardScannerErrorCameraNotAvailable userInfo:userInfo]; +} + +- (instancetype)initWithDelegate:(id)delegate { + self = [super init]; + if (self) { + self.delegate = delegate; + self.captureSessionQueue = dispatch_queue_create("com.stripe.CardScanning.CaptureSessionQueue", nil); + self.deviceOrientation = [[UIDevice currentDevice] orientation]; + } + return self; +} + +- (void)dealloc { + if (self.isScanning) { + [self.captureDevice unlockForConfiguration]; + [self.captureSession stopRunning]; + } +} + +- (void)start { + if (self.isScanning) { + return; + } + [[STPAnalyticsClient sharedClient] addClassToProductUsageIfNecessary:[self class]]; + self.startTime = [NSDate date]; + + self.isScanning = YES; + self.didTimeout = NO; + self.timeoutStarted = NO; + + dispatch_async(_captureSessionQueue, ^{ + self.detectedNumbers = [[NSCountedSet alloc] initWithCapacity:5]; + self.detectedExpirations = [[NSCountedSet alloc] initWithCapacity:5]; + [self setupCamera]; + dispatch_async(dispatch_get_main_queue(), ^{ + self.cameraView.captureSession = self.captureSession; + self.cameraView.videoPreviewLayer.connection.videoOrientation = self.videoOrientation; + }); + }); +} + +- (void)stop { + [self stopWithError:nil]; +} + +- (void)stopWithError:(nullable NSError *)error { + if (self.isScanning) { + [self finishWithParams:nil error:error]; + } +} + +#pragma mark Setup + +- (void)setupCamera { + __weak typeof(self) weakSelf = self; + self.textRequest = [[VNRecognizeTextRequest alloc] initWithCompletionHandler:^(VNRequest * _Nonnull request, NSError * _Nullable error) { + __strong typeof(self) strongSelf = weakSelf; + if (!strongSelf.isScanning) { + return; + } + if (error) { + [strongSelf stopWithError:[STPCardScanner stp_cardScanningError]]; + return; + } + [strongSelf processVNRequest:request]; + }]; + + AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInWideAngleCamera mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack]; + self.captureDevice = captureDevice; + + self.captureSession = [[AVCaptureSession alloc] init]; + self.captureSession.sessionPreset = AVCaptureSessionPreset1920x1080; + + NSError *deviceInputError; + AVCaptureDeviceInput *deviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:captureDevice error:&deviceInputError]; + if (deviceInputError) { + [self stopWithError:[STPCardScanner stp_cardScanningError]]; + return; + } + + if ([self.captureSession canAddInput:deviceInput]) { + [self.captureSession addInput:deviceInput]; + } else { + [self stopWithError:[STPCardScanner stp_cardScanningError]]; + return; + } + + self.videoDataOutputQueue = dispatch_queue_create("com.stripe.CardScanning.VideoDataOutputQueue", nil); + self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init]; + self.videoDataOutput.alwaysDiscardsLateVideoFrames = YES; + [self.videoDataOutput setSampleBufferDelegate:self queue:self.videoDataOutputQueue]; + + // This is the recommended pixel buffer format for Vision: + [self.videoDataOutput setVideoSettings:@{(id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)}]; + + if ([self.captureSession canAddOutput:self.videoDataOutput]) { + [self.captureSession addOutput:self.videoDataOutput]; + } else { + [self stopWithError:[STPCardScanner stp_cardScanningError]]; + return; + } + + // This improves recognition quality, but means the VideoDataOutput buffers won't match what we're seeing on screen. + [[self.videoDataOutput connectionWithMediaType:AVMediaTypeVideo] setPreferredVideoStabilizationMode:AVCaptureVideoStabilizationModeAuto]; + + [self.captureSession startRunning]; + + NSError *lockError; + [self.captureDevice lockForConfiguration:&lockError]; + if (lockError == nil) { + self.captureDevice.autoFocusRangeRestriction = AVCaptureAutoFocusRangeRestrictionNear; + } +} + +#pragma mark Processing + +- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { + if (!self.isScanning) { + return; + } + CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + if (pixelBuffer == nil) { + return; + } + self.textRequest.recognitionLevel = VNRequestTextRecognitionLevelAccurate; + self.textRequest.usesLanguageCorrection = NO; + self.textRequest.regionOfInterest = self.regionOfInterest; + VNImageRequestHandler *handler = [[VNImageRequestHandler alloc] initWithCVPixelBuffer:pixelBuffer orientation:self.textOrientation options:@{}]; + __unused NSError *requestError; + [handler performRequests:@[self.textRequest] error:&requestError]; +} + +- (void)processVNRequest:(VNRequest * _Nonnull)request { + NSMutableArray *allNumbers = [[NSMutableArray alloc] init]; + for (VNRecognizedTextObservation *observation in request.results) { + NSArray *candidates = [observation topCandidates:5]; + NSString *topCandidate = [[candidates firstObject] string]; + if ([[STPCardValidator sanitizedNumericStringForString:topCandidate] length] >= 4) { + [allNumbers addObject:topCandidate]; + } + for (VNRecognizedText *recognizedText in candidates) { + NSString *possibleNumber = [STPCardValidator sanitizedNumericStringForString:recognizedText.string]; + if ([possibleNumber length] < 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 validationStateForNumber:possibleNumber validatingCardBrand:YES] == STPCardValidationStateValid) { + [self addDetectedNumber:possibleNumber]; + } else if ([possibleNumber length] >= 4 && [possibleNumber length] <= 6 && [STPStringUtils stringMayContainExpirationDate:recognizedText.string]) { + // Try to parse anything that looks like an expiration date. + NSString *expirationString = [STPStringUtils expirationDateStringFromString:recognizedText.string]; + NSString *sanitizedExpiration = [STPCardValidator sanitizedNumericStringForString:expirationString]; + NSString *month = [sanitizedExpiration substringToIndex:2]; + NSString *year = [sanitizedExpiration substringFromIndex:2]; + + // Ignore expiration dates 10+ years in the future, as they're likely to be incorrect recognitions + NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; + NSDateComponents *currentDateComponents = [calendar components:NSCalendarUnitYear fromDate:[NSDate date]]; + NSInteger maxYear = ([currentDateComponents year] % 100) + 10; + + if ([STPCardValidator validationStateForExpirationYear:year inMonth:month] == STPCardValidationStateValid && [year integerValue] < maxYear) { + [self 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: + NSArray *allGroups = [[allNumbers componentsJoinedByString:@" "] componentsSeparatedByString:@" "]; + for (NSInteger i = 0; i < (NSInteger)[allGroups count] - 3; i++) { + NSString *string1 = allGroups[i]; + NSString *string2 = allGroups[i + 1]; + NSString *string3 = allGroups[i + 2]; + NSString *string4 = @""; + if (i + 3 < (NSInteger)[allGroups count]) { + string4 = allGroups[i + 3]; + } + // Then we'll go through each group and build a potential match: + NSString *potentialCardString = [NSString stringWithFormat:@"%@%@%@%@", string1, string2, string3, string4]; + NSString *potentialAmexString = [NSString stringWithFormat:@"%@%@%@", 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 validationStateForNumber:potentialCardString validatingCardBrand:YES] == STPCardValidationStateValid) { + [self addDetectedNumber:potentialCardString]; + } else if ([STPCardValidator validationStateForNumber:potentialAmexString validatingCardBrand:YES] == STPCardValidationStateValid) { + [self addDetectedNumber:potentialAmexString]; + } + } +} + + +- (void)addDetectedNumber:(NSString *)number { + [self.detectedNumbers addObject:number]; + + // Set a timeout: If we don't get enough scans in the next 1 second, we'll use the best option we have. + if (!self.timeoutStarted) { + self.timeoutStarted = YES; + __weak typeof(self) weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + __strong typeof(self) strongSelf = weakSelf; + [strongSelf.cameraView playSnapshotAnimation]; + }); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kSTPCardScanningTimeout * NSEC_PER_SEC)), self.videoDataOutputQueue, ^{ + __strong typeof(self) strongSelf = weakSelf; + if (strongSelf.isScanning) { + strongSelf.didTimeout = YES; + [strongSelf finishIfReady]; + } + }); + } + + if ([_detectedNumbers countForObject:number] >= kSTPCardScanningMinimumValidScans) { + [self finishIfReady]; + } +} + +- (void)addDetectedExpiration:(NSString *)expiration { + [self.detectedExpirations addObject:expiration]; + if ([self.detectedExpirations countForObject:expiration] >= kSTPCardScanningMinimumValidScans) { + [self finishIfReady]; + } +} + +#pragma mark Completion + +- (void)finishIfReady { + if (!self.isScanning) { + return; + } + NSCountedSet *detectedNumbers = self.detectedNumbers; + NSCountedSet *detectedExpirations = self.detectedExpirations; + + NSString *topNumber = [[detectedNumbers.allObjects sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) { + NSUInteger c1 = [detectedNumbers countForObject:obj1]; + NSUInteger c2 = [detectedNumbers countForObject:obj2]; + if (c1 < c2) { + return NSOrderedAscending; + } else if (c1 > c2) { + return NSOrderedDescending; + } else { + return NSOrderedSame; + } + }] lastObject]; + NSString *topExpiration = [[detectedExpirations.allObjects sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) { + NSUInteger c1 = [detectedExpirations countForObject:obj1]; + NSUInteger c2 = [detectedExpirations countForObject:obj2]; + if (c1 < c2) { + return NSOrderedAscending; + } else if (c1 > c2) { + return NSOrderedDescending; + } else { + return NSOrderedSame; + } + }] lastObject]; + + if (self.didTimeout || + (([detectedNumbers countForObject:topNumber] >= kSTPCardScanningMinimumValidScans) && ([detectedExpirations countForObject:topExpiration] >= kSTPCardScanningMinimumValidScans)) + || ([detectedNumbers countForObject:topNumber] >= kSTPCardScanningMaxValidScans) + ) { + STPPaymentMethodCardParams *params = [[STPPaymentMethodCardParams alloc] init]; + params.number = topNumber; + if (topExpiration) { + params.expMonth = @([[topExpiration substringToIndex:2] integerValue]); + params.expYear = @([[topExpiration substringFromIndex:2] integerValue]); + } + [self finishWithParams:params error:nil]; + } +} + +- (void)finishWithParams:(STPPaymentMethodCardParams *)params error:(NSError *)error { + NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:self.startTime]; + self.isScanning = NO; + [self.captureDevice unlockForConfiguration]; + [self.captureSession stopRunning]; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (params == nil) { + [[STPAnalyticsClient sharedClient] logCardScanCancelledWithDuration:duration]; + } else { + [[STPAnalyticsClient sharedClient] logCardScanSucceededWithDuration:duration]; + } + + self.cameraView.captureSession = nil; + [self.delegate cardScanner:self didFinishWithCardParams:params error:error]; + }); +} + +#pragma mark Orientation + +- (void)setDeviceOrientation:(UIDeviceOrientation)newDeviceOrientation { + self._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. + CGRect kSTPCardScanningScreenCenter = CGRectMake(0, (CGFloat)0.3, 1, (CGFloat)0.4); + + // iOS camera image data is returned in LandcapeLeft orientation by default. We'll flip it as needed: + switch (newDeviceOrientation) { + case UIDeviceOrientationPortrait: + case UIDeviceOrientationFaceUp: + case UIDeviceOrientationFaceDown: + self.videoOrientation = AVCaptureVideoOrientationPortrait; + self.textOrientation = kCGImagePropertyOrientationRight; + self.regionOfInterest = kSTPCardScanningScreenCenter; + break; + case UIDeviceOrientationPortraitUpsideDown: + self.videoOrientation = AVCaptureVideoOrientationPortraitUpsideDown; + self.textOrientation = kCGImagePropertyOrientationLeft; + self.regionOfInterest = kSTPCardScanningScreenCenter; + break; + case UIDeviceOrientationLandscapeLeft: + self.videoOrientation = AVCaptureVideoOrientationLandscapeRight; + self.textOrientation = kCGImagePropertyOrientationUp; + self.regionOfInterest = CGRectMake(0, 0, 1, 1); + break; + case UIDeviceOrientationLandscapeRight: + self.videoOrientation = AVCaptureVideoOrientationLandscapeLeft; + self.textOrientation = kCGImagePropertyOrientationDown; + self.regionOfInterest = CGRectMake(0, 0, 1, 1); + break; + default: + break; + } + self.cameraView.videoPreviewLayer.connection.videoOrientation = _videoOrientation; +} + +- (UIDeviceOrientation)deviceOrientation { + return self._stp_deviceOrientation; +} + +@end diff --git a/Stripe/STPCardScannerTableViewCell.h b/Stripe/STPCardScannerTableViewCell.h new file mode 100644 index 00000000000..3705b5f10a9 --- /dev/null +++ b/Stripe/STPCardScannerTableViewCell.h @@ -0,0 +1,23 @@ +// +// STPCardScannerTableViewCell.h +// Stripe +// +// Created by David Estes on 8/17/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +#import + +@class STPTheme; +@class STPCameraView; + +NS_ASSUME_NONNULL_BEGIN + +@interface STPCardScannerTableViewCell : UITableViewCell + +@property (nonatomic, weak, readonly) STPCameraView *cameraView; +@property (nonatomic, copy) STPTheme *theme; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Stripe/STPCardScannerTableViewCell.m b/Stripe/STPCardScannerTableViewCell.m new file mode 100644 index 00000000000..a14eda84661 --- /dev/null +++ b/Stripe/STPCardScannerTableViewCell.m @@ -0,0 +1,59 @@ +// +// STPCardScannerTableViewCell.m +// Stripe +// +// Created by David Estes on 8/17/20. +// Copyright © 2020 Stripe, Inc. All rights reserved. +// + +#import "STPCardScannerTableViewCell.h" +#import "STPCameraView.h" + +#import "STPTheme.h" +#import "UIView+Stripe_SafeAreaBounds.h" + +@interface STPCardScannerTableViewCell() + +@property (nonatomic, weak) STPCameraView *cameraView; + +@end + +@implementation STPCardScannerTableViewCell + +static const CGFloat cardSizeRatio = 2.125f/3.370f; // ID-1 card size (in inches) + +- (instancetype)init { + self = [super init]; + if (self) { + STPCameraView *cameraView = [[STPCameraView alloc] initWithFrame:self.bounds]; + [self.contentView addSubview:cameraView]; + _cameraView = cameraView; + _theme = [STPTheme defaultTheme]; + [self.cameraView setTranslatesAutoresizingMaskIntoConstraints:NO]; + [self.contentView addConstraints:@[ + [cameraView.heightAnchor constraintEqualToAnchor:cameraView.widthAnchor multiplier:cardSizeRatio], + [cameraView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:0], + [cameraView.leftAnchor constraintEqualToAnchor:self.contentView.leftAnchor constant:0], + [cameraView.rightAnchor constraintEqualToAnchor:self.contentView.rightAnchor constant:0], + [cameraView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:0]]]; + [self updateAppearance]; + } + return self; +} + +- (void)layoutSubviews { + + [super layoutSubviews]; +} + +- (void)setTheme:(STPTheme *)theme { + _theme = theme; + [self updateAppearance]; +} + +- (void)updateAppearance { + // The first few frames of the camera view will be black, so our background should be black too. + self.cameraView.backgroundColor = [UIColor blackColor]; +} + +@end diff --git a/Stripe/STPPaymentConfiguration.m b/Stripe/STPPaymentConfiguration.m index c6da1775985..e8bf3f63910 100644 --- a/Stripe/STPPaymentConfiguration.m +++ b/Stripe/STPPaymentConfiguration.m @@ -48,6 +48,7 @@ - (instancetype)init { _shippingType = STPShippingTypeShipping; _companyName = [NSBundle stp_applicationName]; _canDeletePaymentOptions = YES; + _cardScanningEnabled = NO; } return self; } @@ -141,6 +142,7 @@ - (NSString *)description { [NSString stringWithFormat:@"companyName = %@", self.companyName], [NSString stringWithFormat:@"appleMerchantIdentifier = %@", self.appleMerchantIdentifier], [NSString stringWithFormat:@"canDeletePaymentOptions = %@", (self.canDeletePaymentOptions) ? @"YES" : @"NO"], + [NSString stringWithFormat:@"cardScanningEnabled = %@", (self.cardScanningEnabled) ? @"YES" : @"NO"], ]; return [NSString stringWithFormat:@"<%@>", [props componentsJoinedByString:@"; "]]; @@ -158,6 +160,7 @@ - (id)copyWithZone:(__unused NSZone *)zone { copy.companyName = self.companyName; copy.appleMerchantIdentifier = self.appleMerchantIdentifier; copy.canDeletePaymentOptions = self.canDeletePaymentOptions; + copy.cardScanningEnabled = self.cardScanningEnabled; copy.availableCountries = _availableCountries; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated" diff --git a/Stripe/STPStringUtils.h b/Stripe/STPStringUtils.h index aebff938088..ba2b9c89178 100644 --- a/Stripe/STPStringUtils.h +++ b/Stripe/STPStringUtils.h @@ -61,4 +61,10 @@ typedef void(^STPTaggedSubstringsCompletionBlock)(NSString *string, NSDictionary + (NSString *)expirationDateStringFromString:(NSString *)string; +/** + * Returns YES if the string is likely to contain something formatted similar to an expiration date. + * It doesn't confirm that the expiration date is valid, or that it is even a date. + */ ++ (BOOL)stringMayContainExpirationDate:(NSString *)string; + @end diff --git a/Stripe/STPStringUtils.m b/Stripe/STPStringUtils.m index 9ef193254bd..71faf813a39 100644 --- a/Stripe/STPStringUtils.m +++ b/Stripe/STPStringUtils.m @@ -133,4 +133,16 @@ + (NSString *)expirationDateStringFromString:(NSString *)string { return string; } ++ (BOOL)stringMayContainExpirationDate:(NSString *)string { + static dispatch_once_t onceToken; + static NSRegularExpression *regex = nil; + dispatch_once(&onceToken, ^{ + regex = [[NSRegularExpression alloc] initWithPattern:@"(\\w{2}(\\/|\\.)(\\w{2}|\\w{4}))" + options:0 + error:NULL]; + }); + NSTextCheckingResult *result = [[regex matchesInString:string options:0 range:NSMakeRange(0, string.length)] firstObject]; + return (result && [result numberOfRanges] > 0); +} + @end diff --git a/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testDefaultAppearance_STPAUBECSDebitFormView_defaultAppearance@2x.png b/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testDefaultAppearance_STPAUBECSDebitFormView_defaultAppearance@2x.png index 380a88a827e..8ff65e5008f 100644 Binary files a/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testDefaultAppearance_STPAUBECSDebitFormView_defaultAppearance@2x.png and b/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testDefaultAppearance_STPAUBECSDebitFormView_defaultAppearance@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testInvalidBSBAndEmailAppearance_STPAUBECSDebitFormView_invalidBSBAndEmailAppearance@2x.png b/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testInvalidBSBAndEmailAppearance_STPAUBECSDebitFormView_invalidBSBAndEmailAppearance@2x.png index 36cdecefc63..dc9da8b03d5 100644 Binary files a/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testInvalidBSBAndEmailAppearance_STPAUBECSDebitFormView_invalidBSBAndEmailAppearance@2x.png and b/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testInvalidBSBAndEmailAppearance_STPAUBECSDebitFormView_invalidBSBAndEmailAppearance@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testInvalidBSBAndEmailCustomization_STPAUBECSDebitFormView_invalidBSBAndEmailCustomization@2x.png b/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testInvalidBSBAndEmailCustomization_STPAUBECSDebitFormView_invalidBSBAndEmailCustomization@2x.png index 428c5c2f296..1a0f6f59c60 100644 Binary files a/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testInvalidBSBAndEmailCustomization_STPAUBECSDebitFormView_invalidBSBAndEmailCustomization@2x.png and b/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testInvalidBSBAndEmailCustomization_STPAUBECSDebitFormView_invalidBSBAndEmailCustomization@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testNoDataCustomization_STPAUBECSDebitFormView_noDataCustomization@2x.png b/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testNoDataCustomization_STPAUBECSDebitFormView_noDataCustomization@2x.png index 17b589a53ea..b1f59d12af1 100644 Binary files a/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testNoDataCustomization_STPAUBECSDebitFormView_noDataCustomization@2x.png and b/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testNoDataCustomization_STPAUBECSDebitFormView_noDataCustomization@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testWithDataAppearance_STPAUBECSDebitFormView_withDataAppearance@2x.png b/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testWithDataAppearance_STPAUBECSDebitFormView_withDataAppearance@2x.png index facf9e67e28..d4aa9dec674 100644 Binary files a/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testWithDataAppearance_STPAUBECSDebitFormView_withDataAppearance@2x.png and b/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testWithDataAppearance_STPAUBECSDebitFormView_withDataAppearance@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testWithDataCustomization_STPAUBECSDebitFormView_withDataAppearance@2x.png b/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testWithDataCustomization_STPAUBECSDebitFormView_withDataAppearance@2x.png index 2d4fc5c2b6a..e7b48b7e9c1 100644 Binary files a/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testWithDataCustomization_STPAUBECSDebitFormView_withDataAppearance@2x.png and b/Tests/ReferenceImages_64/STPAUBECSDebitFormViewSnapshotTests/testWithDataCustomization_STPAUBECSDebitFormView_withDataAppearance@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_CA@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_CA@2x.png index 8acaef53273..09e3d0e2527 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_CA@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_CA@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_GB@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_GB@2x.png index fc75c60186d..df1fcbe4ed1 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_GB@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_GB@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_MX@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_MX@2x.png index 5c62281bd4f..af10aa5a5b7 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_MX@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_MX@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_US@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_US@2x.png index 3298dbea8bd..266c0654bab 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_US@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_US@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_delivery@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_delivery@2x.png index 3fe8192d2c2..ea8035690a4 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_delivery@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_delivery@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_no_country@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_no_country@2x.png index f898b32c500..419279510ab 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_no_country@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testChinese_no_country@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_CA@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_CA@2x.png index 0f1abb93d58..1d8987b21f3 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_CA@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_CA@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_GB@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_GB@2x.png index 84b688fd645..6b47ecb0d95 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_GB@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_GB@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_MX@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_MX@2x.png index 82fc8e04915..fbda5cfea84 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_MX@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_MX@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_US@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_US@2x.png index 03b54f89985..c9e4c4e5793 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_US@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_US@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_delivery@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_delivery@2x.png index d36057620f6..6402deb2e06 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_delivery@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_delivery@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_no_country@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_no_country@2x.png index 2c39fa8f107..f34c1304fa9 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_no_country@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testDutch_no_country@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_CA@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_CA@2x.png index bac5b02172c..b4e0715b82b 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_CA@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_CA@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_GB@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_GB@2x.png index 65da22922a7..6d456bfb1e0 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_GB@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_GB@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_MX@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_MX@2x.png index f4a2b0d23c2..fd242ad143a 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_MX@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_MX@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_US@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_US@2x.png index f9e584bf8c8..5e945ea04f5 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_US@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_US@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_delivery@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_delivery@2x.png index bfc3056d285..ad4f14ee71f 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_delivery@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_delivery@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_no_country@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_no_country@2x.png index da141d23caa..f98de9d38ff 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_no_country@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testEnglish_no_country@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_CA@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_CA@2x.png index efb55ebcab0..63d7df4c807 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_CA@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_CA@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_GB@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_GB@2x.png index a389d4288e7..674a9b7b9f7 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_GB@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_GB@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_MX@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_MX@2x.png index 09b05e65a6d..5a96e1d32be 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_MX@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_MX@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_US@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_US@2x.png index 052ec03d111..7e965e6d9be 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_US@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_US@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_delivery@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_delivery@2x.png index e915e164f21..90cbe44414b 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_delivery@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_delivery@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_no_country@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_no_country@2x.png index e915e164f21..90cbe44414b 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_no_country@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testFrench_no_country@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_CA@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_CA@2x.png index 336a7e85d69..741df1cbc9f 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_CA@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_CA@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_GB@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_GB@2x.png index 465a827d180..cd5da20354d 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_GB@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_GB@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_MX@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_MX@2x.png index 46d8ce01f70..a34f355512f 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_MX@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_MX@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_US@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_US@2x.png index 45b3fb98833..b903438c218 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_US@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_US@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_delivery@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_delivery@2x.png index 2543f530f49..4e3e54a4efd 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_delivery@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_delivery@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_no_country@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_no_country@2x.png index 1fb6a68e797..7efd71a74c5 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_no_country@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testGerman_no_country@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_CA@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_CA@2x.png index 3c4d1243d02..d8c7c5e2ffa 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_CA@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_CA@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_GB@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_GB@2x.png index 403d5599eb7..477e727ff6e 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_GB@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_GB@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_MX@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_MX@2x.png index e01defc329e..89cbcd2a257 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_MX@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_MX@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_US@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_US@2x.png index 140bda51751..5aee8ad3f7b 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_US@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_US@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_delivery@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_delivery@2x.png index e75b1350845..d20bdbad7c6 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_delivery@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_delivery@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_no_country@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_no_country@2x.png index 87598a5164a..5ee465b6ffe 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_no_country@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testItalian_no_country@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_CA@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_CA@2x.png index bf40045f1ef..08c40a43751 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_CA@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_CA@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_GB@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_GB@2x.png index 75a99b9c3f5..9378bae660b 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_GB@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_GB@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_MX@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_MX@2x.png index 664c6bd1bd5..357d4eb1ce2 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_MX@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_MX@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_US@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_US@2x.png index d3864990943..7c339c3d6a4 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_US@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_US@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_delivery@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_delivery@2x.png index bf3ee905c14..d39103089c0 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_delivery@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_delivery@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_no_country@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_no_country@2x.png index 7190ddf49ef..4c8e69186eb 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_no_country@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testJapanese_no_country@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_CA@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_CA@2x.png index f3ca4390a7c..8d48d1bddcc 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_CA@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_CA@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_GB@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_GB@2x.png index 4f106e417df..a0a4b8e82cf 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_GB@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_GB@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_MX@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_MX@2x.png index eb30709afc5..49586d7e255 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_MX@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_MX@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_US@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_US@2x.png index 376bc7f7cfa..984bb2f6d1d 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_US@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_US@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_delivery@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_delivery@2x.png index 295d4b40fb4..e9716d407c8 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_delivery@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_delivery@2x.png differ diff --git a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_no_country@2x.png b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_no_country@2x.png index 80b2c724726..f9d628850cc 100644 Binary files a/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_no_country@2x.png and b/Tests/ReferenceImages_64/STPAddCardViewControllerLocalizationTests/testSpanish_no_country@2x.png differ diff --git a/Tests/ReferenceImages_64/STPLabeledFormTextFieldViewSnapshotTests/testAppearance_STPLabeledFormTextFieldView_defaultAppearance@2x.png b/Tests/ReferenceImages_64/STPLabeledFormTextFieldViewSnapshotTests/testAppearance_STPLabeledFormTextFieldView_defaultAppearance@2x.png index f5887598ebc..d612fab625f 100644 Binary files a/Tests/ReferenceImages_64/STPLabeledFormTextFieldViewSnapshotTests/testAppearance_STPLabeledFormTextFieldView_defaultAppearance@2x.png and b/Tests/ReferenceImages_64/STPLabeledFormTextFieldViewSnapshotTests/testAppearance_STPLabeledFormTextFieldView_defaultAppearance@2x.png differ diff --git a/Tests/ReferenceImages_64/STPLabeledMultiFormTextFieldViewSnapshotTests/testAppearance_STPLabeledMultiFormTextFieldView_defaultAppearance@2x.png b/Tests/ReferenceImages_64/STPLabeledMultiFormTextFieldViewSnapshotTests/testAppearance_STPLabeledMultiFormTextFieldView_defaultAppearance@2x.png index b312596540d..185af53a487 100644 Binary files a/Tests/ReferenceImages_64/STPLabeledMultiFormTextFieldViewSnapshotTests/testAppearance_STPLabeledMultiFormTextFieldView_defaultAppearance@2x.png and b/Tests/ReferenceImages_64/STPLabeledMultiFormTextFieldViewSnapshotTests/testAppearance_STPLabeledMultiFormTextFieldView_defaultAppearance@2x.png differ diff --git a/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushPaymentOptionsLargeTitle@2x.png b/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushPaymentOptionsLargeTitle@2x.png index 2e0684ea807..689769714ef 100644 Binary files a/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushPaymentOptionsLargeTitle@2x.png and b/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushPaymentOptionsLargeTitle@2x.png differ diff --git a/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushPaymentOptionsSmallTitle@2x.png b/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushPaymentOptionsSmallTitle@2x.png index 4efcbd157ef..58ca57cf365 100644 Binary files a/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushPaymentOptionsSmallTitle@2x.png and b/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushPaymentOptionsSmallTitle@2x.png differ diff --git a/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushShippingAddressLargeTitle@2x.png b/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushShippingAddressLargeTitle@2x.png index 786b3893b14..c49ae346822 100644 Binary files a/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushShippingAddressLargeTitle@2x.png and b/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushShippingAddressLargeTitle@2x.png differ diff --git a/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushShippingAddressSmallTitle@2x.png b/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushShippingAddressSmallTitle@2x.png index 113d1bc0e3d..e8198c99cf7 100644 Binary files a/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushShippingAddressSmallTitle@2x.png and b/Tests/ReferenceImages_64/STPPaymentContextSnapshotTests/testPushShippingAddressSmallTitle@2x.png differ diff --git a/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testChinese@2x.png b/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testChinese@2x.png index 5c950001023..b675a1dcb59 100644 Binary files a/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testChinese@2x.png and b/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testChinese@2x.png differ diff --git a/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testDutch@2x.png b/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testDutch@2x.png index 6183d16f739..0ae64a56470 100644 Binary files a/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testDutch@2x.png and b/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testDutch@2x.png differ diff --git a/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testEnglish@2x.png b/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testEnglish@2x.png index 4efcbd157ef..58ca57cf365 100644 Binary files a/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testEnglish@2x.png and b/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testEnglish@2x.png differ diff --git a/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testFrench@2x.png b/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testFrench@2x.png index b74c13b94f1..a831b26cd6e 100644 Binary files a/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testFrench@2x.png and b/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testFrench@2x.png differ diff --git a/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testGerman@2x.png b/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testGerman@2x.png index 962fa4053d5..b2c88d099e7 100644 Binary files a/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testGerman@2x.png and b/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testGerman@2x.png differ diff --git a/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testItalian@2x.png b/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testItalian@2x.png index 39f2651a69f..cb119d5d06a 100644 Binary files a/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testItalian@2x.png and b/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testItalian@2x.png differ diff --git a/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testJapanese@2x.png b/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testJapanese@2x.png index 91a482e13fa..17c46d372d1 100644 Binary files a/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testJapanese@2x.png and b/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testJapanese@2x.png differ diff --git a/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testSpanish@2x.png b/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testSpanish@2x.png index 9ec958801ec..f8a696521b3 100644 Binary files a/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testSpanish@2x.png and b/Tests/ReferenceImages_64/STPPaymentOptionsViewControllerLocalizationTests/testSpanish@2x.png differ diff --git a/Tests/ReferenceImages_64/STPSTPViewWithSeparatorSnapshotTests/testDefaultAppearance_STPViewWithSeparator_defaultAppearance@2x.png b/Tests/ReferenceImages_64/STPSTPViewWithSeparatorSnapshotTests/testDefaultAppearance_STPViewWithSeparator_defaultAppearance@2x.png index 6de8c27ed7e..eaa8ec0e70b 100644 Binary files a/Tests/ReferenceImages_64/STPSTPViewWithSeparatorSnapshotTests/testDefaultAppearance_STPViewWithSeparator_defaultAppearance@2x.png and b/Tests/ReferenceImages_64/STPSTPViewWithSeparatorSnapshotTests/testDefaultAppearance_STPViewWithSeparator_defaultAppearance@2x.png differ diff --git a/Tests/ReferenceImages_64/STPSTPViewWithSeparatorSnapshotTests/testHiddenTopSeparator_STPViewWithSeparator_hiddenTopSeparator@2x.png b/Tests/ReferenceImages_64/STPSTPViewWithSeparatorSnapshotTests/testHiddenTopSeparator_STPViewWithSeparator_hiddenTopSeparator@2x.png index 759745bb4e0..e0f93b818cc 100644 Binary files a/Tests/ReferenceImages_64/STPSTPViewWithSeparatorSnapshotTests/testHiddenTopSeparator_STPViewWithSeparator_hiddenTopSeparator@2x.png and b/Tests/ReferenceImages_64/STPSTPViewWithSeparatorSnapshotTests/testHiddenTopSeparator_STPViewWithSeparator_hiddenTopSeparator@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testChinese_contact@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testChinese_contact@2x.png index 5fdcf1d69ee..1e8fa1e2c6e 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testChinese_contact@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testChinese_contact@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testChinese_delivery@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testChinese_delivery@2x.png index b2f7ec9273c..dd260484c9c 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testChinese_delivery@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testChinese_delivery@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testChinese_shipping@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testChinese_shipping@2x.png index 16c158b8526..4c4a505be98 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testChinese_shipping@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testChinese_shipping@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testDutch_contact@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testDutch_contact@2x.png index 721976634ec..d1a8e2592a7 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testDutch_contact@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testDutch_contact@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testDutch_delivery@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testDutch_delivery@2x.png index a1de6869e62..737896c1ab8 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testDutch_delivery@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testDutch_delivery@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testDutch_shipping@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testDutch_shipping@2x.png index 2422f8ee3bd..9ece4be8109 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testDutch_shipping@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testDutch_shipping@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testEnglish_contact@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testEnglish_contact@2x.png index 113d1bc0e3d..e8198c99cf7 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testEnglish_contact@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testEnglish_contact@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testEnglish_delivery@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testEnglish_delivery@2x.png index 4361c42b4a5..d5c2270c3f6 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testEnglish_delivery@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testEnglish_delivery@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testEnglish_shipping@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testEnglish_shipping@2x.png index 19ac01e53b1..9ff9b24a912 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testEnglish_shipping@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testEnglish_shipping@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testFrench_contact@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testFrench_contact@2x.png index 9072661c1bf..21977511bd3 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testFrench_contact@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testFrench_contact@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testFrench_delivery@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testFrench_delivery@2x.png index 1152392cc80..38704113e82 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testFrench_delivery@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testFrench_delivery@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testFrench_shipping@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testFrench_shipping@2x.png index 16f4aba2188..32f87c03a29 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testFrench_shipping@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testFrench_shipping@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testGerman_contact@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testGerman_contact@2x.png index dd57ca599b7..389c7d286e1 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testGerman_contact@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testGerman_contact@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testGerman_delivery@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testGerman_delivery@2x.png index 562dbf01e85..db0637eefa0 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testGerman_delivery@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testGerman_delivery@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testGerman_shipping@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testGerman_shipping@2x.png index 4c1f03b1534..ed8b9204511 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testGerman_shipping@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testGerman_shipping@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testItalian_contact@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testItalian_contact@2x.png index 46e8f1341c3..a66dc3924a3 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testItalian_contact@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testItalian_contact@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testItalian_delivery@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testItalian_delivery@2x.png index 344f30c476d..3396c300ce4 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testItalian_delivery@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testItalian_delivery@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testItalian_shipping@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testItalian_shipping@2x.png index 3c0156281de..e57a7be3c49 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testItalian_shipping@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testItalian_shipping@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testJapanese_contact@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testJapanese_contact@2x.png index ee369dee173..003fa3c1b5e 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testJapanese_contact@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testJapanese_contact@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testJapanese_delivery@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testJapanese_delivery@2x.png index f7cdf31beed..892cb79177d 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testJapanese_delivery@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testJapanese_delivery@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testJapanese_shipping@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testJapanese_shipping@2x.png index ab0adbf48df..04c3ade2356 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testJapanese_shipping@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testJapanese_shipping@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testSpanish_contact@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testSpanish_contact@2x.png index a99f93d1cab..e46887eed4a 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testSpanish_contact@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testSpanish_contact@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testSpanish_delivery@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testSpanish_delivery@2x.png index 3ac193d9aa5..932fba6c795 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testSpanish_delivery@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testSpanish_delivery@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testSpanish_shipping@2x.png b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testSpanish_shipping@2x.png index b1fd2be29c9..d8070c2ac48 100644 Binary files a/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testSpanish_shipping@2x.png and b/Tests/ReferenceImages_64/STPShippingAddressViewControllerLocalizationTests/testSpanish_shipping@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testChinese@2x.png b/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testChinese@2x.png index c473dedaef0..06a11f81d6b 100644 Binary files a/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testChinese@2x.png and b/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testChinese@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testDutch@2x.png b/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testDutch@2x.png index 7db44b7ac00..c99eedca215 100644 Binary files a/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testDutch@2x.png and b/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testDutch@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testEnglish@2x.png b/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testEnglish@2x.png index 3a800e55492..cb2aac4069f 100644 Binary files a/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testEnglish@2x.png and b/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testEnglish@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testFrench@2x.png b/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testFrench@2x.png index c7d950f092a..0c1aae8670a 100644 Binary files a/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testFrench@2x.png and b/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testFrench@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testGerman@2x.png b/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testGerman@2x.png index 10af3412cc7..31970398863 100644 Binary files a/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testGerman@2x.png and b/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testGerman@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testItalian@2x.png b/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testItalian@2x.png index 71fed961ec5..9d377f3c15c 100644 Binary files a/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testItalian@2x.png and b/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testItalian@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testJapanese@2x.png b/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testJapanese@2x.png index a63f43c9289..2ec7b88953d 100644 Binary files a/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testJapanese@2x.png and b/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testJapanese@2x.png differ diff --git a/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testSpanish@2x.png b/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testSpanish@2x.png index b1da76f5635..47e6f1e1fad 100644 Binary files a/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testSpanish@2x.png and b/Tests/ReferenceImages_64/STPShippingMethodsViewControllerLocalizationTests/testSpanish@2x.png differ diff --git a/Tests/Tests/STPAddCardViewControllerLocalizationTests.m b/Tests/Tests/STPAddCardViewControllerLocalizationTests.m index 34622ab6b96..c9176f36d38 100644 --- a/Tests/Tests/STPAddCardViewControllerLocalizationTests.m +++ b/Tests/Tests/STPAddCardViewControllerLocalizationTests.m @@ -16,7 +16,6 @@ #import "STPAddressViewModel.h" #import "STPAddressFieldTableViewCell.h" #import "STPBundleLocator.h" -#import "STPCardIOProxy.h" #import "STPFixtures.h" #import "STPLocalizationUtils.h" #import "STPLocalizationUtils+STPTestAdditions.h" @@ -38,15 +37,12 @@ @implementation STPAddCardViewControllerLocalizationTests //} - (void)performSnapshotTestForLanguage:(NSString *)language delivery:(BOOL)delivery { - id mockCardIOProxy = OCMClassMock([STPCardIOProxy class]); - OCMStub([mockCardIOProxy isCardIOAvailable]).andReturn(YES); - STPPaymentConfiguration *config = [STPFixtures paymentConfiguration]; config.companyName = @"Test Company"; config.requiredBillingAddressFields = STPBillingAddressFieldsFull; config.additionalPaymentOptions = STPPaymentOptionTypeDefault; config.shippingType = (delivery) ? STPShippingTypeDelivery : STPShippingTypeShipping; - + config.cardScanningEnabled = YES; [STPLocalizationUtils overrideLanguageTo:language]; STPAddCardViewController *addCardVC = [[STPAddCardViewController alloc] initWithConfiguration:config diff --git a/Tests/Tests/STPPaymentCardTextFieldTest.m b/Tests/Tests/STPPaymentCardTextFieldTest.m index 70af450cf25..ecfc009956b 100644 --- a/Tests/Tests/STPPaymentCardTextFieldTest.m +++ b/Tests/Tests/STPPaymentCardTextFieldTest.m @@ -75,7 +75,11 @@ - (void)testIntrinsicContentSize { XCTAssertEqualWithAccuracy(textField.intrinsicContentSize.width, 259, 0.1); textField.font = [UIFont fontWithName:@"Avenir" size:44]; - XCTAssertEqualWithAccuracy(textField.intrinsicContentSize.height, 61, 0.1); + if (@available(iOS 13.0, *)) { + XCTAssertEqualWithAccuracy(textField.intrinsicContentSize.height, 62, 0.1); + } else { + XCTAssertEqualWithAccuracy(textField.intrinsicContentSize.height, 61, 0.1); + } XCTAssertEqualWithAccuracy(textField.intrinsicContentSize.width, 478, 0.1); } diff --git a/Tests/Tests/STPPaymentConfigurationTest.m b/Tests/Tests/STPPaymentConfigurationTest.m index 7cff94a0c8f..83f34824262 100644 --- a/Tests/Tests/STPPaymentConfigurationTest.m +++ b/Tests/Tests/STPPaymentConfigurationTest.m @@ -39,6 +39,7 @@ - (void)testInit { XCTAssertEqualObjects(paymentConfiguration.companyName, @"applicationName"); XCTAssertNil(paymentConfiguration.appleMerchantIdentifier); XCTAssert(paymentConfiguration.canDeletePaymentOptions); + XCTAssert(!paymentConfiguration.cardScanningEnabled); } - (void)testApplePayEnabledSatisfied { @@ -115,6 +116,7 @@ - (void)testCopyWithZone { paymentConfigurationA.companyName = @"companyName"; paymentConfigurationA.appleMerchantIdentifier = @"appleMerchantIdentifier"; paymentConfigurationA.canDeletePaymentOptions = NO; + paymentConfigurationA.cardScanningEnabled = YES; STPPaymentConfiguration *paymentConfigurationB = [paymentConfigurationA copy]; XCTAssertNotEqual(paymentConfigurationA, paymentConfigurationB); @@ -134,6 +136,7 @@ - (void)testCopyWithZone { NSSet *availableCountries = [NSSet setWithArray:@[@"US", @"CA", @"BT"]]; XCTAssertEqualObjects(paymentConfigurationB.availableCountries, availableCountries); XCTAssertEqual(paymentConfigurationA.canDeletePaymentOptions, paymentConfigurationB.canDeletePaymentOptions); + XCTAssertEqual(paymentConfigurationA.cardScanningEnabled, paymentConfigurationB.cardScanningEnabled); } @end diff --git a/ci_scripts/run_tests.sh b/ci_scripts/run_tests.sh index d96ebd439af..c70d3ae2d09 100755 --- a/ci_scripts/run_tests.sh +++ b/ci_scripts/run_tests.sh @@ -29,15 +29,15 @@ if [[ "${carthage_exit_code}" != 0 ]]; then die "Executing carthage failed with status code: ${carthage_exit_code}" fi -# Execute tests (iPhone 7 @ iOS 12.4) -info "Executing tests (iPhone 7 @ iOS 12.4)..." +# Execute tests (iPhone 8 @ iOS 13.6) +info "Executing tests (iPhone 8 @ iOS 13.6)..." xcodebuild clean test \ -workspace "Stripe.xcworkspace" \ -scheme "StripeiOS" \ -configuration "Debug" \ -sdk "iphonesimulator" \ - -destination "platform=iOS Simulator,name=iPhone 7,OS=12.4" \ + -destination "platform=iOS Simulator,name=iPhone 8,OS=13.6" \ | xcpretty exit_code="${PIPESTATUS[0]}"