From ddf04ed2483b599716adb13d52d09b108b05b91f Mon Sep 17 00:00:00 2001 From: Nick Porter <88012362+porter-stripe@users.noreply.github.com> Date: Mon, 19 Sep 2022 09:14:21 -0700 Subject: [PATCH] Add UPI prototype (#1415) * Add UPI prototype * Move UPI support to playground * remove prints * Update playground for Indian merchants * Update PaymentSheetTestPlayground.swift * Add UPI snapshot * Update PaymentSheetSnapshotTests.swift * Use CompatibleColor * respect appearance api in polling vc * Mark polling VC as internal * clean up * Move VPA validation to own file * update todos * Update STPIntentAction.swift * Handle todo * Localize countdown timer * Remove post_confirm_handling_pi_status_specs from form spec * Remove upi next action, update intent directly * Revert "Remove post_confirm_handling_pi_status_specs from form spec" This reverts commit 1989a15ba322c1e426595d49b1ecc7b49577f517. * add back india to playground * Remove UPI from luxe spec * Add UPI automated UI tests * Clean up some white space * Add STPVPANumberValidatorTest * PR feedback --- .../Base.lproj/Main.storyboard | 136 +++++------ .../PaymentSheetTestPlayground.swift | 6 +- .../PaymentSheetSnapshotTests.swift | 20 ++ .../PaymentSheetUITest.swift | 50 ++++ Stripe.xcodeproj/project.pbxproj | 21 +- Stripe/BottomSheetViewController.swift | 10 + Stripe/Date+Distance.swift | 22 ++ Stripe/Enums+CustomStringConvertible.swift | 2 + Stripe/Images.swift | 1 + Stripe/IntentStatusPoller.swift | 95 ++++++++ Stripe/PaymentMethodType.swift | 5 + Stripe/PaymentOption+Images.swift | 2 + Stripe/PaymentSheet+API.swift | 5 + Stripe/PaymentSheetFlowController.swift | 15 +- Stripe/PaymentSheetFormFactory+UPI.swift | 33 +++ Stripe/PaymentSheetFormFactory.swift | 4 +- Stripe/PollingViewController.swift | 227 ++++++++++++++++++ .../Images/PaymentMethods/icon-pm-upi@3x.png | Bin 0 -> 3260 bytes .../en.lproj/Localizable.strings | 12 + Stripe/STPIntentAction.swift | 11 + Stripe/STPPaymentHandler.swift | 12 +- Stripe/SheetNavigationBar.swift | 5 + Stripe/String+Localized.swift | 21 ++ .../StripeUICore.xcodeproj/project.pbxproj | 8 + .../en.lproj/Localizable.strings | 6 + .../Factories/TextFieldElement+Factory.swift | 27 +++ .../Source/Helpers/String+Localized.swift | 10 + .../Validators/STPVPANumberValidator.swift | 27 +++ .../STPVPANumberValidatorTest.swift | 26 ++ .../testPaymentSheet_LPM_upi_only@3x.png | Bin 0 -> 83214 bytes 30 files changed, 744 insertions(+), 75 deletions(-) create mode 100644 Stripe/Date+Distance.swift create mode 100644 Stripe/IntentStatusPoller.swift create mode 100644 Stripe/PaymentSheetFormFactory+UPI.swift create mode 100644 Stripe/PollingViewController.swift create mode 100644 Stripe/Resources/Images/PaymentMethods/icon-pm-upi@3x.png create mode 100644 StripeUICore/StripeUICore/Source/Validators/STPVPANumberValidator.swift create mode 100644 StripeUICore/StripeUICoreTests/Unit/Validators/STPVPANumberValidatorTest.swift create mode 100644 Tests/ReferenceImages_64/PaymentSheetUITest.PaymentSheetSnapshotTests/testPaymentSheet_LPM_upi_only@3x.png diff --git a/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheet Example/Base.lproj/Main.storyboard b/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheet Example/Base.lproj/Main.storyboard index 7e354fdfdce..dd255249bff 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheet Example/Base.lproj/Main.storyboard +++ b/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheet Example/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -18,22 +18,22 @@ - + - + - + @@ -462,7 +464,7 @@ - + - + diff --git a/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift b/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift index 968054848e4..e6bc8d2e708 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift @@ -52,6 +52,7 @@ class PaymentSheetTestPlayground: UIViewController { case eur case aud case gbp + case inr } enum MerchantCountryCode: String, CaseIterable { @@ -59,6 +60,7 @@ class PaymentSheetTestPlayground: UIViewController { case GB case AU case FR + case IN } enum IntentMode: String, CaseIterable { @@ -229,8 +231,8 @@ class PaymentSheetTestPlayground: UIViewController { super.viewDidLoad() // Enable experimental payment methods. - // PaymentSheet.supportedPaymentMethods += [.link] - + PaymentSheet.supportedPaymentMethods += [.UPI] + checkoutButton.addTarget(self, action: #selector(didTapCheckoutButton), for: .touchUpInside) checkoutButton.isEnabled = false diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetSnapshotTests.swift b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetSnapshotTests.swift index 6cca91c60b2..33c1480ad51 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetSnapshotTests.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetSnapshotTests.swift @@ -594,6 +594,26 @@ class PaymentSheetSnapshotTests: FBSnapshotTestCase { presentPaymentSheet(darkMode: false) verify(paymentSheet.bottomSheetViewController.view!) } + + func testPaymentSheet_LPM_upi_only() { + PaymentSheet.supportedPaymentMethods += [.UPI] // TODO: (porter) Remove when UPI launches + + stubSessions(fileMock: .elementsSessionsPaymentMethod_200, + responseCallback: { data in + return self.updatePaymentMethodDetail(data: data, variables: ["": "\"upi\"", + "": "\"inr\""]) + }) + stubPaymentMethods(stubRequestCallback: nil, fileMock: .saved_payment_methods_200) + stubCustomers() + + preparePaymentSheet(currency: "inr", + override_payment_methods_types: ["upi"], + automaticPaymentMethods: false, + useLink: false) + presentPaymentSheet(darkMode: false) + verify(paymentSheet.bottomSheetViewController.view!) + + } private func updatePaymentMethodDetail(data: Data, variables: [String:String]) -> Data { var template = String(data: data, encoding: .utf8)! for (templateKey, templateValue) in variables { diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift index 82ed9f8abfb..10e5f136402 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift @@ -459,6 +459,56 @@ class PaymentSheetUITest: XCTestCase { // no pay button tap because linked account is stubbed/fake in UI test } + + func testUPIPaymentMethod() throws { + loadPlayground(app, settings: [ + "customer_mode": "new", + "merchant_country_code": "IN", + "currency": "INR" + ]) + + app.buttons["Checkout (Complete)"].tap() + + let payButton = app.buttons["Pay ₹50.99"] + guard let upi = scroll(collectionView: app.collectionViews.firstMatch, toFindCellWithId: "UPI") else { + XCTFail() + return + } + upi.tap() + + XCTAssertFalse(payButton.isEnabled) + let vpa = app.textFields["VPA number"] + vpa.tap() + vpa.typeText("payment.success@stripeupi") + vpa.typeText(XCUIKeyboardKey.return.rawValue) + + payButton.tap() + } + + func testUPIPaymentMethod_invalidVPA() throws { + loadPlayground(app, settings: [ + "customer_mode": "new", + "merchant_country_code": "IN", + "currency": "INR" + ]) + + app.buttons["Checkout (Complete)"].tap() + + let payButton = app.buttons["Pay ₹50.99"] + guard let upi = scroll(collectionView: app.collectionViews.firstMatch, toFindCellWithId: "UPI") else { + XCTFail() + return + } + upi.tap() + + XCTAssertFalse(payButton.isEnabled) + let vpa = app.textFields["VPA number"] + vpa.tap() + vpa.typeText("payment.success") + vpa.typeText(XCUIKeyboardKey.return.rawValue) + + XCTAssertFalse(payButton.isEnabled) + } } // MARK: - Link diff --git a/Stripe.xcodeproj/project.pbxproj b/Stripe.xcodeproj/project.pbxproj index 1b9b79eb7ff..58227a2f3f5 100644 --- a/Stripe.xcodeproj/project.pbxproj +++ b/Stripe.xcodeproj/project.pbxproj @@ -482,6 +482,9 @@ 3CD1D3A127C8682E001575BB /* ConnectionsSDKAvailability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CD1D3A027C8682D001575BB /* ConnectionsSDKAvailability.swift */; }; 448895AF245255D800F7D0C2 /* STPPaymentMethodPrzelewy24ParamsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 448895AE245255D800F7D0C2 /* STPPaymentMethodPrzelewy24ParamsTests.m */; }; 44BDCFDF245A46CC007EE6D5 /* STPPaymentMethodBancontactParamsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 44BDCFDE245A46CC007EE6D5 /* STPPaymentMethodBancontactParamsTests.m */; }; + 61078DA428C278B3007C7001 /* PollingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61078DA328C278B3007C7001 /* PollingViewController.swift */; }; + 61078DA828C7C49C007C7001 /* PaymentSheetFormFactory+UPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61078DA728C7C49C007C7001 /* PaymentSheetFormFactory+UPI.swift */; }; + 61078DAA28C7F28F007C7001 /* IntentStatusPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61078DA928C7F28F007C7001 /* IntentStatusPoller.swift */; }; 61202525285AD33F00B55402 /* AutoCompleteViewControllerSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61202524285AD33F00B55402 /* AutoCompleteViewControllerSnapshotTests.swift */; }; 612A871A285788D400E91CA8 /* MKPlacemark+PaymentSheetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612A8719285788D400E91CA8 /* MKPlacemark+PaymentSheetTests.swift */; }; 612A871C2857EC5400E91CA8 /* String+AutoComplete.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612A871B2857EC5400E91CA8 /* String+AutoComplete.swift */; }; @@ -494,7 +497,9 @@ 6164582C27E963C800FEAB8E /* icon-pm-paypal_dark@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6164582B27E963C800FEAB8E /* icon-pm-paypal_dark@3x.png */; }; 6164582E27E964A800FEAB8E /* icon-pm-aubecsdebit@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6164582D27E964A800FEAB8E /* icon-pm-aubecsdebit@3x.png */; }; 6169CD1E28512EA300CEAD22 /* AddressSearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6169CD1D28512EA300CEAD22 /* AddressSearchResult.swift */; }; + 616B573F28CA42DB0026B4E4 /* icon-pm-upi@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 616B573E28CA42DB0026B4E4 /* icon-pm-upi@3x.png */; }; 6171960E2864D3B90040ECE3 /* AutoCompleteConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6171960D2864D3B80040ECE3 /* AutoCompleteConstants.swift */; }; + 61806A1028CB99F500C33002 /* Date+Distance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61806A0F28CB99F500C33002 /* Date+Distance.swift */; }; 618E787B26EFDD310034A01F /* ServerErrorMapperTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618E787A26EFDD310034A01F /* ServerErrorMapperTest.swift */; }; 61924D47273999E1003CF2DB /* icon-pm-paypal@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 61924D45273999E1003CF2DB /* icon-pm-paypal@3x.png */; }; 6198555D27DBF4A1003F8951 /* icon_chevron_left_standalone@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6198555B27DBF4A1003F8951 /* icon_chevron_left_standalone@3x.png */; }; @@ -1517,6 +1522,9 @@ 448895AE245255D800F7D0C2 /* STPPaymentMethodPrzelewy24ParamsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPPaymentMethodPrzelewy24ParamsTests.m; sourceTree = ""; }; 44BDCFDE245A46CC007EE6D5 /* STPPaymentMethodBancontactParamsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = STPPaymentMethodBancontactParamsTests.m; sourceTree = ""; }; 4A0D74F918F6106100966D7B /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 61078DA328C278B3007C7001 /* PollingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollingViewController.swift; sourceTree = ""; }; + 61078DA728C7C49C007C7001 /* PaymentSheetFormFactory+UPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PaymentSheetFormFactory+UPI.swift"; sourceTree = ""; }; + 61078DA928C7F28F007C7001 /* IntentStatusPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentStatusPoller.swift; sourceTree = ""; }; 61202524285AD33F00B55402 /* AutoCompleteViewControllerSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteViewControllerSnapshotTests.swift; sourceTree = ""; }; 612A8719285788D400E91CA8 /* MKPlacemark+PaymentSheetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MKPlacemark+PaymentSheetTests.swift"; sourceTree = ""; }; 612A871B2857EC5400E91CA8 /* String+AutoComplete.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+AutoComplete.swift"; sourceTree = ""; }; @@ -1530,7 +1538,9 @@ 6164582D27E964A800FEAB8E /* icon-pm-aubecsdebit@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-pm-aubecsdebit@3x.png"; sourceTree = ""; }; 6169CD1D28512EA300CEAD22 /* AddressSearchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressSearchResult.swift; sourceTree = ""; }; 6169CD1F28512EC700CEAD22 /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/iOSSupport/System/Library/Frameworks/MapKit.framework; sourceTree = DEVELOPER_DIR; }; + 616B573E28CA42DB0026B4E4 /* icon-pm-upi@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-pm-upi@3x.png"; sourceTree = ""; }; 6171960D2864D3B80040ECE3 /* AutoCompleteConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteConstants.swift; sourceTree = ""; }; + 61806A0F28CB99F500C33002 /* Date+Distance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Distance.swift"; sourceTree = ""; }; 618E787A26EFDD310034A01F /* ServerErrorMapperTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerErrorMapperTest.swift; sourceTree = ""; }; 61924D45273999E1003CF2DB /* icon-pm-paypal@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon-pm-paypal@3x.png"; sourceTree = ""; }; 6198555B27DBF4A1003F8951 /* icon_chevron_left_standalone@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_chevron_left_standalone@3x.png"; sourceTree = ""; }; @@ -2149,6 +2159,7 @@ B66FA0B1267D6F03008D7F1D /* icon-pm-sepa@3x.png */, 6164582B27E963C800FEAB8E /* icon-pm-paypal_dark@3x.png */, 61924D45273999E1003CF2DB /* icon-pm-paypal@3x.png */, + 616B573E28CA42DB0026B4E4 /* icon-pm-upi@3x.png */, ); path = PaymentMethods; sourceTree = ""; @@ -2974,6 +2985,7 @@ children = ( B6E9B91A266EE54F00C1308D /* FormSpec */, B66F0CA326717B8C0097C2E8 /* PaymentSheetFormFactory.swift */, + 61078DA728C7C49C007C7001 /* PaymentSheetFormFactory+UPI.swift */, B645878827EAAA660011FA64 /* PaymentSheetFormFactory+Card.swift */, B64C503E27BC721800E95B66 /* PaymentSheetFormFactory+FormSpec.swift */, ); @@ -2985,11 +2997,11 @@ children = ( B6AEC92627ED3BEB0084CD67 /* Elements */, 61EA8CED26DD84DF00B2879D /* Error+PaymentSheet.swift */, + 61806A0F28CB99F500C33002 /* Date+Distance.swift */, B6BB89CF266EF7F8005E044F /* Intent.swift */, 61DBE71D27308195008565C8 /* KlarnaHelper.swift */, B6689185265324C600A5488F /* New Payment Method Screen */, B6E40E8C254253E400A5BABD /* BottomSheet */, - B6E40E8C254253E400A5BABD /* PanModal */, 6BD80544282C87B20049857B /* PaymentMethodType.swift */, B648F38E25E45A770009FB36 /* PaymentOption+Images.swift */, D0E845642887327D00CB0461 /* PaymentSheet-LinkConfirmOption.swift */, @@ -3031,6 +3043,8 @@ B694F27428874DA20006DD60 /* Address */, 3667949F25B8DF8B0094831B /* BottomSheet3DS2ViewController.swift */, 36F61202254C888F006656BD /* BottomSheetViewController.swift */, + 61078DA328C278B3007C7001 /* PollingViewController.swift */, + 61078DA928C7F28F007C7001 /* IntentStatusPoller.swift */, B684476625538740005C4089 /* ChoosePaymentOptionViewController.swift */, B65E749425832A290080D9B3 /* LoadingViewController.swift */, 36F61204254C888F006656BD /* PaymentSheetViewController.swift */, @@ -3961,6 +3975,7 @@ F35E2DB2267ABA6700BE074B /* clearpay_mark@3x.png in Resources */, 3180E1032592BB1800CE3D7E /* stp_bank_fpx_hong_leong_bank@3x.png in Resources */, 3180E0E12592BB1100CE3D7E /* nab@3x.png in Resources */, + 616B573F28CA42DB0026B4E4 /* icon-pm-upi@3x.png in Resources */, D0BEB409273CABFC0031D677 /* icon_add_bordered@3x.png in Resources */, 6198556027DBF4E6003F8951 /* icon_x_standalone@3x.png in Resources */, 3180E10A2592BB1800CE3D7E /* stp_bank_fpx_alliance_bank@3x.png in Resources */, @@ -4313,6 +4328,7 @@ 31D4D6882512EBAC00809066 /* UIToolbar+Stripe_InputAccessory.swift in Sources */, 366ECD36254B4AFA0082868E /* STPCardNumberInputTextFieldValidator.swift in Sources */, 61EC82F8288EF13E003D741F /* STPAnalyticsClient+Address.swift in Sources */, + 61806A1028CB99F500C33002 /* Date+Distance.swift in Sources */, D0E152922810D2F900BCB49F /* LinkSettings.swift in Sources */, D092E37127C06F2F00B72609 /* PaymentSheet-Configuration+Link.swift in Sources */, 61D30DB926D5B5F2002872DE /* TestModeView.swift in Sources */, @@ -4363,6 +4379,7 @@ 316F811A25410B12000A80B5 /* STPPaymentMethodOXXOParams.swift in Sources */, D03B1DDA2819F4C1009F4C9A /* LinkNavigationBar.swift in Sources */, 6145429D2850EC3E002A2901 /* ManualEntryButton.swift in Sources */, + 61078DAA28C7F28F007C7001 /* IntentStatusPoller.swift in Sources */, D075F87827443E3F00585EB8 /* SeparatorLabel.swift in Sources */, 317ABF40251196A600CC59EF /* STPCameraView.swift in Sources */, 3176C2142519723B00300ADE /* STPPaymentCardTextField.swift in Sources */, @@ -4588,6 +4605,7 @@ B67243172524E3E5002E1AAF /* STPPaymentMethodPrzelewy24.swift in Sources */, B67BC546257B024200B7349B /* PaymentMethodTypeCollectionView.swift in Sources */, D0AF32DB2833005700BDE839 /* PayWithLinkViewController-SignUpViewModel.swift in Sources */, + 61078DA828C7C49C007C7001 /* PaymentSheetFormFactory+UPI.swift in Sources */, 310AF46B271E0961007339F4 /* STPPaymentMethodCard.swift in Sources */, 36ADAE182523A5B100302DFB /* STPPaymentIntentLastPaymentError.swift in Sources */, 31F2E8782524143F004D4B5E /* STPPaymentResult.swift in Sources */, @@ -4668,6 +4686,7 @@ D0D28E622757398200098245 /* Button+Link.swift in Sources */, D092E37D27C5A01700B72609 /* STPAnalyticsClient+Link.swift in Sources */, D0A621472886034800F7876D /* PayWithLinkController.swift in Sources */, + 61078DA428C278B3007C7001 /* PollingViewController.swift in Sources */, 3111BE802513057C00288D28 /* STPMultiFormTextField.swift in Sources */, 319490592513CC6200AD8F0B /* STPImageLibrary.swift in Sources */, B6D9CEAB2514809B00AAD424 /* STPPaymentMethodCardNetworks.swift in Sources */, diff --git a/Stripe/BottomSheetViewController.swift b/Stripe/BottomSheetViewController.swift index 00259c9924d..e9c9e1175e1 100644 --- a/Stripe/BottomSheetViewController.swift +++ b/Stripe/BottomSheetViewController.swift @@ -12,6 +12,8 @@ import UIKit @_spi(STP) import StripeUICore protocol BottomSheetContentViewController: UIViewController { + + /// - Note: Implementing `navigationBar` as a computed variable will result in undefined behavior. var navigationBar: SheetNavigationBar { get } var requiresFullScreen: Bool { get } func didTapOrSwipeToDismiss() @@ -283,6 +285,14 @@ extension BottomSheetViewController: PaymentSheetAuthenticationContext { } _ = popContentViewController() } + + func present(_ viewController: BottomSheetContentViewController) { + pushContentViewController(viewController) + } + + func dismiss(_ viewController: BottomSheetContentViewController) { + _ = popContentViewController() + } } // MARK: - UIViewControllerTransitioningDelegate diff --git a/Stripe/Date+Distance.swift b/Stripe/Date+Distance.swift new file mode 100644 index 00000000000..bc577350c8e --- /dev/null +++ b/Stripe/Date+Distance.swift @@ -0,0 +1,22 @@ +// +// Date+Distance.swift +// StripeiOS +// +// Created by Nick Porter on 9/9/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation + +extension Date { + + public func compatibleDistance(to other: Date) -> TimeInterval { + if #available(iOS 13.0, *) { + return self.distance(to: other) + } + + return TimeInterval( + Calendar.autoupdatingCurrent.dateComponents([Calendar.Component.second], from: self, to: other).second ?? 0 + ) + } +} diff --git a/Stripe/Enums+CustomStringConvertible.swift b/Stripe/Enums+CustomStringConvertible.swift index 88301f16d2c..31dae69ada1 100644 --- a/Stripe/Enums+CustomStringConvertible.swift +++ b/Stripe/Enums+CustomStringConvertible.swift @@ -242,6 +242,8 @@ extension STPIntentActionType: CustomStringConvertible { return "verifyWithMicrodeposits" case .weChatPayRedirectToApp: return "weChatPayRedirectToApp" + case .upiAwaitNotification: + return "upiAwaitNotification" } } } diff --git a/Stripe/Images.swift b/Stripe/Images.swift index 37f629baeeb..b34f400fa9c 100644 --- a/Stripe/Images.swift +++ b/Stripe/Images.swift @@ -35,6 +35,7 @@ enum Image: String, CaseIterable, ImageMaker { case pm_type_sepa = "icon-pm-sepa" case pm_type_paypal = "icon-pm-paypal" case pm_type_link = "icon-pm-link" + case pm_type_upi = "icon-pm-upi" // Icons/symbols case icon_checkmark = "icon_checkmark" diff --git a/Stripe/IntentStatusPoller.swift b/Stripe/IntentStatusPoller.swift new file mode 100644 index 00000000000..b3074ee731c --- /dev/null +++ b/Stripe/IntentStatusPoller.swift @@ -0,0 +1,95 @@ +// +// IntentStatusPoller.swift +// StripeiOS +// +// Created by Nick Porter on 9/6/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import StripeCore + +protocol IntentStatusPollerDelegate: AnyObject { + func didUpdate(paymentIntent: STPPaymentIntent) +} + +class IntentStatusPoller { + let apiClient: STPAPIClient + let clientSecret: String + let maxRetries: Int + + private var lastStatus: STPPaymentIntentStatus = .unknown + private var retryCount = 0 + weak var delegate: IntentStatusPollerDelegate? + + var isPolling: Bool = false { + didSet { + // Start polling if we weren't already polling + if !oldValue && isPolling { + forcePoll() + } + } + } + + init(apiClient: STPAPIClient, clientSecret: String, maxRetries: Int) { + self.apiClient = apiClient + self.clientSecret = clientSecret + self.maxRetries = maxRetries + } + + // MARK: Public APIs + + public func beginPolling() { + isPolling = true + } + + public func suspendPolling() { + isPolling = false + } + + public func forcePoll() { + fetchStatus(forcePoll: true) + } + + // MARK: Private functions + + private func fetchStatus(forcePoll: Bool = false) { + guard forcePoll || (isPolling && retryCount < maxRetries) else { return } + retryCount += 1 + + apiClient.retrievePaymentIntent(withClientSecret: clientSecret) { [weak self] paymentIntent, error in + print("PI status") + print(paymentIntent?.status as Any) + print(self?.retryCount as Any) + guard let isPolling = self?.isPolling else { + return + } + + // If latest status is different than last known status notify our delegate + if let paymentIntent = paymentIntent, + paymentIntent.status != self?.lastStatus, + isPolling { + self?.lastStatus = paymentIntent.status + self?.delegate?.didUpdate(paymentIntent: paymentIntent) + } + + // If we are polling and have retries left, schedule a status fetch + if isPolling, let maxRetries = self?.maxRetries, let retryCount = self?.retryCount { + self?.retryWithExponentialDelay(retryCount: maxRetries - retryCount) { + self?.fetchStatus() + } + } + } + } + + private func retryWithExponentialDelay(retryCount: Int, block: @escaping () -> ()) { + // Add some backoff time + let delayTime = TimeInterval( + pow(Double(1 + maxRetries - retryCount), Double(2)) + ) + + DispatchQueue.main.asyncAfter(deadline: .now() + delayTime) { + block() + } + } +} diff --git a/Stripe/PaymentMethodType.swift b/Stripe/PaymentMethodType.swift index 11da32acf89..295a9cec84c 100644 --- a/Stripe/PaymentMethodType.swift +++ b/Stripe/PaymentMethodType.swift @@ -31,6 +31,7 @@ extension PaymentSheet { case linkInstantDebit case link case dynamic(String) + case UPI public init(from str: String) { switch(str) { @@ -40,6 +41,8 @@ extension PaymentSheet { self = .USBankAccount case STPPaymentMethod.string(from: .link): self = .link + case STPPaymentMethod.string(from: .UPI): + self = .UPI default: self = .dynamic(str) } @@ -55,6 +58,8 @@ extension PaymentSheet { return STPPaymentMethod.string(from: .link) case .linkInstantDebit: return nil + case .UPI: + return STPPaymentMethod.string(from: .UPI) case .dynamic(let str): return str } diff --git a/Stripe/PaymentOption+Images.swift b/Stripe/PaymentOption+Images.swift index 3d4d4146cdc..c6c0a822c46 100644 --- a/Stripe/PaymentOption+Images.swift +++ b/Stripe/PaymentOption+Images.swift @@ -144,6 +144,8 @@ extension STPPaymentMethodType { return .pm_type_aubecsdebit case .USBankAccount, .linkInstantDebit: return .pm_type_us_bank + case .UPI: + return .pm_type_upi default: return nil } diff --git a/Stripe/PaymentSheet+API.swift b/Stripe/PaymentSheet+API.swift index a623db39bbd..0b986ffc9d6 100644 --- a/Stripe/PaymentSheet+API.swift +++ b/Stripe/PaymentSheet+API.swift @@ -494,6 +494,11 @@ private func isEqual(_ lhs: STPPaymentIntentShippingDetails?, _ rhs: STPPaymentI /// Internal authentication context for PaymentSheet magic protocol PaymentSheetAuthenticationContext: STPAuthenticationContext { + var appearance: PaymentSheet.Appearance { get } + func present(_ threeDS2ChallengeViewController: UIViewController, completion: @escaping () -> Void) func dismiss(_ threeDS2ChallengeViewController: UIViewController) + + func present(_ viewController: BottomSheetContentViewController) + func dismiss(_ viewController: BottomSheetContentViewController) } diff --git a/Stripe/PaymentSheetFlowController.swift b/Stripe/PaymentSheetFlowController.swift index 8ad264dff96..2bcdc275173 100644 --- a/Stripe/PaymentSheetFlowController.swift +++ b/Stripe/PaymentSheetFlowController.swift @@ -241,7 +241,7 @@ extension PaymentSheet { return } - let authenticationContext = AuthenticationContext(presentingViewController: presentingViewController) + let authenticationContext = AuthenticationContext(presentingViewController: presentingViewController, appearance: configuration.appearance) PaymentSheet.confirm( configuration: configuration, @@ -325,6 +325,15 @@ extension PaymentSheet.FlowController: ChoosePaymentOptionViewControllerDelegate /// For internal SDK use only @objc(STP_Internal_AuthenticationContext) class AuthenticationContext: NSObject, PaymentSheetAuthenticationContext { + func present(_ viewController: BottomSheetContentViewController) { + presentingViewController.present(viewController, animated: true, completion: nil) + + } + + func dismiss(_ viewController: BottomSheetContentViewController) { + viewController.dismiss(animated: true, completion: nil) + } + func present(_ threeDS2ChallengeViewController: UIViewController, completion: @escaping () -> Void) { presentingViewController.present(threeDS2ChallengeViewController, animated: true, completion: nil) } @@ -334,9 +343,11 @@ class AuthenticationContext: NSObject, PaymentSheetAuthenticationContext { } let presentingViewController: UIViewController + let appearance: PaymentSheet.Appearance - init(presentingViewController: UIViewController) { + init(presentingViewController: UIViewController, appearance: PaymentSheet.Appearance) { self.presentingViewController = presentingViewController + self.appearance = appearance super.init() } func authenticationPresentingViewController() -> UIViewController { diff --git a/Stripe/PaymentSheetFormFactory+UPI.swift b/Stripe/PaymentSheetFormFactory+UPI.swift new file mode 100644 index 00000000000..0a66628b272 --- /dev/null +++ b/Stripe/PaymentSheetFormFactory+UPI.swift @@ -0,0 +1,33 @@ +// +// PaymentSheetFormFactory+UPI.swift +// StripeiOS +// +// Created by Nick Porter on 9/6/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit +@_spi(STP) import StripeUICore +@_spi(STP) import StripeCore + +extension PaymentSheetFormFactory { + + func makeUPI() -> FormElement { + return FormElement(autoSectioningElements: [makeUPIHeader(), makeVPAField()], theme: theme) + } + + private func makeUPIHeader() -> StaticElement { + return makeSectionTitleLabelWith(text: STPLocalizedString("Buy using a VPA number", + "Header text shown above a VPA text field")) + } + + private func makeVPAField() -> PaymentMethodElementWrapper { + return PaymentMethodElementWrapper(TextFieldElement.makeVPA(theme: theme)) { vpa, params in + let upi = params.paymentMethodParams.upi ?? STPPaymentMethodUPIParams() + upi.vpa = vpa.text + params.paymentMethodParams.upi = upi + return params + } + } +} diff --git a/Stripe/PaymentSheetFormFactory.swift b/Stripe/PaymentSheetFormFactory.swift index 04e1e503711..328249654e9 100644 --- a/Stripe/PaymentSheetFormFactory.swift +++ b/Stripe/PaymentSheetFormFactory.swift @@ -87,6 +87,8 @@ class PaymentSheetFormFactory { return ConnectionsElement() } else if paymentMethod == .USBankAccount { return makeUSBankAccount(merchantName: configuration.merchantDisplayName) + } else if paymentMethod == .UPI { + return makeUPI() } // 2. Element-based forms defined in JSON @@ -326,7 +328,7 @@ extension PaymentSheetFormFactory { "US Bank Account copy title for Mobile payment element form")) } - private func makeSectionTitleLabelWith(text: String) -> StaticElement { + func makeSectionTitleLabelWith(text: String) -> StaticElement { let label = UILabel() label.text = text label.font = theme.fonts.subheadline diff --git a/Stripe/PollingViewController.swift b/Stripe/PollingViewController.swift new file mode 100644 index 00000000000..663780192d6 --- /dev/null +++ b/Stripe/PollingViewController.swift @@ -0,0 +1,227 @@ +// +// PollingViewController.swift +// StripeiOS +// +// Created by Nick Porter on 9/2/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import Foundation +import UIKit +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore + +/// For internal SDK use only +@objc(STP_Internal_PollingViewController) +class PollingViewController: UIViewController { + + // MARK: State + + private let deadline = Date().addingTimeInterval(60 * 5) // in 5 minutes + private var oneSecondTimer = Timer() + private let currentAction: STPPaymentHandlerActionParams + private let apperance: PaymentSheet.Appearance + + private lazy var intentPoller: IntentStatusPoller = { + guard let currentAction = currentAction as? STPPaymentHandlerPaymentIntentActionParams, + let clientSecret = currentAction.paymentIntent?.clientSecret else { fatalError() } + + let intentPoller = IntentStatusPoller(apiClient: currentAction.apiClient, + clientSecret: clientSecret, + maxRetries: 12) + intentPoller.delegate = self + return intentPoller + }() + + private var timeRemaining: TimeInterval { + return Date().compatibleDistance(to: deadline) + } + + private var dateFormatter: DateComponentsFormatter { + let formatter = DateComponentsFormatter() + if timeRemaining > 60 { + formatter.zeroFormattingBehavior = .dropLeading + } else { + formatter.zeroFormattingBehavior = .pad + } + formatter.allowedUnits = [.minute, .second] + return formatter + } + + // MARK: Views + + lazy var stackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [actvityIndicator, + titleLabel, + instructionLabel, + cancelButton]) + stackView.directionalLayoutMargins = PaymentSheetUI.defaultMargins + stackView.isLayoutMarginsRelativeArrangement = true + stackView.axis = .vertical + stackView.spacing = 14 + + return stackView + }() + + private lazy var actvityIndicator: ActivityIndicator = { + let indicator = ActivityIndicator(size: .large) + indicator.tintColor = apperance.colors.primary + indicator.startAnimating() + return indicator + }() + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.numberOfLines = 0 + label.textAlignment = .center + label.text = .Localized.approve_payment + label.textColor = apperance.colors.text + label.font = UIFont.preferredFont(forTextStyle: .body) + label.sizeToFit() + return label + }() + + private lazy var instructionLabel: UILabel = { + let label = UILabel() + label.numberOfLines = 0 + label.textAlignment = .center + label.font = UIFont.preferredFont(forTextStyle: .subheadline) + label.textColor = apperance.colors.textSecondary + label.text = String( + format: .Localized.open_upi_app, + dateFormatter.string(from: timeRemaining) ?? "" + ) + label.sizeToFit() + return label + }() + + private lazy var cancelButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle(.Localized.cancel_pay_another_way, for: .normal) + button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .subheadline) + button.addTarget(self, action: #selector(didTapCancel), for: .touchUpInside) + button.tintColor = apperance.colors.primary + return button + }() + + internal lazy var navigationBar: SheetNavigationBar = { + let navBar = SheetNavigationBar(isTestMode: false, + appearance: apperance) + navBar.setStyle(.none) + return navBar + }() + + // MARK: Overrides + + init(currentAction: STPPaymentHandlerActionParams, appearance: PaymentSheet.Appearance) { + self.currentAction = currentAction + self.apperance = appearance + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + let stackView = UIStackView(arrangedSubviews: [stackView]) + stackView.spacing = PaymentSheetUI.defaultPadding + stackView.axis = .vertical + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.backgroundColor = apperance.colors.background + view.addSubview(stackView) + + NSLayoutConstraint.activate([ + stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor), + stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor), + stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), + view.heightAnchor.constraint(equalTo: stackView.heightAnchor) + ]) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + self.intentPoller.beginPolling() + } + oneSecondTimer = Timer.scheduledTimer(timeInterval: 1, + target: self, + selector: (#selector(updateTimer)), + userInfo: nil, + repeats: true) // TODO will get this out of sync if shown, then hide, then show? + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + intentPoller.suspendPolling() + } + + // MARK: Handlers + + @objc func didTapCancel() { + currentAction.complete(with: .canceled, error: nil) + dismiss() + } + + private func dismiss() { + if let authContext = currentAction.authenticationContext as? PaymentSheetAuthenticationContext { + authContext.authenticationContextWillDismiss?(self) + authContext.dismiss(self) + } + } + + // MARK: Timer + + @objc func updateTimer() { + guard timeRemaining > 0 else { + oneSecondTimer.invalidate() + // Do one last force poll after 5 min + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + self.intentPoller.forcePoll() + } + // TODO(porter) dismiss and cancel after x seconds if force poll doesn't result in a success + return + } + + instructionLabel.text = String( + format: .Localized.open_upi_app, + dateFormatter.string(from: timeRemaining) ?? "" + ) + } + +} + +// MARK: BottomSheetContentViewController + +extension PollingViewController: BottomSheetContentViewController { + + var allowsDragToDismiss: Bool { + return false + } + + func didTapOrSwipeToDismiss() { + // no-op + } + + var requiresFullScreen: Bool { + return false + } +} + +// MARK: IntentStatusPollerDelegate + +extension PollingViewController: IntentStatusPollerDelegate { + func didUpdate(paymentIntent: STPPaymentIntent) { + // TODO(porter) Handle other intent terminal states? + guard let currentAction = currentAction as? STPPaymentHandlerPaymentIntentActionParams else { return } + + if paymentIntent.status == .succeeded { + currentAction.paymentIntent = paymentIntent + currentAction.complete(with: .succeeded, error: nil) + dismiss() + } + } +} diff --git a/Stripe/Resources/Images/PaymentMethods/icon-pm-upi@3x.png b/Stripe/Resources/Images/PaymentMethods/icon-pm-upi@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..254d65f325a1a164b3f08cc98a0db72b5a983ff7 GIT binary patch literal 3260 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 SrFVZL0HafQY>nR8^Zx-Vgrj`` literal 0 HcmV?d00001 diff --git a/Stripe/Resources/Localizations/en.lproj/Localizable.strings b/Stripe/Resources/Localizations/en.lproj/Localizable.strings index 5e931e7c6d1..b625a851014 100644 --- a/Stripe/Resources/Localizations/en.lproj/Localizable.strings +++ b/Stripe/Resources/Localizations/en.lproj/Localizable.strings @@ -48,6 +48,9 @@ /* Text for Apple Pay payment method */ "Apple Pay" = "Apple Pay"; +/* Text on a screen asking the user to approve a payment */ +"Approve payment" = "Approve payment"; + /* Caption for Apartment/Address line 2 field on address form */ "Apt." = "Apt."; @@ -94,6 +97,9 @@ /* Klarna buy now or pay later copy */ "Buy now or pay later with Klarna." = "Buy now or pay later with Klarna."; +/* Header text shown above a VPA text field */ +"Buy using a VPA number" = "Buy using a VPA number"; + /* Text providing link to terms for ACH payments */ "By continuing, you agree to authorize payments pursuant to these terms." = "By continuing, you agree to authorize payments pursuant to these terms."; @@ -112,6 +118,9 @@ /* Mandate text with link to terms when saving a bank account payment method to a merchant (merchant name replaces %@). */ "By saving your bank account for %@ you agree to authorize payments pursuant to these terms." = "By saving your bank account for %@ you agree to authorize payments pursuant to these terms."; +/* Button text on a screen asking the user to approve a payment */ +"Cancel payment and pay another way" = "Cancel payment and pay another way"; + /* Payment Method for credit card Title for credit card number entry field */ "Card" = "Card"; @@ -260,6 +269,9 @@ re-entering the security code (CVV/CVC). */ /* Button to pay with a Bank Account (using FPX). */ "Online Banking (FPX)" = "Online Banking (FPX)"; +/* Countdown timer text on a screen asking the user to approve a payment */ +"Open your UPI app to approve your payment within %@" = "Open your UPI app to approve your payment within %@"; + /* Separator label between two options */ "Or" = "Or"; diff --git a/Stripe/STPIntentAction.swift b/Stripe/STPIntentAction.swift index 7c6bdbef9d9..a5427f68c90 100644 --- a/Stripe/STPIntentAction.swift +++ b/Stripe/STPIntentAction.swift @@ -51,6 +51,9 @@ import Foundation /// Contains details describing the microdeposits verification flow for US Bank Account payments case verifyWithMicrodeposits + + /// The action type for UPI payment methods. The customer must complete the transaction in their banking app within 5 minutes. + case upiAwaitNotification /// Parse the string and return the correct `STPIntentActionType`, /// or `STPIntentActionTypeUnknown` if it's unrecognized by this version of the SDK. @@ -73,6 +76,8 @@ import Foundation self = .BLIKAuthorize case "verify_with_microdeposits": self = .verifyWithMicrodeposits + case "upi_await_notification": + self = .upiAwaitNotification default: self = .unknown } @@ -99,6 +104,8 @@ import Foundation return "boleto_display_details" case .verifyWithMicrodeposits: return "verify_with_microdeposits" + case .upiAwaitNotification: + return "upi_await_notification" case .unknown: break } @@ -183,6 +190,8 @@ public class STPIntentAction: NSObject { if let verifyWithMicrodeposits = verifyWithMicrodeposits { props.append("verifyWithMicrodeposits = \(verifyWithMicrodeposits)") } + case .upiAwaitNotification: + props.append("upiAwaitNotification != nil") case .unknown: // unrecognized type, just show the original dictionary for debugging help props.append("allResponseFields = \(allResponseFields)") @@ -285,6 +294,8 @@ extension STPIntentAction: STPAPIResponseDecodable { if verifyWithMicrodeposits == nil { type = .unknown } + case .upiAwaitNotification: + break // no additional details } return STPIntentAction( diff --git a/Stripe/STPPaymentHandler.swift b/Stripe/STPPaymentHandler.swift index c13e87caea4..75b8fa0b441 100644 --- a/Stripe/STPPaymentHandler.swift +++ b/Stripe/STPPaymentHandler.swift @@ -1239,6 +1239,13 @@ public class STPPaymentHandler: NSObject { // which may take 1-2 business days currentAction.complete(with: .succeeded, error: nil) + case .upiAwaitNotification: + guard let presentingVC = currentAction.authenticationContext as? PaymentSheetAuthenticationContext else { + return + } + presentingVC.present(PollingViewController(currentAction: currentAction, + appearance: presentingVC.appearance)) + break @unknown default: fatalError() } @@ -1566,7 +1573,8 @@ public class STPPaymentHandler: NSObject { return false case .OXXODisplayDetails, .boletoDisplayDetails, - .verifyWithMicrodeposits: + .verifyWithMicrodeposits, + .upiAwaitNotification: return true } } @@ -1590,7 +1598,7 @@ public class STPPaymentHandler: NSObject { case .useStripeSDK: threeDSSourceID = nextAction.useStripeSDK?.threeDSSourceID case .OXXODisplayDetails, .alipayHandleRedirect, .unknown, .BLIKAuthorize, - .weChatPayRedirectToApp, .boletoDisplayDetails, .verifyWithMicrodeposits: + .weChatPayRedirectToApp, .boletoDisplayDetails, .verifyWithMicrodeposits, .upiAwaitNotification: break @unknown default: fatalError() diff --git a/Stripe/SheetNavigationBar.swift b/Stripe/SheetNavigationBar.swift index eb80f74f955..f7ff4d2e13c 100644 --- a/Stripe/SheetNavigationBar.swift +++ b/Stripe/SheetNavigationBar.swift @@ -124,6 +124,7 @@ class SheetNavigationBar: UIView { enum Style { case close(showAdditionalButton: Bool) case back + case none } func setStyle(_ style: Style) { @@ -140,6 +141,10 @@ class SheetNavigationBar: UIView { bringSubviewToFront(additionalButton) } backButton.isHidden = true + case .none: + closeButton.isHidden = true + additionalButton.isHidden = true + backButton.isHidden = true } } diff --git a/Stripe/String+Localized.swift b/Stripe/String+Localized.swift index ada4f387be4..d163cd741b8 100644 --- a/Stripe/String+Localized.swift +++ b/Stripe/String+Localized.swift @@ -195,6 +195,27 @@ extension String.Localized { "Separator label between two options" ) } + + static var approve_payment: String { + STPLocalizedString( + "Approve payment", + "Text on a screen asking the user to approve a payment" + ) + } + + static var cancel_pay_another_way: String { + STPLocalizedString( + "Cancel payment and pay another way", + "Button text on a screen asking the user to approve a payment" + ) + } + + static var open_upi_app: String { + STPLocalizedString( + "Open your UPI app to approve your payment within %@", + "Countdown timer text on a screen asking the user to approve a payment" + ) + } } // MARK: - Legacy strings diff --git a/StripeUICore/StripeUICore.xcodeproj/project.pbxproj b/StripeUICore/StripeUICore.xcodeproj/project.pbxproj index 14387653baf..04a8ac93a3a 100644 --- a/StripeUICore/StripeUICore.xcodeproj/project.pbxproj +++ b/StripeUICore/StripeUICore.xcodeproj/project.pbxproj @@ -17,8 +17,10 @@ 36E1894C26FBD60200A57EF4 /* PhoneNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E1894B26FBD60200A57EF4 /* PhoneNumber.swift */; }; 36E1895027067A8700A57EF4 /* String+CountryEmoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E1894F27067A8700A57EF4 /* String+CountryEmoji.swift */; }; 61078DA628C28D2D007C7001 /* TextOrDropdownElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61078DA528C28D2D007C7001 /* TextOrDropdownElement.swift */; }; + 6138B89C28CAA6ED0096E7FC /* STPVPANumberValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6138B89B28CAA6ED0096E7FC /* STPVPANumberValidator.swift */; }; 614E0763284FDCE100FB70F4 /* icon_clear@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 614E0762284FDCE100FB70F4 /* icon_clear@3x.png */; }; 61A6294527E401C900C8DF08 /* CALayer+StripeUICore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A6294427E401C900C8DF08 /* CALayer+StripeUICore.swift */; }; + 61E3A03D28D4232300B3A019 /* STPVPANumberValidatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61E3A03C28D4232300B3A019 /* STPVPANumberValidatorTest.swift */; }; 6B573C9727C5BEF00082C0B3 /* TextFieldElement+AccountFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B573C9627C5BEF00082C0B3 /* TextFieldElement+AccountFactory.swift */; }; 6B573C9927C5C0ED0082C0B3 /* BSBNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B573C9827C5C0ED0082C0B3 /* BSBNumber.swift */; }; 6B573C9B27C847730082C0B3 /* TestFieldElement+AccountFactoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B573C9A27C847730082C0B3 /* TestFieldElement+AccountFactoryTest.swift */; }; @@ -134,8 +136,10 @@ 36E1894B26FBD60200A57EF4 /* PhoneNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneNumber.swift; sourceTree = ""; }; 36E1894F27067A8700A57EF4 /* String+CountryEmoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+CountryEmoji.swift"; sourceTree = ""; }; 61078DA528C28D2D007C7001 /* TextOrDropdownElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextOrDropdownElement.swift; sourceTree = ""; }; + 6138B89B28CAA6ED0096E7FC /* STPVPANumberValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPVPANumberValidator.swift; sourceTree = ""; }; 614E0762284FDCE100FB70F4 /* icon_clear@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_clear@3x.png"; sourceTree = ""; }; 61A6294427E401C900C8DF08 /* CALayer+StripeUICore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CALayer+StripeUICore.swift"; sourceTree = ""; }; + 61E3A03C28D4232300B3A019 /* STPVPANumberValidatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPVPANumberValidatorTest.swift; sourceTree = ""; }; 6B573C9627C5BEF00082C0B3 /* TextFieldElement+AccountFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TextFieldElement+AccountFactory.swift"; sourceTree = ""; }; 6B573C9827C5C0ED0082C0B3 /* BSBNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BSBNumber.swift; sourceTree = ""; }; 6B573C9A27C847730082C0B3 /* TestFieldElement+AccountFactoryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TestFieldElement+AccountFactoryTest.swift"; sourceTree = ""; }; @@ -515,6 +519,7 @@ isa = PBXGroup; children = ( E6752DAF26F421350062B821 /* STPEmailAddressValidator.swift */, + 6138B89B28CAA6ED0096E7FC /* STPVPANumberValidator.swift */, 36E1894B26FBD60200A57EF4 /* PhoneNumber.swift */, 6B573C9827C5C0ED0082C0B3 /* BSBNumber.swift */, ); @@ -544,6 +549,7 @@ isa = PBXGroup; children = ( E6752DB626F4228F0062B821 /* STPEmailAddressValidatorTest.swift */, + 61E3A03C28D4232300B3A019 /* STPVPANumberValidatorTest.swift */, 36397F612714FDEF0059DED7 /* PhoneNumberTests.swift */, 6B573C9C27C851950082C0B3 /* BSBNumberTests.swift */, ); @@ -819,6 +825,7 @@ D0031A2C2709D6A20050F242 /* UITraitCollection+StripeUICore.swift in Sources */, E6752D7C26F414920062B821 /* UIView+StripeUICore.swift in Sources */, E60564CC27056DD80023B0B6 /* Locale+StripeUICore.swift in Sources */, + 6138B89C28CAA6ED0096E7FC /* STPVPANumberValidator.swift in Sources */, E6B0DDD02714C19600CE86DC /* DoneButtonToolbar.swift in Sources */, E6AFFB2A26E9787D0067462F /* STPLocalizedString.swift in Sources */, B647A5F72889CEDA00099832 /* AddressSectionElement+DummyAddressLine.swift in Sources */, @@ -902,6 +909,7 @@ E6B0DDCA2711218100CE86DC /* DropdownFieldElementTest.swift in Sources */, E61ADACD270E671E004ED998 /* AddressSpecProviderTest.swift in Sources */, E6752DB926F422900062B821 /* TextFieldElement+AddressFactoryTest.swift in Sources */, + 61E3A03D28D4232300B3A019 /* STPVPANumberValidatorTest.swift in Sources */, 36397F622714FDEF0059DED7 /* PhoneNumberTests.swift in Sources */, B6DA050B2864F83B003696BB /* PhoneNumberElementTests.swift in Sources */, D0CF9137273B397E00EE2E60 /* UIColor+StripeUICoreTests.swift in Sources */, diff --git a/StripeUICore/StripeUICore/Resources/Localizations/en.lproj/Localizable.strings b/StripeUICore/StripeUICore/Resources/Localizations/en.lproj/Localizable.strings index 413593d6337..5fdb8fd515f 100644 --- a/StripeUICore/StripeUICore/Resources/Localizations/en.lproj/Localizable.strings +++ b/StripeUICore/StripeUICore/Resources/Localizations/en.lproj/Localizable.strings @@ -78,6 +78,9 @@ Label of an address field */ /* Error description for incomplete phone number */ "Incomplete phone number" = "Incomplete phone number"; +/* Error message when VPA is invalid */ +"Invalid VPA" = "Invalid VPA"; + /* Label of an address field */ "Island" = "Island"; @@ -153,6 +156,9 @@ Label of an address field */ /* Accessibility hint indicating to use the accessibility rotor to open links. The word 'rotor' should be localized to match Apple's language here: https://support.apple.com/HT204783 */ "Use rotor to access links" = "Use rotor to access links"; +/* Label for VPA number field on form */ +"VPA number" = "VPA number"; + /* Error message when email is invalid */ "Your email is invalid." = "Your email is invalid."; diff --git a/StripeUICore/StripeUICore/Source/Elements/Factories/TextFieldElement+Factory.swift b/StripeUICore/StripeUICore/Source/Elements/Factories/TextFieldElement+Factory.swift index 46775e0f6b8..a2b32e7c531 100644 --- a/StripeUICore/StripeUICore/Source/Elements/Factories/TextFieldElement+Factory.swift +++ b/StripeUICore/StripeUICore/Source/Elements/Factories/TextFieldElement+Factory.swift @@ -96,6 +96,33 @@ import UIKit return TextFieldElement(configuration: EmailConfiguration(defaultValue: defaultValue), theme: theme) } + // MARK: VPA + + struct VPAConfiguration: TextFieldElementConfiguration { + public let label = String.Localized.vpa + public let disallowedCharacters: CharacterSet = .whitespacesAndNewlines + let invalidError = Error.invalid( + localizedDescription: .Localized.invalid_vpa + ) + + public func validate(text: String, isOptional: Bool) -> ValidationState { + guard !text.isEmpty else { + return isOptional ? .valid : .invalid(Error.empty) + } + + return STPVPANumberValidator.stringIsValidVPANumber(text) ? .valid : .invalid(invalidError) + } + + public func keyboardProperties(for text: String) -> TextFieldElement.KeyboardProperties { + return .init(type: .emailAddress, textContentType: .emailAddress, autocapitalization: .none) + } + + } + + static func makeVPA(theme: ElementsUITheme = .default) -> TextFieldElement { + return TextFieldElement(configuration: VPAConfiguration(), theme: theme) + } + // MARK: - Phone number struct PhoneNumberConfiguration: TextFieldElementConfiguration { static let incompleteError = Error.incomplete(localizedDescription: .Localized.incomplete_phone_number) diff --git a/StripeUICore/StripeUICore/Source/Helpers/String+Localized.swift b/StripeUICore/StripeUICore/Source/Helpers/String+Localized.swift index 8a16c454190..8c652a82a9f 100644 --- a/StripeUICore/StripeUICore/Source/Helpers/String+Localized.swift +++ b/StripeUICore/StripeUICore/Source/Helpers/String+Localized.swift @@ -278,4 +278,14 @@ import Foundation "Accessibility hint indicating to use the accessibility rotor to open links. The word 'rotor' should be localized to match Apple's language here: https://support.apple.com/HT204783" ) } + + // MARK: UPI + + static var vpa: String { + STPLocalizedString("VPA number", "Label for VPA number field on form") + } + + static var invalid_vpa: String { + STPLocalizedString("Invalid VPA", "Error message when VPA is invalid") + } } diff --git a/StripeUICore/StripeUICore/Source/Validators/STPVPANumberValidator.swift b/StripeUICore/StripeUICore/Source/Validators/STPVPANumberValidator.swift new file mode 100644 index 00000000000..64915c5efaa --- /dev/null +++ b/StripeUICore/StripeUICore/Source/Validators/STPVPANumberValidator.swift @@ -0,0 +1,27 @@ +// +// STPVPANumberValidator.swift +// StripeUICore +// +// Created by Nick Porter on 9/8/22. +// + +import Foundation + +@_spi(STP) public class STPVPANumberValidator: NSObject { + public class func stringIsValidPartialVPANumber(_ string: String?) -> Bool { + guard let string = string else { + return true // an empty string isn't *invalid* + } + return (string.components(separatedBy: "@").count - 1) <= 1 + } + + public class func stringIsValidVPANumber(_ string: String?) -> Bool { + if string == nil { + return false + } + // regex from https://stackoverflow.com/questions/55143204/how-to-validate-a-upi-id-using-regex + let pattern = "[a-zA-Z0-9.\\-_]{2,256}@[a-zA-Z]{2,64}" + let predicate = NSPredicate(format: "SELF MATCHES %@", pattern) + return predicate.evaluate(with: string?.lowercased()) + } +} diff --git a/StripeUICore/StripeUICoreTests/Unit/Validators/STPVPANumberValidatorTest.swift b/StripeUICore/StripeUICoreTests/Unit/Validators/STPVPANumberValidatorTest.swift new file mode 100644 index 00000000000..a2ff4675dfc --- /dev/null +++ b/StripeUICore/StripeUICoreTests/Unit/Validators/STPVPANumberValidatorTest.swift @@ -0,0 +1,26 @@ +// +// STPVPANumberValidatorTest.swift +// StripeUICoreTests +// +// Created by Nick Porter on 9/15/22. +// + +import Foundation +import XCTest +@_spi(STP) import StripeUICore + +class STPVPANumberValidatorTest: XCTestCase { + func testValidVPAs() { + XCTAssert(STPVPANumberValidator.stringIsValidVPANumber("stripe@icici")) + XCTAssert(STPVPANumberValidator.stringIsValidVPANumber("stripe@okaxis")) + XCTAssert(STPVPANumberValidator.stringIsValidVPANumber("stripe.9897605011@paytm")) + XCTAssert(STPVPANumberValidator.stringIsValidVPANumber("payment.pending@stripeupi")) + } + + func testInvalidVPAs() { + XCTAssertFalse(STPVPANumberValidator.stringIsValidVPANumber("")) + XCTAssertFalse(STPVPANumberValidator.stringIsValidVPANumber("test@stripe.com")) + XCTAssertFalse(STPVPANumberValidator.stringIsValidVPANumber("stripe")) + XCTAssertFalse(STPVPANumberValidator.stringIsValidVPANumber("stripe@gmail.com")) + } +} diff --git a/Tests/ReferenceImages_64/PaymentSheetUITest.PaymentSheetSnapshotTests/testPaymentSheet_LPM_upi_only@3x.png b/Tests/ReferenceImages_64/PaymentSheetUITest.PaymentSheetSnapshotTests/testPaymentSheet_LPM_upi_only@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..d44edf424f0c00e73a70f71be08fd88c7e56d27b GIT binary patch literal 83214 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 MXN`4=wBTX?4{bbVPXGV_ literal 0 HcmV?d00001