diff --git a/.github/workflows/deploy_new_version.yml b/.github/workflows/deploy_new_version.yml index d500306..25bc80f 100644 --- a/.github/workflows/deploy_new_version.yml +++ b/.github/workflows/deploy_new_version.yml @@ -7,12 +7,12 @@ on: jobs: deploy: - runs-on: macos-latest + runs-on: macos-13 steps: - uses: actions/checkout@v3 - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: "14.2" + xcode-version: "15.0.1" - run: swift run working-directory: ./deploy_new_version env: diff --git a/Example-app.xcodeproj/project.pbxproj b/Example-app.xcodeproj/project.pbxproj index ae9a5e1..23ff7af 100644 --- a/Example-app.xcodeproj/project.pbxproj +++ b/Example-app.xcodeproj/project.pbxproj @@ -3,11 +3,21 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ + 04EE79822AC1B657002ACDAA /* CodeScanner in Frameworks */ = {isa = PBXBuildFile; productRef = 04EE79812AC1B657002ACDAA /* CodeScanner */; }; + 04EE79842AC1D07E002ACDAA /* ScanUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04EE79832AC1D07E002ACDAA /* ScanUrl.swift */; }; + 04EE79872AC2A938002ACDAA /* IconButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04EE79862AC2A938002ACDAA /* IconButton.swift */; }; 63E65DE526EB562C002D116E /* Example_app_UITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E65DE426EB562C002D116E /* Example_app_UITests.swift */; }; + 890702972A053FDE0027D7A8 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 890702952A053FDE0027D7A8 /* Localizable.strings */; }; + 8907029A2A0541E60027D7A8 /* String+Localize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890702992A0541E60027D7A8 /* String+Localize.swift */; }; + 8907029C2A0A20030027D7A8 /* StorageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8907029B2A0A20020027D7A8 /* StorageHelper.swift */; }; + 890702A02A0A23ED0027D7A8 /* StandaloneUrlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8907029F2A0A23ED0027D7A8 /* StandaloneUrlView.swift */; }; + 890702A22A0A24820027D7A8 /* PaymentAlternativesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890702A12A0A24820027D7A8 /* PaymentAlternativesViewController.swift */; }; + 890702A42A0A2CC50027D7A8 /* StandaloneUrlViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890702A32A0A2CC50027D7A8 /* StandaloneUrlViewModel.swift */; }; + 89919A382A0140C60011ADF0 /* SwedbankPayConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89919A372A0140C60011ADF0 /* SwedbankPayConfiguration.swift */; }; A548FFB3241BCC76007C7B17 /* EnvironmentSettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A548FFB2241BCC76007C7B17 /* EnvironmentSettingsCell.swift */; }; A569986524113FF100162D69 /* ConsumerSettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A569986424113FF100162D69 /* ConsumerSettingsCell.swift */; }; A56998672417C3B000162D69 /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56998662417C3B000162D69 /* SettingsCell.swift */; }; @@ -17,7 +27,6 @@ A58DAF0724053A5A004F9AF2 /* ShoppingCartHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58DAF0624053A5A004F9AF2 /* ShoppingCartHeaderCell.swift */; }; A5AD795B26664B7800DE62B1 /* StyleSettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5AD795A26664B7800DE62B1 /* StyleSettingsCell.swift */; }; A5AD795F2666744700DE62B1 /* StyleParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5AD795E2666744700DE62B1 /* StyleParser.swift */; }; - A5BE1E6F25791BAB00336A79 /* EnvironmentOptionView.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5BE1E6D25791BAB00336A79 /* EnvironmentOptionView.xib */; }; A5BE1E7225791BC400336A79 /* EnvironmentOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BE1E7125791BC400336A79 /* EnvironmentOptionView.swift */; }; A5C24B5A257E5D8B00D832D9 /* PayerOwnedPaymentTokensViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C24B59257E5D8B00D832D9 /* PayerOwnedPaymentTokensViewController.swift */; }; A5C24B5D257E6A3300D832D9 /* PaymentTokenCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5C24B5C257E6A3300D832D9 /* PaymentTokenCell.swift */; }; @@ -97,6 +106,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 04EE79832AC1D07E002ACDAA /* ScanUrl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanUrl.swift; sourceTree = ""; }; + 04EE79862AC2A938002ACDAA /* IconButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconButton.swift; sourceTree = ""; }; 6321C1A9274F8279009FC005 /* deploy_new_version.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = deploy_new_version.yml; sourceTree = ""; }; 639094BB27550F8F00B8A6CB /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 639094BE2755189100B8A6CB /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; @@ -118,6 +129,14 @@ 63E65DE226EB562C002D116E /* Example-app-UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Example-app-UITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 63E65DE426EB562C002D116E /* Example_app_UITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Example_app_UITests.swift; sourceTree = ""; }; 63E65DE626EB562C002D116E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 890702962A053FDE0027D7A8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Localizable.strings; sourceTree = ""; }; + 890702982A053FE50027D7A8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = ../en.lproj/Localizable.strings; sourceTree = ""; }; + 890702992A0541E60027D7A8 /* String+Localize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localize.swift"; sourceTree = ""; }; + 8907029B2A0A20020027D7A8 /* StorageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageHelper.swift; sourceTree = ""; }; + 8907029F2A0A23ED0027D7A8 /* StandaloneUrlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandaloneUrlView.swift; sourceTree = ""; }; + 890702A12A0A24820027D7A8 /* PaymentAlternativesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentAlternativesViewController.swift; sourceTree = ""; }; + 890702A32A0A2CC50027D7A8 /* StandaloneUrlViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandaloneUrlViewModel.swift; sourceTree = ""; }; + 89919A372A0140C60011ADF0 /* SwedbankPayConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwedbankPayConfiguration.swift; sourceTree = ""; }; A548FFB2241BCC76007C7B17 /* EnvironmentSettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentSettingsCell.swift; sourceTree = ""; }; A569986424113FF100162D69 /* ConsumerSettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsumerSettingsCell.swift; sourceTree = ""; }; A56998662417C3B000162D69 /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = ""; }; @@ -209,6 +228,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 04EE79822AC1B657002ACDAA /* CodeScanner in Frameworks */, A569C75326D4CB2400B2237C /* SwedbankPaySDKMerchantBackend in Frameworks */, A569C75126D4CB2400B2237C /* SwedbankPaySDK in Frameworks */, ); @@ -217,6 +237,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 04EE79852AC2A8DD002ACDAA /* ccomponents */ = { + isa = PBXGroup; + children = ( + 04EE79862AC2A938002ACDAA /* IconButton.swift */, + ); + path = ccomponents; + sourceTree = ""; + }; 6332462C26EF820C00FB705A /* .github */ = { isa = PBXGroup; children = ( @@ -282,6 +310,15 @@ path = "Example-app-UITests"; sourceTree = ""; }; + 89919A2E29FC0D3F0011ADF0 /* Utilities */ = { + isa = PBXGroup; + children = ( + 89919A372A0140C60011ADF0 /* SwedbankPayConfiguration.swift */, + 8907029B2A0A20020027D7A8 /* StorageHelper.swift */, + ); + path = Utilities; + sourceTree = ""; + }; A5AD795D2666743400DE62B1 /* Utils */ = { isa = PBXGroup; children = ( @@ -324,7 +361,8 @@ A5AA1D6D23967AE4008A62CC /* Example-app.entitlements */, C54D556923701F9D00E3B301 /* Info.plist */, C54D556A23701F9D00E3B301 /* AppDelegate.swift */, - C54D557023701F9D00E3B301 /* en.lproj */, + C54D557023701F9D00E3B301 /* Base.lproj */, + 89919A2E29FC0D3F0011ADF0 /* Utilities */, C54D556423701F9D00E3B301 /* Extensions */, C54D557623701F9D00E3B301 /* Models */, C54D558123701F9D00E3B301 /* Resources */, @@ -350,6 +388,7 @@ D50E22C928995E0F007D8E83 /* ColorExtensions.swift */, D50E22CB28996D0F007D8E83 /* TransitionExtensions.swift */, D50E22D5289AF699007D8E83 /* ToggleStyle.swift */, + 890702992A0541E60027D7A8 /* String+Localize.swift */, ); path = Extensions; sourceTree = ""; @@ -362,19 +401,21 @@ C54D556D23701F9D00E3B301 /* PaymentViewModel.swift */, C54D556E23701F9D00E3B301 /* StoreViewModel.swift */, D50E22CD289AAD62007D8E83 /* StoreViewModelObserved.swift */, + 890702A32A0A2CC50027D7A8 /* StandaloneUrlViewModel.swift */, ); path = ViewModels; sourceTree = ""; }; - C54D557023701F9D00E3B301 /* en.lproj */ = { + C54D557023701F9D00E3B301 /* Base.lproj */ = { isa = PBXGroup; children = ( C54D557123701F9D00E3B301 /* LaunchScreen.xib */, A5BE1E6D25791BAB00336A79 /* EnvironmentOptionView.xib */, C54D557323701F9D00E3B301 /* Main.storyboard */, C54D55B92370204000E3B301 /* ProductTableViewCell.xib */, + 890702952A053FDE0027D7A8 /* Localizable.strings */, ); - path = en.lproj; + path = Base.lproj; sourceTree = ""; }; C54D557623701F9D00E3B301 /* Models */ = { @@ -387,6 +428,7 @@ C50CF6B6237AB15C003F79DF /* Product.swift */, C50CF6AA237AB10A003F79DF /* PurchaseData.swift */, C50CF6AC237AB119003F79DF /* PurchaseItem.swift */, + 04EE79832AC1D07E002ACDAA /* ScanUrl.swift */, ); path = Models; sourceTree = ""; @@ -410,6 +452,7 @@ A5BE1E7125791BC400336A79 /* EnvironmentOptionView.swift */, A5C24B59257E5D8B00D832D9 /* PayerOwnedPaymentTokensViewController.swift */, A5C24B5C257E6A3300D832D9 /* PaymentTokenCell.swift */, + 890702A12A0A24820027D7A8 /* PaymentAlternativesViewController.swift */, ); path = ViewControllers; sourceTree = ""; @@ -448,6 +491,7 @@ D5D6E2A328742B14008E75D7 /* Views */ = { isa = PBXGroup; children = ( + 04EE79852AC2A8DD002ACDAA /* ccomponents */, D5D6E2A428742BB7008E75D7 /* LogView.swift */, D5D6E2D5287EBD52008E75D7 /* GeneralSettings.swift */, D5D6E2DF287F4DF0008E75D7 /* PayerReferenceSettings.swift */, @@ -461,6 +505,7 @@ D50E22D7289BB013007D8E83 /* EnvironmentSettingsView.swift */, D50E22D9289BEE38007D8E83 /* ConsumerSettingsView.swift */, D554F1AF28AA5E8B007867A4 /* StyleSettings.swift */, + 8907029F2A0A23ED0027D7A8 /* StandaloneUrlView.swift */, ); path = Views; sourceTree = ""; @@ -502,6 +547,7 @@ packageProductDependencies = ( A569C75026D4CB2400B2237C /* SwedbankPaySDK */, A569C75226D4CB2400B2237C /* SwedbankPaySDKMerchantBackend */, + 04EE79812AC1B657002ACDAA /* CodeScanner */, ); productName = "Swedbank Pay SDK Example"; productReference = C54D554C23701E6B00E3B301 /* Example-app.app */; @@ -538,6 +584,7 @@ mainGroup = C54D554323701E6B00E3B301; packageReferences = ( A569C74F26D4CB2400B2237C /* XCRemoteSwiftPackageReference "swedbank-pay-sdk-ios" */, + 04EE79802AC1B657002ACDAA /* XCRemoteSwiftPackageReference "CodeScanner" */, ); productRefGroup = C54D554D23701E6B00E3B301 /* Products */; projectDirPath = ""; @@ -571,7 +618,7 @@ C54D55AF23701F9D00E3B301 /* IBMPlexMono-ExtraLightItalic.ttf in Resources */, C54D559D23701F9D00E3B301 /* LaunchScreen.xib in Resources */, C54D55B123701F9D00E3B301 /* IBMPlexMono-Regular.ttf in Resources */, - A5BE1E6F25791BAB00336A79 /* EnvironmentOptionView.xib in Resources */, + 890702972A053FDE0027D7A8 /* Localizable.strings in Resources */, C54D55AB23701F9D00E3B301 /* IBMPlexMono-MediumItalic.ttf in Resources */, C54D55B723701F9D00E3B301 /* IBMPlexMono-LightItalic.ttf in Resources */, C54D55A823701F9D00E3B301 /* Images.xcassets in Resources */, @@ -611,6 +658,7 @@ D50E22CE289AAD62007D8E83 /* StoreViewModelObserved.swift in Sources */, D5D6E2DC287EFF44008E75D7 /* ViewExtensions.swift in Sources */, A56998672417C3B000162D69 /* SettingsCell.swift in Sources */, + 890702A42A0A2CC50027D7A8 /* StandaloneUrlViewModel.swift in Sources */, C50CF6AB237AB10A003F79DF /* PurchaseData.swift in Sources */, C54D559523701F9D00E3B301 /* UIView+Extension.swift in Sources */, D50E22CA28995E0F007D8E83 /* ColorExtensions.swift in Sources */, @@ -625,6 +673,7 @@ D554F1B028AA5E8B007867A4 /* StyleSettings.swift in Sources */, C54D559323701F9D00E3B301 /* UIFont+Extension.swift in Sources */, C50CF6B3237AB149003F79DF /* Country.swift in Sources */, + 890702A22A0A24820027D7A8 /* PaymentAlternativesViewController.swift in Sources */, D50E22D2289AAE58007D8E83 /* ProductListView.swift in Sources */, C50CF6AF237AB126003F79DF /* PaymentResult.swift in Sources */, D5D6E2E2288191DA008E75D7 /* PaymentTokenSettings.swift in Sources */, @@ -633,6 +682,7 @@ C50CF6B7237AB15C003F79DF /* Product.swift in Sources */, C54D55A723701F9D00E3B301 /* ShoppingCartProductTableViewCell.swift in Sources */, D50E22D8289BB013007D8E83 /* EnvironmentSettingsView.swift in Sources */, + 04EE79872AC2A938002ACDAA /* IconButton.swift in Sources */, D50E22D6289AF699007D8E83 /* ToggleStyle.swift in Sources */, D5083C3D2897FAEE00F4A307 /* ShoppingBasketView.swift in Sources */, C54D55A623701F9D00E3B301 /* ShoppingCartSummaryFooterView.swift in Sources */, @@ -644,12 +694,17 @@ D5D6E2DE287F4DAA008E75D7 /* ImageExtensions.swift in Sources */, D50E22DA289BEE38007D8E83 /* ConsumerSettingsView.swift in Sources */, D5D6E2DA287EFF23008E75D7 /* TextExtensions.swift in Sources */, + 8907029A2A0541E60027D7A8 /* String+Localize.swift in Sources */, D5D6E2D6287EBD52008E75D7 /* GeneralSettings.swift in Sources */, C54D55A323701F9D00E3B301 /* ShoppingCartViewController.swift in Sources */, D5D6E2E6288195F3008E75D7 /* InstrumentModeSelector.swift in Sources */, C54D55A123701F9D00E3B301 /* StoreViewController.swift in Sources */, + 8907029C2A0A20030027D7A8 /* StorageHelper.swift in Sources */, + 04EE79842AC1D07E002ACDAA /* ScanUrl.swift in Sources */, + 890702A02A0A23ED0027D7A8 /* StandaloneUrlView.swift in Sources */, C54D559623701F9D00E3B301 /* UINavigationController+Extension.swift in Sources */, D50E22D4289ABA97007D8E83 /* ShoppingCartTable.swift in Sources */, + 89919A382A0140C60011ADF0 /* SwedbankPayConfiguration.swift in Sources */, C54D55A223701F9D00E3B301 /* ResultViewController.swift in Sources */, D5D6E2A528742BB7008E75D7 /* LogView.swift in Sources */, C50CF6AD237AB119003F79DF /* PurchaseItem.swift in Sources */, @@ -667,6 +722,15 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 890702952A053FDE0027D7A8 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 890702962A053FDE0027D7A8 /* Base */, + 890702982A053FE50027D7A8 /* en */, + ); + name = Localizable.strings; + sourceTree = ""; + }; A5BE1E6D25791BAB00336A79 /* EnvironmentOptionView.xib */ = { isa = PBXVariantGroup; children = ( @@ -871,17 +935,17 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = MUD77FXNE9; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8ZL2DT9DQM; INFOPLIST_FILE = "Example-app/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.swedbank.swedbank-pay-testapp-prod"; + PRODUCT_BUNDLE_IDENTIFIER = com.swedbankpay.exampleapp; PRODUCT_NAME = "Example-app"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Swedbank Pay SDK Demo App Distribution"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "iOS SDK Example App Distribution"; SWEDBANKPAY_SDK_CALLBACK_SCHEME = swedbankexample; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -899,17 +963,17 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = MUD77FXNE9; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 8ZL2DT9DQM; INFOPLIST_FILE = "Example-app/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = "com.swedbank.swedbank-pay-testapp-prod"; + PRODUCT_BUNDLE_IDENTIFIER = com.swedbankpay.exampleapp; PRODUCT_NAME = "Example-app"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Swedbank Pay SDK Demo App Distribution"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "iOS SDK Example App Distribution"; SWEDBANKPAY_SDK_CALLBACK_SCHEME = swedbankexample; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -949,17 +1013,30 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ + 04EE79802AC1B657002ACDAA /* XCRemoteSwiftPackageReference "CodeScanner" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/twostraws/CodeScanner"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.0.0; + }; + }; A569C74F26D4CB2400B2237C /* XCRemoteSwiftPackageReference "swedbank-pay-sdk-ios" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/SwedbankPay/swedbank-pay-sdk-ios.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 4.0.3; + minimumVersion = 5.0.0; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 04EE79812AC1B657002ACDAA /* CodeScanner */ = { + isa = XCSwiftPackageProductDependency; + package = 04EE79802AC1B657002ACDAA /* XCRemoteSwiftPackageReference "CodeScanner" */; + productName = CodeScanner; + }; A569C75026D4CB2400B2237C /* SwedbankPaySDK */ = { isa = XCSwiftPackageProductDependency; package = A569C74F26D4CB2400B2237C /* XCRemoteSwiftPackageReference "swedbank-pay-sdk-ios" */; diff --git a/Example-app/Base.lproj/Localizable.strings b/Example-app/Base.lproj/Localizable.strings new file mode 100644 index 0000000..60019ca --- /dev/null +++ b/Example-app/Base.lproj/Localizable.strings @@ -0,0 +1,33 @@ +//General +"general_checkout" = "Checkout"; +"general_ok" = "OK"; +"general_retry" = "Retry"; + +// Stand-alone URL View Controller +"stand_alone_url_payment_successful" = "Payment completed"; +"stand_alone_url_payment_cancelled" = "Payment cancelled"; + +"stand_alone_url_payment_view_checkout_url" = "View checkout URL"; +"stand_alone_url_payment_base_url" = "Base URL"; +"stand_alone_url_payment_complete_url" = "Complete URL"; +"stand_alone_url_payment_cancel_url" = "Cancel URL"; +"stand_alone_url_payment_checkout_v3" = "Use Checkout V3"; +"stand_alone_url_payment_payment_url" = "Payment URL"; +"stand_alone_url_payment_payment_url_scheme" = "swedbankexample://"; +"stand_alone_url_payment_session_url" = "Session URL"; +"stand_alone_url_payment_get_session" = "Get Session"; +"stand_alone_url_payment_swish_number" = "Swish number"; +"stand_alone_url_payment_swish" = "Swish"; +"stand_alone_url_payment_swish_device" = "Swish using this device"; +"stand_alone_url_payment_swish_prefill %@" = "Swish: %@"; +"stand_alone_url_payment_credit_card_prefill %@" = "Credit Card: %@"; +"stand_alone_url_payment_new_credit_card" = "New Credit Card"; +"stand_alone_url_payment_abort" = "Abort"; +"stand_alone_url_payment_session_end_state_reached" = "Something went wrong with the payment"; +"stand_alone_internal_inconsistency_error" = "Something was called it the wrong order"; +"stand_alone_generic_error_title" = "Something went wrong"; +"stand_alone_url_seamless_title" = "Seamless View"; +"stand_alone_url_payment_web" = "Get payment menu"; +"stand_alone_url_payment_web_restricted" = "Get payment menu\nRestricted to non native"; +"stand_alone_url_payment_apple_pay" = "ApplePay"; +"stand_alone_url_payment_web_based" = "Web Based"; diff --git a/Example-app/Base.lproj/Main.storyboard b/Example-app/Base.lproj/Main.storyboard index 862ab83..45c6648 100644 --- a/Example-app/Base.lproj/Main.storyboard +++ b/Example-app/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -29,7 +29,7 @@ - + @@ -45,12 +45,75 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -61,7 +124,7 @@ - + @@ -130,7 +193,7 @@ - + @@ -166,7 +229,7 @@ - + @@ -178,7 +241,7 @@ - + @@ -226,7 +289,7 @@ - + @@ -297,7 +360,7 @@ - + @@ -383,7 +446,7 @@ - + @@ -449,7 +512,7 @@ - + @@ -634,7 +697,7 @@ - + @@ -880,7 +943,7 @@ - + @@ -950,20 +1013,20 @@ - + - + @@ -1617,18 +1680,18 @@ - + - + @@ -1692,7 +1755,7 @@ @@ -1738,7 +1801,7 @@ - + @@ -1780,7 +1843,7 @@ - + @@ -1826,7 +1889,7 @@ - + @@ -1875,7 +1938,7 @@ - + @@ -1923,7 +1986,7 @@ - + @@ -1965,7 +2028,17 @@ - + + + + + + + + + + + @@ -1976,7 +2049,7 @@ - + diff --git a/Example-app/Example-app.entitlements b/Example-app/Example-app.entitlements index 8c1be53..80ec256 100644 --- a/Example-app/Example-app.entitlements +++ b/Example-app/Example-app.entitlements @@ -9,5 +9,10 @@ applinks:pp-dot-payex-merchant-samples.ey.r.appspot.com applinks:payex-merchant-samples-prod.appspot.com + com.apple.developer.in-app-payments + + merchant.com.swedbankpay.exampleapp + merchant.com.swedbankpay.charity + diff --git a/Example-app/Extensions/String+Localize.swift b/Example-app/Extensions/String+Localize.swift new file mode 100644 index 0000000..720cecc --- /dev/null +++ b/Example-app/Extensions/String+Localize.swift @@ -0,0 +1,11 @@ +import Foundation + +extension String { + var localize: String { + return NSLocalizedString(self, comment: "") + } + + func localize(_ arguments: CVarArg...) -> String { + return String(format: self.localize, arguments: arguments) + } +} diff --git a/Example-app/Images.xcassets/Contents.json b/Example-app/Images.xcassets/Contents.json index da4a164..73c0059 100644 --- a/Example-app/Images.xcassets/Contents.json +++ b/Example-app/Images.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Example-app/Images.xcassets/payment_failed_icon.imageset/Contents.json b/Example-app/Images.xcassets/payment_failed_icon.imageset/Contents.json new file mode 100644 index 0000000..fd0e53a --- /dev/null +++ b/Example-app/Images.xcassets/payment_failed_icon.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "download-2.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/Example-app/Images.xcassets/payment_failed_icon.imageset/download-2.png b/Example-app/Images.xcassets/payment_failed_icon.imageset/download-2.png new file mode 100644 index 0000000..7f640f1 Binary files /dev/null and b/Example-app/Images.xcassets/payment_failed_icon.imageset/download-2.png differ diff --git a/Example-app/Images.xcassets/payment_success_icon.imageset/Contents.json b/Example-app/Images.xcassets/payment_success_icon.imageset/Contents.json new file mode 100644 index 0000000..952a18e --- /dev/null +++ b/Example-app/Images.xcassets/payment_success_icon.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "download.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/Example-app/Images.xcassets/payment_success_icon.imageset/download.png b/Example-app/Images.xcassets/payment_success_icon.imageset/download.png new file mode 100644 index 0000000..9eb97a8 Binary files /dev/null and b/Example-app/Images.xcassets/payment_success_icon.imageset/download.png differ diff --git a/Example-app/Info.plist b/Example-app/Info.plist index d6bb7b7..ac8e129 100644 --- a/Example-app/Info.plist +++ b/Example-app/Info.plist @@ -39,6 +39,8 @@ LSRequiresIPhoneOS + NSCameraUsageDescription + Appen behöver åtkomst till kameran för att kunna skanna koder. UIAppFonts IBMPlexMono-Medium.ttf diff --git a/Example-app/Models/ScanUrl.swift b/Example-app/Models/ScanUrl.swift new file mode 100644 index 0000000..778e562 --- /dev/null +++ b/Example-app/Models/ScanUrl.swift @@ -0,0 +1,25 @@ +enum ScanUrl: String { + case checkout + case base + case complete + case cancel + case payment + case sessionApi + case swish + case unknown + + func toKey() -> StorageHelper.Key? { + switch self { + case .base: + return .baseUrl + case .complete: + return .completeUrl + case .cancel: + return .cancelUrl + case .payment: + return .paymentUrl + default: + return nil + } + } +} diff --git a/Example-app/Utilities/StorageHelper.swift b/Example-app/Utilities/StorageHelper.swift new file mode 100644 index 0000000..a430f75 --- /dev/null +++ b/Example-app/Utilities/StorageHelper.swift @@ -0,0 +1,29 @@ +import Foundation + +struct StorageHelper { + + enum Key: String, CaseIterable { + case baseUrl + case completeUrl + case cancelUrl + case useCheckoutV3 + case paymentUrl + + var storageString: String { + return "Storage\(self.rawValue)" + } + } + + static let shared = StorageHelper() + + private let store = UserDefaults.standard + + // MARK: Generic methods + func save(value: T, forKey key: Key) { + store.set(value, forKey: key.storageString) + } + + func value(for key: Key) -> T? { + return store.value(forKey: key.storageString) as? T + } +} diff --git a/Example-app/Utilities/SwedbankPayConfiguration.swift b/Example-app/Utilities/SwedbankPayConfiguration.swift new file mode 100644 index 0000000..59dbaf1 --- /dev/null +++ b/Example-app/Utilities/SwedbankPayConfiguration.swift @@ -0,0 +1,36 @@ +import Foundation +import SwedbankPaySDK + +enum SwedbankPayConfigurationError: Error { + case notImplemented +} + +class SwedbankPayConfiguration { + let orderInfo: SwedbankPaySDK.ViewPaymentOrderInfo + + init(isV3: Bool = true, webViewBaseURL: URL?, + viewPaymentLink: URL, completeUrl: URL, cancelUrl: URL?, + paymentUrl: URL? = nil, termsOfServiceUrl: URL? = nil) { + self.orderInfo = SwedbankPaySDK.ViewPaymentOrderInfo( + isV3: isV3, + webViewBaseURL: webViewBaseURL, + viewPaymentLink: viewPaymentLink, + completeUrl: completeUrl, + cancelUrl: cancelUrl, + paymentUrl: paymentUrl, + termsOfServiceUrl: termsOfServiceUrl + ) + } +} + +extension SwedbankPayConfiguration: SwedbankPaySDKConfiguration { + + // This delegate method is not used but required + func postConsumers(consumer: SwedbankPaySDK.Consumer?, userData: Any?, completion: @escaping (Result) -> Void) { + completion(.failure(SwedbankPayConfigurationError.notImplemented)) + } + + func postPaymentorders(paymentOrder: SwedbankPaySDK.PaymentOrder?, userData: Any?, consumerProfileRef: String?, options: SwedbankPaySDK.VersionOptions, completion: @escaping (Result) -> Void) { + completion(.success(orderInfo)) + } +} diff --git a/Example-app/ViewControllers/PaymentAlternativesViewController.swift b/Example-app/ViewControllers/PaymentAlternativesViewController.swift new file mode 100644 index 0000000..1823882 --- /dev/null +++ b/Example-app/ViewControllers/PaymentAlternativesViewController.swift @@ -0,0 +1,10 @@ +import Foundation +import SwiftUI +import UIKit + +class PaymentAlternativesViewController: UIViewController { + + @IBSegueAction func showStandaloneUrlView(_ coder: NSCoder) -> UIViewController? { + return UIHostingController(coder: coder, rootView: StandaloneUrlView()) + } +} diff --git a/Example-app/ViewControllers/PaymentViewController.swift b/Example-app/ViewControllers/PaymentViewController.swift index 2abdd58..311619b 100644 --- a/Example-app/ViewControllers/PaymentViewController.swift +++ b/Example-app/ViewControllers/PaymentViewController.swift @@ -200,11 +200,25 @@ extension PaymentViewController: SwedbankPaySDKDelegate { performSegue(withIdentifier: "showResult", sender: self) } + func sessionProblemOccurred(problem: SwedbankPaySDK.ProblemDetails) { + PaymentViewModel.shared.setResult(.unknown) + performSegue(withIdentifier: "showResult", sender: self) + } + + func sdkProblemOccurred(problem: SwedbankPaySDK.PaymentSessionProblem) { + PaymentViewModel.shared.setResult(.unknown) + performSegue(withIdentifier: "showResult", sender: self) + } + func paymentCanceled() { PaymentViewModel.shared.setResult(.unknown) performSegue(withIdentifier: "backToStore", sender: self) } + func paymentSessionFetched(availableInstruments: [SwedbankPaySDK.AvailableInstrument]) { + + } + func updatePaymentOrderFailed(updateInfo: Any, error: Error) { updateInstrumentUI() diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift new file mode 100644 index 0000000..beaf584 --- /dev/null +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -0,0 +1,229 @@ +import Foundation +import SwiftUI +import SwedbankPaySDK +import UIKit + +extension StandaloneUrlView { + class StandaloneUrlViewModel: ObservableObject, SwedbankPaySDKDelegate, SwedbankPaySDKPaymentSessionDelegate { + @Published var viewCheckoutUrl: String = "" + @Published var baseUrl: String + @Published var completeUrl: String + @Published var cancelUrl: String + @Published var sessionApiUrl: String = "" + @Published var swishNumber: String = "" + @Published var useCheckoutV3: Bool + + @Published var paymentUrlAuthorityAndPath: String + @Published var paymentUrlScheme: String + + @Published var displaySwedbankPayController: Bool = false + @Published var displayScannerSheet: Bool = false + @Published var isLoadingNativePayment: Bool = false + + @Published var displayPaymentSessionSwedbankPayController: Bool = false + @Published var paymentSessionSwedbankPayController: SwedbankPaySDKController? + + @Published var showingAlert = false + @Published var errorTitle: String? + @Published var errorMessage: String? + @Published var retry: (()->Void)? + + @Published var paymentResultIcon: String? + @Published var paymentResultMessage: String? + + @Published var nativePayment: SwedbankPaySDK.SwedbankPayPaymentSession? + @Published var availableInstruments: [SwedbankPaySDK.AvailableInstrument]? + + @Published var show3DSecureViewController = false + @Published var paymentSession3DSecureViewController: UIViewController? + + init() { + baseUrl = String(StorageHelper.shared.value(for: .baseUrl) ?? "") + completeUrl = String(StorageHelper.shared.value(for: .completeUrl) ?? "") + cancelUrl = String(StorageHelper.shared.value(for: .cancelUrl) ?? "") + useCheckoutV3 = Bool(StorageHelper.shared.value(for: .useCheckoutV3) ?? true) + paymentUrlAuthorityAndPath = String(StorageHelper.shared.value(for: .paymentUrl) ?? "") + paymentUrlScheme = "stand_alone_url_payment_payment_url_scheme".localize + } + + var isCheckoutEnabled: Bool { + return !viewCheckoutUrl.isEmpty && !completeUrl.isEmpty + } + + func configurePayment() -> SwedbankPayConfiguration? { + guard let completeUrl = URL(string: completeUrl) else { + return nil + } + + var viewPaymentLink: URL + if let url = URL(string: viewCheckoutUrl) { + viewPaymentLink = url + } else { + viewPaymentLink = URL(string: "https://")! + } + + StorageHelper.shared.save(value: baseUrl, forKey: .baseUrl) + StorageHelper.shared.save(value: self.completeUrl, forKey: .completeUrl) + StorageHelper.shared.save(value: cancelUrl, forKey: .cancelUrl) + StorageHelper.shared.save(value: useCheckoutV3, forKey: .useCheckoutV3) + StorageHelper.shared.save(value: paymentUrlAuthorityAndPath, forKey: .paymentUrl) + + let paymentUrl = paymentUrlScheme+paymentUrlAuthorityAndPath + + let configuration = SwedbankPayConfiguration( + isV3: useCheckoutV3, + webViewBaseURL: URL(string: baseUrl), + viewPaymentLink: viewPaymentLink, + completeUrl: completeUrl, + cancelUrl: URL(string: cancelUrl), + paymentUrl: URL(string: paymentUrl) + ) + + return configuration + } + + func saveUrl(urlType: ScanUrl, url: String) { + if let key = urlType.toKey() { + StorageHelper.shared.save(value: url, forKey: key) + } + } + + private func setPaymentResult(success: Bool, resultText: String) { + paymentResultIcon = success ? "payment_success_icon" : "payment_failed_icon" + + paymentResultMessage = resultText + + displaySwedbankPayController = false + displayPaymentSessionSwedbankPayController = false + isLoadingNativePayment = false + + viewCheckoutUrl = "" + sessionApiUrl = "" + swishNumber = "" + + nativePayment = nil + availableInstruments = nil + } + + private func showAlert(error: Error, retry: (()->Void)? = nil) { + showAlert(errorTitle: nil, + errorMessage: "\((error as NSError).code): \(error.localizedDescription)\n\n\((error as NSError).domain)", + retry: retry) + } + + private func showAlert(errorTitle: String?, errorMessage: String?, retry: (()->Void)? = nil) { + self.errorTitle = errorTitle + self.errorMessage = errorMessage + self.retry = retry + self.showingAlert = true + + isLoadingNativePayment = false + } + + private func showAlertOnPaymentSession3DSecureViewController(error: Error, retry: (()->Void)?) { + let alert = UIAlertController(title: nil, + message: "\((error as NSError).code): \(error.localizedDescription)\n\n\((error as NSError).domain)", + preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "general_ok".localize, style: .cancel, handler: { _ in + self.show3DSecureViewController = false + self.paymentSession3DSecureViewController = nil + })) + + if let retry = retry { + alert.addAction(UIAlertAction(title: "general_retry".localize, style: .default, handler: { _ in + retry() + })) + } + + self.paymentSession3DSecureViewController?.present(alert, animated: true, completion: nil) + } + + func paymentComplete() { + setPaymentResult(success: true, resultText: "stand_alone_url_payment_successful".localize) + } + + func paymentCanceled() { + setPaymentResult(success: false, resultText: "stand_alone_url_payment_cancelled".localize) + } + + func paymentFailed(error: Error) { + setPaymentResult(success: false, resultText: error.localizedDescription) + } + + func paymentSessionComplete() { + setPaymentResult(success: true, resultText: "stand_alone_url_payment_successful".localize) + } + + func sessionProblemOccurred(problem: SwedbankPaySDK.ProblemDetails) { + var errorMessages: [String] = [] + + if let status = problem.status { + errorMessages.append(String(status)) + } + + if let detail = problem.detail { + errorMessages.append(detail) + } + + showAlert(errorTitle: problem.title, + errorMessage: "\(errorMessages.joined(separator: ": "))\n\n\(problem.type)") + } + + func sdkProblemOccurred(problem: SwedbankPaySDK.PaymentSessionProblem) { + switch problem { + case .paymentSessionAPIRequestFailed(let error, let retry): + showAlert(error: error, retry: retry) + case .paymentControllerPaymentFailed(error: let error, retry: let retry): + showAlert(error: error, retry: retry) + case .paymentSession3DSecureViewControllerLoadFailed(error: let error, retry: let retry): + showAlertOnPaymentSession3DSecureViewController(error: error, retry: retry) + case .paymentSessionEndStateReached: + setPaymentResult(success: false, resultText: "stand_alone_url_payment_session_end_state_reached".localize) + case .internalInconsistencyError: + setPaymentResult(success: false, resultText: "stand_alone_internal_inconsistency_error".localize) + case .automaticConfigurationFailed: + setPaymentResult(success: false, resultText: "stand_alone_automatic_configuration_failed".localize) + } + } + + func paymentSessionCanceled() { + setPaymentResult(success: false, resultText: "stand_alone_url_payment_cancelled".localize) + } + + func paymentSessionFetched(availableInstruments: [SwedbankPaySDK.AvailableInstrument]) { + self.availableInstruments = availableInstruments + isLoadingNativePayment = false + } + + func show3DSecureViewController(viewController: UIViewController) { + self.paymentSession3DSecureViewController = viewController + self.show3DSecureViewController = true + } + + func dismiss3DSecureViewController() { + self.show3DSecureViewController = false + self.paymentSession3DSecureViewController = nil + } + + func showSwedbankPaySDKController(viewController: SwedbankPaySDKController) { + paymentSessionSwedbankPayController = viewController + paymentSessionSwedbankPayController?.delegate = self + displayPaymentSessionSwedbankPayController = true + isLoadingNativePayment = false + } + } +} + +struct SomeView: UIViewControllerRepresentable { + var viewController: UIViewController + + typealias UIViewControllerType = UIViewController + + func makeUIViewController(context: Context) -> UIViewController { + return viewController + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) { + //update Content + } +} diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift new file mode 100644 index 0000000..4b8219a --- /dev/null +++ b/Example-app/Views/StandaloneUrlView.swift @@ -0,0 +1,668 @@ +// +// StandaloneUrlView.swift +// Example-app +// +// Created by Andreas Petersson on 2023-05-09. +// Copyright © 2023 Swedbank. All rights reserved. +// + +import SwiftUI +import SwedbankPaySDK +import CodeScanner + +struct StandaloneUrlView: View { + @StateObject private var viewModel = StandaloneUrlViewModel() + @FocusState private var isFocused: Bool + + @State var latestClickedUrl: ScanUrl = .unknown + + var scannerSheet : some View { + CodeScannerView( + codeTypes: [.qr], + completion: { result in + if case let .success(code) = result { + switch self.latestClickedUrl { + case .checkout: + viewModel.viewCheckoutUrl = code.string + break + case .base: + viewModel.baseUrl = code.string + saveEnteredUrl(scanUrl: .base) + break + case .complete: + viewModel.completeUrl = code.string + saveEnteredUrl(scanUrl: .complete) + break + case .cancel: + viewModel.cancelUrl = code.string + saveEnteredUrl(scanUrl: .cancel) + break + case .payment: + viewModel.paymentUrlAuthorityAndPath = code.string + break + case .sessionApi: + viewModel.sessionApiUrl = code.string + break + case .swish: + viewModel.swishNumber = code.string + break + case .unknown: + break + } + viewModel.displayScannerSheet = false + } + }) + } + + var body: some View { + ScrollViewReader { reader in + ScrollView { + VStack { + // Display result information from payment + if let icon = viewModel.paymentResultIcon, let text = viewModel.paymentResultMessage { + Image(icon) + Text(text) + } + } + .id(0) + + VStack(alignment: .leading, spacing: 16) { + VStack(spacing: 4) { + Text("stand_alone_url_payment_base_url") + .frame(maxWidth: .infinity, alignment: .leading) + .font(.subheadline) + + HStack { + TextField( + "stand_alone_url_payment_base_url", + text: $viewModel.baseUrl, + onEditingChanged: { focused in + if (!focused) { + saveEnteredUrl(scanUrl: .base) + } + } + ) + .disableAutocorrection(true) + .autocapitalization(.none) + .lightTextField() + .keyboardType(.URL) + .focused($isFocused) + + IconButton(systemName: "qrcode.viewfinder") { + viewModel.displayScannerSheet = true + self.isFocused = false + self.latestClickedUrl = .base + } + } + } + + VStack(spacing: 4) { + Text("stand_alone_url_payment_complete_url") + .frame(maxWidth: .infinity, alignment: .leading) + .font(.subheadline) + + HStack { + TextField( + "stand_alone_url_payment_complete_url", + text: $viewModel.completeUrl, + onEditingChanged: { focused in + if (!focused) { + saveEnteredUrl(scanUrl: .complete) + } + } + ) + .disableAutocorrection(true) + .autocapitalization(.none) + .lightTextField() + .keyboardType(.URL) + .focused($isFocused) + + IconButton(systemName: "qrcode.viewfinder") { + viewModel.displayScannerSheet = true + self.isFocused = false + self.latestClickedUrl = .complete + } + } + } + + VStack(spacing: 4) { + Text("stand_alone_url_payment_cancel_url") + .frame(maxWidth: .infinity, alignment: .leading) + .font(.subheadline) + + HStack { + TextField( + "stand_alone_url_payment_cancel_url", + text: $viewModel.cancelUrl, + onEditingChanged: { focused in + if (!focused) { + saveEnteredUrl(scanUrl: .cancel) + } + } + ) + .disableAutocorrection(true) + .autocapitalization(.none) + .lightTextField() + .keyboardType(.URL) + .focused($isFocused) + + IconButton(systemName: "qrcode.viewfinder") { + viewModel.displayScannerSheet = true + self.isFocused = false + self.latestClickedUrl = .cancel + } + } + } + + VStack(spacing: 4) { + Text("stand_alone_url_payment_payment_url") + .frame(maxWidth: .infinity, alignment: .leading) + .font(.subheadline) + + HStack { + Text("stand_alone_url_payment_payment_url_scheme") + + TextField( + "stand_alone_url_payment_payment_url", + text: $viewModel.paymentUrlAuthorityAndPath, + onEditingChanged: { focused in + if(!focused) { + saveEnteredUrl(scanUrl: .payment) + } + } + ) + .disableAutocorrection(true) + .autocapitalization(.none) + .lightTextField() + .keyboardType(.URL) + .focused($isFocused) + } + } + + Divider() + + Text("stand_alone_url_seamless_title") + .font(.headline) + + VStack(spacing: 4) { + Text("stand_alone_url_payment_view_checkout_url") + .frame(maxWidth: .infinity, alignment: .leading) + .font(.subheadline) + + HStack { + TextField( + "stand_alone_url_payment_view_checkout_url", + text: $viewModel.viewCheckoutUrl + ) + .disableAutocorrection(true) + .autocapitalization(.none) + .lightTextField() + .keyboardType(.URL) + .focused($isFocused) + + IconButton(systemName: "qrcode.viewfinder") { + viewModel.displayScannerSheet = true + self.isFocused = false + self.latestClickedUrl = .checkout + } + } + } + + Toggle("stand_alone_url_payment_checkout_v3", isOn: $viewModel.useCheckoutV3) + + Button { + isFocused = false + viewModel.displaySwedbankPayController = true + } label: { + Text("general_checkout") + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("checkoutButton") + } + .disabled(!viewModel.isCheckoutEnabled) + .foregroundColor(viewModel.isCheckoutEnabled ? .white : .gray) + .background(viewModel.isCheckoutEnabled ? .black : .backgroundGray) + .cornerRadius(30) + .padding(.top, 10) + + Divider() + + HStack(spacing: 10) { + Text("Native Payment") + .font(.headline) + + if viewModel.isLoadingNativePayment { + ProgressView() + } + } + + VStack(spacing: 4) { + Text("stand_alone_url_payment_session_url") + .frame(maxWidth: .infinity, alignment: .leading) + .font(.subheadline) + + HStack { + TextField( + "stand_alone_url_payment_session_url", + text: $viewModel.sessionApiUrl, + onEditingChanged: { focused in + if (!focused) { + saveEnteredUrl(scanUrl: .sessionApi) + } + } + ) + .disableAutocorrection(true) + .autocapitalization(.none) + .lightTextField() + .keyboardType(.URL) + .focused($isFocused) + + IconButton(systemName: "qrcode.viewfinder") { + viewModel.displayScannerSheet = true + self.isFocused = false + self.latestClickedUrl = .sessionApi + } + } + } + + Button { + isFocused = false + + if let sessionURL = URL(string: viewModel.sessionApiUrl) { + viewModel.nativePayment = SwedbankPaySDK.SwedbankPayPaymentSession() + viewModel.nativePayment?.delegate = viewModel + + viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.fetchPaymentSession(sessionURL: sessionURL) + viewModel.paymentResultIcon = nil + viewModel.paymentResultMessage = nil + } + } label: { + Text("stand_alone_url_payment_get_session") + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("getSessionButton") + } + .disabled(viewModel.sessionApiUrl.isEmpty) + .foregroundColor(!viewModel.sessionApiUrl.isEmpty ? .white : .gray) + .background(!viewModel.sessionApiUrl.isEmpty ? .black : .backgroundGray) + .cornerRadius(30) + .padding(.top, 10) + + if let availableInstruments = viewModel.availableInstruments { + ForEach(availableInstruments, id: \.self) { instrument in + switch instrument { + case .swish(let prefills): + VStack(spacing: 4) { + Text("stand_alone_url_payment_swish_number") + .frame(maxWidth: .infinity, alignment: .leading) + .font(.subheadline) + + HStack { + TextField( + "stand_alone_url_payment_swish_number", + text: $viewModel.swishNumber, + onEditingChanged: { focused in + if (!focused) { + saveEnteredUrl(scanUrl: .sessionApi) + } + } + ) + .disableAutocorrection(true) + .autocapitalization(.none) + .lightTextField() + .keyboardType(.phonePad) + .focused($isFocused) + + IconButton(systemName: "qrcode.viewfinder") { + viewModel.displayScannerSheet = true + self.isFocused = false + self.latestClickedUrl = .swish + } + } + } + + Button { + isFocused = false + + viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.makeNativePaymentAttempt(instrument: .swish(msisdn: viewModel.swishNumber)) + } label: { + Text("stand_alone_url_payment_swish") + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("swishButton") + } + .disabled(viewModel.swishNumber.isEmpty) + .foregroundColor(!viewModel.swishNumber.isEmpty ? .white : .gray) + .background(!viewModel.swishNumber.isEmpty ? .black : .backgroundGray) + .cornerRadius(30) + .padding(.top, 10) + + Button { + isFocused = false + + viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.makeNativePaymentAttempt(instrument: .swish(msisdn: nil)) + } label: { + Text("stand_alone_url_payment_swish_device") + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("swishButton") + } + .foregroundColor(.white) + .background(.black) + .cornerRadius(30) + .padding(.top, 10) + + if let prefills = prefills { + ForEach(prefills, id: \.self) { prefill in + Button { + isFocused = false + + viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.makeNativePaymentAttempt(instrument: .swish(msisdn: prefill.msisdn)) + } label: { + Text("stand_alone_url_payment_swish_prefill \(prefill.msisdn)") + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("swishPrefillButton") + } + .foregroundColor(.white) + .background(.black) + .cornerRadius(30) + .padding(.top, 10) + } + } + case .creditCard(prefills: let prefills): + if let prefills = prefills { + ForEach(prefills, id: \.rank) { prefill in + Button { + isFocused = false + + viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.makeNativePaymentAttempt(instrument: .creditCard(prefill: prefill)) + } label: { + VStack(spacing: 0) { + Text("stand_alone_url_payment_credit_card_prefill \(prefill.cardBrand)") + Text("\(prefill.maskedPan) \(prefill.expiryString)") + } + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("creditCardPrefillButton") + + } + .foregroundColor(.white) + .background(.black) + .cornerRadius(30) + .padding(.top, 10) + } + } + + Button { + isFocused = false + + viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.makeNativePaymentAttempt(instrument: .newCreditCard(enabledPaymentDetailsConsentCheckbox: true)) + } label: { + Text("stand_alone_url_payment_new_credit_card") + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("newCreditCardButton") + } + .foregroundColor(.white) + .background(.black) + .cornerRadius(30) + .padding(.top, 10) + case .applePay: + Button { + isFocused = false + + viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.makeNativePaymentAttempt(instrument: .applePay(merchantIdentifier: "merchant.com.swedbankpay.exampleapp")) + } label: { + VStack(spacing: 0) { + Text("stand_alone_url_payment_apple_pay") + Text("merchant.com.swedbankpay.exampleapp") + } + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("applePayExampleAppButton") + } + .foregroundColor(.white) + .background(.black) + .cornerRadius(30) + .padding(.top, 10) + + Button { + isFocused = false + + viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.makeNativePaymentAttempt(instrument: .applePay(merchantIdentifier: "merchant.com.swedbankpay.charity")) + } label: { + VStack(spacing: 0) { + Text("stand_alone_url_payment_apple_pay") + Text("merchant.com.swedbankpay.charity") + } + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("applePayCharityButton") + } + .foregroundColor(.white) + .background(.black) + .cornerRadius(30) + .padding(.top, 10) + case .webBased(let paymentMethod): + Button { + isFocused = false + + viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.createSwedbankPaySDKController(mode: .instrumentMode(instrument: instrument)) + } label: { + VStack(spacing: 0) { + Text("stand_alone_url_payment_web_based") + Text(paymentMethod) + } + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("webBasedButton") + + } + .foregroundColor(.white) + .background(.black) + .cornerRadius(30) + .padding(.top, 10) + } + } + + Button { + isFocused = false + + viewModel.nativePayment?.createSwedbankPaySDKController(mode: .menu(restrictedToInstruments: nil)) + } label: { + Text("stand_alone_url_payment_web") + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("webBasedButton") + + } + .foregroundColor(.white) + .background(.black) + .cornerRadius(30) + .padding(.top, 10) + + Button { + isFocused = false + + let restrictedToInstruments = availableInstruments.filter { + if case .webBased = $0 { + return true + } + + return false + } + viewModel.nativePayment?.createSwedbankPaySDKController(mode: .menu(restrictedToInstruments: restrictedToInstruments)) + + } label: { + Text("stand_alone_url_payment_web_restricted") + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("webBasedButton") + + } + .foregroundColor(.white) + .background(.black) + .cornerRadius(30) + .padding(.top, 10) + } + + if viewModel.nativePayment != nil { + Button { + isFocused = false + + viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.abortPaymentSession() + } label: { + Text("stand_alone_url_payment_abort") + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("abortButton") + } + .disabled(viewModel.sessionApiUrl.isEmpty) + .foregroundColor(!viewModel.sessionApiUrl.isEmpty ? .white : .gray) + .background(!viewModel.sessionApiUrl.isEmpty ? .black : .backgroundGray) + .cornerRadius(30) + .padding(.top, 10) + .id(1) + } + } + .padding() + .onChange(of: viewModel.paymentResultIcon) { _ in + guard viewModel.paymentResultIcon != nil else { + return + } + + withAnimation { + reader.scrollTo(0, anchor: .top) + } + } + .onChange(of: viewModel.availableInstruments) { _ in + guard viewModel.availableInstruments != nil else { + return + } + + withAnimation { + reader.scrollTo(1, anchor: .bottom) + } + } + .sheet(isPresented: $viewModel.displaySwedbankPayController) { + if let configuration = viewModel.configurePayment() { + SwedbankPayView(swedbankPayConfiguration: configuration, delegate: viewModel, nativePaymentDelegate: viewModel) + } + } + .sheet(isPresented: $viewModel.displayPaymentSessionSwedbankPayController) { + SomeView(viewController: viewModel.paymentSessionSwedbankPayController!) + } + .sheet(isPresented: $viewModel.displayScannerSheet) { + self.scannerSheet + } + .sheet(isPresented: $viewModel.show3DSecureViewController) { + SomeView(viewController: viewModel.paymentSession3DSecureViewController!) + } + .alert(viewModel.errorTitle ?? "stand_alone_generic_error_title".localize, + isPresented: $viewModel.showingAlert, + actions: { + Button("general_ok".localize, role: .cancel) { } + + if let retry = viewModel.retry { + Button("general_retry".localize) { + viewModel.isLoadingNativePayment = true + retry() + } + } + }, + message: { + if let errorMessage = viewModel.errorMessage { + Text(errorMessage) + } + }) + } + } + } + + func saveEnteredUrl(scanUrl: ScanUrl) { + switch scanUrl { + case .checkout: + break + case .base: + viewModel.saveUrl(urlType: scanUrl, url: viewModel.baseUrl) + break + case .complete: + viewModel.saveUrl(urlType: scanUrl, url: viewModel.completeUrl) + break + case .cancel: + viewModel.saveUrl(urlType: scanUrl, url: viewModel.cancelUrl) + break + case .payment: + viewModel.saveUrl(urlType: scanUrl, url: viewModel.paymentUrlAuthorityAndPath) + break + case .sessionApi: + viewModel.saveUrl(urlType: scanUrl, url: viewModel.sessionApiUrl) + break + case .swish: + viewModel.saveUrl(urlType: scanUrl, url: viewModel.swishNumber) + break + case .unknown: + break + } + } +} + +struct SwedbankPayView: UIViewControllerRepresentable { + typealias UIViewControllerType = SwedbankPaySDKController + + private let swedbankPayConfiguration: SwedbankPayConfiguration + private let delegate: SwedbankPaySDKDelegate + private let nativePaymentDelegate: SwedbankPaySDKPaymentSessionDelegate + + init(swedbankPayConfiguration: SwedbankPayConfiguration, delegate: SwedbankPaySDKDelegate, nativePaymentDelegate: SwedbankPaySDKPaymentSessionDelegate) { + self.swedbankPayConfiguration = swedbankPayConfiguration + self.delegate = delegate + self.nativePaymentDelegate = nativePaymentDelegate + } + + func makeUIViewController(context: Context) -> SwedbankPaySDKController { + let vc = SwedbankPaySDKController( + configuration: swedbankPayConfiguration, + withCheckin: false, + consumer: nil, + paymentOrder: nil, + userData: nil) + + vc.delegate = delegate + + return vc + } + + func updateUIViewController(_ uiViewController: SwedbankPaySDKController, context: Context) { + } +} + +struct StandaloneUrlView_Previews: PreviewProvider { + static var previews: some View { + StandaloneUrlView() + } +} diff --git a/Example-app/Views/ccomponents/IconButton.swift b/Example-app/Views/ccomponents/IconButton.swift new file mode 100644 index 0000000..bdc274c --- /dev/null +++ b/Example-app/Views/ccomponents/IconButton.swift @@ -0,0 +1,14 @@ +import SwiftUI + +struct IconButton: View { + let systemName: String + let action: () -> Void + + var body: some View { + Button(action: action) { + Image(systemName: systemName) + .font(.title) + .foregroundColor(.black) + } + } +} diff --git a/Example-app/en.lproj/Localizable.strings b/Example-app/en.lproj/Localizable.strings new file mode 100644 index 0000000..c7b5490 --- /dev/null +++ b/Example-app/en.lproj/Localizable.strings @@ -0,0 +1,33 @@ +//General +"general_checkout" = "Checkout"; +"general_ok" = "OK"; +"general_retry" = "Retry"; + +// Stand-alone URL View Controller +"stand_alone_url_payment_successful" = "Payment completed"; +"stand_alone_url_payment_cancelled" = "Payment cancelled"; + +"stand_alone_url_payment_view_checkout_url" = "View checkout URL"; +"stand_alone_url_payment_base_url" = "Base URL"; +"stand_alone_url_payment_complete_url" = "Complete URL"; +"stand_alone_url_payment_cancel_url" = "Cancel URL"; +"stand_alone_url_payment_checkout_v3" = "Use CheckoutV3"; +"stand_alone_url_payment_payment_url" = "Payment URL"; +"stand_alone_url_payment_payment_url_scheme" = "swedbankexample://"; +"stand_alone_url_payment_session_url" = "Session URL"; +"stand_alone_url_payment_get_session" = "Get Session"; +"stand_alone_url_payment_swish_number" = "Swish number"; +"stand_alone_url_payment_swish" = "Swish"; +"stand_alone_url_payment_swish_device" = "Swish using this device"; +"stand_alone_url_payment_swish_prefill %@" = "Swish: %@"; +"stand_alone_url_payment_credit_card_prefill %@" = "Credit Card: %@"; +"stand_alone_url_payment_new_credit_card" = "New Credit Card"; +"stand_alone_url_payment_abort" = "Abort"; +"stand_alone_url_payment_session_end_state_reached" = "Something went wrong with the payment"; +"stand_alone_internal_inconsistency_error" = "Something was called it the wrong order"; +"stand_alone_generic_error_title" = "Something went wrong"; +"stand_alone_url_seamless_title" = "Seamless View"; +"stand_alone_url_payment_web" = "Get payment menu"; +"stand_alone_url_payment_web_restricted" = "Get payment menu\nRestricted to non native"; +"stand_alone_url_payment_apple_pay" = "ApplePay"; +"stand_alone_url_payment_web_based" = "Web Based"; diff --git a/deploy_new_version/Sources/deploy/build_xcode_project.swift b/deploy_new_version/Sources/deploy/build_xcode_project.swift index 5173b39..7f577e3 100644 --- a/deploy_new_version/Sources/deploy/build_xcode_project.swift +++ b/deploy_new_version/Sources/deploy/build_xcode_project.swift @@ -47,11 +47,13 @@ func codeSignBundle( projectDirectory: URL, archiveURL: URL, identityName: String, + entitlements: String, appBuildName: String //e.g. Example-app ) throws { let path = archiveURL.appendingPathComponent("/Products/Applications/\(appBuildName).app").path try run(name: "codeSignBundle", workingDirectory: projectDirectory, "/usr/bin/codesign", [ "-s", identityName, + "--entitlements", entitlements, path ]) } diff --git a/deploy_new_version/Sources/deploy/deploy_new_version.swift b/deploy_new_version/Sources/deploy/deploy_new_version.swift index b375630..03ad222 100644 --- a/deploy_new_version/Sources/deploy/deploy_new_version.swift +++ b/deploy_new_version/Sources/deploy/deploy_new_version.swift @@ -7,6 +7,7 @@ func deployNewVersion( projectDirectory: URL, scheme: String, bundleId: String, + entitlements: String, profileData: Data, identityData: Data, identityPassword: String, @@ -39,7 +40,7 @@ func deployNewVersion( let archiveURL = tempDir.appendingPathComponent("Archive.xcarchive", isDirectory: false) try archiveUnsigned(projectDirectory: projectDirectory, archiveURL: archiveURL, scheme: scheme, version: nextVersion) - try codeSignBundle(projectDirectory: projectDirectory, archiveURL: archiveURL, identityName: identityName, appBuildName: "Example-app") + try codeSignBundle(projectDirectory: projectDirectory, archiveURL: archiveURL, identityName: identityName, entitlements: entitlements, appBuildName: "Example-app") let exportDirectory = tempDir.appendingPathComponent("export", isDirectory: true) try exportArchive(projectDirectory: projectDirectory, archiveURL: archiveURL, exportDirectory: exportDirectory, exportOptionsPlistURL: exportOptionsPlistURL) diff --git a/deploy_new_version/Sources/deploy/main.swift b/deploy_new_version/Sources/deploy/main.swift index d8c08c5..e99f69d 100644 --- a/deploy_new_version/Sources/deploy/main.swift +++ b/deploy_new_version/Sources/deploy/main.swift @@ -5,7 +5,8 @@ do { try deployNewVersion( projectDirectory: URL(fileURLWithPath: env("GITHUB_WORKSPACE")), scheme: "Example-app", - bundleId: "com.swedbank.swedbank-pay-testapp-prod", + bundleId: "com.swedbankpay.exampleapp", + entitlements: "Example-app/Example-app.entitlements", profileData: envBase64("XCODE_PROVISIONING_PROFILE"), identityData: envBase64("XCODE_SIGNING_CERT"), identityPassword: env("XCODE_SIGNING_CERT_PASSWORD"), @@ -14,7 +15,7 @@ do { appStoreConnectKeyIssuer: env("APPLE_APP_STORE_CONNECT_KEY_ISSUER_ID"), buildSettings: [ "SWIFT_ACTIVE_COMPILATION_CONDITIONS=PROD_DEMO", - "SWEDBANKPAY_SDK_CALLBACK_SCHEME=swedbankprodtest" + "SWEDBANKPAY_SDK_CALLBACK_SCHEME=swedbankexample" ] ) } catch {