diff --git a/StripePaymentSheet/StripePaymentSheet.xcodeproj/project.pbxproj b/StripePaymentSheet/StripePaymentSheet.xcodeproj/project.pbxproj index 7722b7733b4..dcc7888e314 100644 --- a/StripePaymentSheet/StripePaymentSheet.xcodeproj/project.pbxproj +++ b/StripePaymentSheet/StripePaymentSheet.xcodeproj/project.pbxproj @@ -57,6 +57,22 @@ 31699A832BE183D40048677F /* DownloadManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31699A822BE183D40048677F /* DownloadManagerTest.swift */; }; 316B33122B5F171C0008D2E5 /* UserDefaults+StripePaymentSheetTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316B33112B5F171C0008D2E5 /* UserDefaults+StripePaymentSheetTest.swift */; }; 31AD3BE72B0C2D080080C800 /* UIApplication+StripePaymentSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AD3BE62B0C2D080080C800 /* UIApplication+StripePaymentSheet.swift */; }; + 31CC9B792CB5F69600E84A38 /* LinkNavigationBarSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC9B712CB5F69600E84A38 /* LinkNavigationBarSnapshotTests.swift */; }; + 31CC9B7D2CB5F69600E84A38 /* ButtonLinkSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC9B6C2CB5F69600E84A38 /* ButtonLinkSnapshotTests.swift */; }; + 31CC9B7E2CB5F69600E84A38 /* LinkNoticeViewSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC9B722CB5F69600E84A38 /* LinkNoticeViewSnapshotTests.swift */; }; + 31CC9B7F2CB5F69600E84A38 /* LinkPopupURLParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC9B742CB5F69600E84A38 /* LinkPopupURLParserTests.swift */; }; + 31CC9B802CB5F69600E84A38 /* LinkToastSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC9B752CB5F69600E84A38 /* LinkToastSnapshotTests.swift */; }; + 31CC9B812CB5F69600E84A38 /* LinkInstantDebitMandateViewSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC9B702CB5F69600E84A38 /* LinkInstantDebitMandateViewSnapshotTests.swift */; }; + 31CC9B822CB5F69600E84A38 /* LinkURLGeneratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC9B762CB5F69600E84A38 /* LinkURLGeneratorTests.swift */; }; + 31CC9B852CB5F69600E84A38 /* LinkBadgeViewSnapshotTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC9B6D2CB5F69600E84A38 /* LinkBadgeViewSnapshotTest.swift */; }; + 31CC9B972CB5F74A00E84A38 /* LinkNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC9B882CB5F74A00E84A38 /* LinkNavigationBar.swift */; }; + 31CC9B982CB5F74A00E84A38 /* LinkNoticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC9B8A2CB5F74A00E84A38 /* LinkNoticeView.swift */; }; + 31CC9B9A2CB5F74A00E84A38 /* LinkToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC9B932CB5F74A00E84A38 /* LinkToast.swift */; }; + 31CC9B9C2CB5F74A00E84A38 /* LinkBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC9B862CB5F74A00E84A38 /* LinkBadgeView.swift */; }; + 31CC9BA12CB5F93100E84A38 /* Button+Link.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC9BA02CB5F93100E84A38 /* Button+Link.swift */; }; + 31CC9BA42CB5F9D400E84A38 /* LinkKeyboardAvoidingScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC9BA32CB5F9D400E84A38 /* LinkKeyboardAvoidingScrollView.swift */; }; + 31CC9BA52CB5F9D400E84A38 /* LinkInstantDebitMandateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC9BA22CB5F9D400E84A38 /* LinkInstantDebitMandateView.swift */; }; + 31CC9BA72CB610A300E84A38 /* ConfirmButton+Link.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CC9BA62CB610A300E84A38 /* ConfirmButton+Link.swift */; }; 31CDFC362BA8E66200B3DD91 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 31CDFC352BA8E66200B3DD91 /* PrivacyInfo.xcprivacy */; }; 335A19D93A5979557DB4CA4D /* PaymentMethodElementWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5012364ED0F2EEC6EC2AB52 /* PaymentMethodElementWrapper.swift */; }; 34CF08CBC636F596B8BA4C12 /* TextFieldElement+CardTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7F5905759525C9BF8BD4 /* TextFieldElement+CardTest.swift */; }; @@ -418,6 +434,22 @@ 31699A822BE183D40048677F /* DownloadManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadManagerTest.swift; sourceTree = ""; }; 316B33112B5F171C0008D2E5 /* UserDefaults+StripePaymentSheetTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+StripePaymentSheetTest.swift"; sourceTree = ""; }; 31AD3BE62B0C2D080080C800 /* UIApplication+StripePaymentSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "UIApplication+StripePaymentSheet.swift"; path = "StripePaymentSheet/Source/Categories/UIApplication+StripePaymentSheet.swift"; sourceTree = ""; }; + 31CC9B6C2CB5F69600E84A38 /* ButtonLinkSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonLinkSnapshotTests.swift; sourceTree = ""; }; + 31CC9B6D2CB5F69600E84A38 /* LinkBadgeViewSnapshotTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkBadgeViewSnapshotTest.swift; sourceTree = ""; }; + 31CC9B702CB5F69600E84A38 /* LinkInstantDebitMandateViewSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkInstantDebitMandateViewSnapshotTests.swift; sourceTree = ""; }; + 31CC9B712CB5F69600E84A38 /* LinkNavigationBarSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNavigationBarSnapshotTests.swift; sourceTree = ""; }; + 31CC9B722CB5F69600E84A38 /* LinkNoticeViewSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNoticeViewSnapshotTests.swift; sourceTree = ""; }; + 31CC9B742CB5F69600E84A38 /* LinkPopupURLParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPopupURLParserTests.swift; sourceTree = ""; }; + 31CC9B752CB5F69600E84A38 /* LinkToastSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkToastSnapshotTests.swift; sourceTree = ""; }; + 31CC9B762CB5F69600E84A38 /* LinkURLGeneratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkURLGeneratorTests.swift; sourceTree = ""; }; + 31CC9B862CB5F74A00E84A38 /* LinkBadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkBadgeView.swift; sourceTree = ""; }; + 31CC9B882CB5F74A00E84A38 /* LinkNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNavigationBar.swift; sourceTree = ""; }; + 31CC9B8A2CB5F74A00E84A38 /* LinkNoticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkNoticeView.swift; sourceTree = ""; }; + 31CC9B932CB5F74A00E84A38 /* LinkToast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkToast.swift; sourceTree = ""; }; + 31CC9BA02CB5F93100E84A38 /* Button+Link.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Button+Link.swift"; sourceTree = ""; }; + 31CC9BA22CB5F9D400E84A38 /* LinkInstantDebitMandateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkInstantDebitMandateView.swift; sourceTree = ""; }; + 31CC9BA32CB5F9D400E84A38 /* LinkKeyboardAvoidingScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkKeyboardAvoidingScrollView.swift; sourceTree = ""; }; + 31CC9BA62CB610A300E84A38 /* ConfirmButton+Link.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConfirmButton+Link.swift"; sourceTree = ""; }; 31CDFC352BA8E66200B3DD91 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 32332E0DB0AE12377EBDDEF1 /* PaymentSheetIntentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSheetIntentConfiguration.swift; sourceTree = ""; }; 32BDC53A88FB17F378C6B413 /* CardSectionWithScannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardSectionWithScannerView.swift; sourceTree = ""; }; @@ -812,6 +844,49 @@ path = Services; sourceTree = ""; }; + 31CC9B872CB5F74A00E84A38 /* Badge */ = { + isa = PBXGroup; + children = ( + 31CC9B862CB5F74A00E84A38 /* LinkBadgeView.swift */, + ); + path = Badge; + sourceTree = ""; + }; + 31CC9B892CB5F74A00E84A38 /* NavigationBar */ = { + isa = PBXGroup; + children = ( + 31CC9B882CB5F74A00E84A38 /* LinkNavigationBar.swift */, + ); + path = NavigationBar; + sourceTree = ""; + }; + 31CC9B8B2CB5F74A00E84A38 /* Notice */ = { + isa = PBXGroup; + children = ( + 31CC9B8A2CB5F74A00E84A38 /* LinkNoticeView.swift */, + ); + path = Notice; + sourceTree = ""; + }; + 31CC9B942CB5F74A00E84A38 /* Toast */ = { + isa = PBXGroup; + children = ( + 31CC9B932CB5F74A00E84A38 /* LinkToast.swift */, + ); + path = Toast; + sourceTree = ""; + }; + 31CC9B952CB5F74A00E84A38 /* Components */ = { + isa = PBXGroup; + children = ( + 31CC9B872CB5F74A00E84A38 /* Badge */, + 31CC9B892CB5F74A00E84A38 /* NavigationBar */, + 31CC9B8B2CB5F74A00E84A38 /* Notice */, + 31CC9B942CB5F74A00E84A38 /* Toast */, + ); + path = Components; + sourceTree = ""; + }; 35C0AFFBC393AF76586DAE4A /* Localizations */ = { isa = PBXGroup; children = ( @@ -1093,6 +1168,8 @@ 843180340B5C7406A1117541 /* Views */ = { isa = PBXGroup; children = ( + 31CC9BA22CB5F9D400E84A38 /* LinkInstantDebitMandateView.swift */, + 31CC9BA32CB5F9D400E84A38 /* LinkKeyboardAvoidingScrollView.swift */, 13FB3274557B85BA4C9FA6C0 /* LinkLegalTermsView.swift */, E5240ECFD40B8605939C4E09 /* LinkMoreInfoView.swift */, ); @@ -1102,6 +1179,8 @@ 86FB97D65ED6CB44B3E8B66C /* Extensions */ = { isa = PBXGroup; children = ( + 31CC9BA02CB5F93100E84A38 /* Button+Link.swift */, + 31CC9BA62CB610A300E84A38 /* ConfirmButton+Link.swift */, EFDE97D76542848E7821BA43 /* FormElement+Link.swift */, 04C8047FD8994D3FAA3D1A7A /* Intent+Link.swift */, FD0EACE5F259BDE586A4A20C /* STPAnalyticsClient+Link.swift */, @@ -1305,6 +1384,7 @@ isa = PBXGroup; children = ( F8599012936F1A32FC191F06 /* ACH */, + 31CC9B952CB5F74A00E84A38 /* Components */, FD205A472E1B92651A0BB16F /* Controllers */, E46EF43F3DC5F4348B844EE7 /* Elements */, 86FB97D65ED6CB44B3E8B66C /* Extensions */, @@ -1525,6 +1605,14 @@ FCA28FF8CD5BA829A44CDCE7 /* Link */ = { isa = PBXGroup; children = ( + 31CC9B6C2CB5F69600E84A38 /* ButtonLinkSnapshotTests.swift */, + 31CC9B6D2CB5F69600E84A38 /* LinkBadgeViewSnapshotTest.swift */, + 31CC9B702CB5F69600E84A38 /* LinkInstantDebitMandateViewSnapshotTests.swift */, + 31CC9B712CB5F69600E84A38 /* LinkNavigationBarSnapshotTests.swift */, + 31CC9B722CB5F69600E84A38 /* LinkNoticeViewSnapshotTests.swift */, + 31CC9B742CB5F69600E84A38 /* LinkPopupURLParserTests.swift */, + 31CC9B752CB5F69600E84A38 /* LinkToastSnapshotTests.swift */, + 31CC9B762CB5F69600E84A38 /* LinkURLGeneratorTests.swift */, 22E4212F4A865B5AB5D72F99 /* LinkPopupURLParserTests.swift */, 9872CF28C8CA1D2C5499B8C5 /* LinkURLGeneratorTests.swift */, ); @@ -1734,6 +1822,14 @@ B63B2CF12BF8313D003810F3 /* VerticalPaymentMethodListViewControllerTest.swift in Sources */, 61CB0BD02BED985100E24A4C /* VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift in Sources */, 8180BC3615767F896E2F9355 /* AddressViewControllerSnapshotTests.swift in Sources */, + 31CC9B792CB5F69600E84A38 /* LinkNavigationBarSnapshotTests.swift in Sources */, + 31CC9B7D2CB5F69600E84A38 /* ButtonLinkSnapshotTests.swift in Sources */, + 31CC9B7E2CB5F69600E84A38 /* LinkNoticeViewSnapshotTests.swift in Sources */, + 31CC9B7F2CB5F69600E84A38 /* LinkPopupURLParserTests.swift in Sources */, + 31CC9B802CB5F69600E84A38 /* LinkToastSnapshotTests.swift in Sources */, + 31CC9B812CB5F69600E84A38 /* LinkInstantDebitMandateViewSnapshotTests.swift in Sources */, + 31CC9B822CB5F69600E84A38 /* LinkURLGeneratorTests.swift in Sources */, + 31CC9B852CB5F69600E84A38 /* LinkBadgeViewSnapshotTest.swift in Sources */, B65FE7092BED33EA009A73FC /* VerticalPaymentMethodListViewControllerSnapshotTest.swift in Sources */, 37F750E1C99D6257E845A66E /* BacsDDMandateViewSnapshotTests.swift in Sources */, 694A3B36AC19FC1F87EF0CB1 /* CustomerSheetPaymentMethodAvailabilityTests.swift in Sources */, @@ -1817,6 +1913,10 @@ 06976DDC67A61176FC54AA76 /* Data+SHA256.swift in Sources */, 6180A5CB2C8249D2009D1536 /* UIStackView+Separator.swift in Sources */, B63B2CF52BFBEEAD003810F3 /* PaymentMethodFormViewController.swift in Sources */, + 31CC9B972CB5F74A00E84A38 /* LinkNavigationBar.swift in Sources */, + 31CC9B982CB5F74A00E84A38 /* LinkNoticeView.swift in Sources */, + 31CC9B9A2CB5F74A00E84A38 /* LinkToast.swift in Sources */, + 31CC9B9C2CB5F74A00E84A38 /* LinkBadgeView.swift in Sources */, 108846A3D8EFD1D4DCC0DDBC /* NSAttributedString+Stripe.swift in Sources */, B55EFA2557B5BE39CC12E357 /* STPPaymentMethod+PaymentSheet.swift in Sources */, 3CB64564D5B6F092A2A3A5BE /* STPPaymentMethodParams+PaymentSheet.swift in Sources */, @@ -1847,6 +1947,7 @@ A4FF52567582E9774AE13348 /* PaymentDetails.swift in Sources */, 3E2279C28944A87EC6472101 /* STPAPIClient+Link.swift in Sources */, B8A7575878C5124CF5482097 /* VerificationSession.swift in Sources */, + 31CC9BA72CB610A300E84A38 /* ConfirmButton+Link.swift in Sources */, 9326393E775D29F8C661624B /* STPAPIClient+PaymentSheet.swift in Sources */, AA3A96D74B1659CB5725E95F /* CardExpiryDate.swift in Sources */, 64DE5688E4FBE92E1F49810C /* ExternalPaymentMethod.swift in Sources */, @@ -1902,6 +2003,8 @@ 9E77F1E9F801AE970F1A5BE1 /* CustomerSheetConfiguration.swift in Sources */, AB8E1556F008083257A99E91 /* CustomerSheetError.swift in Sources */, B67D01B62C46FE9900ED8172 /* CVCReconfirmationVerticalViewController.swift in Sources */, + 31CC9BA42CB5F9D400E84A38 /* LinkKeyboardAvoidingScrollView.swift in Sources */, + 31CC9BA52CB5F9D400E84A38 /* LinkInstantDebitMandateView.swift in Sources */, 47B19F96CCEA290541E3B988 /* CardSectionElement.swift in Sources */, 04FEA90F2D0CB9D1C2029D21 /* CardSectionWithScannerView.swift in Sources */, 6BA8D3342B0C1F79008C51FF /* CVCRecollectionElement.swift in Sources */, @@ -1965,6 +2068,7 @@ F4EA474D60D0889E7D48E1CF /* BankAccountInfoView.swift in Sources */, 057A899F4123F3716F2AC0FA /* USBankAccountPaymentMethodElement.swift in Sources */, 985DAA770BC0289D24A5999C /* AddressSearchResult.swift in Sources */, + 31CC9BA12CB5F93100E84A38 /* Button+Link.swift in Sources */, 9DEDA3E0FFF73F9275F5F8F0 /* AutoCompleteViewController.swift in Sources */, A4CD99B2032CBFA7F957B1B8 /* String+AutoComplete.swift in Sources */, 190A1A5A871A82E5B6C09F41 /* BottomSheet3DS2ViewController.swift in Sources */, diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/back_button.imageset/Contents.json b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/back_button.imageset/Contents.json new file mode 100644 index 00000000000..f831e5441f5 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/back_button.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "back_button.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "back_button@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "back_button@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/back_button.imageset/back_button.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/back_button.imageset/back_button.png new file mode 100644 index 00000000000..bfc423f9658 Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/back_button.imageset/back_button.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/back_button.imageset/back_button@2x.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/back_button.imageset/back_button@2x.png new file mode 100644 index 00000000000..8b83c43bce3 Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/back_button.imageset/back_button@2x.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/back_button.imageset/back_button@3x.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/back_button.imageset/back_button@3x.png new file mode 100644 index 00000000000..8b83c43bce3 Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/back_button.imageset/back_button@3x.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_add_bordered.imageset/Contents.json b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_add_bordered.imageset/Contents.json new file mode 100644 index 00000000000..0e0263b9db1 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_add_bordered.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "icon_add_bordered.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_add_bordered@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_add_bordered@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_add_bordered.imageset/icon_add_bordered.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_add_bordered.imageset/icon_add_bordered.png new file mode 100644 index 00000000000..15d2508054d Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_add_bordered.imageset/icon_add_bordered.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_add_bordered.imageset/icon_add_bordered@2x.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_add_bordered.imageset/icon_add_bordered@2x.png new file mode 100644 index 00000000000..87b303cd88f Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_add_bordered.imageset/icon_add_bordered@2x.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_add_bordered.imageset/icon_add_bordered@3x.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_add_bordered.imageset/icon_add_bordered@3x.png new file mode 100644 index 00000000000..4571fd931ce Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_add_bordered.imageset/icon_add_bordered@3x.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_cancel.imageset/Contents.json b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_cancel.imageset/Contents.json new file mode 100644 index 00000000000..2f1b18ab5fa --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_cancel.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "icon_cancel.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_cancel@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_cancel@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_cancel.imageset/icon_cancel.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_cancel.imageset/icon_cancel.png new file mode 100644 index 00000000000..e27b64694d0 Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_cancel.imageset/icon_cancel.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_cancel.imageset/icon_cancel@2x.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_cancel.imageset/icon_cancel@2x.png new file mode 100644 index 00000000000..eeb51c456b4 Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_cancel.imageset/icon_cancel@2x.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_cancel.imageset/icon_cancel@3x.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_cancel.imageset/icon_cancel@3x.png new file mode 100644 index 00000000000..eeb51c456b4 Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_cancel.imageset/icon_cancel@3x.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_error.imageset/Contents.json b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_error.imageset/Contents.json new file mode 100644 index 00000000000..9dee62243c9 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_error.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "icon_link_error.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_link_error@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_link_error@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_error.imageset/icon_link_error.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_error.imageset/icon_link_error.png new file mode 100644 index 00000000000..e521f3bba39 Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_error.imageset/icon_link_error.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_error.imageset/icon_link_error@2x.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_error.imageset/icon_link_error@2x.png new file mode 100644 index 00000000000..eef19e4671e Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_error.imageset/icon_link_error@2x.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_error.imageset/icon_link_error@3x.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_error.imageset/icon_link_error@3x.png new file mode 100644 index 00000000000..84a7a861ec6 Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_error.imageset/icon_link_error@3x.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_success.imageset/Contents.json b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_success.imageset/Contents.json new file mode 100644 index 00000000000..81a04815045 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_success.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "icon_link_success.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_link_success@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_link_success@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_success.imageset/icon_link_success.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_success.imageset/icon_link_success.png new file mode 100644 index 00000000000..17360b0575d Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_success.imageset/icon_link_success.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_success.imageset/icon_link_success@2x.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_success.imageset/icon_link_success@2x.png new file mode 100644 index 00000000000..80e34a26042 Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_success.imageset/icon_link_success@2x.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_success.imageset/icon_link_success@3x.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_success.imageset/icon_link_success@3x.png new file mode 100644 index 00000000000..db8ff7fc4b5 Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_link_success.imageset/icon_link_success@3x.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu.imageset/Contents.json b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu.imageset/Contents.json new file mode 100644 index 00000000000..af57887a5e6 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "icon_menu.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_menu@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_menu@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu.imageset/icon_menu.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu.imageset/icon_menu.png new file mode 100644 index 00000000000..cf55cc41078 Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu.imageset/icon_menu.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu.imageset/icon_menu@2x.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu.imageset/icon_menu@2x.png new file mode 100644 index 00000000000..e5c93ba3640 Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu.imageset/icon_menu@2x.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu.imageset/icon_menu@3x.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu.imageset/icon_menu@3x.png new file mode 100644 index 00000000000..202876bfaad Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu.imageset/icon_menu@3x.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu_horizontal.imageset/Contents.json b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu_horizontal.imageset/Contents.json new file mode 100644 index 00000000000..7edcc25ee4e --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu_horizontal.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "icon_menu_horizontal.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_menu_horizontal@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_menu_horizontal@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu_horizontal.imageset/icon_menu_horizontal.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu_horizontal.imageset/icon_menu_horizontal.png new file mode 100644 index 00000000000..2fc3f99532e Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu_horizontal.imageset/icon_menu_horizontal.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu_horizontal.imageset/icon_menu_horizontal@2x.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu_horizontal.imageset/icon_menu_horizontal@2x.png new file mode 100644 index 00000000000..8f172afd222 Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu_horizontal.imageset/icon_menu_horizontal@2x.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu_horizontal.imageset/icon_menu_horizontal@3x.png b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu_horizontal.imageset/icon_menu_horizontal@3x.png new file mode 100644 index 00000000000..431051c7863 Binary files /dev/null and b/StripePaymentSheet/StripePaymentSheet/Resources/StripePaymentSheet.xcassets/Link/icon_menu_horizontal.imageset/icon_menu_horizontal@3x.png differ diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Categories/String+Localized.swift b/StripePaymentSheet/StripePaymentSheet/Source/Categories/String+Localized.swift index 1b992c75128..53b06bb4921 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/Categories/String+Localized.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/Categories/String+Localized.swift @@ -69,6 +69,10 @@ extension String.Localized { STPLocalizedString("Pay with Link", "Text for the 'Pay with Link' button. 'Link' is a Stripe brand, please do not translate the word 'Link'.") } + static var bank_continue_mandate_text: String { + STPLocalizedString("By continuing, you agree to authorize payments pursuant to these terms.", "Text providing link to terms for ACH payments") + } + static var back: String { STPLocalizedString("Back", "Text for back button") } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Helpers/Images.swift b/StripePaymentSheet/StripePaymentSheet/Source/Helpers/Images.swift index 064e76c144d..6febbdc2516 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/Helpers/Images.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/Helpers/Images.swift @@ -49,6 +49,8 @@ enum Image: String, CaseIterable, ImageMaker { case icon_chevron_left = "icon_chevron_left" case icon_chevron_right = "icon_chevron_right" case icon_lock = "icon_lock" + case icon_menu = "icon_menu" + case icon_menu_horizontal = "icon_menu_horizontal" case icon_plus = "icon_plus" case icon_x = "icon_x" case icon_x_standalone = "icon_x_standalone" @@ -56,6 +58,11 @@ enum Image: String, CaseIterable, ImageMaker { case icon_edit = "icon_edit" // Link + case back_button = "back_button" + case icon_cancel = "icon_cancel" + case icon_add_bordered = "icon_add_bordered" + case icon_link_success = "icon_link_success" + case icon_link_error = "icon_link_error" case link_logo = "link_logo" case link_logo_bw = "link_logo_bw" case link_logo_knockout = "link_logo_knockout" diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Components/Badge/LinkBadgeView.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Components/Badge/LinkBadgeView.swift new file mode 100644 index 00000000000..a2c5bc0e10e --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Components/Badge/LinkBadgeView.swift @@ -0,0 +1,133 @@ +// +// LinkBadgeView.swift +// StripePaymentSheet +// +// Created by Ramon Torres on 4/29/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) import StripeUICore + +/// For internal SDK use only +@objc(STP_Internal_LinkBadgeView) +final class LinkBadgeView: UIView { + struct Constants { + static let spacing: CGFloat = 4 + static let margins: NSDirectionalEdgeInsets = .insets(top: 2, leading: 4, bottom: 2, trailing: 4) + static let iconSize: CGSize = .init(width: 12, height: 12) + static let maxFontSize: CGFloat = 16 + } + + enum BadgeType { + case neutral + case error + } + + let type: BadgeType + + var text: String? { + get { + return textLabel.text + } + set { + textLabel.text = newValue + } + } + + private lazy var iconView: UIImageView? = { + guard let icon = type.icon else { + return nil + } + + let imageView = UIImageView(image: icon) + imageView.tintColor = type.foregroundColor + imageView.setContentHuggingPriority(.required, for: .horizontal) + imageView.setContentCompressionResistancePriority(.required, for: .horizontal) + imageView.isHidden = imageView.image == nil + + NSLayoutConstraint.activate([ + imageView.widthAnchor.constraint(equalToConstant: Constants.iconSize.width), + imageView.heightAnchor.constraint(equalToConstant: Constants.iconSize.height), + ]) + + return imageView + }() + + private lazy var textLabel: UILabel = { + let label = UILabel() + label.textColor = type.foregroundColor + label.font = LinkUI.font(forTextStyle: .captionEmphasized, maximumPointSize: Constants.maxFontSize) + label.adjustsFontForContentSizeCategory = true + return label + }() + + override var intrinsicContentSize: CGSize { + return CGSize(width: UIView.noIntrinsicMetric, height: 20) + } + + convenience init(type: BadgeType, text: String) { + self.init(type: type) + self.text = text + } + + init(type: BadgeType) { + self.type = type + super.init(frame: .zero) + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupUI() { + setContentHuggingPriority(.required, for: .vertical) + setContentHuggingPriority(.required, for: .horizontal) + + let stackView = UIStackView(arrangedSubviews: [iconView, textLabel].compactMap({ $0 })) + + stackView.axis = .horizontal + stackView.spacing = Constants.spacing + stackView.alignment = .center + stackView.isLayoutMarginsRelativeArrangement = true + stackView.directionalLayoutMargins = Constants.margins + addAndPinSubview(stackView) + + backgroundColor = type.backgroundColor + layer.cornerRadius = LinkUI.smallCornerRadius + } + +} + +private extension LinkBadgeView.BadgeType { + + var icon: UIImage? { + switch self { + case .neutral: + return nil + case .error: + return Image.icon_link_error.makeImage(template: true) + } + } + + var backgroundColor: UIColor { + switch self { + case .neutral: + return .linkNeutralBackground + case .error: + return .linkDangerBackground + } + } + + var foregroundColor: UIColor { + switch self { + case .neutral: + return .linkNeutralForeground + case .error: + return .linkDangerForeground + } + } + +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Components/NavigationBar/LinkNavigationBar.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Components/NavigationBar/LinkNavigationBar.swift new file mode 100644 index 00000000000..8773650df66 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Components/NavigationBar/LinkNavigationBar.swift @@ -0,0 +1,189 @@ +// +// LinkNavigationBar.swift +// StripePaymentSheet +// +// Created by Ramon Torres on 3/10/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripePaymentsUI +@_spi(STP) import StripeUICore + +/// For internal SDK use only +@objc(STP_Internal_LinkNavigationBar) +final class LinkNavigationBar: UIView { + struct Constants { + static let buttonSize: CGSize = .init(width: 60, height: 44) + static let labelMargin: CGFloat = 20 + static let maxFontSize: CGFloat = 18 + static let logoVerticalOffset: CGFloat = 14 + static let defaultHeight: CGFloat = 44 + static let largeHeight: CGFloat = 66 + } + + var linkAccount: PaymentSheetLinkAccountInfoProtocol? { + didSet { + update() + } + } + + var showBackButton: Bool = false { + didSet { + if showBackButton != oldValue { + update() + } + } + } + + var isLarge: Bool { + // The nav bar is considered large as long as we need to display the email label. + return showEmailLabel + } + + private var showEmailLabel: Bool = false { + didSet { + emailLabel.isHidden = !showEmailLabel + invalidateIntrinsicContentSize() + } + } + + let backButton: UIButton = { + let button = UIButton(type: .system) + button.setImage(Image.back_button.makeImage(), for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + button.accessibilityLabel = String.Localized.back + button.isHidden = true + return button + }() + + let closeButton: UIButton = { + let button = UIButton(type: .system) + button.setImage(Image.icon_cancel.makeImage(), for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + button.accessibilityLabel = String.Localized.close + return button + }() + + let menuButton: UIButton = { + let button = UIButton(type: .system) + button.setImage(Image.icon_menu_horizontal.makeImage(), for: .normal) + button.translatesAutoresizingMaskIntoConstraints = false + button.accessibilityLabel = String.Localized.show_menu + return button + }() + + private let logoView: UIImageView = { + let imageView = UIImageView(image: Image.link_logo.makeImage(template: false)) + imageView.tintColor = .linkNavLogo + imageView.isAccessibilityElement = true + imageView.accessibilityTraits = .header + imageView.accessibilityLabel = STPPaymentMethodType.link.displayName + imageView.translatesAutoresizingMaskIntoConstraints = false + return imageView + }() + + private let emailLabel: UILabel = { + let label = UILabel() + label.font = LinkUI.font(forTextStyle: .body, maximumPointSize: Constants.maxFontSize) + label.textColor = .linkTertiaryText + label.textAlignment = .center + label.lineBreakMode = .byTruncatingMiddle + label.adjustsFontForContentSizeCategory = true + label.translatesAutoresizingMaskIntoConstraints = false + label.isHidden = true + return label + }() + + override var intrinsicContentSize: CGSize { + let baseHeight: CGFloat = isLarge + ? Constants.largeHeight + : Constants.defaultHeight + return CGSize( + width: UIView.noIntrinsicMetric, + height: baseHeight + safeAreaInsets.top + safeAreaInsets.bottom + ) + } + + init() { + super.init(frame: .zero) + + setContentHuggingPriority(.defaultHigh, for: .vertical) + setContentHuggingPriority(.defaultLow, for: .horizontal) + + tintColor = .linkNavTint + backgroundColor = .linkBackground + + addSubview(logoView) + addSubview(emailLabel) + addSubview(backButton) + addSubview(closeButton) + addSubview(menuButton) + + NSLayoutConstraint.activate([ + // Back button + backButton.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + backButton.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + backButton.widthAnchor.constraint(equalToConstant: Constants.buttonSize.width), + backButton.heightAnchor.constraint(equalToConstant: Constants.buttonSize.height), + // Close button + closeButton.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + closeButton.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor), + closeButton.widthAnchor.constraint(equalToConstant: Constants.buttonSize.width), + closeButton.heightAnchor.constraint(equalToConstant: Constants.buttonSize.height), + // Logo + logoView.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: Constants.logoVerticalOffset), + logoView.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor), + logoView.bottomAnchor.constraint(lessThanOrEqualTo: layoutMarginsGuide.bottomAnchor), + // Email label + emailLabel.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor), + emailLabel.bottomAnchor.constraint(equalTo: bottomAnchor), + emailLabel.leadingAnchor.constraint( + greaterThanOrEqualTo: safeAreaLayoutGuide.leadingAnchor, + constant: Constants.labelMargin + ), + emailLabel.trailingAnchor.constraint( + lessThanOrEqualTo: safeAreaLayoutGuide.trailingAnchor, + constant: Constants.labelMargin + ), + // Menu button + menuButton.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + menuButton.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor), + menuButton.widthAnchor.constraint(equalToConstant: Constants.buttonSize.width), + menuButton.heightAnchor.constraint(equalToConstant: Constants.buttonSize.height), + ]) + + update() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func safeAreaInsetsDidChange() { + super.safeAreaInsetsDidChange() + invalidateIntrinsicContentSize() + } + + private func update() { +// TODO(link): Set isLoggedIn once the SDK supports logging in again +// let isLoggedIn = linkAccount?.isLoggedIn ?? false + let isLoggedIn = false + + emailLabel.text = linkAccount?.email + showEmailLabel = isLoggedIn && !showBackButton + + // Back and close button are mutually exclusive. + backButton.isHidden = !showBackButton + closeButton.isHidden = showBackButton + + // Hide the logo if showing the back button. + logoView.isHidden = showBackButton + + // Menu should be hidden if not logged in or we are currently showing the back button. + menuButton.isHidden = !isLoggedIn || showBackButton + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Components/Notice/LinkNoticeView.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Components/Notice/LinkNoticeView.swift new file mode 100644 index 00000000000..f73659ec946 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Components/Notice/LinkNoticeView.swift @@ -0,0 +1,113 @@ +// +// LinkNoticeView.swift +// StripePaymentSheet +// +// Created by Ramon Torres on 3/30/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) import StripeUICore + +/// A view for displaying text notices. +/// +/// For internal SDK use only +@objc(STP_Internal_LinkNoticeView) +final class LinkNoticeView: UIView { + struct Constants { + static let spacing: CGFloat = 10 + static let margins: NSDirectionalEdgeInsets = .insets(amount: 12) + } + + enum NoticeType { + case error + } + + let type: NoticeType + + var text: String? { + get { + return textLabel.text + } + set { + textLabel.text = newValue + } + } + + private lazy var iconView: UIImageView = { + let imageView = UIImageView(image: type.icon) + imageView.tintColor = type.foregroundColor + imageView.adjustsImageSizeForAccessibilityContentSizeCategory = true + imageView.setContentHuggingPriority(.required, for: .horizontal) + imageView.setContentCompressionResistancePriority(.required, for: .horizontal) + return imageView + }() + + private lazy var textLabel: UILabel = { + let label = UILabel() + label.textColor = type.foregroundColor + label.numberOfLines = 0 + label.font = LinkUI.font(forTextStyle: .detail) + label.adjustsFontForContentSizeCategory = true + return label + }() + + convenience init(type: NoticeType, text: String) { + self.init(type: type) + self.text = text + } + + init(type: NoticeType) { + self.type = type + super.init(frame: .zero) + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupUI() { + let stackView = UIStackView(arrangedSubviews: [ + iconView, + textLabel, + ]) + + stackView.axis = .horizontal + stackView.spacing = Constants.spacing + stackView.alignment = .top + stackView.isLayoutMarginsRelativeArrangement = true + stackView.directionalLayoutMargins = Constants.margins + addAndPinSubview(stackView) + + backgroundColor = type.backgroundColor + layer.cornerRadius = LinkUI.mediumCornerRadius + } + +} + +private extension LinkNoticeView.NoticeType { + + var icon: UIImage { + switch self { + case .error: + return Image.icon_link_error.makeImage(template: true) + } + } + + var backgroundColor: UIColor { + switch self { + case .error: + return .linkDangerBackground + } + } + + var foregroundColor: UIColor { + switch self { + case .error: + return .linkDangerForeground + } + } + +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Components/Toast/LinkToast.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Components/Toast/LinkToast.swift new file mode 100644 index 00000000000..2eac4eac24a --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Components/Toast/LinkToast.swift @@ -0,0 +1,187 @@ +// +// LinkToast.swift +// StripePaymentSheet +// +// Created by Ramon Torres on 11/23/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import UIKit + +@_spi(STP) import StripeCore +@_spi(STP) import StripeUICore + +/// A view for displaying a brief message to the user. +/// For internal SDK use only +@objc(STP_Internal_LinkToast) +final class LinkToast: UIView { + struct Constants { + static let padding: CGFloat = 12 + /// Space between the icon and label. + static let spacing: CGFloat = 8 + static let animationDuration: TimeInterval = 0.2 + static let animationTravelDistance: CGFloat = 40 + static let defaultDuration: TimeInterval = 2 + } + + enum ToastType { + case success + } + + let toastType: ToastType + + let text: String + + private let iconView = UIImageView() + + private let label: UILabel = { + let label = UILabel() + label.textColor = .linkToastForeground + label.font = LinkUI.font(forTextStyle: .detail, maximumPointSize: 20) + return label + }() + #if !os(visionOS) + private let feedbackGenerator = UINotificationFeedbackGenerator() + #endif + + /// Creates a new toast. + /// - Parameters: + /// - type: Toast type. + /// - text: Text to show. + init(type: ToastType, text: String) { + self.toastType = type + self.text = text + super.init(frame: .zero) + setup() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setup() { + backgroundColor = .linkToastBackground + directionalLayoutMargins = .insets(amount: Constants.padding) + + insetsLayoutMarginsFromSafeArea = false + + label.text = text + iconView.image = toastType.icon + iconView.tintColor = toastType.iconColor + + let stackView = UIStackView(arrangedSubviews: [iconView, label]) + stackView.spacing = Constants.spacing + stackView.alignment = .center + stackView.translatesAutoresizingMaskIntoConstraints = false + + addSubview(stackView) + + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor), + stackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), + stackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + ]) + } + + override func layoutSubviews() { + super.layoutSubviews() + layer.cornerRadius = bounds.height / 2 + } + +} + +// MARK: - Show/Hide + +extension LinkToast { + + /// Show the toast from the given view as context. + /// - Parameters: + /// - view: View to show the toast from. + /// - duration: How long to show the toast for. + func show(from view: UIView, duration: TimeInterval = Constants.defaultDuration) { + let presentingView = view.window ?? view + + translatesAutoresizingMaskIntoConstraints = false + presentingView.addSubview(self) + + NSLayoutConstraint.activate([ + // Horizontally center + centerXAnchor.constraint(equalTo: presentingView.safeAreaLayoutGuide.centerXAnchor), + + // Pin edges + topAnchor.constraint(equalTo: presentingView.safeAreaLayoutGuide.topAnchor), + leadingAnchor.constraint(greaterThanOrEqualTo: presentingView.layoutMarginsGuide.leadingAnchor), + trailingAnchor.constraint(lessThanOrEqualTo: presentingView.layoutMarginsGuide.trailingAnchor), + ]) + + alpha = 0 + transform = CGAffineTransform(translationX: 0, y: -Constants.animationTravelDistance) + + UIView.animate( + withDuration: Constants.animationDuration, + delay: 0, + options: .curveEaseOut + ) { + self.alpha = 1 + self.transform = .identity + } + + UIAccessibility.post(notification: .announcement, argument: text) + #if !os(visionOS) + generateHapticFeedback() + #endif + + DispatchQueue.main.asyncAfter(deadline: .now() + duration) { + self.hide() + } + } + + /// Hides toast. + /// + /// You normally don't need to call this, as the toast will hide on its own after an specific duration. + func hide() { + guard superview != nil else { + return + } + + UIView.animate( + withDuration: Constants.animationDuration, + delay: 0, + options: .curveEaseOut + ) { + self.alpha = 0 + self.transform = CGAffineTransform(translationX: 0, y: -Constants.animationTravelDistance) + } completion: { _ in + self.removeFromSuperview() + } + } + +#if !os(visionOS) + private func generateHapticFeedback() { + switch toastType { + case .success: + feedbackGenerator.notificationOccurred(.success) + } + } +#endif + +} + +extension LinkToast.ToastType { + + var icon: UIImage { + switch self { + case .success: + return Image.icon_link_success.makeImage(template: true) + } + } + + var iconColor: UIColor { + switch self { + case .success: + return .linkBrand + } + } + +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Extensions/Button+Link.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Extensions/Button+Link.swift new file mode 100644 index 00000000000..383afb5370c --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Extensions/Button+Link.swift @@ -0,0 +1,70 @@ +// +// Button+Link.swift +// StripePaymentSheet +// +// Created by Ramon Torres on 12/1/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeUICore +import UIKit + +extension Button.Configuration { + + static func linkPrimary() -> Self { + var configuration: Button.Configuration = .primary() + configuration.font = LinkUI.font(forTextStyle: .bodyEmphasized) + configuration.insets = LinkUI.buttonMargins + configuration.cornerRadius = LinkUI.cornerRadius + + // Colors + configuration.foregroundColor = .linkPrimaryButtonForeground + configuration.backgroundColor = .linkBrand + configuration.disabledBackgroundColor = .linkBrand + + configuration.colorTransforms.disabledForeground = .setAlpha(amount: 0.5) + configuration.colorTransforms.highlightedForeground = .darken(amount: 0.2) + + return configuration + } + + static func linkSecondary() -> Self { + var configuration: Button.Configuration = .linkPrimary() + + // Colors + configuration.foregroundColor = .linkSecondaryButtonForeground + configuration.backgroundColor = .linkSecondaryButtonBackground + configuration.disabledBackgroundColor = .linkSecondaryButtonBackground + + return configuration + } + + static func linkPlain() -> Self { + var configuration: Button.Configuration = .plain() + configuration.font = LinkUI.font(forTextStyle: .body) + configuration.foregroundColor = .linkBrandDark + configuration.disabledForegroundColor = nil + configuration.colorTransforms.highlightedForeground = .setAlpha(amount: 0.4) + configuration.colorTransforms.disabledForeground = .setAlpha(amount: 0.3) + return configuration + } + + static func linkBordered() -> Self { + var configuration: Button.Configuration = .plain() + configuration.font = LinkUI.font(forTextStyle: .detailEmphasized) + configuration.insets = .insets(top: 4, leading: 12, bottom: 4, trailing: 12) + configuration.borderWidth = 1 + configuration.cornerRadius = LinkUI.mediumCornerRadius + + // Colors + configuration.foregroundColor = .label + configuration.backgroundColor = .clear + configuration.borderColor = .linkControlBorder + + configuration.colorTransforms.highlightedForeground = .setAlpha(amount: 0.5) + configuration.colorTransforms.highlightedBorder = .setAlpha(amount: 0.5) + + return configuration + } + +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Extensions/ConfirmButton+Link.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Extensions/ConfirmButton+Link.swift new file mode 100644 index 00000000000..ab6795a9c91 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Extensions/ConfirmButton+Link.swift @@ -0,0 +1,39 @@ +// +// ConfirmButton+Link.swift +// StripePaymentSheet +// +// Created by Ramon Torres on 1/12/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import UIKit + +extension ConfirmButton { + + static func makeLinkButton( + callToAction: CallToActionType, + compact: Bool = false, + didTap: @escaping () -> Void + ) -> ConfirmButton { + let button = ConfirmButton( + callToAction: callToAction, + appearance: LinkUI.appearance, + didTap: didTap + ) + + // Override the background color of the `.succeeded` state. Make it match + // the background color of the `.enabled` state. + // TODO(link): Needs refactor +// button.succeededBackgroundColor = ( +// LinkUI.appearance.primaryButton.backgroundColor ?? +// LinkUI.appearance.colors.primary +// ) + + button.directionalLayoutMargins = compact + ? LinkUI.compactButtonMargins + : LinkUI.buttonMargins + + return button + } + +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Extensions/UIColor+Link.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Extensions/UIColor+Link.swift index 8c8a3b55dd6..be519121811 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Extensions/UIColor+Link.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Extensions/UIColor+Link.swift @@ -21,6 +21,17 @@ extension UIColor { UIColor(red: 0, green: 0.84, blue: 0.44, alpha: 1.0) } + /// Darker version of the brand color. + /// + /// Use it as accent color on small UI elements or text links. + static let linkBrandDark: UIColor = UIColor(red: 0.020, green: 0.659, blue: 0.498, alpha: 1.0) + + /// Main background color. + static let linkBackground: UIColor = .dynamic( + light: .white, + dark: UIColor(red: 0.11, green: 0.11, blue: 0.118, alpha: 1) + ) + /// Level 400 variant of Link brand color. /// /// Use for separator bars over the Link brand color. @@ -34,11 +45,87 @@ extension UIColor { dark: .white ) + /// Tint color of the nav. Affects the color of nav buttons. + static let linkNavTint: UIColor = .dynamic( + light: UIColor(red: 0.188, green: 0.192, blue: 0.239, alpha: 1.0), + dark: UIColor(red: 0.922, green: 0.922, blue: 0.961, alpha: 0.6) + ) + + /// Color for borders and dividers. + static let linkSeparator: UIColor = .dynamic( + light: UIColor(red: 0.878, green: 0.902, blue: 0.922, alpha: 1), + dark: UIColor(red: 0.471, green: 0.471, blue: 0.502, alpha: 0.36) + ) + + /// Border color for custom controls. Currently an alias of `linkSeparator`. + static let linkControlBorder: UIColor = .linkSeparator + + /// Background color for custom controls. + static let linkControlBackground: UIColor = .dynamic( + light: .white, + dark: UIColor(red: 0.17, green: 0.17, blue: 0.19, alpha: 1) + ) + + /// Background color to be used when a custom control is highlighted. + static let linkControlHighlight: UIColor = .dynamic( + light: UIColor(red: 0.95, green: 0.95, blue: 0.96, alpha: 1), + dark: UIColor(white: 1, alpha: 0.07) + ) + + /// A very subtle color to be used on placeholder content of a control. + /// + /// - Note: Only recommended for shapes/non-text content due to very low contrast ratio with `linkControlBackground`. + static let linkControlLightPlaceholder: UIColor = .dynamic( + light: UIColor(red: 0.922, green: 0.933, blue: 0.945, alpha: 1.0), + dark: UIColor(red: 0.471, green: 0.471, blue: 0.502, alpha: 0.36) + ) + + /// Background color of the toast component. + static let linkToastBackground: UIColor = UIColor(red: 0.19, green: 0.19, blue: 0.24, alpha: 1.0) + + /// Foreground color of the toast component. + static let linkToastForeground: UIColor = .white + /// Foreground color of the primary button. static var linkPrimaryButtonForeground: UIColor { UIColor(red: 0, green: 0.12, blue: 0.06, alpha: 1.0) } + /// Foreground color of the secondary button. + static let linkSecondaryButtonForeground: UIColor = .dynamic( + light: UIColor(red: 0.114, green: 0.224, blue: 0.267, alpha: 1.0), + dark: UIColor(red: 0.020, green: 0.659, blue: 0.498, alpha: 1.0) + ) + + /// Background color of the secondary button/ + static let linkSecondaryButtonBackground: UIColor = .dynamic( + light: UIColor(red: 0.965, green: 0.973, blue: 0.980, alpha: 1.0), + dark: UIColor(red: 0.455, green: 0.455, blue: 0.502, alpha: 0.18) + ) + + /// Background color of a neutral badge or notice. + static let linkNeutralBackground: UIColor = .dynamic( + light: UIColor(red: 0.96, green: 0.97, blue: 0.98, alpha: 1.0), + dark: UIColor(white: 1, alpha: 0.1) + ) + + /// Foreground color of a neutral badge or notice. + static let linkNeutralForeground: UIColor = .dynamic( + light: UIColor(red: 0.416, green: 0.451, blue: 0.514, alpha: 1), + dark: UIColor(red: 0.922, green: 0.922, blue: 0.961, alpha: 0.6) + ) + + /// Background color of an error badge or notice. + static let linkDangerBackground: UIColor = .dynamic( + light: UIColor(red: 1.0, green: 0.906, blue: 0.949, alpha: 1.0), + dark: UIColor(red: 0.996, green: 0.529, blue: 0.631, alpha: 0.1) + ) + + /// Foreground color of an error badge or notice. + static let linkDangerForeground: UIColor = .dynamic( + light: UIColor(red: 1.0, green: 0.184, blue: 0.298, alpha: 1.0), + dark: UIColor(red: 1.0, green: 0.184, blue: 0.298, alpha: 1.0) + ) } // MARK: - Text color diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/LinkUI.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/LinkUI.swift index 13b9a403aef..671a5704f4f 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/LinkUI.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/LinkUI.swift @@ -143,3 +143,36 @@ extension LinkUI { } } + +// MARK: - Appearance + +extension LinkUI { + + static let appearance: PaymentSheet.Appearance = { + var appearance = PaymentSheet.Appearance.default + appearance.cornerRadius = LinkUI.mediumCornerRadius + appearance.colors.primary = .linkBrandDark + appearance.colors.background = .linkBackground + + // Text + appearance.colors.text = .linkPrimaryText + appearance.colors.textSecondary = .linkSecondaryText + + // Components + appearance.colors.componentText = .linkPrimaryText + appearance.colors.componentPlaceholderText = .linkSecondaryText + appearance.colors.componentBackground = .linkControlBackground + appearance.colors.componentBorder = .linkControlBorder + appearance.colors.componentDivider = .linkSeparator + + // Primary button + appearance.primaryButton.textColor = .linkPrimaryButtonForeground + appearance.primaryButton.backgroundColor = .linkBrand + appearance.primaryButton.borderWidth = 0 + appearance.primaryButton.cornerRadius = LinkUI.cornerRadius + appearance.primaryButton.font = LinkUI.font(forTextStyle: .bodyEmphasized) + + return appearance + }() + +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Views/LinkInstantDebitMandateView.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Views/LinkInstantDebitMandateView.swift new file mode 100644 index 00000000000..97d296f85bd --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Views/LinkInstantDebitMandateView.swift @@ -0,0 +1,104 @@ +// +// LinkInstantDebitMandateView.swift +// StripePaymentSheet +// +// Created by Ramon Torres on 2/17/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeCore +@_spi(STP) import StripePaymentsUI +@_spi(STP) import StripeUICore +import UIKit + +protocol LinkInstantDebitMandateViewDelegate: AnyObject { + /// Called when the user taps on a link. + /// + /// - Parameters: + /// - mandateView: The view that the user interacted with. + /// - url: URL of the link. + func instantDebitMandateView(_ mandateView: LinkInstantDebitMandateView, didTapOnLinkWithURL url: URL) +} + +// TODO(ramont): extract common code with `LinkLegalTermsView`. + +/// For internal SDK use only +@objc(STP_Internal_LinkInstantDebitMandateViewDelegate) +final class LinkInstantDebitMandateView: UIView { + struct Constants { + static let lineHeight: CGFloat = 1.5 + } + + // TODO(ramont): Update with final URLs + private let links: [String: URL] = [ + "terms": URL(string: "https://stripe.com/ach-payments/authorization")! + ] + + weak var delegate: LinkInstantDebitMandateViewDelegate? + + private lazy var textView: UITextView = { + let textView = UITextView() + textView.isScrollEnabled = false + textView.isEditable = false + textView.backgroundColor = .clear + textView.attributedText = formattedLegalText() + textView.textColor = .linkSecondaryText + textView.textAlignment = .center + textView.textContainerInset = .zero + textView.textContainer.lineFragmentPadding = 0 + textView.delegate = self + textView.clipsToBounds = false + textView.adjustsFontForContentSizeCategory = true + textView.linkTextAttributes = [ + .foregroundColor: UIColor.linkBrandDark + ] + textView.font = LinkUI.font(forTextStyle: .caption) + return textView + }() + + init(delegate: LinkInstantDebitMandateViewDelegate? = nil) { + super.init(frame: .zero) + self.delegate = delegate + addAndPinSubview(textView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func formattedLegalText() -> NSAttributedString { + let string = String.Localized.bank_continue_mandate_text + + let formattedString = STPStringUtils.applyLinksToString(template: string, links: links) + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineSpacing = LinkUI.lineSpacing( + fromRelativeHeight: Constants.lineHeight, + textStyle: .caption + ) + + formattedString.addAttributes([.paragraphStyle: paragraphStyle], range: formattedString.extent) + + return formattedString + } + +} + +extension LinkInstantDebitMandateView: UITextViewDelegate { + + #if !os(visionOS) + func textView( + _ textView: UITextView, + shouldInteractWith URL: URL, + in characterRange: NSRange, + interaction: UITextItemInteraction + ) -> Bool { + if interaction == .invokeDefaultAction { + delegate?.instantDebitMandateView(self, didTapOnLinkWithURL: URL) + } + + return false + } + #endif + +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Views/LinkKeyboardAvoidingScrollView.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Views/LinkKeyboardAvoidingScrollView.swift new file mode 100644 index 00000000000..ba793bacfd3 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/Internal/Link/Views/LinkKeyboardAvoidingScrollView.swift @@ -0,0 +1,75 @@ +// +// LinkKeyboardAvoidingScrollView.swift +// StripePaymentSheet +// +// Created by Ramon Torres on 1/11/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +@_spi(STP) import StripeUICore +import UIKit + +/// A UIScrollView subclass that actively prevents its content from being covered by the software keyboard. +/// For internal SDK use only +@objc(STP_Internal_LinkKeyboardAvoidingScrollView) +final class LinkKeyboardAvoidingScrollView: UIScrollView { + + init() { + super.init(frame: .zero) + + NotificationCenter.default.addObserver(self, + selector: #selector(keyboardFrameChanged(_:)), + name: UIResponder.keyboardWillChangeFrameNotification, + object: nil) + } + + /// Creates a new keyboard-avoiding scrollview with the given view configured as content view. + /// + /// This initializer adds the content view as a subview and installs the appropriate set of constraints. + /// + /// - Parameter contentView: The view to be used as content view. + convenience init(contentView: UIView) { + self.init() + + contentView.translatesAutoresizingMaskIntoConstraints = false + addSubview(contentView) + + NSLayoutConstraint.activate([ + contentView.topAnchor.constraint(equalTo: topAnchor), + contentView.bottomAnchor.constraint(equalTo: bottomAnchor), + contentView.leadingAnchor.constraint(equalTo: leadingAnchor), + contentView.trailingAnchor.constraint(equalTo: trailingAnchor), + contentView.widthAnchor.constraint(equalTo: widthAnchor), + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + NotificationCenter.default.removeObserver(self) + } +} + +// MARK: - Event Handling + +private extension LinkKeyboardAvoidingScrollView { + + @objc func keyboardFrameChanged(_ notification: Notification) { + let userInfo = notification.userInfo + + guard let keyboardFrame = userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { + return + } + + let absoluteFrame = convert(bounds, to: window) + let intersection = absoluteFrame.intersection(keyboardFrame) + + UIView.animateAlongsideKeyboard(notification) { + self.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: intersection.height, right: 0) + self.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: intersection.height, right: 0) + } + } + +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/InstantDebitsPaymentMethodElement.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/InstantDebitsPaymentMethodElement.swift index ffb2ed1b8a9..45d1fcbbaee 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/InstantDebitsPaymentMethodElement.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/InstantDebitsPaymentMethodElement.swift @@ -40,7 +40,7 @@ final class InstantDebitsPaymentMethodElement: ContainerElement { var mandateString: NSMutableAttributedString? { var string: String? if linkedBank != nil { - string = USBankAccountPaymentMethodElement.ContinueMandateText + string = String.Localized.bank_continue_mandate_text } else { string = nil } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/USBankAccountPaymentMethodElement.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/USBankAccountPaymentMethodElement.swift index 60649171600..ec1707a659d 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/USBankAccountPaymentMethodElement.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/USBankAccountPaymentMethodElement.swift @@ -48,7 +48,6 @@ final class USBankAccountPaymentMethodElement: ContainerElement { "terms": URL(string: "https://stripe.com/legal/ach-payments/authorization")!, ] - static let ContinueMandateText: String = STPLocalizedString("By continuing, you agree to authorize payments pursuant to these terms.", "Text providing link to terms for ACH payments") static let SaveAccountMandateText: String = STPLocalizedString("By saving your bank account for %@ you agree to authorize payments pursuant to these terms.", "Mandate text with link to terms when saving a bank account payment method to a merchant (merchant name replaces %@).") static let MicrodepositCopy: String = STPLocalizedString("Stripe will deposit $0.01 to your account in 1-2 business days. Then you’ll get an email with instructions to complete payment to %@.", "Prompt for microdeposit verification before completing purchase with merchant. %@ will be replaced by merchant business name") static let MicrodepositCopy_CustomerSheet: String = STPLocalizedString("Stripe will deposit $0.01 to your account in 1-2 business days. Then you'll get an email with instructions to finish saving your bank account with %@.", "Prompt for microdeposit verification before completing saving payment method with merchant. %@ will be replaced by merchant business name") @@ -171,7 +170,7 @@ final class USBankAccountPaymentMethodElement: ContainerElement { return nil } - var mandateText = isSaving ? String(format: Self.SaveAccountMandateText, merchantName) : Self.ContinueMandateText + var mandateText = isSaving ? String(format: Self.SaveAccountMandateText, merchantName) : String.Localized.bank_continue_mandate_text if case .customerSheet = configuration, !linkedBank.instantlyVerified { mandateText = String.init(format: Self.MicrodepositCopy_CustomerSheet, merchantName) + "\n" + mandateText } else if case .paymentSheet = configuration, !linkedBank.instantlyVerified { @@ -183,7 +182,7 @@ final class USBankAccountPaymentMethodElement: ContainerElement { } static func attributedMandateTextSavedPaymentMethod(alignment: NSTextAlignment = .center, theme: ElementsAppearance) -> NSMutableAttributedString { - let mandateText = Self.ContinueMandateText + let mandateText = String.Localized.bank_continue_mandate_text let formattedString = STPStringUtils.applyLinksToString(template: mandateText, links: links) applyStyle(formattedString: formattedString, alignment: alignment, theme: theme) return formattedString diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/ButtonLinkSnapshotTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/ButtonLinkSnapshotTests.swift new file mode 100644 index 00000000000..949626c18e7 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/ButtonLinkSnapshotTests.swift @@ -0,0 +1,72 @@ +// +// ButtonLinkSnapshotTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 2/14/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import UIKit + +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripeUICore + +class ButtonLinkSnapshotTests: STPSnapshotTestCase { + + func testPrimary() { + let sut = makeSUT(configuration: .linkPrimary(), title: "Primary Button") + verify(sut) + } + + func testSecondary() { + let sut = makeSUT(configuration: .linkSecondary(), title: "Secondary Button") + verify(sut) + } + + func testBordered() { + let sut = makeSUT(configuration: .linkBordered(), title: "Bordered Button") + verify(sut) + } + + func testPlain() { + let sut = makeSUT(configuration: .linkPlain(), title: "Plain Button") + verify(sut) + } + + func verify( + _ sut: Button, + identifier: String? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) { + let size = sut.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + sut.bounds = CGRect(origin: .zero, size: size) + STPSnapshotVerifyView(sut, file: file, line: line) + + sut.isHighlighted = true + STPSnapshotVerifyView(sut, identifier: "Highlighted", file: file, line: line) + + sut.isHighlighted = false + sut.isEnabled = false + STPSnapshotVerifyView(sut, identifier: "Disabled", file: file, line: line) + + sut.isHighlighted = false + sut.isEnabled = true + sut.isLoading = true + STPSnapshotVerifyView(sut, identifier: "Loading", file: file, line: line) + } + +} + +extension ButtonLinkSnapshotTests { + + func makeSUT( + configuration: Button.Configuration, + title: String + ) -> Button { + return Button(configuration: configuration, title: title) + } + +} diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkBadgeViewSnapshotTest.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkBadgeViewSnapshotTest.swift new file mode 100644 index 00000000000..8aaef64aa83 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkBadgeViewSnapshotTest.swift @@ -0,0 +1,53 @@ +// +// LinkBadgeViewSnapshotTest.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 4/29/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import UIKit + +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class LinkBadgeViewSnapshotTest: STPSnapshotTestCase { + + func testNeutral() { + verify( + LinkBadgeView( + type: .neutral, + text: "Neutral message" + ) + ) + } + + func testError() { + verify( + LinkBadgeView( + type: .error, + text: "Error message" + ) + ) + } + + func verify( + _ sut: UIView, + identifier: String? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) { + let size = sut.systemLayoutSizeFitting( + UIView.layoutFittingCompressedSize, + withHorizontalFittingPriority: .fittingSizeLevel, + verticalFittingPriority: .fittingSizeLevel + ) + + sut.bounds = CGRect(origin: .zero, size: size) + STPSnapshotVerifyView(sut, identifier: identifier, file: file, line: line) + } + +} diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkInstantDebitMandateViewSnapshotTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkInstantDebitMandateViewSnapshotTests.swift new file mode 100644 index 00000000000..88d2bbeb569 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkInstantDebitMandateViewSnapshotTests.swift @@ -0,0 +1,72 @@ +// +// LinkInstantDebitMandateViewSnapshotTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 2/17/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import UIKit + +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI +@testable@_spi(STP) import StripeUICore + +class LinkInstantDebitMandateViewSnapshotTests: STPSnapshotTestCase { + + func testDefault() { + let sut = makeSUT() + verify(sut) + } + + func testLocalization() { + performLocalizedSnapshotTest(forLanguage: "de") + performLocalizedSnapshotTest(forLanguage: "es") + performLocalizedSnapshotTest(forLanguage: "el-GR") + performLocalizedSnapshotTest(forLanguage: "it") + performLocalizedSnapshotTest(forLanguage: "ja") + performLocalizedSnapshotTest(forLanguage: "ko") + performLocalizedSnapshotTest(forLanguage: "zh-Hans") + } + +} + +// MARK: - Helpers + +extension LinkInstantDebitMandateViewSnapshotTests { + + func verify( + _ view: UIView, + identifier: String? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) { + view.autosizeHeight(width: 250) + STPSnapshotVerifyView(view, identifier: identifier, file: file, line: line) + } + + func performLocalizedSnapshotTest( + forLanguage language: String, + file: StaticString = #filePath, + line: UInt = #line + ) { + STPLocalizationUtils.overrideLanguage(to: language) + let sut = makeSUT() + STPLocalizationUtils.overrideLanguage(to: nil) + verify(sut, identifier: language, file: file, line: line) + } + +} + +// MARK: - Factory + +extension LinkInstantDebitMandateViewSnapshotTests { + + func makeSUT() -> LinkInstantDebitMandateView { + return LinkInstantDebitMandateView() + } + +} diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkNavigationBarSnapshotTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkNavigationBarSnapshotTests.swift new file mode 100644 index 00000000000..23709ecff5c --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkNavigationBarSnapshotTests.swift @@ -0,0 +1,83 @@ +// +// LinkNavigationBarSnapshotTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 4/27/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import UIKit + +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class LinkNavigationBarSnapshotTests: STPSnapshotTestCase { + + func testDefault() { + let sut = makeSUT() + verify(sut) + + sut.showBackButton = true + verify(sut, identifier: "BackButton") + } + + func testWithEmailAddress() { + let sut = makeSUT() + sut.linkAccount = makeAccountStub(email: "user@example.com") + verify(sut) + + sut.showBackButton = true + verify(sut, identifier: "BackButton") + } + + func testWithLongEmailAddress() { + let sut = makeSUT() + sut.linkAccount = makeAccountStub(email: "a.very.very.long.customer.name@example.com") + verify(sut) + + sut.showBackButton = true + verify(sut, identifier: "BackButton") + } + + func verify( + _ sut: UIView, + identifier: String? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) { + let size = sut.systemLayoutSizeFitting( + CGSize(width: 375, height: UIView.layoutFittingCompressedSize.height), + withHorizontalFittingPriority: .required, + verticalFittingPriority: .fittingSizeLevel + ) + + sut.bounds = CGRect(origin: .zero, size: size) + STPSnapshotVerifyView(sut, identifier: identifier, file: file, line: line) + } + +} + +extension LinkNavigationBarSnapshotTests { + fileprivate struct LinkAccountStub: PaymentSheetLinkAccountInfoProtocol { + let email: String + let redactedPhoneNumber: String? + let isRegistered: Bool + let isLoggedIn: Bool + } + + fileprivate func makeAccountStub(email: String) -> LinkAccountStub { + return LinkAccountStub( + email: email, + redactedPhoneNumber: "+1********55", + isRegistered: true, + isLoggedIn: true + ) + } + + fileprivate func makeSUT() -> LinkNavigationBar { + return LinkNavigationBar() + } +} diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkNoticeViewSnapshotTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkNoticeViewSnapshotTests.swift new file mode 100644 index 00000000000..6175c1aa68a --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkNoticeViewSnapshotTests.swift @@ -0,0 +1,45 @@ +// +// LinkNoticeViewSnapshotTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 3/31/22. +// Copyright © 2022 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import UIKit + +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class LinkNoticeViewSnapshotTests: STPSnapshotTestCase { + + func testError() { + verify( + LinkNoticeView( + type: .error, + text: + "This card has expired. Update it to keep using it or use a different payment method." + ) + ) + } + + func verify( + _ sut: UIView, + identifier: String? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) { + let size = sut.systemLayoutSizeFitting( + CGSize(width: 340, height: UIView.layoutFittingCompressedSize.height), + withHorizontalFittingPriority: .required, + verticalFittingPriority: .fittingSizeLevel + ) + + sut.bounds = CGRect(origin: .zero, size: size) + STPSnapshotVerifyView(sut, identifier: identifier, file: file, line: line) + } + +} diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkToastSnapshotTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkToastSnapshotTests.swift new file mode 100644 index 00000000000..e3163848d86 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/Link/LinkToastSnapshotTests.swift @@ -0,0 +1,35 @@ +// +// LinkToastSnapshotTests.swift +// StripeiOS Tests +// +// Created by Ramon Torres on 12/7/21. +// Copyright © 2021 Stripe, Inc. All rights reserved. +// + +import StripeCoreTestUtils +import UIKit + +@testable@_spi(STP) import StripeCore +@testable@_spi(STP) import StripePayments +@testable@_spi(STP) import StripePaymentSheet +@testable@_spi(STP) import StripePaymentsUI + +class LinkToastSnapshotTests: STPSnapshotTestCase { + + func testSuccess() { + let toast = LinkToast(type: .success, text: "Success message!") + verify(toast) + } + + func verify( + _ toast: LinkToast, + identifier: String? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) { + let size = toast.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + toast.bounds = CGRect(origin: .zero, size: size) + STPSnapshotVerifyView(toast, identifier: identifier, file: file, line: line) + } + +} diff --git a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/CardBrandView.swift b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/CardBrandView.swift index 5344b75c956..d94c880aaba 100644 --- a/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/CardBrandView.swift +++ b/StripePaymentsUI/StripePaymentsUI/Source/Internal/UI/Views/CardBrandView.swift @@ -47,7 +47,7 @@ import UIKit private var centeringPadding: UIEdgeInsets { return UIEdgeInsets( top: 0, - left: 0, + left: centerHorizontally ? Self.iconPadding.right : 0, bottom: Self.iconPadding.top, right: 0 ) @@ -71,6 +71,9 @@ import UIKit /// If `true`, the view will display the CVC hint icon instead of the card brand image. let showCVC: Bool + /// If `true`, will center the card brand icon horizontally in the containing view + let centerHorizontally: Bool + /// If `true`, show a CBC indicator arrow var isShowingCBCIndicator: Bool = false { didSet { @@ -125,9 +128,11 @@ import UIKit /// Creates and returns an initialized card brand view. /// - Parameter showCVC: Whether or not to show the CVC hint icon instead of the card brand image. @_spi(STP) public init( - showCVC: Bool = false + showCVC: Bool = false, + centerHorizontally: Bool = false ) { self.showCVC = showCVC + self.centerHorizontally = centerHorizontally super.init(frame: .zero) addSubview(imageView) diff --git a/StripeUICore/StripeUICoreTests/Snapshot/Controls/ButtonSnapshotTest.swift b/StripeUICore/StripeUICoreTests/Snapshot/Controls/ButtonSnapshotTest.swift index accbce379ab..e0db6c7c2ef 100644 --- a/StripeUICore/StripeUICoreTests/Snapshot/Controls/ButtonSnapshotTest.swift +++ b/StripeUICore/StripeUICoreTests/Snapshot/Controls/ButtonSnapshotTest.swift @@ -6,9 +6,9 @@ // Copyright © 2021 Stripe, Inc. All rights reserved. // -import iOSSnapshotTestCase import StripeCoreTestUtils @_spi(STP) import StripeUICore +import UIKit final class ButtonSnapshotTest: STPSnapshotTestCase { diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testBordered@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testBordered@3x.png new file mode 100644 index 00000000000..2aed510d18f Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testBordered@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testBordered_Disabled@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testBordered_Disabled@3x.png new file mode 100644 index 00000000000..28ff08090a6 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testBordered_Disabled@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testBordered_Highlighted@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testBordered_Highlighted@3x.png new file mode 100644 index 00000000000..e25f96205e8 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testBordered_Highlighted@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testBordered_Loading@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testBordered_Loading@3x.png new file mode 100644 index 00000000000..2c703c7de11 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testBordered_Loading@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPlain@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPlain@3x.png new file mode 100644 index 00000000000..37e46b260e4 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPlain@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPlain_Disabled@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPlain_Disabled@3x.png new file mode 100644 index 00000000000..10479eb9cdc Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPlain_Disabled@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPlain_Highlighted@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPlain_Highlighted@3x.png new file mode 100644 index 00000000000..3bc2e79e77d Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPlain_Highlighted@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPlain_Loading@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPlain_Loading@3x.png new file mode 100644 index 00000000000..392ffa96d28 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPlain_Loading@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPrimary@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPrimary@3x.png new file mode 100644 index 00000000000..bcb28a3fe40 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPrimary@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPrimary_Disabled@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPrimary_Disabled@3x.png new file mode 100644 index 00000000000..3d262813aba Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPrimary_Disabled@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPrimary_Highlighted@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPrimary_Highlighted@3x.png new file mode 100644 index 00000000000..836d566c29d Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPrimary_Highlighted@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPrimary_Loading@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPrimary_Loading@3x.png new file mode 100644 index 00000000000..895535324c9 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testPrimary_Loading@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testSecondary@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testSecondary@3x.png new file mode 100644 index 00000000000..bac8586566b Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testSecondary@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testSecondary_Disabled@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testSecondary_Disabled@3x.png new file mode 100644 index 00000000000..a25f6bb41c7 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testSecondary_Disabled@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testSecondary_Highlighted@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testSecondary_Highlighted@3x.png new file mode 100644 index 00000000000..543b301dfe6 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testSecondary_Highlighted@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testSecondary_Loading@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testSecondary_Loading@3x.png new file mode 100644 index 00000000000..f2071e2f22d Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.ButtonLinkSnapshotTests/testSecondary_Loading@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkBadgeViewSnapshotTest/testError@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkBadgeViewSnapshotTest/testError@3x.png new file mode 100644 index 00000000000..2dfd6ab35cf Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkBadgeViewSnapshotTest/testError@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkBadgeViewSnapshotTest/testNeutral@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkBadgeViewSnapshotTest/testNeutral@3x.png new file mode 100644 index 00000000000..261e6537d5d Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkBadgeViewSnapshotTest/testNeutral@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testDefault@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testDefault@3x.png new file mode 100644 index 00000000000..b0d05bf1d21 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testDefault@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_de@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_de@3x.png new file mode 100644 index 00000000000..61f59deef8d Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_de@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_el_GR@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_el_GR@3x.png new file mode 100644 index 00000000000..73b1a64cf03 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_el_GR@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_es@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_es@3x.png new file mode 100644 index 00000000000..3820687c95c Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_es@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_it@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_it@3x.png new file mode 100644 index 00000000000..47a373822ef Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_it@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_ja@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_ja@3x.png new file mode 100644 index 00000000000..5e46504c1c2 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_ja@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_ko@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_ko@3x.png new file mode 100644 index 00000000000..f164b5dcb95 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_ko@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_zh_Hans@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_zh_Hans@3x.png new file mode 100644 index 00000000000..4a5083bca48 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkInstantDebitMandateViewSnapshotTests/testLocalization_zh_Hans@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testDefault@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testDefault@3x.png new file mode 100644 index 00000000000..c8c48b8f881 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testDefault@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testDefault_BackButton@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testDefault_BackButton@3x.png new file mode 100644 index 00000000000..374006515a6 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testDefault_BackButton@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testWithEmailAddress@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testWithEmailAddress@3x.png new file mode 100644 index 00000000000..c8c48b8f881 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testWithEmailAddress@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testWithEmailAddress_BackButton@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testWithEmailAddress_BackButton@3x.png new file mode 100644 index 00000000000..374006515a6 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testWithEmailAddress_BackButton@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testWithLongEmailAddress@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testWithLongEmailAddress@3x.png new file mode 100644 index 00000000000..c8c48b8f881 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testWithLongEmailAddress@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testWithLongEmailAddress_BackButton@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testWithLongEmailAddress_BackButton@3x.png new file mode 100644 index 00000000000..374006515a6 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNavigationBarSnapshotTests/testWithLongEmailAddress_BackButton@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNoticeViewSnapshotTests/testError@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNoticeViewSnapshotTests/testError@3x.png new file mode 100644 index 00000000000..78fb53787f1 Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkNoticeViewSnapshotTests/testError@3x.png differ diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkToastSnapshotTests/testSuccess@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkToastSnapshotTests/testSuccess@3x.png new file mode 100644 index 00000000000..3e85d4ac97b Binary files /dev/null and b/Tests/ReferenceImages_64/StripePaymentSheetTests.LinkToastSnapshotTests/testSuccess@3x.png differ