From d3e9384158b23e8b7e53b4d6b059561fb8cb50e6 Mon Sep 17 00:00:00 2001 From: Andreas Petersson Date: Mon, 15 May 2023 14:30:09 +0000 Subject: [PATCH 01/31] SP-5 Added Standalone URL payment feature --- Example-app.xcodeproj/project.pbxproj | 54 ++++++- Example-app/Base.lproj/Localizable.strings | 12 ++ Example-app/Base.lproj/Main.storyboard | 145 +++++++++++++----- Example-app/Extensions/String+Localize.swift | 11 ++ Example-app/Images.xcassets/Contents.json | 6 +- .../Contents.json | 15 ++ .../download-2.png | Bin 0 -> 8691 bytes .../Contents.json | 15 ++ .../download.png | Bin 0 -> 8006 bytes Example-app/Utilities/StorageHelper.swift | 28 ++++ .../Utilities/SwedbankPayConfiguration.swift | 36 +++++ .../PaymentAlternativesViewController.swift | 10 ++ .../ViewModels/StandaloneUrlViewModel.swift | 65 ++++++++ Example-app/Views/StandaloneUrlView.swift | 100 ++++++++++++ Example-app/en.lproj/Localizable.strings | 13 ++ 15 files changed, 466 insertions(+), 44 deletions(-) create mode 100644 Example-app/Base.lproj/Localizable.strings create mode 100644 Example-app/Extensions/String+Localize.swift create mode 100644 Example-app/Images.xcassets/payment_failed_icon.imageset/Contents.json create mode 100644 Example-app/Images.xcassets/payment_failed_icon.imageset/download-2.png create mode 100644 Example-app/Images.xcassets/payment_success_icon.imageset/Contents.json create mode 100644 Example-app/Images.xcassets/payment_success_icon.imageset/download.png create mode 100644 Example-app/Utilities/StorageHelper.swift create mode 100644 Example-app/Utilities/SwedbankPayConfiguration.swift create mode 100644 Example-app/ViewControllers/PaymentAlternativesViewController.swift create mode 100644 Example-app/ViewModels/StandaloneUrlViewModel.swift create mode 100644 Example-app/Views/StandaloneUrlView.swift create mode 100644 Example-app/en.lproj/Localizable.strings diff --git a/Example-app.xcodeproj/project.pbxproj b/Example-app.xcodeproj/project.pbxproj index ae9a5e1..907f158 100644 --- a/Example-app.xcodeproj/project.pbxproj +++ b/Example-app.xcodeproj/project.pbxproj @@ -8,6 +8,13 @@ /* Begin PBXBuildFile section */ 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 +24,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 */; }; @@ -118,6 +124,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 = ""; }; @@ -282,6 +296,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 +347,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 +374,7 @@ D50E22C928995E0F007D8E83 /* ColorExtensions.swift */, D50E22CB28996D0F007D8E83 /* TransitionExtensions.swift */, D50E22D5289AF699007D8E83 /* ToggleStyle.swift */, + 890702992A0541E60027D7A8 /* String+Localize.swift */, ); path = Extensions; sourceTree = ""; @@ -362,19 +387,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 */ = { @@ -410,6 +437,7 @@ A5BE1E7125791BC400336A79 /* EnvironmentOptionView.swift */, A5C24B59257E5D8B00D832D9 /* PayerOwnedPaymentTokensViewController.swift */, A5C24B5C257E6A3300D832D9 /* PaymentTokenCell.swift */, + 890702A12A0A24820027D7A8 /* PaymentAlternativesViewController.swift */, ); path = ViewControllers; sourceTree = ""; @@ -461,6 +489,7 @@ D50E22D7289BB013007D8E83 /* EnvironmentSettingsView.swift */, D50E22D9289BEE38007D8E83 /* ConsumerSettingsView.swift */, D554F1AF28AA5E8B007867A4 /* StyleSettings.swift */, + 8907029F2A0A23ED0027D7A8 /* StandaloneUrlView.swift */, ); path = Views; sourceTree = ""; @@ -571,7 +600,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 +640,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 +655,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 */, @@ -644,12 +675,16 @@ 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 */, + 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 +702,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 = ( diff --git a/Example-app/Base.lproj/Localizable.strings b/Example-app/Base.lproj/Localizable.strings new file mode 100644 index 0000000..bd21304 --- /dev/null +++ b/Example-app/Base.lproj/Localizable.strings @@ -0,0 +1,12 @@ +//General +"general_checkout" = "Checkout"; + +// Stand-alone URL View Controller +"stand_alone_url_payment_successful" = "Payment completed"; +"stand_alone_url_payment_cancelled" = "Payment cancelled"; + +"stand_alone_url_payment_view_payment_url" = "View payment 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"; 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/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 0000000000000000000000000000000000000000..7f640f1d5bffb860e79f4bb646f1e21fc09cb10a GIT binary patch literal 8691 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9eb97a836637d35803e510ad2c4d5bbf19d8d84c GIT binary patch literal 8006 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 literal 0 HcmV?d00001 diff --git a/Example-app/Utilities/StorageHelper.swift b/Example-app/Utilities/StorageHelper.swift new file mode 100644 index 0000000..1f39991 --- /dev/null +++ b/Example-app/Utilities/StorageHelper.swift @@ -0,0 +1,28 @@ +import Foundation + +struct StorageHelper { + + enum Key: String, CaseIterable { + case baseUrl + case completeUrl + case cancelUrl + case useCheckoutV3 + + 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/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift new file mode 100644 index 0000000..4a6d279 --- /dev/null +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -0,0 +1,65 @@ +import Foundation +import SwedbankPaySDK + +extension StandaloneUrlView { + class StandaloneUrlViewModel: ObservableObject, SwedbankPaySDKDelegate { + @Published var viewPaymentUrl: String = "" + @Published var baseUrl: String + @Published var completeUrl: String + @Published var cancelUrl: String + @Published var useCheckoutV3: Bool + + @Published var displaySwedbankPayController: Bool = false + + @Published var paymentResultIcon: String? + @Published var paymentResultMessage: String? + + 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) + } + + func configurePayment() -> SwedbankPayConfiguration? { + guard let viewPaymentUrl = URL(string: viewPaymentUrl), let completeUrl = URL(string: completeUrl) else { + return nil + } + + 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) + + let configuration = SwedbankPayConfiguration( + isV3: useCheckoutV3, + webViewBaseURL: URL(string: baseUrl), + viewPaymentLink: viewPaymentUrl, + completeUrl: completeUrl, + cancelUrl: URL(string: cancelUrl) + ) + + return configuration + } + + private func setPaymentResult(success: Bool, resultText: String) { + paymentResultIcon = success ? "payment_success_icon" : "payment_failed_icon" + + paymentResultMessage = resultText + + displaySwedbankPayController = false + } + + func paymentComplete() { + setPaymentResult(success: true, resultText: "stand_alone_url_payment_successful".localize) + } + + func paymentFailed(error: Error) { + setPaymentResult(success: false, resultText: error.localizedDescription) + } + + func paymentCanceled() { + setPaymentResult(success: false, resultText: "stand_alone_url_payment_cancelled".localize) + } + } +} diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift new file mode 100644 index 0000000..776e09f --- /dev/null +++ b/Example-app/Views/StandaloneUrlView.swift @@ -0,0 +1,100 @@ +// +// StandaloneUrlView.swift +// Example-app +// +// Created by Andreas Petersson on 2023-05-09. +// Copyright © 2023 Swedbank. All rights reserved. +// + +import SwiftUI +import SwedbankPaySDK + +struct StandaloneUrlView: View { + @StateObject private var viewModel = StandaloneUrlViewModel() + + var body: some View { + ScrollView { + VStack { + // Display result information from payment + if let icon = viewModel.paymentResultIcon, let text = viewModel.paymentResultMessage { + Image(icon) + Text(text) + } + } + + VStack(alignment: .leading) { + TextField("stand_alone_url_payment_view_payment_url", text: $viewModel.viewPaymentUrl) + .disableAutocorrection(true) + .lightTextField() + + TextField("stand_alone_url_payment_base_url", text: $viewModel.baseUrl) + .disableAutocorrection(true) + .lightTextField() + + TextField("stand_alone_url_payment_complete_url", text: $viewModel.completeUrl) + .disableAutocorrection(true) + .lightTextField() + + TextField("stand_alone_url_payment_cancel_url", text: $viewModel.cancelUrl) + .disableAutocorrection(true) + .lightTextField() + + Toggle("stand_alone_url_payment_checkout_v3", isOn: $viewModel.useCheckoutV3) + + Button { + viewModel.displaySwedbankPayController = true + } label: { + Text("general_checkout") + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("checkoutButton") + } + .foregroundColor(.white) + .background(Color.black) + .cornerRadius(30) + .padding(.top, 10) + } + .padding() + .sheet(isPresented: $viewModel.displaySwedbankPayController) { + if let configuration = viewModel.configurePayment() { + SwedbankPayView(swedbankPayConfiguration: configuration, delegate: viewModel) + } + } + } + } +} + +struct SwedbankPayView: UIViewControllerRepresentable { + typealias UIViewControllerType = SwedbankPaySDKController + + private let swedbankPayConfiguration: SwedbankPayConfiguration + private let delegate: SwedbankPaySDKDelegate + + init(swedbankPayConfiguration: SwedbankPayConfiguration, delegate: SwedbankPaySDKDelegate) { + self.swedbankPayConfiguration = swedbankPayConfiguration + self.delegate = delegate + } + + 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/en.lproj/Localizable.strings b/Example-app/en.lproj/Localizable.strings new file mode 100644 index 0000000..da412d1 --- /dev/null +++ b/Example-app/en.lproj/Localizable.strings @@ -0,0 +1,13 @@ +//General +"general_checkout" = "Checkout"; + +// Stand-alone URL View Controller +"stand_alone_url_payment_successful" = "Payment completed"; +"stand_alone_url_payment_cancelled" = "Payment cancelled"; + +"stand_alone_url_payment_view_payment_url" = "View payment 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"; + From ba83498cba25bbb742623b69b43dd1281930b9a6 Mon Sep 17 00:00:00 2001 From: Johan Hansson Date: Tue, 26 Sep 2023 08:08:10 +0200 Subject: [PATCH 02/31] added codescanner to scan qrs and fill url fields --- Example-app.xcodeproj/project.pbxproj | 33 ++++++++ Example-app/Info.plist | 2 + Example-app/Models/ScanUrl.swift | 7 ++ .../ViewModels/StandaloneUrlViewModel.swift | 1 + Example-app/Views/StandaloneUrlView.swift | 84 +++++++++++++++---- .../Views/ccomponents/IconButton.swift | 14 ++++ 6 files changed, 127 insertions(+), 14 deletions(-) create mode 100644 Example-app/Models/ScanUrl.swift create mode 100644 Example-app/Views/ccomponents/IconButton.swift diff --git a/Example-app.xcodeproj/project.pbxproj b/Example-app.xcodeproj/project.pbxproj index 907f158..5e5c70c 100644 --- a/Example-app.xcodeproj/project.pbxproj +++ b/Example-app.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ 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 */; }; @@ -103,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 = ""; }; @@ -223,6 +228,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 04EE79822AC1B657002ACDAA /* CodeScanner in Frameworks */, A569C75326D4CB2400B2237C /* SwedbankPaySDKMerchantBackend in Frameworks */, A569C75126D4CB2400B2237C /* SwedbankPaySDK in Frameworks */, ); @@ -231,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 = ( @@ -414,6 +428,7 @@ C50CF6B6237AB15C003F79DF /* Product.swift */, C50CF6AA237AB10A003F79DF /* PurchaseData.swift */, C50CF6AC237AB119003F79DF /* PurchaseItem.swift */, + 04EE79832AC1D07E002ACDAA /* ScanUrl.swift */, ); path = Models; sourceTree = ""; @@ -476,6 +491,7 @@ D5D6E2A328742B14008E75D7 /* Views */ = { isa = PBXGroup; children = ( + 04EE79852AC2A8DD002ACDAA /* ccomponents */, D5D6E2A428742BB7008E75D7 /* LogView.swift */, D5D6E2D5287EBD52008E75D7 /* GeneralSettings.swift */, D5D6E2DF287F4DF0008E75D7 /* PayerReferenceSettings.swift */, @@ -531,6 +547,7 @@ packageProductDependencies = ( A569C75026D4CB2400B2237C /* SwedbankPaySDK */, A569C75226D4CB2400B2237C /* SwedbankPaySDKMerchantBackend */, + 04EE79812AC1B657002ACDAA /* CodeScanner */, ); productName = "Swedbank Pay SDK Example"; productReference = C54D554C23701E6B00E3B301 /* Example-app.app */; @@ -567,6 +584,7 @@ mainGroup = C54D554323701E6B00E3B301; packageReferences = ( A569C74F26D4CB2400B2237C /* XCRemoteSwiftPackageReference "swedbank-pay-sdk-ios" */, + 04EE79802AC1B657002ACDAA /* XCRemoteSwiftPackageReference "CodeScanner" */, ); productRefGroup = C54D554D23701E6B00E3B301 /* Products */; projectDirPath = ""; @@ -664,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 */, @@ -681,6 +700,7 @@ 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 */, @@ -993,6 +1013,14 @@ /* 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"; @@ -1004,6 +1032,11 @@ /* 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/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..7f02e5b --- /dev/null +++ b/Example-app/Models/ScanUrl.swift @@ -0,0 +1,7 @@ +enum ScanUrl: String { + case payment + case base + case complete + case cancel + case unknown +} diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index 4a6d279..2b8ea14 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -10,6 +10,7 @@ extension StandaloneUrlView { @Published var useCheckoutV3: Bool @Published var displaySwedbankPayController: Bool = false + @Published var displayScannerSheet: Bool = false @Published var paymentResultIcon: String? @Published var paymentResultMessage: String? diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index 776e09f..e84c404 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -8,10 +8,39 @@ import SwiftUI import SwedbankPaySDK +import CodeScanner struct StandaloneUrlView: View { @StateObject private var viewModel = StandaloneUrlViewModel() + @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 .payment: + viewModel.viewPaymentUrl = code.string + break + case .base: + viewModel.baseUrl = code.string + break + case .complete: + viewModel.completeUrl = code.string + break + case .cancel: + viewModel.cancelUrl = code.string + break + case .unknown: + break + } + viewModel.displayScannerSheet = false + } + }) + } + var body: some View { ScrollView { VStack { @@ -22,22 +51,46 @@ struct StandaloneUrlView: View { } } - VStack(alignment: .leading) { - TextField("stand_alone_url_payment_view_payment_url", text: $viewModel.viewPaymentUrl) - .disableAutocorrection(true) - .lightTextField() - - TextField("stand_alone_url_payment_base_url", text: $viewModel.baseUrl) - .disableAutocorrection(true) - .lightTextField() + VStack(alignment: .leading, spacing: 16) { + HStack { + TextField("stand_alone_url_payment_view_payment_url", text: $viewModel.viewPaymentUrl) + .disableAutocorrection(true) + .lightTextField() + IconButton(systemName: "qrcode.viewfinder") { + viewModel.displayScannerSheet = true + self.latestClickedUrl = .payment + } + } + + HStack { + TextField("stand_alone_url_payment_base_url", text: $viewModel.baseUrl) + .disableAutocorrection(true) + .lightTextField() + IconButton(systemName: "qrcode.viewfinder") { + viewModel.displayScannerSheet = true + self.latestClickedUrl = .base + } + } - TextField("stand_alone_url_payment_complete_url", text: $viewModel.completeUrl) - .disableAutocorrection(true) - .lightTextField() + HStack { + TextField("stand_alone_url_payment_complete_url", text: $viewModel.completeUrl) + .disableAutocorrection(true) + .lightTextField() + IconButton(systemName: "qrcode.viewfinder") { + viewModel.displayScannerSheet = true + self.latestClickedUrl = .complete + } + } - TextField("stand_alone_url_payment_cancel_url", text: $viewModel.cancelUrl) - .disableAutocorrection(true) - .lightTextField() + HStack { + TextField("stand_alone_url_payment_cancel_url", text: $viewModel.cancelUrl) + .disableAutocorrection(true) + .lightTextField() + IconButton(systemName: "qrcode.viewfinder") { + viewModel.displayScannerSheet = true + self.latestClickedUrl = .cancel + } + } Toggle("stand_alone_url_payment_checkout_v3", isOn: $viewModel.useCheckoutV3) @@ -61,6 +114,9 @@ struct StandaloneUrlView: View { SwedbankPayView(swedbankPayConfiguration: configuration, delegate: viewModel) } } + .sheet(isPresented: $viewModel.displayScannerSheet) { + self.scannerSheet + } } } } 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) + } + } +} From 6eadc41d880a745a8a3fec7a401558e43cf3b6c6 Mon Sep 17 00:00:00 2001 From: Johan Hansson Date: Thu, 2 Nov 2023 15:39:27 +0000 Subject: [PATCH 03/31] standalone textfield updates, add payment URL scheme, iOS bump 14.0 -> 15.0 --- Example-app.xcodeproj/project.pbxproj | 4 +- Example-app/Base.lproj/Localizable.strings | 4 +- Example-app/Models/ScanUrl.swift | 18 ++- Example-app/Utilities/StorageHelper.swift | 1 + .../ViewModels/StandaloneUrlViewModel.swift | 27 ++++- Example-app/Views/StandaloneUrlView.swift | 114 ++++++++++++++++-- Example-app/en.lproj/Localizable.strings | 5 +- 7 files changed, 153 insertions(+), 20 deletions(-) diff --git a/Example-app.xcodeproj/project.pbxproj b/Example-app.xcodeproj/project.pbxproj index 5e5c70c..c430d81 100644 --- a/Example-app.xcodeproj/project.pbxproj +++ b/Example-app.xcodeproj/project.pbxproj @@ -937,7 +937,7 @@ DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = MUD77FXNE9; INFOPLIST_FILE = "Example-app/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -965,7 +965,7 @@ DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = MUD77FXNE9; INFOPLIST_FILE = "Example-app/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Example-app/Base.lproj/Localizable.strings b/Example-app/Base.lproj/Localizable.strings index bd21304..31765cc 100644 --- a/Example-app/Base.lproj/Localizable.strings +++ b/Example-app/Base.lproj/Localizable.strings @@ -5,8 +5,10 @@ "stand_alone_url_payment_successful" = "Payment completed"; "stand_alone_url_payment_cancelled" = "Payment cancelled"; -"stand_alone_url_payment_view_payment_url" = "View payment URL"; +"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://"; diff --git a/Example-app/Models/ScanUrl.swift b/Example-app/Models/ScanUrl.swift index 7f02e5b..6260648 100644 --- a/Example-app/Models/ScanUrl.swift +++ b/Example-app/Models/ScanUrl.swift @@ -1,7 +1,23 @@ enum ScanUrl: String { - case payment + case checkout case base case complete case cancel + case payment 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 index 1f39991..a430f75 100644 --- a/Example-app/Utilities/StorageHelper.swift +++ b/Example-app/Utilities/StorageHelper.swift @@ -7,6 +7,7 @@ struct StorageHelper { case completeUrl case cancelUrl case useCheckoutV3 + case paymentUrl var storageString: String { return "Storage\(self.rawValue)" diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index 2b8ea14..d1887a1 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -3,12 +3,15 @@ import SwedbankPaySDK extension StandaloneUrlView { class StandaloneUrlViewModel: ObservableObject, SwedbankPaySDKDelegate { - @Published var viewPaymentUrl: String = "" + @Published var viewCheckoutUrl: String = "" @Published var baseUrl: String @Published var completeUrl: String @Published var cancelUrl: String @Published var useCheckoutV3: Bool + @Published var paymentUrlAuthorityAndPath: String + @Published var paymentUrlScheme: String + @Published var displaySwedbankPayController: Bool = false @Published var displayScannerSheet: Bool = false @@ -20,10 +23,16 @@ extension StandaloneUrlView { 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 viewPaymentUrl = URL(string: viewPaymentUrl), let completeUrl = URL(string: completeUrl) else { + guard let viewCheckoutUrl = URL(string: viewCheckoutUrl), let completeUrl = URL(string: completeUrl) else { return nil } @@ -31,18 +40,28 @@ extension StandaloneUrlView { 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: viewPaymentUrl, + viewPaymentLink: viewCheckoutUrl, completeUrl: completeUrl, - cancelUrl: URL(string: cancelUrl) + 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" diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index e84c404..4ca26b3 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -12,27 +12,33 @@ 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 .payment: - viewModel.viewPaymentUrl = code.string + 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 case .unknown: break } @@ -53,41 +59,88 @@ struct StandaloneUrlView: View { VStack(alignment: .leading, spacing: 16) { HStack { - TextField("stand_alone_url_payment_view_payment_url", text: $viewModel.viewPaymentUrl) + 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.latestClickedUrl = .payment + self.isFocused = false + self.latestClickedUrl = .checkout } } HStack { - TextField("stand_alone_url_payment_base_url", text: $viewModel.baseUrl) + 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 } } HStack { - TextField("stand_alone_url_payment_complete_url", text: $viewModel.completeUrl) + 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 } } HStack { - TextField("stand_alone_url_payment_cancel_url", text: $viewModel.cancelUrl) + 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 } } @@ -95,6 +148,7 @@ struct StandaloneUrlView: View { Toggle("stand_alone_url_payment_checkout_v3", isOn: $viewModel.useCheckoutV3) Button { + isFocused = false viewModel.displaySwedbankPayController = true } label: { Text("general_checkout") @@ -103,10 +157,29 @@ struct StandaloneUrlView: View { .frame(height: 48) .accessibilityIdentifier("checkoutButton") } - .foregroundColor(.white) - .background(Color.black) + .disabled(!viewModel.isCheckoutEnabled) + .foregroundColor(viewModel.isCheckoutEnabled ? .white : .gray) + .background(viewModel.isCheckoutEnabled ? .black : .backgroundGray) .cornerRadius(30) .padding(.top, 10) + + 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) + } } .padding() .sheet(isPresented: $viewModel.displaySwedbankPayController) { @@ -119,6 +192,27 @@ struct StandaloneUrlView: View { } } } + + 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 .unknown: + break + } + } } struct SwedbankPayView: UIViewControllerRepresentable { diff --git a/Example-app/en.lproj/Localizable.strings b/Example-app/en.lproj/Localizable.strings index da412d1..1979e38 100644 --- a/Example-app/en.lproj/Localizable.strings +++ b/Example-app/en.lproj/Localizable.strings @@ -5,9 +5,10 @@ "stand_alone_url_payment_successful" = "Payment completed"; "stand_alone_url_payment_cancelled" = "Payment cancelled"; -"stand_alone_url_payment_view_payment_url" = "View payment URL"; +"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://"; From 3fda9288dacc5c17934ab18f8a493d68bdc8463e Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Wed, 15 May 2024 13:55:52 +0200 Subject: [PATCH 04/31] Native Payment --- Example-app/Base.lproj/Localizable.strings | 12 ++ Example-app/Models/ScanUrl.swift | 2 + .../PaymentViewController.swift | 14 ++ .../ViewModels/StandaloneUrlViewModel.swift | 40 ++++- Example-app/Views/StandaloneUrlView.swift | 164 ++++++++++++++++++ Example-app/en.lproj/Localizable.strings | 12 ++ 6 files changed, 242 insertions(+), 2 deletions(-) diff --git a/Example-app/Base.lproj/Localizable.strings b/Example-app/Base.lproj/Localizable.strings index 31765cc..4b95047 100644 --- a/Example-app/Base.lproj/Localizable.strings +++ b/Example-app/Base.lproj/Localizable.strings @@ -1,5 +1,7 @@ //General "general_checkout" = "Checkout"; +"general_ok" = "OK"; +"general_retry" = "Retry"; // Stand-alone URL View Controller "stand_alone_url_payment_successful" = "Payment completed"; @@ -12,3 +14,13 @@ "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_abort" = "Abort"; +"stand_alone_client_app_launch_failed" = "Failed to launch external app"; +"stand_alone_url_payment_session_end_state_reached" = "Something went wrong with the payment"; diff --git a/Example-app/Models/ScanUrl.swift b/Example-app/Models/ScanUrl.swift index 6260648..778e562 100644 --- a/Example-app/Models/ScanUrl.swift +++ b/Example-app/Models/ScanUrl.swift @@ -4,6 +4,8 @@ enum ScanUrl: String { case complete case cancel case payment + case sessionApi + case swish case unknown func toKey() -> StorageHelper.Key? { diff --git a/Example-app/ViewControllers/PaymentViewController.swift b/Example-app/ViewControllers/PaymentViewController.swift index 2abdd58..2d2dba8 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.NativePaymentProblem) { + PaymentViewModel.shared.setResult(.unknown) + performSegue(withIdentifier: "showResult", sender: self) + } + func paymentCanceled() { PaymentViewModel.shared.setResult(.unknown) performSegue(withIdentifier: "backToStore", sender: self) } + func availableInstrumentsFetched(_ availableInstruments: [SwedbankPaySDK.AvailableInstrument]) { + + } + func updatePaymentOrderFailed(updateInfo: Any, error: Error) { updateInstrumentUI() diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index d1887a1..812cf56 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -7,6 +7,8 @@ extension StandaloneUrlView { @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 @@ -15,9 +17,15 @@ extension StandaloneUrlView { @Published var displaySwedbankPayController: Bool = false @Published var displayScannerSheet: Bool = false + @Published var showingAlert = false + @Published var error: Error? + @Published var retry: (()->Void)? + @Published var paymentResultIcon: String? @Published var paymentResultMessage: String? + @Published var availableInstrument: [SwedbankPaySDK.AvailableInstrument]? + init() { baseUrl = String(StorageHelper.shared.value(for: .baseUrl) ?? "") completeUrl = String(StorageHelper.shared.value(for: .completeUrl) ?? "") @@ -32,10 +40,17 @@ extension StandaloneUrlView { } func configurePayment() -> SwedbankPayConfiguration? { - guard let viewCheckoutUrl = URL(string: viewCheckoutUrl), let completeUrl = URL(string: completeUrl) else { + 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) @@ -47,7 +62,7 @@ extension StandaloneUrlView { let configuration = SwedbankPayConfiguration( isV3: useCheckoutV3, webViewBaseURL: URL(string: baseUrl), - viewPaymentLink: viewCheckoutUrl, + viewPaymentLink: viewPaymentLink, completeUrl: completeUrl, cancelUrl: URL(string: cancelUrl), paymentUrl: URL(string: paymentUrl) @@ -78,8 +93,29 @@ extension StandaloneUrlView { setPaymentResult(success: false, resultText: error.localizedDescription) } + func sessionProblemOccurred(problem: SwedbankPaySDK.ProblemDetails) { + setPaymentResult(success: false, resultText: problem.detail ?? "") + } + + func sdkProblemOccurred(problem: SwedbankPaySDK.NativePaymentProblem) { + switch problem { + case .clientAppLaunchFailed: + setPaymentResult(success: false, resultText: "stand_alone_client_app_launch_failed".localize) + case .paymentSessionAPIRequestFailed(let error, let retry): + self.error = error + self.retry = retry + self.showingAlert = true + case .paymentSessionEndStateReached: + setPaymentResult(success: false, resultText: "stand_alone_url_payment_session_end_state_reached".localize) + } + } + func paymentCanceled() { setPaymentResult(success: false, resultText: "stand_alone_url_payment_cancelled".localize) } + + func availableInstrumentsFetched(_ availableInstruments: [SwedbankPaySDK.AvailableInstrument]) { + self.availableInstrument = availableInstruments + } } } diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index 4ca26b3..c93c89f 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -14,6 +14,7 @@ struct StandaloneUrlView: View { @StateObject private var viewModel = StandaloneUrlViewModel() @FocusState private var isFocused: Bool + @State var nativePayment: SwedbankPaySDK.NativePayment? @State var latestClickedUrl: ScanUrl = .unknown var scannerSheet : some View { @@ -39,6 +40,13 @@ struct StandaloneUrlView: View { 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 } @@ -180,6 +188,151 @@ struct StandaloneUrlView: View { .keyboardType(.URL) .focused($isFocused) } + + 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 configuration = viewModel.configurePayment() { + nativePayment = SwedbankPaySDK.NativePayment(orderInfo: configuration.orderInfo) + nativePayment?.delegate = viewModel + + nativePayment?.startPaymentSession(with: viewModel.sessionApiUrl) + } + } 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 availableInstrument = viewModel.availableInstrument { + ForEach(availableInstrument, id: \.self) { instrument in + switch instrument { + case .swish(let prefills): + 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 + + nativePayment?.makePaymentAttempt(with: .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 + + nativePayment?.makePaymentAttempt(with: .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 + + nativePayment?.makePaymentAttempt(with: .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) + } + } + } + } + } + + Button { + isFocused = false + + 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) } .padding() .sheet(isPresented: $viewModel.displaySwedbankPayController) { @@ -190,6 +343,11 @@ struct StandaloneUrlView: View { .sheet(isPresented: $viewModel.displayScannerSheet) { self.scannerSheet } + .alert(viewModel.error?.localizedDescription ?? "", + isPresented: $viewModel.showingAlert) { + Button("general_ok".localize, role: .cancel) { } + Button("general_retry".localize) { viewModel.retry?() } + } } } @@ -209,6 +367,12 @@ struct StandaloneUrlView: View { 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 } diff --git a/Example-app/en.lproj/Localizable.strings b/Example-app/en.lproj/Localizable.strings index 1979e38..27bf9a7 100644 --- a/Example-app/en.lproj/Localizable.strings +++ b/Example-app/en.lproj/Localizable.strings @@ -1,5 +1,7 @@ //General "general_checkout" = "Checkout"; +"general_ok" = "OK"; +"general_retry" = "Retry"; // Stand-alone URL View Controller "stand_alone_url_payment_successful" = "Payment completed"; @@ -12,3 +14,13 @@ "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_abort" = "Abort"; +"stand_alone_client_app_launch_failed" = "Failed to launch external app"; +"stand_alone_url_payment_session_end_state_reached" = "Something went wrong with the payment"; From 4d4248e830a14ea100b93924493d9d01cdd0efa0 Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Thu, 30 May 2024 14:34:11 +0200 Subject: [PATCH 05/31] Added internalInconsistencyError --- Example-app/Base.lproj/Localizable.strings | 1 + Example-app/ViewModels/StandaloneUrlViewModel.swift | 2 ++ Example-app/en.lproj/Localizable.strings | 1 + 3 files changed, 4 insertions(+) diff --git a/Example-app/Base.lproj/Localizable.strings b/Example-app/Base.lproj/Localizable.strings index 4b95047..cb733b1 100644 --- a/Example-app/Base.lproj/Localizable.strings +++ b/Example-app/Base.lproj/Localizable.strings @@ -24,3 +24,4 @@ "stand_alone_url_payment_abort" = "Abort"; "stand_alone_client_app_launch_failed" = "Failed to launch external app"; "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"; diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index 812cf56..08d241b 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -107,6 +107,8 @@ extension StandaloneUrlView { self.showingAlert = true 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) } } diff --git a/Example-app/en.lproj/Localizable.strings b/Example-app/en.lproj/Localizable.strings index 27bf9a7..577e3eb 100644 --- a/Example-app/en.lproj/Localizable.strings +++ b/Example-app/en.lproj/Localizable.strings @@ -24,3 +24,4 @@ "stand_alone_url_payment_abort" = "Abort"; "stand_alone_client_app_launch_failed" = "Failed to launch external app"; "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" From ff349cd85bd05067d9d85b92b10ebaf1d6e223e4 Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Fri, 31 May 2024 10:59:32 +0200 Subject: [PATCH 06/31] Code Review Fixes --- .../ViewModels/StandaloneUrlViewModel.swift | 2 +- Example-app/Views/StandaloneUrlView.swift | 21 +++++++++++-------- Example-app/en.lproj/Localizable.strings | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index 08d241b..6df4ab9 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -2,7 +2,7 @@ import Foundation import SwedbankPaySDK extension StandaloneUrlView { - class StandaloneUrlViewModel: ObservableObject, SwedbankPaySDKDelegate { + class StandaloneUrlViewModel: ObservableObject, SwedbankPaySDKDelegate, SwedbankPaySDKNativePaymentDelegate { @Published var viewCheckoutUrl: String = "" @Published var baseUrl: String @Published var completeUrl: String diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index c93c89f..4c6b8b8 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -215,11 +215,12 @@ struct StandaloneUrlView: View { Button { isFocused = false - if let configuration = viewModel.configurePayment() { + if let configuration = viewModel.configurePayment(), + let sessionURL = URL(string: viewModel.sessionApiUrl) { nativePayment = SwedbankPaySDK.NativePayment(orderInfo: configuration.orderInfo) nativePayment?.delegate = viewModel - nativePayment?.startPaymentSession(with: viewModel.sessionApiUrl) + nativePayment?.startPaymentSession(sessionURL: sessionURL) } } label: { Text("stand_alone_url_payment_get_session") @@ -264,7 +265,7 @@ struct StandaloneUrlView: View { Button { isFocused = false - nativePayment?.makePaymentAttempt(with: .swish(msisdn: viewModel.swishNumber)) + nativePayment?.makePaymentAttempt(instrument: .swish(msisdn: viewModel.swishNumber)) } label: { Text("stand_alone_url_payment_swish") .smallFont() @@ -281,7 +282,7 @@ struct StandaloneUrlView: View { Button { isFocused = false - nativePayment?.makePaymentAttempt(with: .swish(msisdn: nil)) + nativePayment?.makePaymentAttempt(instrument: .swish(msisdn: nil)) } label: { Text("stand_alone_url_payment_swish_device") .smallFont() @@ -299,9 +300,9 @@ struct StandaloneUrlView: View { Button { isFocused = false - nativePayment?.makePaymentAttempt(with: .swish(msisdn: prefill.msisdn)) + nativePayment?.makePaymentAttempt(instrument: .swish(msisdn: prefill.msisdn)) } label: { - Text("stand_alone_url_payment_swish_prefill \(prefill.msisdn ?? "-")") + Text("stand_alone_url_payment_swish_prefill \(prefill.msisdn)") .smallFont() .frame(maxWidth: .infinity) .frame(height: 48) @@ -337,7 +338,7 @@ struct StandaloneUrlView: View { .padding() .sheet(isPresented: $viewModel.displaySwedbankPayController) { if let configuration = viewModel.configurePayment() { - SwedbankPayView(swedbankPayConfiguration: configuration, delegate: viewModel) + SwedbankPayView(swedbankPayConfiguration: configuration, delegate: viewModel, nativePaymentDelegate: viewModel) } } .sheet(isPresented: $viewModel.displayScannerSheet) { @@ -384,10 +385,12 @@ struct SwedbankPayView: UIViewControllerRepresentable { private let swedbankPayConfiguration: SwedbankPayConfiguration private let delegate: SwedbankPaySDKDelegate - - init(swedbankPayConfiguration: SwedbankPayConfiguration, delegate: SwedbankPaySDKDelegate) { + private let nativePaymentDelegate: SwedbankPaySDKNativePaymentDelegate + + init(swedbankPayConfiguration: SwedbankPayConfiguration, delegate: SwedbankPaySDKDelegate, nativePaymentDelegate: SwedbankPaySDKNativePaymentDelegate) { self.swedbankPayConfiguration = swedbankPayConfiguration self.delegate = delegate + self.nativePaymentDelegate = nativePaymentDelegate } func makeUIViewController(context: Context) -> SwedbankPaySDKController { diff --git a/Example-app/en.lproj/Localizable.strings b/Example-app/en.lproj/Localizable.strings index 577e3eb..52d4272 100644 --- a/Example-app/en.lproj/Localizable.strings +++ b/Example-app/en.lproj/Localizable.strings @@ -24,4 +24,4 @@ "stand_alone_url_payment_abort" = "Abort"; "stand_alone_client_app_launch_failed" = "Failed to launch external app"; "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_internal_inconsistency_error" = "Something was called it the wrong order"; From 3927597e0adf013a966858e2617a4dad57ead20d Mon Sep 17 00:00:00 2001 From: Martin Alleus Date: Fri, 31 May 2024 16:21:23 +0200 Subject: [PATCH 07/31] Moving Swedbank Pay SDK SPM dependency to feature/native-payments branch --- Example-app.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Example-app.xcodeproj/project.pbxproj b/Example-app.xcodeproj/project.pbxproj index c430d81..bfcfa54 100644 --- a/Example-app.xcodeproj/project.pbxproj +++ b/Example-app.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -1025,8 +1025,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/SwedbankPay/swedbank-pay-sdk-ios.git"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 4.0.3; + branch = "feature/native-payments"; + kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ From 45655f14e7ee4bad1db98bddf2b2b72e1d015f98 Mon Sep 17 00:00:00 2001 From: Martin Alleus Date: Fri, 31 May 2024 16:25:58 +0200 Subject: [PATCH 08/31] Bumping Xcode version for deploy script --- .github/workflows/deploy_new_version.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_new_version.yml b/.github/workflows/deploy_new_version.yml index d500306..264f8a8 100644 --- a/.github/workflows/deploy_new_version.yml +++ b/.github/workflows/deploy_new_version.yml @@ -12,7 +12,7 @@ jobs: - 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: From 9fa89912eca6dcff00b445d96bcd464ade8a0636 Mon Sep 17 00:00:00 2001 From: Martin Alleus Date: Wed, 8 Nov 2023 09:26:13 +0100 Subject: [PATCH 09/31] Deployment workflow test --- .github/workflows/deploy_new_version.yml | 2 +- Example-app.xcodeproj/project.pbxproj | 12 ++++++------ deploy_new_version/Sources/deploy/main.swift | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deploy_new_version.yml b/.github/workflows/deploy_new_version.yml index 264f8a8..25bc80f 100644 --- a/.github/workflows/deploy_new_version.yml +++ b/.github/workflows/deploy_new_version.yml @@ -7,7 +7,7 @@ on: jobs: deploy: - runs-on: macos-latest + runs-on: macos-13 steps: - uses: actions/checkout@v3 - uses: maxim-lobanov/setup-xcode@v1 diff --git a/Example-app.xcodeproj/project.pbxproj b/Example-app.xcodeproj/project.pbxproj index bfcfa54..656d5b0 100644 --- a/Example-app.xcodeproj/project.pbxproj +++ b/Example-app.xcodeproj/project.pbxproj @@ -935,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 = 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; @@ -963,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 = 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"; diff --git a/deploy_new_version/Sources/deploy/main.swift b/deploy_new_version/Sources/deploy/main.swift index d8c08c5..acc9019 100644 --- a/deploy_new_version/Sources/deploy/main.swift +++ b/deploy_new_version/Sources/deploy/main.swift @@ -5,7 +5,7 @@ do { try deployNewVersion( projectDirectory: URL(fileURLWithPath: env("GITHUB_WORKSPACE")), scheme: "Example-app", - bundleId: "com.swedbank.swedbank-pay-testapp-prod", + bundleId: "com.swedbankpay.exampleapp", profileData: envBase64("XCODE_PROVISIONING_PROFILE"), identityData: envBase64("XCODE_SIGNING_CERT"), identityPassword: env("XCODE_SIGNING_CERT_PASSWORD"), @@ -14,7 +14,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 { From 1006231a2fa63b8f3a5b274ae58da66152c0ecc7 Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Mon, 3 Jun 2024 15:56:10 +0200 Subject: [PATCH 10/31] SP-43 Example app improvements --- Example-app/Base.lproj/Localizable.strings | 1 + .../ViewModels/StandaloneUrlViewModel.swift | 51 +- Example-app/Views/StandaloneUrlView.swift | 601 ++++++++++-------- Example-app/en.lproj/Localizable.strings | 1 + 4 files changed, 394 insertions(+), 260 deletions(-) diff --git a/Example-app/Base.lproj/Localizable.strings b/Example-app/Base.lproj/Localizable.strings index cb733b1..bb6eabc 100644 --- a/Example-app/Base.lproj/Localizable.strings +++ b/Example-app/Base.lproj/Localizable.strings @@ -25,3 +25,4 @@ "stand_alone_client_app_launch_failed" = "Failed to launch external app"; "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"; diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index 6df4ab9..afb1b83 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -16,14 +16,17 @@ extension StandaloneUrlView { @Published var displaySwedbankPayController: Bool = false @Published var displayScannerSheet: Bool = false - + @Published var isLoadingNativePayment: Bool = false + @Published var showingAlert = false - @Published var error: Error? + @Published var errorTitle: String? + @Published var errorMessage: String? @Published var retry: (()->Void)? @Published var paymentResultIcon: String? @Published var paymentResultMessage: String? + @Published var nativePayment: SwedbankPaySDK.NativePayment? @Published var availableInstrument: [SwedbankPaySDK.AvailableInstrument]? init() { @@ -83,6 +86,29 @@ extension StandaloneUrlView { paymentResultMessage = resultText displaySwedbankPayController = false + isLoadingNativePayment = false + + viewCheckoutUrl = "" + sessionApiUrl = "" + swishNumber = "" + + nativePayment = nil + availableInstrument = 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 } func paymentComplete() { @@ -94,17 +120,27 @@ extension StandaloneUrlView { } func sessionProblemOccurred(problem: SwedbankPaySDK.ProblemDetails) { - setPaymentResult(success: false, resultText: problem.detail ?? "") + 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.NativePaymentProblem) { switch problem { case .clientAppLaunchFailed: - setPaymentResult(success: false, resultText: "stand_alone_client_app_launch_failed".localize) + showAlert(errorTitle: nil, + errorMessage: "stand_alone_client_app_launch_failed".localize) case .paymentSessionAPIRequestFailed(let error, let retry): - self.error = error - self.retry = retry - self.showingAlert = true + showAlert(error: error, retry: retry) case .paymentSessionEndStateReached: setPaymentResult(success: false, resultText: "stand_alone_url_payment_session_end_state_reached".localize) case .internalInconsistencyError: @@ -118,6 +154,7 @@ extension StandaloneUrlView { func availableInstrumentsFetched(_ availableInstruments: [SwedbankPaySDK.AvailableInstrument]) { self.availableInstrument = availableInstruments + isLoadingNativePayment = false } } } diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index 4c6b8b8..8a26875 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -14,7 +14,6 @@ struct StandaloneUrlView: View { @StateObject private var viewModel = StandaloneUrlViewModel() @FocusState private var isFocused: Bool - @State var nativePayment: SwedbankPaySDK.NativePayment? @State var latestClickedUrl: ScanUrl = .unknown var scannerSheet : some View { @@ -56,298 +55,394 @@ struct StandaloneUrlView: View { } var body: some View { - ScrollView { - VStack { - // Display result information from payment - if let icon = viewModel.paymentResultIcon, let text = viewModel.paymentResultMessage { - Image(icon) - Text(text) - } - } - - VStack(alignment: .leading, spacing: 16) { - 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 + ScrollViewReader { reader in + ScrollView { + VStack { + // Display result information from payment + if let icon = viewModel.paymentResultIcon, let text = viewModel.paymentResultMessage { + Image(icon) + Text(text) } } - - HStack { - TextField( - "stand_alone_url_payment_base_url", - text: $viewModel.baseUrl, - onEditingChanged: { focused in - if (!focused) { - saveEnteredUrl(scanUrl: .base) + .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 } } - ) - .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 + } + } } - } - - HStack { - TextField( - "stand_alone_url_payment_complete_url", - text: $viewModel.completeUrl, - onEditingChanged: { focused in - if (!focused) { - saveEnteredUrl(scanUrl: .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 } } - ) + } + + 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) - - IconButton(systemName: "qrcode.viewfinder") { - viewModel.displayScannerSheet = true - self.isFocused = false - self.latestClickedUrl = .complete } - } - - HStack { - TextField( - "stand_alone_url_payment_cancel_url", - text: $viewModel.cancelUrl, - onEditingChanged: { focused in - if (!focused) { - saveEnteredUrl(scanUrl: .cancel) + + Divider() + + Text("Seamless View") + .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 } } - ) - .disableAutocorrection(true) - .autocapitalization(.none) - .lightTextField() - .keyboardType(.URL) - .focused($isFocused) + } - IconButton(systemName: "qrcode.viewfinder") { - viewModel.displayScannerSheet = true - self.isFocused = false - self.latestClickedUrl = .cancel + 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") } - } - - 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) - - 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) - } + .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() } - ) - .disableAutocorrection(true) - .autocapitalization(.none) - .lightTextField() - .keyboardType(.URL) - .focused($isFocused) - } - - HStack { - TextField( - "stand_alone_url_payment_session_url", - text: $viewModel.sessionApiUrl, - onEditingChanged: { focused in - if (!focused) { - saveEnteredUrl(scanUrl: .sessionApi) + } + + 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 } } - ) - .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 configuration = viewModel.configurePayment(), - let sessionURL = URL(string: viewModel.sessionApiUrl) { - nativePayment = SwedbankPaySDK.NativePayment(orderInfo: configuration.orderInfo) - nativePayment?.delegate = viewModel + Button { + isFocused = false - nativePayment?.startPaymentSession(sessionURL: sessionURL) + if let configuration = viewModel.configurePayment(), + let sessionURL = URL(string: viewModel.sessionApiUrl) { + viewModel.nativePayment = SwedbankPaySDK.NativePayment(orderInfo: configuration.orderInfo) + viewModel.nativePayment?.delegate = viewModel + + viewModel.nativePayment?.startPaymentSession(sessionURL: sessionURL) + viewModel.isLoadingNativePayment = true + viewModel.paymentResultIcon = nil + viewModel.paymentResultMessage = nil + } + } label: { + Text("stand_alone_url_payment_get_session") + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("getSessionButton") } - } 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 availableInstrument = viewModel.availableInstrument { - ForEach(availableInstrument, id: \.self) { instrument in - switch instrument { - case .swish(let prefills): - HStack { - TextField( - "stand_alone_url_payment_swish_number", - text: $viewModel.swishNumber, - onEditingChanged: { focused in - if (!focused) { - saveEnteredUrl(scanUrl: .sessionApi) + .disabled(viewModel.sessionApiUrl.isEmpty) + .foregroundColor(!viewModel.sessionApiUrl.isEmpty ? .white : .gray) + .background(!viewModel.sessionApiUrl.isEmpty ? .black : .backgroundGray) + .cornerRadius(30) + .padding(.top, 10) + + if let availableInstrument = viewModel.availableInstrument { + ForEach(availableInstrument, 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 } } - ) - .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.nativePayment?.makePaymentAttempt(instrument: .swish(msisdn: viewModel.swishNumber)) + viewModel.isLoadingNativePayment = true + } label: { + Text("stand_alone_url_payment_swish") + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("swishButton") } - } - - Button { - isFocused = false + .disabled(viewModel.swishNumber.isEmpty) + .foregroundColor(!viewModel.swishNumber.isEmpty ? .white : .gray) + .background(!viewModel.swishNumber.isEmpty ? .black : .backgroundGray) + .cornerRadius(30) + .padding(.top, 10) - nativePayment?.makePaymentAttempt(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 + Button { + isFocused = false + + viewModel.nativePayment?.makePaymentAttempt(instrument: .swish(msisdn: nil)) + viewModel.isLoadingNativePayment = true + } 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) - nativePayment?.makePaymentAttempt(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 - - nativePayment?.makePaymentAttempt(instrument: .swish(msisdn: prefill.msisdn)) - } label: { - Text("stand_alone_url_payment_swish_prefill \(prefill.msisdn)") - .smallFont() - .frame(maxWidth: .infinity) - .frame(height: 48) - .accessibilityIdentifier("swishPrefillButton") + if let prefills = prefills { + ForEach(prefills, id: \.self) { prefill in + Button { + isFocused = false + + viewModel.nativePayment?.makePaymentAttempt(instrument: .swish(msisdn: prefill.msisdn)) + viewModel.isLoadingNativePayment = true + } 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) } - .foregroundColor(.white) - .background(.black) - .cornerRadius(30) - .padding(.top, 10) } } } } + + if viewModel.nativePayment != nil { + Button { + isFocused = false + + viewModel.nativePayment?.abortPaymentSession() + viewModel.isLoadingNativePayment = true + } 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) + } } - - Button { - isFocused = false + .padding() + .onChange(of: viewModel.paymentResultIcon) { _ in + guard viewModel.paymentResultIcon != nil else { + return + } - nativePayment?.abortPaymentSession() - } label: { - Text("stand_alone_url_payment_abort") - .smallFont() - .frame(maxWidth: .infinity) - .frame(height: 48) - .accessibilityIdentifier("abortButton") + withAnimation { + reader.scrollTo(0, anchor: .top) + } } - .disabled(viewModel.sessionApiUrl.isEmpty) - .foregroundColor(!viewModel.sessionApiUrl.isEmpty ? .white : .gray) - .background(!viewModel.sessionApiUrl.isEmpty ? .black : .backgroundGray) - .cornerRadius(30) - .padding(.top, 10) - } - .padding() - .sheet(isPresented: $viewModel.displaySwedbankPayController) { - if let configuration = viewModel.configurePayment() { - SwedbankPayView(swedbankPayConfiguration: configuration, delegate: viewModel, nativePaymentDelegate: viewModel) + .onChange(of: viewModel.availableInstrument) { _ in + guard viewModel.availableInstrument != nil else { + return + } + + withAnimation { + reader.scrollTo(1, anchor: .bottom) + } } - } - .sheet(isPresented: $viewModel.displayScannerSheet) { - self.scannerSheet - } - .alert(viewModel.error?.localizedDescription ?? "", - isPresented: $viewModel.showingAlert) { - Button("general_ok".localize, role: .cancel) { } - Button("general_retry".localize) { viewModel.retry?() } + .sheet(isPresented: $viewModel.displaySwedbankPayController) { + if let configuration = viewModel.configurePayment() { + SwedbankPayView(swedbankPayConfiguration: configuration, delegate: viewModel, nativePaymentDelegate: viewModel) + } + } + .sheet(isPresented: $viewModel.displayScannerSheet) { + self.scannerSheet + } + .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) { + retry() + viewModel.isLoadingNativePayment = true + } + } + }, + message: { + if let errorMessage = viewModel.errorMessage { + Text(errorMessage) + } + }) } } } diff --git a/Example-app/en.lproj/Localizable.strings b/Example-app/en.lproj/Localizable.strings index 52d4272..aa34403 100644 --- a/Example-app/en.lproj/Localizable.strings +++ b/Example-app/en.lproj/Localizable.strings @@ -25,3 +25,4 @@ "stand_alone_client_app_launch_failed" = "Failed to launch external app"; "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"; From 276f753d748d4dfcaf07b58a528703818c8a9db0 Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Wed, 5 Jun 2024 10:19:07 +0200 Subject: [PATCH 11/31] Added header for Payment URL --- Example-app/Views/StandaloneUrlView.swift | 36 +++++++++++++---------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index 8a26875..0b841e8 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -154,23 +154,29 @@ struct StandaloneUrlView: View { } } - HStack { - Text("stand_alone_url_payment_payment_url_scheme") + VStack(spacing: 4) { + Text("stand_alone_url_payment_payment_url") + .frame(maxWidth: .infinity, alignment: .leading) + .font(.subheadline) - TextField( - "stand_alone_url_payment_payment_url", - text: $viewModel.paymentUrlAuthorityAndPath, - onEditingChanged: { focused in - if(!focused) { - saveEnteredUrl(scanUrl: .payment) + 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) + ) + .disableAutocorrection(true) + .autocapitalization(.none) + .lightTextField() + .keyboardType(.URL) + .focused($isFocused) + } } Divider() From 1dc68d16bee92c1285c59a9479bf3362f2727db0 Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Wed, 5 Jun 2024 14:03:28 +0200 Subject: [PATCH 12/31] SP-45 CreditCard prefills and payment attempt --- Example-app/Views/StandaloneUrlView.swift | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index 0b841e8..d034a9f 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -380,6 +380,31 @@ struct StandaloneUrlView: View { .padding(.top, 10) } } + case .creditCard(prefills: let prefills): + if let prefills = prefills { + ForEach(prefills, id: \.rank) { prefill in + Button { + isFocused = false + + viewModel.nativePayment?.makePaymentAttempt(instrument: .creditCard(prefill: prefill)) + viewModel.isLoadingNativePayment = true + } 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) + } + } } } } From acf3316fae45c6969c3318e1190a834f818ff241 Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Thu, 13 Jun 2024 14:49:29 +0200 Subject: [PATCH 13/31] WIP: SP-51 SCA Redirect --- .../ViewModels/StandaloneUrlViewModel.swift | 30 ++++++++++++++++++- Example-app/Views/StandaloneUrlView.swift | 19 +++++++----- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index afb1b83..83f6641 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -1,5 +1,7 @@ import Foundation +import SwiftUI import SwedbankPaySDK +import UIKit extension StandaloneUrlView { class StandaloneUrlViewModel: ObservableObject, SwedbankPaySDKDelegate, SwedbankPaySDKNativePaymentDelegate { @@ -28,7 +30,10 @@ extension StandaloneUrlView { @Published var nativePayment: SwedbankPaySDK.NativePayment? @Published var availableInstrument: [SwedbankPaySDK.AvailableInstrument]? - + + @Published var presented = false + @Published var viewController: UIViewController? + init() { baseUrl = String(StorageHelper.shared.value(for: .baseUrl) ?? "") completeUrl = String(StorageHelper.shared.value(for: .completeUrl) ?? "") @@ -156,5 +161,28 @@ extension StandaloneUrlView { self.availableInstrument = availableInstruments isLoadingNativePayment = false } + + func showViewController(viewController: UIViewController) { + self.viewController = viewController + self.presented = true + } + + func finishedWithViewController() { + self.presented = false + self.viewController = nil + } + } +} + +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 index d034a9f..692efb0 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -274,8 +274,8 @@ struct StandaloneUrlView: View { viewModel.nativePayment = SwedbankPaySDK.NativePayment(orderInfo: configuration.orderInfo) viewModel.nativePayment?.delegate = viewModel - viewModel.nativePayment?.startPaymentSession(sessionURL: sessionURL) viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.startPaymentSession(sessionURL: sessionURL) viewModel.paymentResultIcon = nil viewModel.paymentResultMessage = nil } @@ -327,9 +327,9 @@ struct StandaloneUrlView: View { Button { isFocused = false - - viewModel.nativePayment?.makePaymentAttempt(instrument: .swish(msisdn: viewModel.swishNumber)) + viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.makePaymentAttempt(instrument: .swish(msisdn: viewModel.swishNumber)) } label: { Text("stand_alone_url_payment_swish") .smallFont() @@ -346,8 +346,8 @@ struct StandaloneUrlView: View { Button { isFocused = false - viewModel.nativePayment?.makePaymentAttempt(instrument: .swish(msisdn: nil)) viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.makePaymentAttempt(instrument: .swish(msisdn: nil)) } label: { Text("stand_alone_url_payment_swish_device") .smallFont() @@ -365,8 +365,8 @@ struct StandaloneUrlView: View { Button { isFocused = false - viewModel.nativePayment?.makePaymentAttempt(instrument: .swish(msisdn: prefill.msisdn)) viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.makePaymentAttempt(instrument: .swish(msisdn: prefill.msisdn)) } label: { Text("stand_alone_url_payment_swish_prefill \(prefill.msisdn)") .smallFont() @@ -386,8 +386,8 @@ struct StandaloneUrlView: View { Button { isFocused = false - viewModel.nativePayment?.makePaymentAttempt(instrument: .creditCard(prefill: prefill)) viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.makePaymentAttempt(instrument: .creditCard(prefill: prefill)) } label: { VStack(spacing: 0) { Text("stand_alone_url_payment_credit_card_prefill \(prefill.cardBrand)") @@ -413,8 +413,8 @@ struct StandaloneUrlView: View { Button { isFocused = false - viewModel.nativePayment?.abortPaymentSession() viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.abortPaymentSession() } label: { Text("stand_alone_url_payment_abort") .smallFont() @@ -457,6 +457,9 @@ struct StandaloneUrlView: View { .sheet(isPresented: $viewModel.displayScannerSheet) { self.scannerSheet } + .sheet(isPresented: $viewModel.presented) { + SomeView(viewController: viewModel.viewController!) + } .alert(viewModel.errorTitle ?? "stand_alone_generic_error_title".localize, isPresented: $viewModel.showingAlert, actions: { @@ -464,8 +467,8 @@ struct StandaloneUrlView: View { if let retry = viewModel.retry { Button("general_retry".localize) { - retry() viewModel.isLoadingNativePayment = true + retry() } } }, From 49da756c59d5681b5d33a95846ec6e7439fd232c Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Mon, 24 Jun 2024 15:05:05 +0200 Subject: [PATCH 14/31] SP-55 Automatic configuration --- .../ViewControllers/PaymentViewController.swift | 4 ++-- Example-app/ViewModels/StandaloneUrlViewModel.swift | 10 ++++++---- Example-app/Views/StandaloneUrlView.swift | 9 ++++----- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Example-app/ViewControllers/PaymentViewController.swift b/Example-app/ViewControllers/PaymentViewController.swift index 2d2dba8..311619b 100644 --- a/Example-app/ViewControllers/PaymentViewController.swift +++ b/Example-app/ViewControllers/PaymentViewController.swift @@ -205,7 +205,7 @@ extension PaymentViewController: SwedbankPaySDKDelegate { performSegue(withIdentifier: "showResult", sender: self) } - func sdkProblemOccurred(problem: SwedbankPaySDK.NativePaymentProblem) { + func sdkProblemOccurred(problem: SwedbankPaySDK.PaymentSessionProblem) { PaymentViewModel.shared.setResult(.unknown) performSegue(withIdentifier: "showResult", sender: self) } @@ -215,7 +215,7 @@ extension PaymentViewController: SwedbankPaySDKDelegate { performSegue(withIdentifier: "backToStore", sender: self) } - func availableInstrumentsFetched(_ availableInstruments: [SwedbankPaySDK.AvailableInstrument]) { + func paymentSessionFetched(availableInstruments: [SwedbankPaySDK.AvailableInstrument]) { } diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index 83f6641..ca23bbc 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -4,7 +4,7 @@ import SwedbankPaySDK import UIKit extension StandaloneUrlView { - class StandaloneUrlViewModel: ObservableObject, SwedbankPaySDKDelegate, SwedbankPaySDKNativePaymentDelegate { + class StandaloneUrlViewModel: ObservableObject, SwedbankPaySDKDelegate, SwedbankPaySDKPaymentSessionDelegate { @Published var viewCheckoutUrl: String = "" @Published var baseUrl: String @Published var completeUrl: String @@ -28,7 +28,7 @@ extension StandaloneUrlView { @Published var paymentResultIcon: String? @Published var paymentResultMessage: String? - @Published var nativePayment: SwedbankPaySDK.NativePayment? + @Published var nativePayment: SwedbankPaySDK.PaymentSession? @Published var availableInstrument: [SwedbankPaySDK.AvailableInstrument]? @Published var presented = false @@ -139,7 +139,7 @@ extension StandaloneUrlView { errorMessage: "\(errorMessages.joined(separator: ": "))\n\n\(problem.type)") } - func sdkProblemOccurred(problem: SwedbankPaySDK.NativePaymentProblem) { + func sdkProblemOccurred(problem: SwedbankPaySDK.PaymentSessionProblem) { switch problem { case .clientAppLaunchFailed: showAlert(errorTitle: nil, @@ -150,6 +150,8 @@ extension StandaloneUrlView { 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) } } @@ -157,7 +159,7 @@ extension StandaloneUrlView { setPaymentResult(success: false, resultText: "stand_alone_url_payment_cancelled".localize) } - func availableInstrumentsFetched(_ availableInstruments: [SwedbankPaySDK.AvailableInstrument]) { + func paymentSessionFetched(availableInstruments: [SwedbankPaySDK.AvailableInstrument]) { self.availableInstrument = availableInstruments isLoadingNativePayment = false } diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index 692efb0..232f375 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -269,9 +269,8 @@ struct StandaloneUrlView: View { Button { isFocused = false - if let configuration = viewModel.configurePayment(), - let sessionURL = URL(string: viewModel.sessionApiUrl) { - viewModel.nativePayment = SwedbankPaySDK.NativePayment(orderInfo: configuration.orderInfo) + if let sessionURL = URL(string: viewModel.sessionApiUrl) { + viewModel.nativePayment = SwedbankPaySDK.PaymentSession() viewModel.nativePayment?.delegate = viewModel viewModel.isLoadingNativePayment = true @@ -514,9 +513,9 @@ struct SwedbankPayView: UIViewControllerRepresentable { private let swedbankPayConfiguration: SwedbankPayConfiguration private let delegate: SwedbankPaySDKDelegate - private let nativePaymentDelegate: SwedbankPaySDKNativePaymentDelegate + private let nativePaymentDelegate: SwedbankPaySDKPaymentSessionDelegate - init(swedbankPayConfiguration: SwedbankPayConfiguration, delegate: SwedbankPaySDKDelegate, nativePaymentDelegate: SwedbankPaySDKNativePaymentDelegate) { + init(swedbankPayConfiguration: SwedbankPayConfiguration, delegate: SwedbankPaySDKDelegate, nativePaymentDelegate: SwedbankPaySDKPaymentSessionDelegate) { self.swedbankPayConfiguration = swedbankPayConfiguration self.delegate = delegate self.nativePaymentDelegate = nativePaymentDelegate From 239a23ab1e4f0805397fc9277dceb4d035b8d32a Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Thu, 27 Jun 2024 14:17:31 +0200 Subject: [PATCH 15/31] WIP: Renames --- .../ViewModels/StandaloneUrlViewModel.swift | 43 +++++++++++++++---- Example-app/Views/StandaloneUrlView.swift | 20 ++++----- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index ca23bbc..6688800 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -28,8 +28,8 @@ extension StandaloneUrlView { @Published var paymentResultIcon: String? @Published var paymentResultMessage: String? - @Published var nativePayment: SwedbankPaySDK.PaymentSession? - @Published var availableInstrument: [SwedbankPaySDK.AvailableInstrument]? + @Published var nativePayment: SwedbankPaySDK.SwedbankPayPaymentSession? + @Published var availableInstruments: [SwedbankPaySDK.AvailableInstrument]? @Published var presented = false @Published var viewController: UIViewController? @@ -98,7 +98,7 @@ extension StandaloneUrlView { swishNumber = "" nativePayment = nil - availableInstrument = nil + availableInstruments = nil } private func showAlert(error: Error, retry: (()->Void)? = nil) { @@ -119,11 +119,19 @@ extension StandaloneUrlView { 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] = [] @@ -155,24 +163,40 @@ extension StandaloneUrlView { } } - func paymentCanceled() { + func paymentSessionCanceled() { setPaymentResult(success: false, resultText: "stand_alone_url_payment_cancelled".localize) } func paymentSessionFetched(availableInstruments: [SwedbankPaySDK.AvailableInstrument]) { - self.availableInstrument = availableInstruments + self.availableInstruments = availableInstruments isLoadingNativePayment = false } - func showViewController(viewController: UIViewController) { + func show3DSecureViewController(viewController: UIViewController) { self.viewController = viewController self.presented = true } - func finishedWithViewController() { + func dismiss3DSecureViewController() { self.presented = false self.viewController = nil } + + func paymentSession3DSecureViewControllerLoadFailed(error: Error, retry: @escaping ()->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.presented = false + self.viewController = nil + })) + + alert.addAction(UIAlertAction(title: "general_retry".localize, style: .default, handler: { _ in + retry() + })) + + self.viewController?.present(alert, animated: true, completion: nil) + } } } @@ -184,6 +208,7 @@ struct SomeView: UIViewControllerRepresentable { 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 index 232f375..4260b61 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -270,11 +270,11 @@ struct StandaloneUrlView: View { isFocused = false if let sessionURL = URL(string: viewModel.sessionApiUrl) { - viewModel.nativePayment = SwedbankPaySDK.PaymentSession() + viewModel.nativePayment = SwedbankPaySDK.SwedbankPayPaymentSession() viewModel.nativePayment?.delegate = viewModel viewModel.isLoadingNativePayment = true - viewModel.nativePayment?.startPaymentSession(sessionURL: sessionURL) + viewModel.nativePayment?.fetchPaymentSession(sessionURL: sessionURL) viewModel.paymentResultIcon = nil viewModel.paymentResultMessage = nil } @@ -291,8 +291,8 @@ struct StandaloneUrlView: View { .cornerRadius(30) .padding(.top, 10) - if let availableInstrument = viewModel.availableInstrument { - ForEach(availableInstrument, id: \.self) { instrument in + if let availableInstruments = viewModel.availableInstruments { + ForEach(availableInstruments, id: \.self) { instrument in switch instrument { case .swish(let prefills): VStack(spacing: 4) { @@ -328,7 +328,7 @@ struct StandaloneUrlView: View { isFocused = false viewModel.isLoadingNativePayment = true - viewModel.nativePayment?.makePaymentAttempt(instrument: .swish(msisdn: viewModel.swishNumber)) + viewModel.nativePayment?.makeNativePaymentAttempt(instrument: .swish(msisdn: viewModel.swishNumber)) } label: { Text("stand_alone_url_payment_swish") .smallFont() @@ -346,7 +346,7 @@ struct StandaloneUrlView: View { isFocused = false viewModel.isLoadingNativePayment = true - viewModel.nativePayment?.makePaymentAttempt(instrument: .swish(msisdn: nil)) + viewModel.nativePayment?.makeNativePaymentAttempt(instrument: .swish(msisdn: nil)) } label: { Text("stand_alone_url_payment_swish_device") .smallFont() @@ -365,7 +365,7 @@ struct StandaloneUrlView: View { isFocused = false viewModel.isLoadingNativePayment = true - viewModel.nativePayment?.makePaymentAttempt(instrument: .swish(msisdn: prefill.msisdn)) + viewModel.nativePayment?.makeNativePaymentAttempt(instrument: .swish(msisdn: prefill.msisdn)) } label: { Text("stand_alone_url_payment_swish_prefill \(prefill.msisdn)") .smallFont() @@ -386,7 +386,7 @@ struct StandaloneUrlView: View { isFocused = false viewModel.isLoadingNativePayment = true - viewModel.nativePayment?.makePaymentAttempt(instrument: .creditCard(prefill: prefill)) + viewModel.nativePayment?.makeNativePaymentAttempt(instrument: .creditCard(prefill: prefill)) } label: { VStack(spacing: 0) { Text("stand_alone_url_payment_credit_card_prefill \(prefill.cardBrand)") @@ -439,8 +439,8 @@ struct StandaloneUrlView: View { reader.scrollTo(0, anchor: .top) } } - .onChange(of: viewModel.availableInstrument) { _ in - guard viewModel.availableInstrument != nil else { + .onChange(of: viewModel.availableInstruments) { _ in + guard viewModel.availableInstruments != nil else { return } From 3bb971b387ca129ba0b5f2afa6f722016c5c038a Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Fri, 28 Jun 2024 15:02:48 +0200 Subject: [PATCH 16/31] =?UTF-8?q?SP-59=20St=C3=B6d=20f=C3=B6r=20att=20visa?= =?UTF-8?q?=20betalmenyn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModels/StandaloneUrlViewModel.swift | 4 ++++ Example-app/Views/StandaloneUrlView.swift | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index 6688800..e4c771e 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -20,6 +20,9 @@ extension StandaloneUrlView { @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? @@ -91,6 +94,7 @@ extension StandaloneUrlView { paymentResultMessage = resultText displaySwedbankPayController = false + displayPaymentSessionSwedbankPayController = false isLoadingNativePayment = false viewCheckoutUrl = "" diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index 4260b61..d094346 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -404,8 +404,29 @@ struct StandaloneUrlView: View { .padding(.top, 10) } } + case .webBased(identifier: let identifier): + EmptyView() } } + + Button { + isFocused = false + + viewModel.paymentSessionSwedbankPayController = viewModel.nativePayment?.createSwedbankPaySDKController() + viewModel.paymentSessionSwedbankPayController?.delegate = viewModel + viewModel.displayPaymentSessionSwedbankPayController = true + } label: { + Text("Get payment menu") + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("webBasedButton") + + } + .foregroundColor(.white) + .background(.black) + .cornerRadius(30) + .padding(.top, 10) } if viewModel.nativePayment != nil { @@ -453,6 +474,9 @@ struct StandaloneUrlView: View { SwedbankPayView(swedbankPayConfiguration: configuration, delegate: viewModel, nativePaymentDelegate: viewModel) } } + .sheet(isPresented: $viewModel.displayPaymentSessionSwedbankPayController) { + SomeView(viewController: viewModel.paymentSessionSwedbankPayController!) + } .sheet(isPresented: $viewModel.displayScannerSheet) { self.scannerSheet } From dc21eeffaea15f639c452bb89843f3505368a73f Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Fri, 28 Jun 2024 15:52:02 +0200 Subject: [PATCH 17/31] Code Review fixes --- .../ViewModels/StandaloneUrlViewModel.swift | 18 +++++++++--------- Example-app/Views/StandaloneUrlView.swift | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index e4c771e..4f4265b 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -34,8 +34,8 @@ extension StandaloneUrlView { @Published var nativePayment: SwedbankPaySDK.SwedbankPayPaymentSession? @Published var availableInstruments: [SwedbankPaySDK.AvailableInstrument]? - @Published var presented = false - @Published var viewController: UIViewController? + @Published var show3DSecureViewController = false + @Published var paymentSession3DSecureViewController: UIViewController? init() { baseUrl = String(StorageHelper.shared.value(for: .baseUrl) ?? "") @@ -177,13 +177,13 @@ extension StandaloneUrlView { } func show3DSecureViewController(viewController: UIViewController) { - self.viewController = viewController - self.presented = true + self.paymentSession3DSecureViewController = viewController + self.show3DSecureViewController = true } func dismiss3DSecureViewController() { - self.presented = false - self.viewController = nil + self.show3DSecureViewController = false + self.paymentSession3DSecureViewController = nil } func paymentSession3DSecureViewControllerLoadFailed(error: Error, retry: @escaping ()->Void) { @@ -191,15 +191,15 @@ extension StandaloneUrlView { 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.presented = false - self.viewController = nil + self.show3DSecureViewController = false + self.paymentSession3DSecureViewController = nil })) alert.addAction(UIAlertAction(title: "general_retry".localize, style: .default, handler: { _ in retry() })) - self.viewController?.present(alert, animated: true, completion: nil) + self.paymentSession3DSecureViewController?.present(alert, animated: true, completion: nil) } } } diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index d094346..4b58ed4 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -480,8 +480,8 @@ struct StandaloneUrlView: View { .sheet(isPresented: $viewModel.displayScannerSheet) { self.scannerSheet } - .sheet(isPresented: $viewModel.presented) { - SomeView(viewController: viewModel.viewController!) + .sheet(isPresented: $viewModel.show3DSecureViewController) { + SomeView(viewController: viewModel.paymentSession3DSecureViewController!) } .alert(viewModel.errorTitle ?? "stand_alone_generic_error_title".localize, isPresented: $viewModel.showingAlert, From 147c5fcf1d8bddb61419703578595c93904386c8 Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Mon, 1 Jul 2024 14:16:38 +0200 Subject: [PATCH 18/31] Clean up --- Example-app/Base.lproj/Localizable.strings | 2 ++ Example-app/Views/StandaloneUrlView.swift | 4 ++-- Example-app/en.lproj/Localizable.strings | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Example-app/Base.lproj/Localizable.strings b/Example-app/Base.lproj/Localizable.strings index bb6eabc..a61a06a 100644 --- a/Example-app/Base.lproj/Localizable.strings +++ b/Example-app/Base.lproj/Localizable.strings @@ -26,3 +26,5 @@ "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"; diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index 4b58ed4..34d833f 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -181,7 +181,7 @@ struct StandaloneUrlView: View { Divider() - Text("Seamless View") + Text("stand_alone_url_seamless_title") .font(.headline) VStack(spacing: 4) { @@ -416,7 +416,7 @@ struct StandaloneUrlView: View { viewModel.paymentSessionSwedbankPayController?.delegate = viewModel viewModel.displayPaymentSessionSwedbankPayController = true } label: { - Text("Get payment menu") + Text("stand_alone_url_payment_web") .smallFont() .frame(maxWidth: .infinity) .frame(height: 48) diff --git a/Example-app/en.lproj/Localizable.strings b/Example-app/en.lproj/Localizable.strings index aa34403..36e2c18 100644 --- a/Example-app/en.lproj/Localizable.strings +++ b/Example-app/en.lproj/Localizable.strings @@ -26,3 +26,5 @@ "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"; From 8897b4486ad8ec739e98bcfe486f3950db33f7e7 Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Wed, 18 Sep 2024 09:06:19 +0200 Subject: [PATCH 19/31] =?UTF-8?q?WIP:=20SP-72=20Grundfunktionalitet=20f?= =?UTF-8?q?=C3=B6r=20Apple=20Pay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Example-app/Base.lproj/Localizable.strings | 1 + .../ViewModels/StandaloneUrlViewModel.swift | 2 ++ Example-app/Views/StandaloneUrlView.swift | 17 +++++++++++++++++ Example-app/en.lproj/Localizable.strings | 1 + 4 files changed, 21 insertions(+) diff --git a/Example-app/Base.lproj/Localizable.strings b/Example-app/Base.lproj/Localizable.strings index a61a06a..657e8d6 100644 --- a/Example-app/Base.lproj/Localizable.strings +++ b/Example-app/Base.lproj/Localizable.strings @@ -28,3 +28,4 @@ "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_apple_pay" = "ApplePay"; diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index 4f4265b..6daacdd 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -164,6 +164,8 @@ extension StandaloneUrlView { setPaymentResult(success: false, resultText: "stand_alone_internal_inconsistency_error".localize) case .automaticConfigurationFailed: setPaymentResult(success: false, resultText: "stand_alone_automatic_configuration_failed".localize) + case .applePayFailed(error: let error): + showAlert(error: error) } } diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index 34d833f..55fe067 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -404,6 +404,23 @@ struct StandaloneUrlView: View { .padding(.top, 10) } } + case .applePay: + Button { + isFocused = false + + viewModel.isLoadingNativePayment = true + viewModel.nativePayment?.makeNativePaymentAttempt(instrument: .applePay) + } label: { + Text("stand_alone_url_payment_apple_pay") + .smallFont() + .frame(maxWidth: .infinity) + .frame(height: 48) + .accessibilityIdentifier("applePayPrefillButton") + } + .foregroundColor(.white) + .background(.black) + .cornerRadius(30) + .padding(.top, 10) case .webBased(identifier: let identifier): EmptyView() } diff --git a/Example-app/en.lproj/Localizable.strings b/Example-app/en.lproj/Localizable.strings index 36e2c18..d72c69e 100644 --- a/Example-app/en.lproj/Localizable.strings +++ b/Example-app/en.lproj/Localizable.strings @@ -28,3 +28,4 @@ "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_apple_pay" = "ApplePay"; From fe844f650ad10aa3c1f53c44c4c9ba2031f9bde2 Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Wed, 18 Sep 2024 09:32:30 +0200 Subject: [PATCH 20/31] com.apple.developer.in-app-payments --- Example-app/Example-app.entitlements | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Example-app/Example-app.entitlements b/Example-app/Example-app.entitlements index 8c1be53..95802c4 100644 --- a/Example-app/Example-app.entitlements +++ b/Example-app/Example-app.entitlements @@ -9,5 +9,9 @@ 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 + From c3d38b560e6581873c9e25444c9d4cdff93700ea Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Thu, 26 Sep 2024 14:18:49 +0200 Subject: [PATCH 21/31] Added merchantIdentifier --- Example-app/Views/StandaloneUrlView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index 55fe067..02649c2 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -409,7 +409,7 @@ struct StandaloneUrlView: View { isFocused = false viewModel.isLoadingNativePayment = true - viewModel.nativePayment?.makeNativePaymentAttempt(instrument: .applePay) + viewModel.nativePayment?.makeNativePaymentAttempt(instrument: .applePay(merchantIdentifier: "merchant.com.swedbankpay.exampleapp")) } label: { Text("stand_alone_url_payment_apple_pay") .smallFont() From ee719711767e1acf298926bdc2d39ee31df3bf35 Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Mon, 30 Sep 2024 13:14:35 +0200 Subject: [PATCH 22/31] Remove ApplePayFailed --- Example-app/ViewModels/StandaloneUrlViewModel.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index 6daacdd..4f4265b 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -164,8 +164,6 @@ extension StandaloneUrlView { setPaymentResult(success: false, resultText: "stand_alone_internal_inconsistency_error".localize) case .automaticConfigurationFailed: setPaymentResult(success: false, resultText: "stand_alone_automatic_configuration_failed".localize) - case .applePayFailed(error: let error): - showAlert(error: error) } } From e36f312eb3eb5b3c7ec5864818c0caa5a48c4387 Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Wed, 25 Sep 2024 09:35:31 +0200 Subject: [PATCH 23/31] =?UTF-8?q?SP-58=20St=C3=B6d=20f=C3=B6r=20nytt=20bet?= =?UTF-8?q?alkort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Example-app/Base.lproj/Localizable.strings | 1 + .../ViewModels/StandaloneUrlViewModel.swift | 7 ++++++ Example-app/Views/StandaloneUrlView.swift | 23 +++++++++++++++---- Example-app/en.lproj/Localizable.strings | 1 + 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Example-app/Base.lproj/Localizable.strings b/Example-app/Base.lproj/Localizable.strings index 657e8d6..d6c7ec7 100644 --- a/Example-app/Base.lproj/Localizable.strings +++ b/Example-app/Base.lproj/Localizable.strings @@ -21,6 +21,7 @@ "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_client_app_launch_failed" = "Failed to launch external app"; "stand_alone_url_payment_session_end_state_reached" = "Something went wrong with the payment"; diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index 4f4265b..fa0d144 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -186,6 +186,13 @@ extension StandaloneUrlView { self.paymentSession3DSecureViewController = nil } + func showSwedbankPaySDKController(viewController: SwedbankPaySDKController) { + paymentSessionSwedbankPayController = viewController + paymentSessionSwedbankPayController?.delegate = self + displayPaymentSessionSwedbankPayController = true + isLoadingNativePayment = false + } + func paymentSession3DSecureViewControllerLoadFailed(error: Error, retry: @escaping ()->Void) { let alert = UIAlertController(title: nil, message: "\((error as NSError).code): \(error.localizedDescription)\n\n\((error as NSError).domain)", diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index 02649c2..c9c6018 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -404,6 +404,23 @@ struct StandaloneUrlView: View { .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 @@ -415,7 +432,7 @@ struct StandaloneUrlView: View { .smallFont() .frame(maxWidth: .infinity) .frame(height: 48) - .accessibilityIdentifier("applePayPrefillButton") + .accessibilityIdentifier("applePayButton") } .foregroundColor(.white) .background(.black) @@ -429,9 +446,7 @@ struct StandaloneUrlView: View { Button { isFocused = false - viewModel.paymentSessionSwedbankPayController = viewModel.nativePayment?.createSwedbankPaySDKController() - viewModel.paymentSessionSwedbankPayController?.delegate = viewModel - viewModel.displayPaymentSessionSwedbankPayController = true + viewModel.nativePayment?.createSwedbankPaySDKController() } label: { Text("stand_alone_url_payment_web") .smallFont() diff --git a/Example-app/en.lproj/Localizable.strings b/Example-app/en.lproj/Localizable.strings index d72c69e..5e113d9 100644 --- a/Example-app/en.lproj/Localizable.strings +++ b/Example-app/en.lproj/Localizable.strings @@ -21,6 +21,7 @@ "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_client_app_launch_failed" = "Failed to launch external app"; "stand_alone_url_payment_session_end_state_reached" = "Something went wrong with the payment"; From 1a4b6494cde4e4608d162400312b57211495908f Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Tue, 1 Oct 2024 13:11:19 +0200 Subject: [PATCH 24/31] Code Review fixes --- Example-app/ViewModels/StandaloneUrlViewModel.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index fa0d144..a09e9a4 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -158,6 +158,8 @@ extension StandaloneUrlView { errorMessage: "stand_alone_client_app_launch_failed".localize) 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 .paymentSessionEndStateReached: setPaymentResult(success: false, resultText: "stand_alone_url_payment_session_end_state_reached".localize) case .internalInconsistencyError: From 5b4ba16ecaa323550cf309deea9eb767030d4ed5 Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Tue, 1 Oct 2024 16:19:55 +0200 Subject: [PATCH 25/31] Move paymentSession3DSecureViewControllerLoadFailed into sdkProblemOccurred --- .../ViewModels/StandaloneUrlViewModel.swift | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index a09e9a4..79fec3d 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -119,7 +119,25 @@ extension StandaloneUrlView { 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) } @@ -160,6 +178,8 @@ extension StandaloneUrlView { 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: @@ -194,22 +214,6 @@ extension StandaloneUrlView { displayPaymentSessionSwedbankPayController = true isLoadingNativePayment = false } - - func paymentSession3DSecureViewControllerLoadFailed(error: Error, retry: @escaping ()->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 - })) - - alert.addAction(UIAlertAction(title: "general_retry".localize, style: .default, handler: { _ in - retry() - })) - - self.paymentSession3DSecureViewController?.present(alert, animated: true, completion: nil) - } } } From e209fa75f144a4a29ad5963ce4d55ae010f946cd Mon Sep 17 00:00:00 2001 From: Martin Alleus Date: Wed, 2 Oct 2024 17:35:58 +0200 Subject: [PATCH 26/31] Adding entitlements file specification to code signing in deploy script --- deploy_new_version/Sources/deploy/build_xcode_project.swift | 2 ++ deploy_new_version/Sources/deploy/deploy_new_version.swift | 3 ++- deploy_new_version/Sources/deploy/main.swift | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) 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 acc9019..e99f69d 100644 --- a/deploy_new_version/Sources/deploy/main.swift +++ b/deploy_new_version/Sources/deploy/main.swift @@ -6,6 +6,7 @@ do { projectDirectory: URL(fileURLWithPath: env("GITHUB_WORKSPACE")), scheme: "Example-app", 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"), From a5530201e166a8e005156603dd26ec66d8e40eab Mon Sep 17 00:00:00 2001 From: Michael Balsiger Date: Thu, 3 Oct 2024 16:53:50 +0200 Subject: [PATCH 27/31] Added a Apple Pay Charity button --- Example-app/Example-app.entitlements | 1 + Example-app/Views/StandaloneUrlView.swift | 33 +++++++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/Example-app/Example-app.entitlements b/Example-app/Example-app.entitlements index 95802c4..80ec256 100644 --- a/Example-app/Example-app.entitlements +++ b/Example-app/Example-app.entitlements @@ -12,6 +12,7 @@ com.apple.developer.in-app-payments merchant.com.swedbankpay.exampleapp + merchant.com.swedbankpay.charity diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index c9c6018..fb32e94 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -428,11 +428,34 @@ struct StandaloneUrlView: View { viewModel.isLoadingNativePayment = true viewModel.nativePayment?.makeNativePaymentAttempt(instrument: .applePay(merchantIdentifier: "merchant.com.swedbankpay.exampleapp")) } label: { - Text("stand_alone_url_payment_apple_pay") - .smallFont() - .frame(maxWidth: .infinity) - .frame(height: 48) - .accessibilityIdentifier("applePayButton") + 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) From 4c11c1c933ec2e302e3165b72fe6446bc5c2dacf Mon Sep 17 00:00:00 2001 From: Martin Alleus Date: Wed, 13 Nov 2024 10:56:20 +0100 Subject: [PATCH 28/31] Support for web based instruments as well as payment menu with or without restriction to instruments --- Example-app/Base.lproj/Localizable.strings | 2 ++ Example-app/Views/StandaloneUrlView.swift | 42 ++++++++++++++++++++-- Example-app/en.lproj/Localizable.strings | 2 ++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/Example-app/Base.lproj/Localizable.strings b/Example-app/Base.lproj/Localizable.strings index d6c7ec7..cd69da0 100644 --- a/Example-app/Base.lproj/Localizable.strings +++ b/Example-app/Base.lproj/Localizable.strings @@ -29,4 +29,6 @@ "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 (restricted 2 first)"; "stand_alone_url_payment_apple_pay" = "ApplePay"; +"stand_alone_url_payment_web_based" = "Web Based"; diff --git a/Example-app/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index fb32e94..5be9245 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -461,15 +461,51 @@ struct StandaloneUrlView: View { .background(.black) .cornerRadius(30) .padding(.top, 10) - case .webBased(identifier: let identifier): - EmptyView() + 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() + viewModel.nativePayment?.createSwedbankPaySDKController(mode: .menu(restrictedToInstruments: [availableInstruments[0], availableInstruments[1]])) + } 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) + + Button { + isFocused = false + + viewModel.nativePayment?.createSwedbankPaySDKController(mode: .menu(restrictedToInstruments: nil)) } label: { Text("stand_alone_url_payment_web") .smallFont() diff --git a/Example-app/en.lproj/Localizable.strings b/Example-app/en.lproj/Localizable.strings index 5e113d9..9d816ee 100644 --- a/Example-app/en.lproj/Localizable.strings +++ b/Example-app/en.lproj/Localizable.strings @@ -29,4 +29,6 @@ "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 (restricted 2 first)"; "stand_alone_url_payment_apple_pay" = "ApplePay"; +"stand_alone_url_payment_web_based" = "Web Based"; From 333019e13a4adc82435ee1a4a4e88e26c3ca36a0 Mon Sep 17 00:00:00 2001 From: Martin Alleus Date: Fri, 15 Nov 2024 16:06:01 +0100 Subject: [PATCH 29/31] Tweaking payment menu buttons and code --- Example-app/Base.lproj/Localizable.strings | 2 +- Example-app/Views/StandaloneUrlView.swift | 16 ++++++++++++---- Example-app/en.lproj/Localizable.strings | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Example-app/Base.lproj/Localizable.strings b/Example-app/Base.lproj/Localizable.strings index cd69da0..8e157a8 100644 --- a/Example-app/Base.lproj/Localizable.strings +++ b/Example-app/Base.lproj/Localizable.strings @@ -29,6 +29,6 @@ "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 (restricted 2 first)"; +"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/Views/StandaloneUrlView.swift b/Example-app/Views/StandaloneUrlView.swift index 5be9245..4b8219a 100644 --- a/Example-app/Views/StandaloneUrlView.swift +++ b/Example-app/Views/StandaloneUrlView.swift @@ -488,9 +488,9 @@ struct StandaloneUrlView: View { Button { isFocused = false - viewModel.nativePayment?.createSwedbankPaySDKController(mode: .menu(restrictedToInstruments: [availableInstruments[0], availableInstruments[1]])) + viewModel.nativePayment?.createSwedbankPaySDKController(mode: .menu(restrictedToInstruments: nil)) } label: { - Text("stand_alone_url_payment_web_restricted") + Text("stand_alone_url_payment_web") .smallFont() .frame(maxWidth: .infinity) .frame(height: 48) @@ -505,9 +505,17 @@ struct StandaloneUrlView: View { Button { isFocused = false - viewModel.nativePayment?.createSwedbankPaySDKController(mode: .menu(restrictedToInstruments: nil)) + 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") + Text("stand_alone_url_payment_web_restricted") .smallFont() .frame(maxWidth: .infinity) .frame(height: 48) diff --git a/Example-app/en.lproj/Localizable.strings b/Example-app/en.lproj/Localizable.strings index 9d816ee..f1dba29 100644 --- a/Example-app/en.lproj/Localizable.strings +++ b/Example-app/en.lproj/Localizable.strings @@ -29,6 +29,6 @@ "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 (restricted 2 first)"; +"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"; From 9b0b0c203098004092a2b264d488caee056860b9 Mon Sep 17 00:00:00 2001 From: Martin Alleus Date: Tue, 19 Nov 2024 10:00:42 +0100 Subject: [PATCH 30/31] Removing handling of legacy local clientAppLaunchFailed error --- Example-app/Base.lproj/Localizable.strings | 1 - Example-app/ViewModels/StandaloneUrlViewModel.swift | 3 --- Example-app/en.lproj/Localizable.strings | 1 - 3 files changed, 5 deletions(-) diff --git a/Example-app/Base.lproj/Localizable.strings b/Example-app/Base.lproj/Localizable.strings index 8e157a8..60019ca 100644 --- a/Example-app/Base.lproj/Localizable.strings +++ b/Example-app/Base.lproj/Localizable.strings @@ -23,7 +23,6 @@ "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_client_app_launch_failed" = "Failed to launch external app"; "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"; diff --git a/Example-app/ViewModels/StandaloneUrlViewModel.swift b/Example-app/ViewModels/StandaloneUrlViewModel.swift index 79fec3d..beaf584 100644 --- a/Example-app/ViewModels/StandaloneUrlViewModel.swift +++ b/Example-app/ViewModels/StandaloneUrlViewModel.swift @@ -171,9 +171,6 @@ extension StandaloneUrlView { func sdkProblemOccurred(problem: SwedbankPaySDK.PaymentSessionProblem) { switch problem { - case .clientAppLaunchFailed: - showAlert(errorTitle: nil, - errorMessage: "stand_alone_client_app_launch_failed".localize) case .paymentSessionAPIRequestFailed(let error, let retry): showAlert(error: error, retry: retry) case .paymentControllerPaymentFailed(error: let error, retry: let retry): diff --git a/Example-app/en.lproj/Localizable.strings b/Example-app/en.lproj/Localizable.strings index f1dba29..c7b5490 100644 --- a/Example-app/en.lproj/Localizable.strings +++ b/Example-app/en.lproj/Localizable.strings @@ -23,7 +23,6 @@ "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_client_app_launch_failed" = "Failed to launch external app"; "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"; From 77f7bf222d3c0245a53e9ddb7d7dcd163e3eb56d Mon Sep 17 00:00:00 2001 From: Martin Alleus Date: Thu, 28 Nov 2024 11:34:46 +0100 Subject: [PATCH 31/31] Moving to SDK production version 5.0.0 --- Example-app.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Example-app.xcodeproj/project.pbxproj b/Example-app.xcodeproj/project.pbxproj index 656d5b0..23ff7af 100644 --- a/Example-app.xcodeproj/project.pbxproj +++ b/Example-app.xcodeproj/project.pbxproj @@ -1025,8 +1025,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/SwedbankPay/swedbank-pay-sdk-ios.git"; requirement = { - branch = "feature/native-payments"; - kind = branch; + kind = upToNextMajorVersion; + minimumVersion = 5.0.0; }; }; /* End XCRemoteSwiftPackageReference section */