diff --git a/ExampleApp.xcodeproj/project.pbxproj b/ExampleApp.xcodeproj/project.pbxproj
index 9a83a935..214d052a 100644
--- a/ExampleApp.xcodeproj/project.pbxproj
+++ b/ExampleApp.xcodeproj/project.pbxproj
@@ -457,7 +457,7 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/omise/omise-ios.git";
requirement = {
- branch = "feature/MIT-1904";
+ branch = "feature/MIT-2470";
kind = branch;
};
};
diff --git a/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index ebb6ef4b..ebd97824 100644
--- a/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -5,8 +5,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/omise/omise-ios.git",
"state" : {
- "branch" : "feature/MIT-1904",
- "revision" : "0ed8ce39701b08433a2d5c0a678e95edd8b2b8ef"
+ "branch" : "feature/MIT-2470",
+ "revision" : "4f1bbf9a0f0828c7531664276fc244fd8433a869"
+ }
+ },
+ {
+ "identity" : "spm",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/ios-3ds-sdk/SPM",
+ "state" : {
+ "revision" : "a4e55d7f10294ea81abd1db393ae05606a5efa46",
+ "version" : "2.4.0"
}
}
],
diff --git a/ExampleApp/Config.local.plist b/ExampleApp/Config.local.plist
index db08d795..5538ecee 100644
--- a/ExampleApp/Config.local.plist
+++ b/ExampleApp/Config.local.plist
@@ -4,9 +4,5 @@
publicKey
pkey_
- devVaultBaseURL
-
- devApiBaseURL
-
diff --git a/ExampleApp/Views/ProductDetailViewController.swift b/ExampleApp/Views/ProductDetailViewController.swift
index 112707c1..aeaf8662 100644
--- a/ExampleApp/Views/ProductDetailViewController.swift
+++ b/ExampleApp/Views/ProductDetailViewController.swift
@@ -55,12 +55,11 @@ class ProductDetailViewController: BaseViewController {
let text = textField.text,
let url = URL(string: text) else { return }
- let deeplinkRedirectURLPattern = AppDeeplink.threeDSChallenge.urlString
- let webRedirectURLPattern = "https://exampleapp.opn.ooo"
self.omiseSDK.presentAuthorizingPayment(
from: self,
authorizeURL: url,
- returnURLs: [deeplinkRedirectURLPattern, webRedirectURLPattern],
+ expectedReturnURLStrings: ["https://omise.co"],
+ threeDSRequestorAppURLString: AppDeeplink.threeDSChallenge.urlString,
delegate: self
)
})
@@ -70,13 +69,13 @@ class ProductDetailViewController: BaseViewController {
// MARK: - Authorizing Payment View Controller Delegate
-extension ProductDetailViewController: AuthorizingPaymentViewControllerDelegate {
- func authorizingPaymentViewController(_ viewController: AuthorizingPaymentViewController, didCompleteAuthorizingPaymentWithRedirectedURL redirectedURL: URL) {
- print("Payment is authorized with redirect url `\(redirectedURL)`")
+extension ProductDetailViewController: AuthorizingPaymentDelegate {
+ func authorizingPaymentDidComplete(with redirectedURL: URL?) {
+ print("Payment is authorized with redirect url `\(String(describing: redirectedURL))`")
omiseSDK.dismiss()
}
-
- func authorizingPaymentViewControllerDidCancel(_ viewController: AuthorizingPaymentViewController) {
+ func authorizingPaymentDidCancel() {
+ print("Payment is not authorized")
omiseSDK.dismiss()
}
}
diff --git a/OmiseSDK/Sources/3DS/Crypto/CryptData.swift b/OmiseSDK/Sources/3DS/Crypto/CryptData.swift
new file mode 100644
index 00000000..bac2cadb
--- /dev/null
+++ b/OmiseSDK/Sources/3DS/Crypto/CryptData.swift
@@ -0,0 +1,98 @@
+import Foundation
+import CommonCrypto
+
+enum CryptoError: Error {
+ case invalidKeyLength
+ case creationError(Int)
+ case updateError(Int)
+ case finalError(Int)
+}
+
+// swiftlint:disable:next function_parameter_count function_body_length
+func cryptData(
+ _ dataIn: Data,
+ operation: CCOperation, // kCCEncrypt, kCCDecrypt
+ mode: CCMode, // kCCModeECB, kCCModeCBC, etc.
+ algorithm: CCAlgorithm, // kCCAlgorithmAES, kCCAlgorithmDES, etc.
+ padding: CCPadding, // ccNoPadding, ccPKCS7Padding
+ keyLength: size_t,
+ iv: Data?,
+ key: Data
+) throws -> Data {
+ guard key.count == keyLength else {
+ throw CryptoError.invalidKeyLength
+ }
+
+ var cryptor: CCCryptorRef?
+ var status = CCCryptorCreateWithMode(operation,
+ mode,
+ algorithm,
+ padding,
+ iv?.withUnsafeBytes { $0.baseAddress },
+ key.withUnsafeBytes { $0.baseAddress },
+ keyLength,
+ nil,
+ 0,
+ 0, // tweak XTS mode, numRounds
+ 0, // CCModeOptions
+ &cryptor)
+
+ if status != kCCSuccess {
+ throw CryptoError.creationError(Int(status))
+ }
+
+ guard let cryptor = cryptor else {
+ throw CryptoError.creationError(Int(status))
+ }
+
+ defer {
+ CCCryptorRelease(cryptor)
+ }
+
+ let dataOutLength = CCCryptorGetOutputLength(cryptor, dataIn.count, true)
+ var dataOut = Data(count: dataOutLength)
+ var dataOutMoved = 0
+
+ status = dataOut.withUnsafeMutableBytes { dataOutPointer in
+ dataIn.withUnsafeBytes { dataInPointer -> CCCryptorStatus in
+ guard let dataInPointerBaseAddress = dataInPointer.baseAddress,
+ let dataOutPointerBaseAddress = dataOutPointer.baseAddress else {
+ return Int32(kCCParamError)
+ }
+ return CCCryptorUpdate(
+ cryptor,
+ dataInPointerBaseAddress,
+ dataIn.count,
+ dataOutPointerBaseAddress,
+ dataOutLength,
+ &dataOutMoved
+ )
+ }
+ }
+
+ if status != kCCSuccess {
+ throw CryptoError.updateError(Int(status))
+ }
+
+ var dataOutMovedFinal = 0
+ status = dataOut.withUnsafeMutableBytes { dataOutPointer in
+ guard let dataOutPointerBaseAddress = dataOutPointer.baseAddress else {
+ return Int32(kCCParamError)
+ }
+
+ return CCCryptorFinal(
+ cryptor,
+ dataOutPointerBaseAddress.advanced(by: dataOutMoved),
+ dataOutLength - dataOutMoved,
+ &dataOutMovedFinal
+ )
+ }
+
+ if status != kCCSuccess {
+ throw CryptoError.finalError(Int(status))
+ }
+
+ dataOut.count = dataOutMoved + dataOutMovedFinal
+
+ return dataOut
+}
diff --git a/OmiseSDK/Sources/3DS/Crypto/String+PemCert.swift b/OmiseSDK/Sources/3DS/Crypto/String+PemCert.swift
new file mode 100644
index 00000000..e65eb7d1
--- /dev/null
+++ b/OmiseSDK/Sources/3DS/Crypto/String+PemCert.swift
@@ -0,0 +1,10 @@
+import Foundation
+
+extension String {
+ var pemCertificate: String {
+ self
+ .replacingOccurrences(of: "-----BEGIN CERTIFICATE-----", with: "")
+ .replacingOccurrences(of: "-----END CERTIFICATE-----", with: "")
+ .replacingOccurrences(of: "\r\n", with: "")
+ }
+}
diff --git a/OmiseSDK/Sources/3DS/Crypto/String+sha512.swift b/OmiseSDK/Sources/3DS/Crypto/String+sha512.swift
new file mode 100644
index 00000000..6f569661
--- /dev/null
+++ b/OmiseSDK/Sources/3DS/Crypto/String+sha512.swift
@@ -0,0 +1,15 @@
+import Foundation
+import CommonCrypto
+
+extension String {
+ public var sha512: Data {
+ let data = Data(self.utf8)
+ var hash = [UInt8](repeating: 0, count: Int(CC_SHA512_DIGEST_LENGTH))
+
+ data.withUnsafeBytes {
+ _ = CC_SHA512($0.baseAddress, CC_LONG(data.count), &hash)
+ }
+
+ return Data(hash)
+ }
+}
diff --git a/OmiseSDK/Sources/3DS/NetceteraConfig.swift b/OmiseSDK/Sources/3DS/NetceteraConfig.swift
new file mode 100644
index 00000000..7fd6460a
--- /dev/null
+++ b/OmiseSDK/Sources/3DS/NetceteraConfig.swift
@@ -0,0 +1,24 @@
+import Foundation
+
+public struct NetceteraConfig {
+ public let id: String
+ public let deviceInfoEncryptionAlg: String
+ public let deviceInfoEncryptionEnc: String
+ public let deviceInfoEncryptionCertPem: String
+ public let directoryServerId: String
+ public let key: String
+ public let messageVersion: String
+}
+
+extension NetceteraConfig: Decodable {
+ /// Mapping keys to encode/decode JSON string
+ private enum CodingKeys: String, CodingKey {
+ case id = "identifier"
+ case deviceInfoEncryptionAlg = "device_info_encryption_alg"
+ case deviceInfoEncryptionEnc = "device_info_encryption_enc"
+ case deviceInfoEncryptionCertPem = "device_info_encryption_cert_pem"
+ case directoryServerId = "directory_server_id"
+ case key
+ case messageVersion = "message_version"
+ }
+}
diff --git a/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift b/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift
new file mode 100644
index 00000000..459daf7a
--- /dev/null
+++ b/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift
@@ -0,0 +1,374 @@
+import Foundation
+import ThreeDS_SDK
+import CommonCrypto
+
+struct AuthResponse: Codable {
+ var serverStatus: String
+ var ares: ARes?
+
+ private enum CodingKeys: String, CodingKey {
+ case serverStatus = "status"
+ case ares
+ }
+
+ enum Status {
+ case challenge
+ case failed
+ case success
+ case unknown
+ }
+
+ struct ARes: Codable {
+ var threeDSServerTransID: String
+ var acsTransID: String
+ var acsSignedContent: String?
+ var acsUIType: String?
+ var acsReferenceNumber: String?
+ }
+
+ var status: Status {
+ switch serverStatus {
+ case "success": return .success
+ case "challenge": return .challenge
+ case "failed": return .failed
+ default: return .unknown
+ }
+ }
+}
+
+protocol NetceteraThreeDSControllerProtocol: AnyObject {
+ typealias NetceteraThreeDSControllerResult = Result
+ func appOpen3DSDeeplinkURL(_ url: URL) -> Bool
+ func processAuthorizedURL(
+ _ authorizeUrl: URL,
+ threeDSRequestorAppURL: String?,
+ uiCustomization: ThreeDSUICustomization?,
+ in viewController: UIViewController,
+ onComplete: @escaping (NetceteraThreeDSControllerResult) -> Void
+ )
+}
+
+class NetceteraThreeDSController {
+
+ enum Errors: Error {
+ case configInvalid
+ case apiKeyInvalid
+ case cancelled
+ case timedout
+ case presentChallenge(error: Error)
+ case protocolError(event: ThreeDS_SDK.ProtocolErrorEvent?)
+ case runtimeError(event: ThreeDS_SDK.RuntimeErrorEvent?)
+ case incomplete(event: ThreeDS_SDK.CompletionEvent?)
+
+ case authResInvalid
+ case authResStatusFailed
+ case authResStatusUnknown(_ status: String)
+ }
+
+// static var sharedController: NetceteraThreeDSControllerProtocol = NetceteraThreeDSController()
+ private static var uiCustomization: ThreeDSUICustomization?
+
+ private var network = NetworkService()
+ private var challengeParameters: ChallengeParameters?
+ private var receiver: OmiseChallengeStatusReceiver?
+ private var transaction: Transaction?
+
+ private func newScheme(config: NetceteraConfig) -> Scheme? {
+ let formattedCert = config.deviceInfoEncryptionCertPem.pemCertificate
+
+ let scheme = Scheme(name: config.id)
+ scheme.ids = [config.directoryServerId]
+ scheme.encryptionKeyValue = formattedCert
+ scheme.rootCertificateValue = formattedCert
+ return scheme
+ }
+
+func apiKey(config: NetceteraConfig) throws -> String {
+ let decryptionKey = config.directoryServerId.sha512.subdata(in: 0..<32)
+
+ guard let ciphertext = Data(base64Encoded: config.key) else {
+ throw Errors.configInvalid
+ }
+
+ let iv = ciphertext.subdata(in: 0..<16)
+ let ciphertextWithoutIV = ciphertext.subdata(in: 16.. ThreeDS2ServiceSDK {
+ let threeDS2Service = ThreeDS2ServiceSDK()
+
+ let configBuilder = ConfigurationBuilder()
+ do {
+ if let scheme = self.newScheme(config: config) {
+ try configBuilder.add(scheme)
+ } else {
+ print("Scheme wasn't created")
+ }
+ let apiKey = try apiKey(config: config)
+ try configBuilder.api(key: apiKey)
+ try configBuilder.log(to: .info)
+ let configParameters = configBuilder.configParameters()
+
+ var netceteraUICustomization: UiCustomization?
+ if let uiCustomization = Self.uiCustomization {
+ netceteraUICustomization = try UiCustomization(uiCustomization)
+ }
+
+ try threeDS2Service.initialize(configParameters,
+ locale: nil,
+ uiCustomization: netceteraUICustomization)
+
+ } catch {
+ print(error)
+ }
+
+ return threeDS2Service
+ }
+}
+
+extension NetceteraThreeDSController: NetceteraThreeDSControllerProtocol {
+ public func appOpen3DSDeeplinkURL(_ url: URL) -> Bool {
+ ThreeDSSDKAppDelegate.shared.appOpened(url: url)
+ }
+
+ // swiftlint:disable:next function_body_length
+ func processAuthorizedURL(
+ _ authorizeUrl: URL,
+ threeDSRequestorAppURL: String?,
+ uiCustomization: ThreeDSUICustomization?,
+ in viewController: UIViewController,
+ onComplete: @escaping ((Result) -> Void)
+ ) {
+ if let uiCustomization = uiCustomization {
+ Self.uiCustomization = uiCustomization
+ }
+
+ // swiftlint:disable:next closure_body_length
+ netceteraConfig(authUrl: authorizeUrl) { [weak self] result in
+ guard let self = self else { return }
+
+ switch result {
+ case .failure(let error):
+ onComplete(.failure(error))
+ case .success(let config):
+ do {
+ let threeDS2Service = self.newService(config: config)
+ let transaction = try threeDS2Service.createTransaction(
+ directoryServerId: config.directoryServerId,
+ messageVersion: config.messageVersion
+ )
+
+ let deviceInfo = try transaction.getAuthenticationRequestParameters().getDeviceData()
+
+ try self.sendAuthenticationRequest(
+ deviceInfo: deviceInfo,
+ transaction: transaction,
+ authorizeUrl: authorizeUrl) { response in
+ guard let response = response else {
+ onComplete(.failure(NetceteraThreeDSController.Errors.authResInvalid))
+ return
+ }
+
+ switch response.status {
+ case .success:
+ onComplete(.success(()))
+ return
+ case .failed:
+ onComplete(.failure(NetceteraThreeDSController.Errors.authResStatusFailed))
+ return
+ case .unknown:
+ onComplete(.failure(NetceteraThreeDSController.Errors.authResStatusUnknown(response.serverStatus)))
+ return
+ case .challenge:
+ break
+ }
+
+ DispatchQueue.main.async {
+ do {
+ try self.presentChallenge(
+ authResponse: response,
+ threeDSRequestorAppURL: threeDSRequestorAppURL,
+ transaction: transaction,
+ from: viewController,
+ onComplete: onComplete
+ )
+ } catch {
+ onComplete(.failure(error))
+ }
+ }
+ }
+ } catch {
+ onComplete(.failure(error))
+ }
+ }
+ }
+ }
+
+ func prepareChallengeParameters(
+ aRes: AuthResponse.ARes,
+ threeDSRequestorAppURL: String?
+ ) -> ChallengeParameters {
+ let serverTransactionID = aRes.threeDSServerTransID
+ let acsTransactionID = aRes.acsTransID
+ let acsSignedContent = aRes.acsSignedContent
+ let acsRefNumber = aRes.acsReferenceNumber
+
+ let challengeParameters = ChallengeParameters(
+ threeDSServerTransactionID: serverTransactionID,
+ acsTransactionID: acsTransactionID,
+ acsRefNumber: acsRefNumber,
+ acsSignedContent: acsSignedContent)
+
+ if let appUrl = updateAppURLString(threeDSRequestorAppURL, transactionID: serverTransactionID) {
+ challengeParameters.setThreeDSRequestorAppURL(threeDSRequestorAppURL: appUrl)
+ }
+
+ return challengeParameters
+ }
+}
+
+private extension NetceteraThreeDSController {
+ func sendAuthenticationRequest(deviceInfo: String, transaction: Transaction, authorizeUrl: URL, onAuthResponse: @escaping (AuthResponse?) -> Void ) throws {
+ let authParams = try transaction.getAuthenticationRequestParameters()
+ let body =
+"""
+ {
+ "areq": {
+ "sdkAppID": "\(authParams.getSDKAppID())",
+ "sdkEphemPubKey": \(authParams.getSDKEphemeralPublicKey()),
+ "sdkMaxTimeout": 5,
+ "sdkTransID": "\(authParams.getSDKTransactionId())"
+ },
+ "encrypted_device_info": "\(deviceInfo)",
+ "device_type": "iOS"
+ }
+"""
+ let bodyData = body.trimmingCharacters(in: .whitespacesAndNewlines).data(using: .utf8)
+
+ var request = URLRequest(url: authorizeUrl)
+ request.setValue("application/json", forHTTPHeaderField: "Content-Type")
+ request.httpMethod = "POST"
+ request.httpBody = bodyData
+
+ let task = URLSession.shared.dataTask(with: request) {(data, _, _) in
+ guard let data = data else {
+ onAuthResponse(nil)
+ return
+ }
+
+ do {
+ let decoder = JSONDecoder()
+ let response = try decoder.decode(AuthResponse.self, from: data)
+ onAuthResponse(response)
+
+ } catch {
+ onAuthResponse(nil)
+ }
+
+ }
+
+ task.resume()
+ }
+
+ func presentChallenge(
+ authResponse: AuthResponse,
+ threeDSRequestorAppURL: String?,
+ transaction: Transaction,
+ from viewController: UIViewController,
+ onComplete: @escaping ((Result) -> Void)
+ ) throws {
+ guard let aRes = authResponse.ares else { return }
+
+ let receiver = OmiseChallengeStatusReceiver()
+ receiver.onComplete = onComplete
+
+ let challengeParameters = prepareChallengeParameters(
+ aRes: aRes,
+ threeDSRequestorAppURL: threeDSRequestorAppURL
+ )
+
+ self.receiver = receiver
+ self.transaction = transaction
+ self.challengeParameters = challengeParameters
+
+ do {
+ try transaction.doChallenge(
+ challengeParameters: challengeParameters,
+ challengeStatusReceiver: receiver,
+ timeOut: 5,
+ inViewController: viewController
+ )
+ } catch {
+ onComplete(.failure(NetceteraThreeDSController.Errors.presentChallenge(error: error)))
+ }
+ }
+
+ func updateAppURLString(_ urlString: String?, transactionID: String) -> String? {
+ guard let urlString = urlString, let url = URL(string: urlString) else {
+ return nil
+ }
+
+ return url.appendQueryItem(name: "transID", value: transactionID)?.absoluteString
+ }
+}
+
+class OmiseChallengeStatusReceiver: ChallengeStatusReceiver {
+ var onComplete: ((Result) -> Void)?
+
+ func completed(completionEvent: ThreeDS_SDK.CompletionEvent) {
+ if completionEvent.getTransactionStatus() == "Y" {
+ onComplete?(.success(()))
+ } else {
+ onComplete?(.failure(NetceteraThreeDSController.Errors.incomplete(event: completionEvent)))
+ }
+ }
+
+ func cancelled() {
+ onComplete?(.failure(NetceteraThreeDSController.Errors.cancelled))
+ }
+
+ func timedout() {
+ onComplete?(.failure(NetceteraThreeDSController.Errors.timedout))
+ }
+
+ func protocolError(protocolErrorEvent: ThreeDS_SDK.ProtocolErrorEvent) {
+ onComplete?(.failure(NetceteraThreeDSController.Errors.protocolError(event: protocolErrorEvent)))
+ }
+
+ func runtimeError(runtimeErrorEvent: ThreeDS_SDK.RuntimeErrorEvent) {
+ onComplete?(.failure(NetceteraThreeDSController.Errors.runtimeError(event: runtimeErrorEvent)))
+ }
+}
+
+extension NetceteraThreeDSController {
+ func netceteraConfig(authUrl: URL, completion: @escaping (Result) -> Void) {
+ let api = OmiseAPI.netceteraConfig(baseUrl: authUrl.removeLastPathComponent)
+ var urlRequest = URLRequest(url: api.url)
+ urlRequest.httpMethod = api.method.rawValue
+ urlRequest.setValue(ContentType.json.rawValue, forHTTPHeaderField: HTTPHeaders.contentType.rawValue)
+
+ network.send(
+ urlRequest: urlRequest,
+ dateFormatter: api.dateFormatter,
+ completion: completion
+ )
+
+ }
+}
diff --git a/OmiseSDK/Sources/3DS/UICustomization/ThreeDSButtonCustomization.swift b/OmiseSDK/Sources/3DS/UICustomization/ThreeDSButtonCustomization.swift
new file mode 100644
index 00000000..367227c1
--- /dev/null
+++ b/OmiseSDK/Sources/3DS/UICustomization/ThreeDSButtonCustomization.swift
@@ -0,0 +1,70 @@
+import Foundation
+import ThreeDS_SDK
+
+public class ThreeDSButtonCustomization: ThreeDSCustomization {
+ /// Background color of the button in Hex format.
+ public var backgroundColorHex: String?
+
+ /// Dark background color of the button in Hex format.
+ public var darkBackgroundColorHex: String?
+
+ /// Radius of the button corners.
+ public var cornerRadius: Int?
+
+ public init(
+ backgroundColorHex: String? = nil,
+ darkBackgroundColorHex: String? = nil,
+ cornerRadius: Int? = nil,
+ textFontName: String? = nil,
+ textColorHex: String? = nil,
+ darkTextColorHex: String? = nil,
+ textFontSize: Int? = nil
+ ) {
+ super.init(textFontName: textFontName,
+ textColorHex: textColorHex,
+ darkTextColorHex: darkTextColorHex,
+ textFontSize: textFontSize)
+ self.backgroundColorHex = backgroundColorHex
+ self.darkBackgroundColorHex = darkBackgroundColorHex
+ self.cornerRadius = cornerRadius
+ }
+
+ @discardableResult
+ public func backgroundColorHex(_ backgroundColorHex: String?) -> Self {
+ self.backgroundColorHex = backgroundColorHex
+ return self
+ }
+ @discardableResult
+ public func darkBackgroundColorHex(_ darkBackgroundColorHex: String?) -> Self {
+ self.darkBackgroundColorHex = darkBackgroundColorHex
+ return self
+ }
+ @discardableResult
+ public func cornerRadius(_ cornerRadius: Int?) -> Self {
+ self.cornerRadius = cornerRadius
+ return self
+ }
+}
+
+extension ThreeDS_SDK.ButtonCustomization {
+ convenience init(_ custom: ThreeDSButtonCustomization) throws {
+ self.init()
+ try customize(custom)
+ }
+
+ func customize(_ custom: ThreeDSButtonCustomization) throws {
+ try customize(omiseThreeDSCustomization: custom)
+
+ if let backgroundColorHex = custom.backgroundColorHex {
+ try setBackgroundColor(hexColorCode: backgroundColorHex)
+ }
+
+ if let darkBackgroundColorHex = custom.darkBackgroundColorHex {
+ try setDarkBackgroundColor(hexColorCode: darkBackgroundColorHex)
+ }
+
+ if let cornerRadius = custom.cornerRadius {
+ try setCornerRadius(cornerRadius: cornerRadius)
+ }
+ }
+}
diff --git a/OmiseSDK/Sources/3DS/UICustomization/ThreeDSCustomization.swift b/OmiseSDK/Sources/3DS/UICustomization/ThreeDSCustomization.swift
new file mode 100644
index 00000000..d45c92c8
--- /dev/null
+++ b/OmiseSDK/Sources/3DS/UICustomization/ThreeDSCustomization.swift
@@ -0,0 +1,70 @@
+import Foundation
+import ThreeDS_SDK
+
+public class ThreeDSCustomization {
+ /// Text font name.
+ public var textFontName: String?
+
+ /// Text color in Hex format.
+ public var textColorHex: String?
+
+ /// Dark text color in Hex format.
+ public var darkTextColorHex: String?
+
+ /// Text font size.
+ public var textFontSize: Int?
+
+ public init(
+ textFontName: String? = nil,
+ textColorHex: String? = nil,
+ darkTextColorHex: String? = nil,
+ textFontSize: Int? = nil
+ ) {
+ self.textFontName = textFontName
+ self.textColorHex = textColorHex
+ self.darkTextColorHex = darkTextColorHex
+ self.textFontSize = textFontSize
+ }
+
+ @discardableResult
+ public func textFontName(_ textFontName: String?) -> Self {
+ self.textFontName = textFontName
+ return self
+ }
+
+ @discardableResult
+ public func textColorHex(_ textColorHex: String?) -> Self {
+ self.textColorHex = textColorHex
+ return self
+ }
+ @discardableResult
+ public func darkTextColorHex(_ darkTextColorHex: String?) -> Self {
+ self.darkTextColorHex = darkTextColorHex
+ return self
+ }
+ @discardableResult
+ public func textFontSize(_ textFontSize: Int?) -> Self {
+ self.textFontSize = textFontSize
+ return self
+ }
+}
+
+extension ThreeDS_SDK.Customization {
+ func customize(omiseThreeDSCustomization custom: ThreeDSCustomization) throws {
+ if let textFontName = custom.textFontName {
+ try setTextFontName(fontName: textFontName)
+ }
+
+ if let textColorHex = custom.textColorHex {
+ try setTextColor(hexColorCode: textColorHex)
+ }
+
+ if let darkTextColorHex = custom.darkTextColorHex {
+ try setDarkTextColor(hexColorCode: darkTextColorHex)
+ }
+
+ if let textFontSize = custom.textFontSize {
+ try setTextFontSize(fontSize: textFontSize)
+ }
+ }
+}
diff --git a/OmiseSDK/Sources/3DS/UICustomization/ThreeDSLabelCustomization.swift b/OmiseSDK/Sources/3DS/UICustomization/ThreeDSLabelCustomization.swift
new file mode 100644
index 00000000..a2cca41e
--- /dev/null
+++ b/OmiseSDK/Sources/3DS/UICustomization/ThreeDSLabelCustomization.swift
@@ -0,0 +1,84 @@
+import Foundation
+import ThreeDS_SDK
+
+public class ThreeDSLabelCustomization: ThreeDSCustomization {
+ /// Color of the heading label text in Hex format.
+ public var headingTextColorHex: String?
+
+ /// Dark color of the heading label text in Hex format.
+ public var headingDarkTextColorHex: String?
+
+ /// Font name of the heading label text.
+ public var headingTextFontName: String?
+
+ /// Font size of the heading label text.
+ public var headingTextFontSize: Int?
+
+ public init(
+ headingTextColorHex: String? = nil,
+ headingDarkTextColorHex: String? = nil,
+ headingTextFontName: String? = nil,
+ headingTextFontSize: Int? = nil,
+ textFontName: String? = nil,
+ textColorHex: String? = nil,
+ darkTextColorHex: String? = nil,
+ textFontSize: Int? = nil
+ ) {
+ super.init(textFontName: textFontName,
+ textColorHex: textColorHex,
+ darkTextColorHex: darkTextColorHex,
+ textFontSize: textFontSize)
+ self.headingTextColorHex = headingTextColorHex
+ self.headingDarkTextColorHex = headingDarkTextColorHex
+ self.headingTextFontName = headingTextFontName
+ self.headingTextFontSize = headingTextFontSize
+ }
+
+ @discardableResult
+ public func headingTextColorHex(_ headingTextColorHex: String?) -> Self {
+ self.headingTextColorHex = headingTextColorHex
+ return self
+ }
+ @discardableResult
+ public func headingDarkTextColorHex(_ headingDarkTextColorHex: String?) -> Self {
+ self.headingDarkTextColorHex = headingDarkTextColorHex
+ return self
+ }
+ @discardableResult
+ public func headingTextFontName(_ headingTextFontName: String?) -> Self {
+ self.headingTextFontName = headingTextFontName
+ return self
+ }
+ @discardableResult
+ public func headingTextFontSize(_ headingTextFontSize: Int?) -> Self {
+ self.headingTextFontSize = headingTextFontSize
+ return self
+ }
+}
+
+extension ThreeDS_SDK.LabelCustomization {
+ convenience init(_ custom: ThreeDSLabelCustomization) throws {
+ self.init()
+ try customize(custom)
+ }
+
+ func customize(_ custom: ThreeDSLabelCustomization) throws {
+ try customize(omiseThreeDSCustomization: custom)
+
+ if let headingTextColorHex = custom.headingTextColorHex {
+ try setHeadingTextColor(hexColorCode: headingTextColorHex)
+ }
+
+ if let headingDarkTextColorHex = custom.headingDarkTextColorHex {
+ try setHeadingDarkTextColor(hexColorCode: headingDarkTextColorHex)
+ }
+
+ if let headingTextFontName = custom.headingTextFontName {
+ try setHeadingTextFontName(fontName: headingTextFontName)
+ }
+
+ if let headingTextFontSize = custom.headingTextFontSize {
+ try setHeadingTextFontSize(fontSize: headingTextFontSize)
+ }
+ }
+}
diff --git a/OmiseSDK/Sources/3DS/UICustomization/ThreeDSTextBoxCustomization.swift b/OmiseSDK/Sources/3DS/UICustomization/ThreeDSTextBoxCustomization.swift
new file mode 100644
index 00000000..1805986e
--- /dev/null
+++ b/OmiseSDK/Sources/3DS/UICustomization/ThreeDSTextBoxCustomization.swift
@@ -0,0 +1,84 @@
+import Foundation
+import ThreeDS_SDK
+
+public class ThreeDSTextBoxCustomization: ThreeDSCustomization {
+ /// Width of the text box border.
+ public var borderWidth: Int?
+
+ /// Color of the text box border in Hex format.
+ public var borderColorHex: String?
+
+ /// Dark color of the text box border in Hex format.
+ public var darkBorderColorHex: String?
+
+ /// Corner radius of the text box corners.
+ public var cornerRadius: Int?
+
+ public init(
+ borderWidth: Int? = nil,
+ borderColorHex: String? = nil,
+ darkBorderColorHex: String? = nil,
+ cornerRadius: Int? = nil,
+ textFontName: String? = nil,
+ textColorHex: String? = nil,
+ darkTextColorHex: String? = nil,
+ textFontSize: Int? = nil
+ ) {
+ super.init(textFontName: textFontName,
+ textColorHex: textColorHex,
+ darkTextColorHex: darkTextColorHex,
+ textFontSize: textFontSize)
+ self.borderWidth = borderWidth
+ self.borderColorHex = borderColorHex
+ self.darkBorderColorHex = darkBorderColorHex
+ self.cornerRadius = cornerRadius
+ }
+
+ @discardableResult
+ public func borderColorHex(_ borderColorHex: String?) -> Self {
+ self.borderColorHex = borderColorHex
+ return self
+ }
+ @discardableResult
+ public func darkBorderColorHex(_ darkBorderColorHex: String?) -> Self {
+ self.darkBorderColorHex = darkBorderColorHex
+ return self
+ }
+ @discardableResult
+ public func cornerRadius(_ cornerRadius: Int?) -> Self {
+ self.cornerRadius = cornerRadius
+ return self
+ }
+ @discardableResult
+ public func borderWidth(_ borderWidth: Int?) -> Self {
+ self.borderWidth = borderWidth
+ return self
+ }
+}
+
+extension ThreeDS_SDK.TextBoxCustomization {
+ convenience init(_ custom: ThreeDSTextBoxCustomization) throws {
+ self.init()
+ try customize(custom)
+ }
+
+ func customize(_ custom: ThreeDSTextBoxCustomization) throws {
+ try customize(omiseThreeDSCustomization: custom)
+
+ if let borderWidth = custom.borderWidth {
+ try setBorderWidth(borderWidth: borderWidth)
+ }
+
+ if let borderColorHex = custom.borderColorHex {
+ try setBorderColor(hexColorCode: borderColorHex)
+ }
+
+ if let darkBorderColorHex = custom.darkBorderColorHex {
+ try setDarkBorderColor(hexColorCode: darkBorderColorHex)
+ }
+
+ if let textFontSize = custom.textFontSize {
+ try setTextFontSize(fontSize: textFontSize)
+ }
+ }
+}
diff --git a/OmiseSDK/Sources/3DS/UICustomization/ThreeDSToolbarCustomization.swift b/OmiseSDK/Sources/3DS/UICustomization/ThreeDSToolbarCustomization.swift
new file mode 100644
index 00000000..be3c6855
--- /dev/null
+++ b/OmiseSDK/Sources/3DS/UICustomization/ThreeDSToolbarCustomization.swift
@@ -0,0 +1,84 @@
+import Foundation
+import ThreeDS_SDK
+
+public class ThreeDSToolbarCustomization: ThreeDSCustomization {
+ /// Background color for the toolbar in Hex format.
+ public var backgroundColorHex: String?
+
+ /// Dark background color for the toolbar in Hex format.
+ public var darkBackgroundColorHex: String?
+
+ /// Header text of the toolbar.
+ public var headerText: String?
+
+ /// Button text of the toolbar. For example, “Cancel”.
+ public var buttonText: String?
+
+ public init(
+ backgroundColorHex: String? = nil,
+ darkBackgroundColorHex: String? = nil,
+ headerText: String? = nil,
+ buttonText: String? = nil,
+ textFontName: String? = nil,
+ textColorHex: String? = nil,
+ darkTextColorHex: String? = nil,
+ textFontSize: Int? = nil
+ ) {
+ super.init(textFontName: textFontName,
+ textColorHex: textColorHex,
+ darkTextColorHex: darkTextColorHex,
+ textFontSize: textFontSize)
+ self.backgroundColorHex = backgroundColorHex
+ self.darkBackgroundColorHex = darkBackgroundColorHex
+ self.headerText = headerText
+ self.buttonText = buttonText
+ }
+
+ @discardableResult
+ public func backgroundColorHex(_ backgroundColorHex: String?) -> Self {
+ self.backgroundColorHex = backgroundColorHex
+ return self
+ }
+ @discardableResult
+ public func darkBackgroundColorHex(_ darkBackgroundColorHex: String?) -> Self {
+ self.darkBackgroundColorHex = darkBackgroundColorHex
+ return self
+ }
+ @discardableResult
+ public func headerText(_ headerText: String?) -> Self {
+ self.headerText = headerText
+ return self
+ }
+ @discardableResult
+ public func buttonText(_ buttonText: String?) -> Self {
+ self.buttonText = buttonText
+ return self
+ }
+}
+
+extension ThreeDS_SDK.ToolbarCustomization {
+ convenience init(_ custom: ThreeDSToolbarCustomization) throws {
+ self.init()
+ try customize(custom)
+ }
+
+ func customize(_ custom: ThreeDSToolbarCustomization) throws {
+ try customize(omiseThreeDSCustomization: custom)
+
+ if let backgroundColorHex = custom.backgroundColorHex {
+ try setBackgroundColor(hexColorCode: backgroundColorHex)
+ }
+
+ if let darkBackgroundColorHex = custom.darkBackgroundColorHex {
+ try setDarkBackgroundColor(hexColorCode: darkBackgroundColorHex)
+ }
+
+ if let headerText = custom.headerText {
+ try setHeaderText(headerText: headerText)
+ }
+
+ if let buttonText = custom.buttonText {
+ try setButtonText(buttonText: buttonText)
+ }
+ }
+}
diff --git a/OmiseSDK/Sources/3DS/UICustomization/ThreeDSUICustomization.swift b/OmiseSDK/Sources/3DS/UICustomization/ThreeDSUICustomization.swift
new file mode 100644
index 00000000..b02f90c1
--- /dev/null
+++ b/OmiseSDK/Sources/3DS/UICustomization/ThreeDSUICustomization.swift
@@ -0,0 +1,77 @@
+import Foundation
+import ThreeDS_SDK
+
+public class ThreeDSUICustomization {
+
+ public static var shared: ThreeDSUICustomization?
+
+ /// Sets the attributes of a ButtonCustomization object for a particular button type.
+ public var buttonCustomization: [ThreeDS_SDK.UiCustomization.ButtonType: ThreeDSButtonCustomization]?
+
+ /// Sets the attributes of a ButtonCustomization object for an implementer-specific button type.
+ public var buttonCustomizationStrings: [String: ThreeDSButtonCustomization]?
+
+ /// Sets the attributes of a ToolbarCustomization object.
+ public var toolbarCustomization: ThreeDSToolbarCustomization?
+
+ /// Sets the attributes of a LabelCustomization object.
+ public var labelCustomization: ThreeDSLabelCustomization?
+
+ /// Sets the attributes of a TextBoxCustomization object.
+ public var textBoxCustomization: ThreeDSTextBoxCustomization?
+
+ public init(
+ buttonCustomization: [ThreeDS_SDK.UiCustomization.ButtonType: ThreeDSButtonCustomization]? = nil,
+ buttonCustomizationStrings: [String: ThreeDSButtonCustomization]? = nil,
+ toolbarCustomization: ThreeDSToolbarCustomization? = nil,
+ labelCustomization: ThreeDSLabelCustomization? = nil,
+ textBoxCustomization: ThreeDSTextBoxCustomization? = nil
+ ) {
+ self.buttonCustomization = buttonCustomization
+ self.buttonCustomizationStrings = buttonCustomizationStrings
+ self.toolbarCustomization = toolbarCustomization
+ self.labelCustomization = labelCustomization
+ self.textBoxCustomization = textBoxCustomization
+ }
+}
+
+extension ThreeDS_SDK.UiCustomization {
+ convenience init(_ custom: ThreeDSUICustomization) throws {
+ self.init()
+ try customize(custom)
+ }
+
+ func customize(_ custom: ThreeDSUICustomization) throws {
+ if let buttonCustomization = custom.buttonCustomization {
+ for (buttonType, style) in buttonCustomization {
+ let omiseButtonCustomization = try ThreeDS_SDK.ButtonCustomization(style)
+ setButtonCustomization(buttonCustomization: omiseButtonCustomization,
+ buttonType: buttonType)
+ }
+ }
+
+ if let buttonCustomization = custom.buttonCustomizationStrings {
+ for (buttonType, style) in buttonCustomization {
+ let omiseButtonCustomization = try ThreeDS_SDK.ButtonCustomization(style)
+ try setButtonCustomization(buttonCustomization: omiseButtonCustomization,
+ btnType: buttonType)
+ }
+ }
+
+ if let toolbarCustomization = custom.toolbarCustomization {
+ let omiseToolbarCustomization = try ThreeDS_SDK.ToolbarCustomization(toolbarCustomization)
+ setToolbarCustomization(toolbarCustomization: omiseToolbarCustomization)
+
+ }
+
+ if let labelCustomization = custom.labelCustomization {
+ let omiseLabelCustomization = try ThreeDS_SDK.LabelCustomization(labelCustomization)
+ setLabelCustomization(labelCustomization: omiseLabelCustomization)
+ }
+
+ if let textBoxCustomization = custom.textBoxCustomization {
+ let omiseTextBoxCustomization = try ThreeDS_SDK.TextBoxCustomization(textBoxCustomization)
+ setTextBoxCustomization(textBoxCustomization: omiseTextBoxCustomization)
+ }
+ }
+}
diff --git a/OmiseSDK/Sources/AuthorizingPaymentDelegate.swift b/OmiseSDK/Sources/AuthorizingPaymentDelegate.swift
new file mode 100644
index 00000000..4e8ee967
--- /dev/null
+++ b/OmiseSDK/Sources/AuthorizingPaymentDelegate.swift
@@ -0,0 +1,7 @@
+import Foundation
+
+/// Omise Payment delegate protocol is required to process 3DS authorization
+public protocol AuthorizingPaymentDelegate: AnyObject {
+ func authorizingPaymentDidComplete(with redirectedURL: URL?)
+ func authorizingPaymentDidCancel()
+}
diff --git a/OmiseSDK/Sources/OmiseAPI/Helpers/APIProtocol.swift b/OmiseSDK/Sources/OmiseAPI/Helpers/APIProtocol.swift
index d0226d1c..dc4e65b9 100644
--- a/OmiseSDK/Sources/OmiseAPI/Helpers/APIProtocol.swift
+++ b/OmiseSDK/Sources/OmiseAPI/Helpers/APIProtocol.swift
@@ -19,7 +19,9 @@ enum ContentType: String {
case json = "application/json; charset=utf8"
}
-enum OmiseServerType {
- case api
- case vault
+enum HTTPHeaders: String {
+ case authorization = "Authorization"
+ case userAgent = "User-Agent"
+ case contentType = "Content-Type"
+ case omiseVersion = "Omise-Version"
}
diff --git a/OmiseSDK/Sources/OmiseAPI/Helpers/Client+URLRequest.swift b/OmiseSDK/Sources/OmiseAPI/Helpers/Client+URLRequest.swift
index c9b2a7ae..8c1c11f9 100644
--- a/OmiseSDK/Sources/OmiseAPI/Helpers/Client+URLRequest.swift
+++ b/OmiseSDK/Sources/OmiseAPI/Helpers/Client+URLRequest.swift
@@ -7,6 +7,7 @@ extension Client {
switch api.server {
case .api: return customApiURL
case .vault: return customVaultURL
+ case .other(let baseURL): return baseURL
}
}()
@@ -47,13 +48,6 @@ extension Client {
// MARK: HTTP Headers
extension Client {
- enum HTTPHeaders: String {
- case authorization = "Authorization"
- case userAgent = "User-Agent"
- case contentType = "Content-Type"
- case omiseVersion = "Omise-Version"
- }
-
func httpHeaders(
publicKey: String,
userAgent: String,
diff --git a/OmiseSDK/Sources/OmiseAPI/OmiseAPI.swift b/OmiseSDK/Sources/OmiseAPI/OmiseAPI.swift
index 95d53314..242c72e8 100644
--- a/OmiseSDK/Sources/OmiseAPI/OmiseAPI.swift
+++ b/OmiseSDK/Sources/OmiseAPI/OmiseAPI.swift
@@ -1,14 +1,21 @@
import Foundation
+enum OmiseServerType {
+ case api
+ case vault
+ case other(baseURL: URL)
+}
+
extension OmiseServerType {
// swiftlint:disable force_unwrapping
private var apiServerURL: String { "https://vault.omise.co" }
private var vaultServerURL: String { "https://vault.omise.co" }
-
+
var url: URL {
switch self {
case .api: return URL(string: apiServerURL)!
case .vault: return URL(string: vaultServerURL)!
+ case .other(let url): return url
}
}
// swiftlint:enable force_unwrapping
@@ -19,6 +26,7 @@ enum OmiseAPI {
case token(tokenID: String)
case createToken(payload: CreateTokenPayload)
case createSource(payload: CreateSourcePayload)
+ case netceteraConfig(baseUrl: URL)
}
extension OmiseAPI: APIProtocol {
@@ -32,6 +40,8 @@ extension OmiseAPI: APIProtocol {
return .api
case .token, .createToken:
return .vault
+ case .netceteraConfig(let baseURL):
+ return .other(baseURL: baseURL)
}
}
@@ -45,15 +55,23 @@ extension OmiseAPI: APIProtocol {
return "tokens"
case .createSource:
return "sources"
+ case .netceteraConfig:
+ return "config"
}
}
+ var url: URL {
+ server.url.appendingLastPathComponent(path)
+ }
+
var method: HTTPMethod {
switch self {
case .capability, .token:
return .get
case .createToken, .createSource:
return .post
+ case .netceteraConfig:
+ return .get
}
}
diff --git a/OmiseSDK/Sources/OmiseSDK.swift b/OmiseSDK/Sources/OmiseSDK.swift
index 3ea86ba8..493e064b 100644
--- a/OmiseSDK/Sources/OmiseSDK.swift
+++ b/OmiseSDK/Sources/OmiseSDK.swift
@@ -24,7 +24,8 @@ public class OmiseSDK {
public private(set) weak var presentedViewController: UIViewController?
- private var handleURLs: [String] = []
+ private var expectedReturnURLStrings: [String] = []
+ private var netceteraThreeDSController: NetceteraThreeDSController?
/// Creates a new instance of Omise SDK that provides interface to functionallity that SDK provides
///
@@ -140,40 +141,61 @@ public class OmiseSDK {
/// - from: ViewController is used to present Choose Payment Methods
/// - animated: Presents controller with animation if `true`
/// - authorizeURL: The authorize URL given in `Charge` object
- /// - expectedReturnURLPatterns: The expected return URL patterns.
+ /// - expectedReturnURLStrings: The expected return URL patterns for web view 3DS.
+ /// - threeDSRequestorAppURLString: 3DS requestor app URL string (deeplink).
/// - delegate: A delegate object that will recieved authorizing payment events.
@available(iOSApplicationExtension, unavailable)
public func presentAuthorizingPayment(
from topViewController: UIViewController,
animated: Bool = true,
authorizeURL: URL,
- returnURLs: [String],
- delegate: AuthorizingPaymentViewControllerDelegate
+ expectedReturnURLStrings: [String],
+ threeDSRequestorAppURLString: String,
+ threeDSUICustomization: ThreeDSUICustomization? = nil,
+ delegate: AuthorizingPaymentDelegate
) {
- dismiss(animated: false)
-
- handleURLs = returnURLs
- let returnURLComponents = returnURLs.compactMap {
- URLComponents(string: $0)
+ guard authorizeURL.absoluteString.contains("acs=true") else {
+ presentWebViewAuthorizingPayment(
+ from: topViewController,
+ animated: animated,
+ authorizeURL: authorizeURL,
+ expectedReturnURLStrings: expectedReturnURLStrings,
+ delegate: delegate
+ )
+ return
}
- let viewController = AuthorizingPaymentViewController(nibName: nil, bundle: .omiseSDK)
- viewController.authorizeURL = authorizeURL
- viewController.expectedReturnURLPatterns = returnURLComponents
- viewController.delegate = delegate
- viewController.applyNavigationBarStyle()
- viewController.title = "AuthorizingPayment.title".localized()
-
- let navigationController = UINavigationController(rootViewController: viewController)
- navigationController.navigationBar.isTranslucent = false
- navigationController.navigationBar.backgroundColor = .white
-
- if #available(iOSApplicationExtension 11.0, *) {
- navigationController.navigationBar.prefersLargeTitles = false
+ netceteraThreeDSController = NetceteraThreeDSController()
+ netceteraThreeDSController?.processAuthorizedURL(
+ authorizeURL,
+ threeDSRequestorAppURL: threeDSRequestorAppURLString,
+ uiCustomization: threeDSUICustomization,
+ in: topViewController) { [weak self] result in
+ switch result {
+ case .success:
+ delegate.authorizingPaymentDidComplete(with: nil)
+ case .failure(let error):
+ typealias NetceteraFlowError = NetceteraThreeDSController.Errors
+ switch error {
+ case NetceteraFlowError.incomplete,
+ NetceteraFlowError.cancelled,
+ NetceteraFlowError.timedout,
+ NetceteraFlowError.authResStatusFailed,
+ NetceteraFlowError.authResStatusUnknown:
+ delegate.authorizingPaymentDidCancel()
+ default:
+ DispatchQueue.main.async {
+ self?.presentWebViewAuthorizingPayment(
+ from: topViewController,
+ animated: animated,
+ authorizeURL: authorizeURL,
+ expectedReturnURLStrings: expectedReturnURLStrings,
+ delegate: delegate
+ )
+ }
+ }
+ }
}
-
- topViewController.present(navigationController, animated: animated, completion: nil)
- presentedViewController = navigationController
}
/// Dismiss any presented UI form by OmiseSDK
@@ -185,7 +207,7 @@ public class OmiseSDK {
/// Handle URL Callback received by AppDelegate
public func handleURLCallback(_ url: URL) -> Bool {
// Will handle callback with 3ds library
- let containsURL = handleURLs
+ let containsURL = expectedReturnURLStrings
.map {
url.match(string: $0)
}
@@ -200,3 +222,46 @@ private extension OmiseSDK {
client.capability { _ in }
}
}
+
+private extension OmiseSDK {
+ @available(iOSApplicationExtension, unavailable)
+ func presentWebViewAuthorizingPayment(
+ from topViewController: UIViewController,
+ animated: Bool = true,
+ authorizeURL: URL,
+ expectedReturnURLStrings: [String],
+ delegate: AuthorizingPaymentDelegate
+ ) {
+ dismiss(animated: false)
+
+ let viewController = AuthorizingPaymentWebViewController(nibName: nil, bundle: .omiseSDK)
+ viewController.authorizeURL = authorizeURL
+ viewController.expectedReturnURLStrings = expectedReturnURLStrings.compactMap {
+ URLComponents(string: $0)
+ }
+ viewController.completion = { [weak delegate] result in
+ switch result {
+ case .cancel:
+ delegate?.authorizingPaymentDidCancel()
+ case .complete(let redirectedURL):
+ delegate?.authorizingPaymentDidComplete(with: redirectedURL)
+ }
+ }
+
+ viewController.applyNavigationBarStyle()
+ viewController.title = "AuthorizingPayment.title".localized()
+
+ let navigationController = UINavigationController(rootViewController: viewController)
+ navigationController.navigationBar.isTranslucent = false
+ navigationController.navigationBar.backgroundColor = .white
+
+ if #available(iOSApplicationExtension 11.0, *) {
+ navigationController.navigationBar.prefersLargeTitles = false
+ }
+
+ topViewController.present(navigationController, animated: animated, completion: nil)
+ presentedViewController = navigationController
+
+ self.expectedReturnURLStrings = expectedReturnURLStrings
+ }
+}
diff --git a/OmiseSDK/Sources/ToolKit/Extensions/URL+Helpers.swift b/OmiseSDK/Sources/ToolKit/Extensions/URL+Helpers.swift
deleted file mode 100644
index ab34e49d..00000000
--- a/OmiseSDK/Sources/ToolKit/Extensions/URL+Helpers.swift
+++ /dev/null
@@ -1,26 +0,0 @@
-import Foundation
-
-extension URL {
- func match(string: String) -> Bool {
- guard
- let expectedURLComponents = URLComponents(string: string),
- let components = URLComponents(url: self, resolvingAgainstBaseURL: false) else {
- return false
- }
-
- return expectedURLComponents.match(components: components)
- }
-}
-
-extension URLComponents {
- func match(string: String) -> Bool {
- guard let expectedURLComponents = URLComponents(string: string) else { return false }
- return match(components: expectedURLComponents)
- }
-
- func match(components expectedURLComponents: URLComponents) -> Bool {
- return expectedURLComponents.scheme?.lowercased() == scheme?.lowercased()
- && expectedURLComponents.host?.lowercased() == host?.lowercased()
- && path.lowercased().hasPrefix(expectedURLComponents.path.lowercased())
- }
-}
diff --git a/OmiseSDK/Sources/Views/Screens/Authorizing Payment/AuthorizingPaymentViewController.swift b/OmiseSDK/Sources/Views/Screens/Authorizing Payment/AuthorizingPaymentWebViewController.swift
similarity index 82%
rename from OmiseSDK/Sources/Views/Screens/Authorizing Payment/AuthorizingPaymentViewController.swift
rename to OmiseSDK/Sources/Views/Screens/Authorizing Payment/AuthorizingPaymentWebViewController.swift
index e0016ac6..02600c78 100644
--- a/OmiseSDK/Sources/Views/Screens/Authorizing Payment/AuthorizingPaymentViewController.swift
+++ b/OmiseSDK/Sources/Views/Screens/Authorizing Payment/AuthorizingPaymentWebViewController.swift
@@ -2,17 +2,6 @@ import Foundation
import WebKit
import os
-@available(iOSApplicationExtension, unavailable)
-public protocol AuthorizingPaymentViewControllerDelegate: AnyObject {
- /// A delegation method called when the authorizing payment process is completed.
- /// - parameter viewController: The authorizing payment controller that call this method.
- /// - parameter redirectedURL: A URL returned from the authorizing payment process.
- func authorizingPaymentViewController(_ viewController: AuthorizingPaymentViewController, didCompleteAuthorizingPaymentWithRedirectedURL redirectedURL: URL)
-
- /// A delegation method called when user cancel the authorizing payment process.
- func authorizingPaymentViewControllerDidCancel(_ viewController: AuthorizingPaymentViewController)
-}
-
/*:
Drop-in authorizing payment handler view controller that automatically display the authorizing payment verification form
which supports `3DS`, `Internet Banking` and other offsite payment methods those need to be authorized via a web browser.
@@ -21,7 +10,7 @@ public protocol AuthorizingPaymentViewControllerDelegate: AnyObject {
This is still an experimental API. If you encountered with any problem with this API, please feel free to report to Omise.
*/
@available(iOSApplicationExtension, unavailable)
-public class AuthorizingPaymentViewController: UIViewController {
+class AuthorizingPaymentWebViewController: UIViewController {
/// Authorize URL given from Omise in the created `Charge` object.
var authorizeURL: URL? {
didSet {
@@ -33,21 +22,25 @@ public class AuthorizingPaymentViewController: UIViewController {
}
}
+ enum CompletionState {
+ case complete(redirectedURL: URL?)
+ case cancel
+ }
+
/// The expected return URL patterns described in the URLComponents object.
///
/// The rule is the scheme and host must be matched and must have the path as a prefix.
/// Example: if the return URL is `https://www.example.com/products/12345` the expected return URL should have a URLComponents with scheme of `https`, host of `www.example.com` and the path of `/products/`
- var expectedReturnURLPatterns: [URLComponents] = []
-
- /// A delegate object that will recieved the authorizing payment events.
- weak var delegate: AuthorizingPaymentViewControllerDelegate?
+ var expectedReturnURLStrings: [URLComponents] = []
+ var completion: ParamClosure?
+
let webView = WKWebView(frame: CGRect.zero, configuration: WKWebViewConfiguration())
let okButtonTitle = NSLocalizedString("OK", comment: "OK button for JavaScript panel")
let confirmButtonTitle = NSLocalizedString("Confirm", comment: "Confirm button for JavaScript panel")
let cancelButtonTitle = NSLocalizedString("Cancel", comment: "Cancel button for JavaScript panel")
- public override func viewDidLoad() {
+ override func viewDidLoad() {
super.viewDidLoad()
webView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(webView)
@@ -68,21 +61,22 @@ public class AuthorizingPaymentViewController: UIViewController {
navigationItem.rightBarButtonItem = cancelButtonItem
}
- public override func viewDidAppear(_ animated: Bool) {
+ override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
startAuthorizingPaymentProcess()
}
@IBAction private func cancelAuthorizingPaymentProcess(_ sender: UIBarButtonItem) {
os_log("Authorization process was cancelled, trying to notify the delegate", log: uiLogObject, type: .info)
- delegate?.authorizingPaymentViewControllerDidCancel(self)
- if delegate == nil {
+ if let completion = completion {
+ completion(.cancel)
+ } else {
os_log("Authorization process was cancelled but no delegate to be notified", log: uiLogObject, type: .default)
}
}
private func startAuthorizingPaymentProcess() {
- guard let authorizeURL = authorizeURL, !expectedReturnURLPatterns.isEmpty else {
+ guard let authorizeURL = authorizeURL, !expectedReturnURLStrings.isEmpty else {
assertionFailure("Insufficient authorizing payment information")
os_log("Refusing to initialize sdk client with a non-public key: %{private}@", log: uiLogObject, type: .error)
return
@@ -98,7 +92,7 @@ public class AuthorizingPaymentViewController: UIViewController {
return false
}
- return expectedReturnURLPatterns.contains { expectedURLComponents -> Bool in
+ return expectedReturnURLStrings.contains { expectedURLComponents -> Bool in
components.match(components: expectedURLComponents)
}
}
@@ -106,7 +100,7 @@ public class AuthorizingPaymentViewController: UIViewController {
}
@available(iOSApplicationExtension, unavailable)
-extension AuthorizingPaymentViewController: WKNavigationDelegate {
+extension AuthorizingPaymentWebViewController: WKNavigationDelegate {
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void) {
if let url = navigationAction.request.url, verifyPaymentURL(url) {
os_log("Redirected to expected %{private}@ URL, trying to notify the delegate",
@@ -114,8 +108,8 @@ extension AuthorizingPaymentViewController: WKNavigationDelegate {
type: .info,
url.absoluteString)
decisionHandler(.cancel)
- delegate?.authorizingPaymentViewController(self, didCompleteAuthorizingPaymentWithRedirectedURL: url)
- if delegate == nil {
+ completion?(.complete(redirectedURL: url))
+ if completion == nil {
os_log("Redirected to expected %{private}@ URL but no delegate to be notified",
log: uiLogObject,
type: .default,
@@ -140,7 +134,7 @@ extension AuthorizingPaymentViewController: WKNavigationDelegate {
}
@available(iOSApplicationExtension, unavailable)
-extension AuthorizingPaymentViewController: WKUIDelegate {
+extension AuthorizingPaymentWebViewController: WKUIDelegate {
public func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert)
@@ -151,7 +145,7 @@ extension AuthorizingPaymentViewController: WKUIDelegate {
alertController.addAction(okAction)
- // NOTE: Must present an UIAlertController on AuthorizingPaymentViewController
+ // NOTE: Must present an UIAlertController on AuthorizingPaymentWebViewController
self.present(alertController, animated: true)
}
@@ -168,7 +162,7 @@ extension AuthorizingPaymentViewController: WKUIDelegate {
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
- // NOTE: Must present an UIAlertController on AuthorizingPaymentViewController
+ // NOTE: Must present an UIAlertController on AuthorizingPaymentWebViewController
self.present(alertController, animated: true, completion: nil)
}
@@ -191,7 +185,7 @@ extension AuthorizingPaymentViewController: WKUIDelegate {
alertController.addAction(okAction)
alertController.addAction(cancelAction)
- // NOTE: Must present an UIAlertController on AuthorizingPaymentViewController
+ // NOTE: Must present an UIAlertController on AuthorizingPaymentWebViewController
self.present(alertController, animated: true, completion: nil)
}
}
diff --git a/OmiseSDK/Sources/Views/Screens/Authorizing Payment/AuthorizingPaymentViewController.xib b/OmiseSDK/Sources/Views/Screens/Authorizing Payment/AuthorizingPaymentWebViewController.xib
similarity index 91%
rename from OmiseSDK/Sources/Views/Screens/Authorizing Payment/AuthorizingPaymentViewController.xib
rename to OmiseSDK/Sources/Views/Screens/Authorizing Payment/AuthorizingPaymentWebViewController.xib
index 3ca6f73e..bc509d1e 100644
--- a/OmiseSDK/Sources/Views/Screens/Authorizing Payment/AuthorizingPaymentViewController.xib
+++ b/OmiseSDK/Sources/Views/Screens/Authorizing Payment/AuthorizingPaymentWebViewController.xib
@@ -2,13 +2,12 @@
-
-
+
diff --git a/OmiseSDK/Sources/ToolKit/Extensions/Array+Helpers.swift b/OmiseSDK/ToolKit/Extensions/Array+Helpers.swift
similarity index 100%
rename from OmiseSDK/Sources/ToolKit/Extensions/Array+Helpers.swift
rename to OmiseSDK/ToolKit/Extensions/Array+Helpers.swift
diff --git a/OmiseSDK/Sources/ToolKit/Extensions/Optional+Helpers.swift b/OmiseSDK/ToolKit/Extensions/Optional+Helpers.swift
similarity index 100%
rename from OmiseSDK/Sources/ToolKit/Extensions/Optional+Helpers.swift
rename to OmiseSDK/ToolKit/Extensions/Optional+Helpers.swift
diff --git a/OmiseSDK/Sources/ToolKit/Extensions/String+Helpers.swift b/OmiseSDK/ToolKit/Extensions/String+Helpers.swift
similarity index 100%
rename from OmiseSDK/Sources/ToolKit/Extensions/String+Helpers.swift
rename to OmiseSDK/ToolKit/Extensions/String+Helpers.swift
diff --git a/OmiseSDK/ToolKit/Extensions/URL+Helpers.swift b/OmiseSDK/ToolKit/Extensions/URL+Helpers.swift
new file mode 100644
index 00000000..5e92dce0
--- /dev/null
+++ b/OmiseSDK/ToolKit/Extensions/URL+Helpers.swift
@@ -0,0 +1,85 @@
+import Foundation
+
+extension URL {
+ func match(string: String) -> Bool {
+ guard
+ let expectedURLComponents = URLComponents(string: string),
+ let components = URLComponents(url: self, resolvingAgainstBaseURL: false) else {
+ return false
+ }
+
+ return expectedURLComponents.match(components: components)
+ }
+}
+
+extension URLComponents {
+ func match(string: String) -> Bool {
+ guard let expectedURLComponents = URLComponents(string: string) else { return false }
+ return match(components: expectedURLComponents)
+ }
+
+ func match(components expectedURLComponents: URLComponents) -> Bool {
+ return expectedURLComponents.scheme?.lowercased() == scheme?.lowercased()
+ && expectedURLComponents.host?.lowercased() == host?.lowercased()
+ && path.lowercased().hasPrefix(expectedURLComponents.path.lowercased())
+ }
+}
+
+extension URL {
+ var removeLastPathComponent: URL {
+ guard var components = URLComponents(url: self, resolvingAgainstBaseURL: false) else {
+ return self
+ }
+
+ // Remove query items to eliminate GET parameters
+ components.queryItems = nil
+
+ // Split the path into components, replace the last one, and reassemble the path
+ var pathComponents = components.path.split(separator: "/").map(String.init)
+
+ if !pathComponents.isEmpty {
+ pathComponents.removeLast()
+ components.path = "/" + pathComponents.joined(separator: "/")
+ }
+
+ return components.url ?? self
+ }
+
+ func appendingLastPathComponent(_ lastPathComponent: String) -> URL {
+ guard var components = URLComponents(url: self, resolvingAgainstBaseURL: false) else {
+ return self
+ }
+
+ var pathComponents = components.path.split(separator: "/").map(String.init)
+
+ if !pathComponents.isEmpty {
+ pathComponents.append(lastPathComponent)
+ components.path = "/" + pathComponents.joined(separator: "/")
+ } else {
+ // If there are no path components, just add the new endpoint
+ components.path = lastPathComponent
+ }
+
+ return components.url ?? self
+ }
+
+ func appendQueryItem(name: String, value: String?) -> URL? {
+
+ guard var urlComponents = URLComponents(string: absoluteString) else { return nil }
+
+ // Create array of existing query items
+ var queryItems: [URLQueryItem] = urlComponents.queryItems ?? []
+
+ // Create query item
+ let queryItem = URLQueryItem(name: name, value: value)
+
+ // Append the new query item in the existing query items array
+ queryItems.append(queryItem)
+
+ // Append updated query items array in the url component object
+ urlComponents.queryItems = queryItems
+
+ // Returns the url from new url components
+ return urlComponents.url
+ }
+}
diff --git a/OmiseSDK/Sources/ToolKit/Property Wrappers/ProxyProperty.swift b/OmiseSDK/ToolKit/Property Wrappers/ProxyProperty.swift
similarity index 100%
rename from OmiseSDK/Sources/ToolKit/Property Wrappers/ProxyProperty.swift
rename to OmiseSDK/ToolKit/Property Wrappers/ProxyProperty.swift
diff --git a/OmiseSDK/ToolKit/Typedefs/Closures.swift b/OmiseSDK/ToolKit/Typedefs/Closures.swift
new file mode 100644
index 00000000..5d3a83df
--- /dev/null
+++ b/OmiseSDK/ToolKit/Typedefs/Closures.swift
@@ -0,0 +1,4 @@
+import Foundation
+
+typealias VoidClosure = () -> Void
+typealias ParamClosure = (T) -> Void
diff --git a/OmiseSDK/Sources/ToolKit/UI Extensions/UIButton+Helpers.swift b/OmiseSDK/ToolKit/UI Extensions/UIButton+Helpers.swift
similarity index 100%
rename from OmiseSDK/Sources/ToolKit/UI Extensions/UIButton+Helpers.swift
rename to OmiseSDK/ToolKit/UI Extensions/UIButton+Helpers.swift
diff --git a/OmiseSDK/Sources/ToolKit/UI Extensions/UIColor+Helpers.swift b/OmiseSDK/ToolKit/UI Extensions/UIColor+Helpers.swift
similarity index 100%
rename from OmiseSDK/Sources/ToolKit/UI Extensions/UIColor+Helpers.swift
rename to OmiseSDK/ToolKit/UI Extensions/UIColor+Helpers.swift
diff --git a/OmiseSDK/Sources/ToolKit/UI Extensions/UIControl.State+Hashable.swift b/OmiseSDK/ToolKit/UI Extensions/UIControl.State+Hashable.swift
similarity index 100%
rename from OmiseSDK/Sources/ToolKit/UI Extensions/UIControl.State+Hashable.swift
rename to OmiseSDK/ToolKit/UI Extensions/UIControl.State+Hashable.swift
diff --git a/OmiseSDK/Sources/ToolKit/UI Extensions/UIImage+Helpers.swift b/OmiseSDK/ToolKit/UI Extensions/UIImage+Helpers.swift
similarity index 100%
rename from OmiseSDK/Sources/ToolKit/UI Extensions/UIImage+Helpers.swift
rename to OmiseSDK/ToolKit/UI Extensions/UIImage+Helpers.swift
diff --git a/OmiseSDK/Sources/ToolKit/UI Extensions/UILabel+Helpers.swift b/OmiseSDK/ToolKit/UI Extensions/UILabel+Helpers.swift
similarity index 100%
rename from OmiseSDK/Sources/ToolKit/UI Extensions/UILabel+Helpers.swift
rename to OmiseSDK/ToolKit/UI Extensions/UILabel+Helpers.swift
diff --git a/OmiseSDK/Sources/ToolKit/UI Extensions/UIScrollView+Helpers.swift b/OmiseSDK/ToolKit/UI Extensions/UIScrollView+Helpers.swift
similarity index 100%
rename from OmiseSDK/Sources/ToolKit/UI Extensions/UIScrollView+Helpers.swift
rename to OmiseSDK/ToolKit/UI Extensions/UIScrollView+Helpers.swift
diff --git a/OmiseSDK/Sources/ToolKit/UI Extensions/UIStackView+Helpers.swift b/OmiseSDK/ToolKit/UI Extensions/UIStackView+Helpers.swift
similarity index 100%
rename from OmiseSDK/Sources/ToolKit/UI Extensions/UIStackView+Helpers.swift
rename to OmiseSDK/ToolKit/UI Extensions/UIStackView+Helpers.swift
diff --git a/OmiseSDK/Sources/ToolKit/UI Extensions/UIView+Helpers.swift b/OmiseSDK/ToolKit/UI Extensions/UIView+Helpers.swift
similarity index 100%
rename from OmiseSDK/Sources/ToolKit/UI Extensions/UIView+Helpers.swift
rename to OmiseSDK/ToolKit/UI Extensions/UIView+Helpers.swift
diff --git a/OmiseSDKTests/3DS/AuthorizingPaymentTests.swift b/OmiseSDKTests/3DS/AuthorizingPaymentTests.swift
new file mode 100644
index 00000000..593cdc2f
--- /dev/null
+++ b/OmiseSDKTests/3DS/AuthorizingPaymentTests.swift
@@ -0,0 +1,280 @@
+import XCTest
+@testable import OmiseSDK
+import ThreeDS_SDK
+
+class NetceteraThreeDSControllerMock: NetceteraThreeDSControllerProtocol {
+ func setAPIKey(_ apiKey: String) {
+ }
+
+ func appOpen3DSDeeplinkURL(_ url: URL) -> Bool {
+ return true
+ }
+
+ var processAuthorizedURLResult = NetceteraThreeDSControllerResult.success(())
+ func processAuthorizedURL(
+ _ authorizeUrl: URL,
+ threeDSRequestorAppURL: String?,
+ uiCustomization: ThreeDSUICustomization?,
+ in viewController: UIViewController,
+ onComplete: @escaping (NetceteraThreeDSControllerResult) -> Void
+ ) {
+ onComplete(processAuthorizedURLResult)
+ }
+}
+
+class TopViewControllerMock: UIViewController {
+ var onPresentViewController: ((UIViewController) -> Void)?
+ override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
+ onPresentViewController?(viewControllerToPresent)
+ }
+}
+
+class AuthorizingPaymentTests: XCTestCase {
+
+ struct AnyError: Error {
+ let message: String
+ init(_ message: String? = nil) {
+ self.message = message ?? ""
+ }
+ }
+
+ // swiftlint:disable force_unwrapping
+ let authorizeURL = URL(string: "https://co.omise.com/authorize")!
+ let expectedWebReturnURL = URL(string: "https://co.omise.com/callback")!
+ let deeplinkURL = URL(string: "omiseExampleApp://authorizePayment")!
+ // swiftlint:enable force_unwrapping
+
+ typealias NetceteraError = NetceteraThreeDSController.Errors
+ let netceteraMockController = NetceteraThreeDSControllerMock()
+ let authorizingPayment = AuthorizingPayment()
+ let topViewController = TopViewControllerMock()
+ let timeout: TimeInterval = 15.0
+
+ override func setUp() {
+ super.setUp()
+ NetceteraThreeDSController.sharedController = netceteraMockController
+ }
+
+ func test3DSUserFailedChallenge() {
+ let expectation = self.expectation(description: "callback")
+
+ netceteraMockController.processAuthorizedURLResult = .failure(NetceteraError.incomplete(event: nil))
+ authorizingPayment.presentAuthPaymentController(
+ from: topViewController,
+ url: authorizeURL,
+ expectedWebViewReturnURL: expectedWebReturnURL,
+ deeplinkURL: deeplinkURL) { result in
+ defer { expectation.fulfill() }
+ XCTAssertEqual(result, AuthorizingPayment.Status.cancel)
+ }
+
+ waitForExpectations(timeout: timeout, handler: nil)
+ }
+
+ func test3DSUserCancelledChallenge() {
+ let expectation = self.expectation(description: "callback")
+
+ netceteraMockController.processAuthorizedURLResult = .failure(NetceteraError.cancelled)
+ authorizingPayment.presentAuthPaymentController(
+ from: topViewController,
+ url: authorizeURL,
+ expectedWebViewReturnURL: expectedWebReturnURL,
+ deeplinkURL: deeplinkURL) { result in
+ defer { expectation.fulfill() }
+ XCTAssertEqual(result, AuthorizingPayment.Status.cancel)
+ }
+
+ waitForExpectations(timeout: timeout, handler: nil)
+ }
+
+ func test3DSUserTimedOutChallenge() {
+ let expectation = self.expectation(description: "callback")
+
+ netceteraMockController.processAuthorizedURLResult = .failure(NetceteraError.timedout)
+ authorizingPayment.presentAuthPaymentController(
+ from: topViewController,
+ url: authorizeURL,
+ expectedWebViewReturnURL: expectedWebReturnURL,
+ deeplinkURL: deeplinkURL) { result in
+ defer { expectation.fulfill() }
+ XCTAssertEqual(result, AuthorizingPayment.Status.cancel)
+ }
+
+ waitForExpectations(timeout: timeout, handler: nil)
+ }
+
+ func test3DSAuthResponseStatusFailed() {
+ let expectation = self.expectation(description: "callback")
+
+ netceteraMockController.processAuthorizedURLResult = .failure(NetceteraError.authResStatusFailed)
+ authorizingPayment.presentAuthPaymentController(
+ from: topViewController,
+ url: authorizeURL,
+ expectedWebViewReturnURL: expectedWebReturnURL,
+ deeplinkURL: deeplinkURL) { result in
+ defer { expectation.fulfill() }
+ XCTAssertEqual(result, AuthorizingPayment.Status.cancel)
+ }
+
+ waitForExpectations(timeout: timeout, handler: nil)
+ }
+
+ func test3DSAuthResponseStatusUnknown() {
+ let expectation = self.expectation(description: "callback")
+
+ netceteraMockController.processAuthorizedURLResult = .failure(NetceteraError.authResStatusUnknown("Some Unknown Status"))
+ authorizingPayment.presentAuthPaymentController(
+ from: topViewController,
+ url: authorizeURL,
+ expectedWebViewReturnURL: expectedWebReturnURL,
+ deeplinkURL: deeplinkURL) { result in
+ defer { expectation.fulfill() }
+ XCTAssertEqual(result, AuthorizingPayment.Status.cancel)
+ }
+
+ waitForExpectations(timeout: timeout, handler: nil)
+ }
+
+ func test3DSSuccess() {
+ let expectation = self.expectation(description: "callback")
+
+ netceteraMockController.processAuthorizedURLResult = .success(())
+ authorizingPayment.presentAuthPaymentController(
+ from: topViewController,
+ url: authorizeURL,
+ expectedWebViewReturnURL: expectedWebReturnURL,
+ deeplinkURL: deeplinkURL) { result in
+ defer { expectation.fulfill() }
+ XCTAssertEqual(result, AuthorizingPayment.Status.complete(redirectURL: nil))
+ }
+
+ waitForExpectations(timeout: timeout, handler: nil)
+ }
+
+ func testWebViewOn3DSAuthResponseInvalid() {
+ let expectation = self.expectation(description: "callback")
+
+ topViewController.onPresentViewController = { viewController in
+ defer { expectation.fulfill() }
+ let presentedVC = (viewController as? UINavigationController)?.topViewController
+ XCTAssertTrue(presentedVC is AuthorizingPaymentWebViewController)
+ }
+
+ netceteraMockController.processAuthorizedURLResult = .failure(NetceteraError.authResInvalid)
+ authorizingPayment.presentAuthPaymentController(
+ from: topViewController,
+ url: authorizeURL,
+ expectedWebViewReturnURL: expectedWebReturnURL,
+ deeplinkURL: deeplinkURL) { _ in
+ XCTFail("Should open webview and don't call this callback")
+ }
+
+ waitForExpectations(timeout: timeout, handler: nil)
+ }
+
+ func testWebViewOn3DSProtocolError() {
+ let expectation = self.expectation(description: "callback")
+
+ topViewController.onPresentViewController = { viewController in
+ defer { expectation.fulfill() }
+ let presentedVC = (viewController as? UINavigationController)?.topViewController
+ XCTAssertTrue(presentedVC is AuthorizingPaymentWebViewController)
+ }
+
+ netceteraMockController.processAuthorizedURLResult = .failure(NetceteraError.protocolError(event: nil))
+ authorizingPayment.presentAuthPaymentController(
+ from: topViewController,
+ url: authorizeURL,
+ expectedWebViewReturnURL: expectedWebReturnURL,
+ deeplinkURL: deeplinkURL) { _ in
+ XCTFail("Should open webview and don't call this callback")
+ }
+
+ waitForExpectations(timeout: timeout, handler: nil)
+ }
+
+ func testWebViewOn3DSRuntimeError() {
+ let expectation = self.expectation(description: "callback")
+
+ topViewController.onPresentViewController = { viewController in
+ defer { expectation.fulfill() }
+ let presentedVC = (viewController as? UINavigationController)?.topViewController
+ XCTAssertTrue(presentedVC is AuthorizingPaymentWebViewController)
+ }
+
+ netceteraMockController.processAuthorizedURLResult = .failure(NetceteraError.runtimeError(event: nil))
+ authorizingPayment.presentAuthPaymentController(
+ from: topViewController,
+ url: authorizeURL,
+ expectedWebViewReturnURL: expectedWebReturnURL,
+ deeplinkURL: deeplinkURL) { _ in
+ XCTFail("Should open webview and don't call this callback")
+ }
+
+ waitForExpectations(timeout: timeout, handler: nil)
+ }
+
+ func testWebViewOn3DSPresentChallengeFailed() {
+ let expectation = self.expectation(description: "callback")
+
+ topViewController.onPresentViewController = { viewController in
+ defer { expectation.fulfill() }
+ let presentedVC = (viewController as? UINavigationController)?.topViewController
+ XCTAssertTrue(presentedVC is AuthorizingPaymentWebViewController)
+ }
+
+ let challengeError = AnyError("Any error on presenting Netcetera Challenge")
+ netceteraMockController.processAuthorizedURLResult = .failure(NetceteraError.presentChallenge(error: challengeError))
+ authorizingPayment.presentAuthPaymentController(
+ from: topViewController,
+ url: authorizeURL,
+ expectedWebViewReturnURL: expectedWebReturnURL,
+ deeplinkURL: deeplinkURL) { _ in
+ XCTFail("Should open webview and don't call this callback")
+ }
+
+ waitForExpectations(timeout: timeout, handler: nil)
+ }
+
+ func testWebViewOn3DSApiKeyInvalid() {
+ let expectation = self.expectation(description: "callback")
+
+ topViewController.onPresentViewController = { viewController in
+ defer { expectation.fulfill() }
+ let presentedVC = (viewController as? UINavigationController)?.topViewController
+ XCTAssertTrue(presentedVC is AuthorizingPaymentWebViewController)
+ }
+
+ netceteraMockController.processAuthorizedURLResult = .failure(NetceteraError.apiKeyInvalid)
+ authorizingPayment.presentAuthPaymentController(
+ from: topViewController,
+ url: authorizeURL,
+ expectedWebViewReturnURL: expectedWebReturnURL,
+ deeplinkURL: deeplinkURL) { _ in
+ XCTFail("Should open webview and don't call this callback")
+ }
+
+ waitForExpectations(timeout: timeout, handler: nil)
+ }
+
+ func testWebViewOnInitializationFailed() {
+ let expectation = self.expectation(description: "callback")
+
+ topViewController.onPresentViewController = { viewController in
+ defer { expectation.fulfill() }
+ let presentedVC = (viewController as? UINavigationController)?.topViewController
+ XCTAssertTrue(presentedVC is AuthorizingPaymentWebViewController)
+ }
+
+ netceteraMockController.processAuthorizedURLResult = .failure(AnyError("Any error during initialization"))
+ authorizingPayment.presentAuthPaymentController(
+ from: topViewController,
+ url: authorizeURL,
+ expectedWebViewReturnURL: expectedWebReturnURL,
+ deeplinkURL: deeplinkURL) { _ in
+ XCTFail("Should open webview and don't call this callback")
+ }
+
+ waitForExpectations(timeout: timeout, handler: nil)
+ }
+}
diff --git a/OmiseSDKTests/3DS/NetceteraThreeDSControllerTests.swift b/OmiseSDKTests/3DS/NetceteraThreeDSControllerTests.swift
new file mode 100644
index 00000000..fee21426
--- /dev/null
+++ b/OmiseSDKTests/3DS/NetceteraThreeDSControllerTests.swift
@@ -0,0 +1,40 @@
+import XCTest
+@testable import OmiseSDK
+import ThreeDS_SDK
+
+class NetceteraThreeDSControllerTests: XCTestCase {
+
+ func testThreeDSRequestorAppURLTransaction() {
+ let transactionId = "1234-5678-9012-ABCD"
+
+ let aRes = AuthResponse.ARes(
+ threeDSServerTransID: transactionId,
+ acsTransID: "123",
+ acsSignedContent: nil,
+ acsUIType: nil
+ )
+
+ let netceteraController = NetceteraThreeDSController()
+
+ let tests = [
+ "": nil,
+
+ "someApp://":
+ "someApp://?transID=\(transactionId)",
+
+ "someApp://3DSChallenge":
+ "someApp://3DSChallenge?transID=\(transactionId)",
+
+ "someApp://3ds/challenge?source=omiseSDK":
+ "someApp://3ds/challenge?source=omiseSDK&transID=\(transactionId)"
+ ]
+
+ for (url, result) in tests {
+ let params = netceteraController.prepareChallengeParameters(
+ aRes: aRes,
+ threeDSRequestorAppURL: url
+ )
+ XCTAssertEqual(params.getThreeDSRequestorAppURL(), result)
+ }
+ }
+}
diff --git a/OmiseSDKTests/3DS/SHA512Tests.swift b/OmiseSDKTests/3DS/SHA512Tests.swift
new file mode 100644
index 00000000..5537188a
--- /dev/null
+++ b/OmiseSDKTests/3DS/SHA512Tests.swift
@@ -0,0 +1,38 @@
+import XCTest
+@testable import OmiseSDK
+import Foundation
+
+class SHA512Tests: XCTestCase {
+
+ func testSHA512EmptyString() {
+ let emptyString = ""
+ let expectedHash = Data(
+ // swiftlint:disable:next multiline_literal_brackets
+ [ // expected SHA-512 hash of empty string
+ 0xcf, 0x83, 0xe1, 0x35, 0x7e, 0xef, 0xb8, 0xbd,
+ 0xf1, 0x54, 0x28, 0x50, 0xd6, 0x6d, 0x80, 0x07,
+ 0xd6, 0x20, 0xe4, 0x05, 0x0b, 0x57, 0x15, 0xdc,
+ 0x83, 0xf4, 0xa9, 0x21, 0xd3, 0x6c, 0xe9, 0xce,
+ 0x47, 0xd0, 0xd1, 0x3c, 0x5d, 0x85, 0xf2, 0xb0,
+ 0xff, 0x83, 0x18, 0xd2, 0x87, 0x7e, 0xec, 0x2f,
+ 0x63, 0xb9, 0x31, 0xbd, 0x47, 0x41, 0x7a, 0x81,
+ 0xa5, 0x38, 0x32, 0x7a, 0xf9, 0x27, 0xda, 0x3e
+ ]
+ )
+ XCTAssertEqual(emptyString.sha512, expectedHash)
+ }
+
+ func testSHA512NonEmptyString() {
+ let testString = "Hello, World!"
+ // swiftlint:disable:next line_length
+ let expectedHexString = "374d794a95cdcfd8b35993185fef9ba368f160d8daf432d08ba9f1ed1e5abe6cc69291e0fa2fe0006a52570ef18c19def4e617c33ce52ef0a6e5fbe318cb0387"
+
+ XCTAssertEqual(testString.sha512.hexadecimalString, expectedHexString)
+ }
+}
+
+extension Data {
+ var hexadecimalString: String {
+ return map { String(format: "%02x", $0) }.joined()
+ }
+}
diff --git a/OmiseSDKTests/3DS/ThreeDSButtonCustomizationTests.swift b/OmiseSDKTests/3DS/ThreeDSButtonCustomizationTests.swift
new file mode 100644
index 00000000..1465a91c
--- /dev/null
+++ b/OmiseSDKTests/3DS/ThreeDSButtonCustomizationTests.swift
@@ -0,0 +1,64 @@
+import XCTest
+@testable import OmiseSDK
+import ThreeDS_SDK
+
+class ThreeDSButtonCustomizationTests: XCTestCase {
+ let backgroundColorHex = "#FFFFFF"
+ let darkBackgroundColorHex = "#000000"
+ let cornerRadius: Int = 12
+
+ let testTextFontName = "Arial-BoldItalicMT"
+ let testTextColorHex = "#C4A484"
+ let testDarkTextColorHex = "#964B00"
+ let testTextFontSize = 14
+
+ func testNetceteraInit() {
+ let uiCustomization = ThreeDSButtonCustomization(
+ backgroundColorHex: backgroundColorHex,
+ darkBackgroundColorHex: darkBackgroundColorHex,
+ cornerRadius: cornerRadius,
+ textFontName: testTextFontName,
+ textColorHex: testTextColorHex,
+ darkTextColorHex: testDarkTextColorHex,
+ textFontSize: testTextFontSize)
+
+ let netCustomization: ThreeDS_SDK.ButtonCustomization
+ do {
+ try netCustomization = ThreeDS_SDK.ButtonCustomization(uiCustomization)
+ } catch {
+ XCTAssert(false, error.localizedDescription)
+ return
+ }
+
+ XCTAssertEqual(netCustomization.getBackgroundColor(), backgroundColorHex)
+ XCTAssertEqual(netCustomization.getCornerRadius(), cornerRadius)
+
+ XCTAssertEqual(netCustomization.getTextFontName(), testTextFontName)
+ XCTAssertEqual(netCustomization.getTextColor(), testTextColorHex)
+ XCTAssertEqual(netCustomization.getTextFontSize(), testTextFontSize)
+ }
+
+ func testNetceteraCustomization() {
+ let uiCustomization = ThreeDSButtonCustomization(
+ backgroundColorHex: backgroundColorHex,
+ darkBackgroundColorHex: darkBackgroundColorHex,
+ cornerRadius: cornerRadius,
+ textFontName: testTextFontName,
+ textColorHex: testTextColorHex,
+ darkTextColorHex: testDarkTextColorHex,
+ textFontSize: testTextFontSize)
+
+ let netCustomization = ThreeDS_SDK.ButtonCustomization()
+ do {
+ try netCustomization.customize(uiCustomization)
+ } catch {
+ XCTAssert(false, error.localizedDescription)
+ }
+
+ XCTAssertEqual(netCustomization.getBackgroundColor(), backgroundColorHex)
+ XCTAssertEqual(netCustomization.getCornerRadius(), cornerRadius)
+ XCTAssertEqual(netCustomization.getTextFontName(), testTextFontName)
+ XCTAssertEqual(netCustomization.getTextColor(), testTextColorHex)
+ XCTAssertEqual(netCustomization.getTextFontSize(), testTextFontSize)
+ }
+}
diff --git a/OmiseSDKTests/3DS/UICustomizationTests.swift b/OmiseSDKTests/3DS/UICustomizationTests.swift
new file mode 100644
index 00000000..abb2cd76
--- /dev/null
+++ b/OmiseSDKTests/3DS/UICustomizationTests.swift
@@ -0,0 +1,29 @@
+import XCTest
+@testable import OmiseSDK
+import ThreeDS_SDK
+
+class ThreeDSCustomizationTests: XCTestCase {
+ let testTextFontName = "Arial-BoldItalicMT"
+ let testTextColorHex = "#C4A484"
+ let testDarkTextColorHex = "#964B00"
+ let testTextFontSize = 14
+
+ func testNetceteraCustomization() {
+ let uiCustomization = ThreeDSCustomization(
+ textFontName: testTextFontName,
+ textColorHex: testTextColorHex,
+ darkTextColorHex: testDarkTextColorHex,
+ textFontSize: testTextFontSize)
+
+ let netCustomization = ThreeDS_SDK.Customization()
+ do {
+ try netCustomization.customize(omiseThreeDSCustomization: uiCustomization)
+ } catch {
+ XCTAssert(false, error.localizedDescription)
+ }
+
+ XCTAssertEqual(netCustomization.getTextFontName(), testTextFontName)
+ XCTAssertEqual(netCustomization.getTextColor(), testTextColorHex)
+ XCTAssertEqual(netCustomization.getTextFontSize(), testTextFontSize)
+ }
+}
diff --git a/OmiseSDKTests/URL+PathComponentsTests.swift b/OmiseSDKTests/URL+PathComponentsTests.swift
new file mode 100644
index 00000000..0e7f1a09
--- /dev/null
+++ b/OmiseSDKTests/URL+PathComponentsTests.swift
@@ -0,0 +1,28 @@
+import XCTest
+@testable import OmiseSDK
+
+private let timeout: TimeInterval = 15.0
+
+class URLPathComponentsTests: XCTestCase {
+
+ func testRemoveLastPathComponent() {
+ let tests = [
+ "https://test.com": "https://test.com/test",
+ "https://test.com/endpoint": "https://test.com/test",
+ "https://test.com/path/endpoint": "https://test.com/path/test",
+ "https://test.com/path/endpoint?test=2": "https://test.com/path/test"
+ ]
+
+ for (urlString, expectedResult) in tests {
+ guard let url = URL(string: urlString) else {
+ XCTFail("Can't resolve test URL")
+ return
+ }
+
+ let result = url.removeLastPathComponent
+ .appendingPathComponent("test", isDirectory: false)
+
+ XCTAssertEqual(result.absoluteString, expectedResult)
+ }
+ }
+}
diff --git a/Package.resolved b/Package.resolved
new file mode 100644
index 00000000..b8079cd0
--- /dev/null
+++ b/Package.resolved
@@ -0,0 +1,16 @@
+{
+ "object": {
+ "pins": [
+ {
+ "package": "ThreeDS_SDK",
+ "repositoryURL": "https://github.com/ios-3ds-sdk/SPM",
+ "state": {
+ "branch": null,
+ "revision": "a4e55d7f10294ea81abd1db393ae05606a5efa46",
+ "version": "2.4.0"
+ }
+ }
+ ]
+ },
+ "version": 1
+}
diff --git a/Package.swift b/Package.swift
index 41fcedb4..83238631 100644
--- a/Package.swift
+++ b/Package.swift
@@ -15,10 +15,15 @@ let package = Package(
targets: ["OmiseSDK"]
)
],
+ dependencies: [
+ .package(url: "https://github.com/ios-3ds-sdk/SPM", .exact("2.4.0"))
+ ],
targets: [
.target(
name: "OmiseSDK",
- dependencies: [],
+ dependencies: [
+ .product(name: "ThreeDS_SDK", package: "SPM")
+ ],
path: "OmiseSDK",
exclude: ["Info.plist"],
resources: [.process("Resources")]
diff --git a/README.md b/README.md
index ee226795..db76cde6 100644
--- a/README.md
+++ b/README.md
@@ -352,13 +352,13 @@ omiseSDK.presentAuthorizingPayment(
Then implement the delegate to receive the `Source` or `Token` object after the user has selected:
```swift
-extension ViewController: AuthorizingPaymentViewControllerDelegate {
- func authorizingPaymentViewController(_ viewController: AuthorizingPaymentViewController, didCompleteAuthorizingPaymentWithRedirectedURL redirectedURL: URL) {
+extension ViewController: AuthorizingPaymentWebViewDelegate {
+ func authorizingPaymentViewController(_ viewController: AuthorizingPaymentWebViewController, didCompleteAuthorizingPaymentWithRedirectedURL redirectedURL: URL) {
// Handle the `redirected URL` here
omiseSDK.dismiss()
}
- func authorizingPaymentViewControllerDidCancel(_ viewController: AuthorizingPaymentViewController) {
+ func authorizingPaymentViewControllerDidCancel(_ viewController: AuthorizingPaymentWebViewController) {
// Handle the case that the user taps cancel.
omiseSDK.dismiss()
}
diff --git a/dev.xcodeproj/project.pbxproj b/dev.xcodeproj/project.pbxproj
index b873a4e1..bce17807 100644
--- a/dev.xcodeproj/project.pbxproj
+++ b/dev.xcodeproj/project.pbxproj
@@ -25,7 +25,7 @@
750126722B8A49BC00ED5E14 /* FPX.Bank+ViewPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750126712B8A49BC00ED5E14 /* FPX.Bank+ViewPresentable.swift */; };
750126742B8A580500ED5E14 /* SelectDuitNowOBWBankViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750126732B8A580500ED5E14 /* SelectDuitNowOBWBankViewModel.swift */; };
750126772B95971A00ED5E14 /* ChoosePaymentCoordinator+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 750126762B95971A00ED5E14 /* ChoosePaymentCoordinator+Helpers.swift */; };
- 750126792B95A34000ED5E14 /* AuthorizingPaymentViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 750126782B95A34000ED5E14 /* AuthorizingPaymentViewController.xib */; };
+ 750126792B95A34000ED5E14 /* AuthorizingPaymentWebViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 750126782B95A34000ED5E14 /* AuthorizingPaymentWebViewController.xib */; };
7502CA532A3011A900766E7D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7502CA562A3011A900766E7D /* LaunchScreen.storyboard */; };
7502CA5F2A31A7D600766E7D /* CountryListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7502CA5E2A31A7D600766E7D /* CountryListViewController.swift */; };
7502CA602A31A7D600766E7D /* CountryListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7502CA5E2A31A7D600766E7D /* CountryListViewController.swift */; };
@@ -90,6 +90,26 @@
757E14352BD61DFE005F08AD /* ChoosePaymentMethodDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 757E14342BD61DFE005F08AD /* ChoosePaymentMethodDelegate.swift */; };
7588011E2BAAB37C00F02934 /* AppDeeplink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7588011D2BAAB37C00F02934 /* AppDeeplink.swift */; };
758801202BAAC70200F02934 /* URL+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7588011F2BAAC70200F02934 /* URL+Helpers.swift */; };
+ 758A4E6E2BE38A5F005E7B5A /* CryptData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4E6B2BE38A5E005E7B5A /* CryptData.swift */; };
+ 758A4E6F2BE38A5F005E7B5A /* String+sha512.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4E6C2BE38A5E005E7B5A /* String+sha512.swift */; };
+ 758A4E702BE38A5F005E7B5A /* String+PemCert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4E6D2BE38A5E005E7B5A /* String+PemCert.swift */; };
+ 758A4E732BE38A7E005E7B5A /* NetceteraThreeDSController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4E712BE38A7E005E7B5A /* NetceteraThreeDSController.swift */; };
+ 758A4E742BE38A7E005E7B5A /* NetceteraConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4E722BE38A7E005E7B5A /* NetceteraConfig.swift */; };
+ 758A4E7C2BE38A89005E7B5A /* ThreeDSButtonCustomization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4E762BE38A88005E7B5A /* ThreeDSButtonCustomization.swift */; };
+ 758A4E7D2BE38A89005E7B5A /* ThreeDSLabelCustomization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4E772BE38A89005E7B5A /* ThreeDSLabelCustomization.swift */; };
+ 758A4E7E2BE38A89005E7B5A /* ThreeDSToolbarCustomization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4E782BE38A89005E7B5A /* ThreeDSToolbarCustomization.swift */; };
+ 758A4E7F2BE38A89005E7B5A /* ThreeDSCustomization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4E792BE38A89005E7B5A /* ThreeDSCustomization.swift */; };
+ 758A4E802BE38A89005E7B5A /* ThreeDSUICustomization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4E7A2BE38A89005E7B5A /* ThreeDSUICustomization.swift */; };
+ 758A4E812BE38A89005E7B5A /* ThreeDSTextBoxCustomization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4E7B2BE38A89005E7B5A /* ThreeDSTextBoxCustomization.swift */; };
+ 758A4E842BE38AC3005E7B5A /* ThreeDS_SDK in Frameworks */ = {isa = PBXBuildFile; productRef = 758A4E832BE38AC3005E7B5A /* ThreeDS_SDK */; };
+ 758A4E862BE38AD0005E7B5A /* ThreeDS_SDK in Frameworks */ = {isa = PBXBuildFile; productRef = 758A4E852BE38AD0005E7B5A /* ThreeDS_SDK */; };
+ 758A4E902BE38AFD005E7B5A /* NetceteraThreeDSControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4E882BE38AFD005E7B5A /* NetceteraThreeDSControllerTests.swift */; };
+ 758A4E922BE38AFD005E7B5A /* UICustomizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4E8A2BE38AFD005E7B5A /* UICustomizationTests.swift */; };
+ 758A4E962BE38AFD005E7B5A /* ThreeDSButtonCustomizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4E8E2BE38AFD005E7B5A /* ThreeDSButtonCustomizationTests.swift */; };
+ 758A4E972BE38AFD005E7B5A /* SHA512Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4E8F2BE38AFD005E7B5A /* SHA512Tests.swift */; };
+ 758A4E9F2BE3DFDF005E7B5A /* URL+PathComponentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4E9E2BE3DFDF005E7B5A /* URL+PathComponentsTests.swift */; };
+ 758A4EA62BE9D5C0005E7B5A /* Closures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4EA52BE9D5C0005E7B5A /* Closures.swift */; };
+ 758A4EA82BE9D722005E7B5A /* AuthorizingPaymentDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758A4EA72BE9D722005E7B5A /* AuthorizingPaymentDelegate.swift */; };
759856242A286C880087B605 /* UIScrollView+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 759856232A286C880087B605 /* UIScrollView+Helpers.swift */; };
759C7EC02B7DC7F50029D555 /* SourceType+MobileBanking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 759C7EBF2B7DC7F50029D555 /* SourceType+MobileBanking.swift */; };
759C7EC22B7DC80A0029D555 /* SourceType+InternetBanking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 759C7EC12B7DC80A0029D555 /* SourceType+InternetBanking.swift */; };
@@ -176,7 +196,7 @@
75F2A0BB2A1A60630038FA54 /* AtomePaymentFormViewContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75F2A0B52A1A5F020038FA54 /* AtomePaymentFormViewContext.swift */; };
75F2A0C12A1A84900038FA54 /* UIColor+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75F2A0C02A1A84900038FA54 /* UIColor+Helpers.swift */; };
75F2A0C22A1A87AE0038FA54 /* UIColor+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75F2A0C02A1A84900038FA54 /* UIColor+Helpers.swift */; };
- 8A188BB01D951E3C00FFF467 /* AuthorizingPaymentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A188BAF1D951E3C00FFF467 /* AuthorizingPaymentViewController.swift */; };
+ 8A188BB01D951E3C00FFF467 /* AuthorizingPaymentWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A188BAF1D951E3C00FFF467 /* AuthorizingPaymentWebViewController.swift */; };
8A2C451920ECC7A60033E2EB /* OmiseSDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 225931181CE3210700841B86 /* OmiseSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
8A40DA29216B441E00749F45 /* PaymentSettingTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7A9EBC2166160300F12D86 /* PaymentSettingTableViewController.swift */; };
8A40DA2B216B53E900749F45 /* Tools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A40DA2A216B53E900749F45 /* Tools.swift */; };
@@ -287,7 +307,7 @@
750126712B8A49BC00ED5E14 /* FPX.Bank+ViewPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FPX.Bank+ViewPresentable.swift"; sourceTree = ""; };
750126732B8A580500ED5E14 /* SelectDuitNowOBWBankViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectDuitNowOBWBankViewModel.swift; sourceTree = ""; };
750126762B95971A00ED5E14 /* ChoosePaymentCoordinator+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChoosePaymentCoordinator+Helpers.swift"; sourceTree = ""; };
- 750126782B95A34000ED5E14 /* AuthorizingPaymentViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AuthorizingPaymentViewController.xib; sourceTree = ""; };
+ 750126782B95A34000ED5E14 /* AuthorizingPaymentWebViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AuthorizingPaymentWebViewController.xib; sourceTree = ""; };
7502CA552A3011A900766E7D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
7502CA582A3011B000766E7D /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/LaunchScreen.strings; sourceTree = ""; };
7502CA5A2A3011B100766E7D /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/LaunchScreen.strings; sourceTree = ""; };
@@ -339,6 +359,25 @@
757E14342BD61DFE005F08AD /* ChoosePaymentMethodDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChoosePaymentMethodDelegate.swift; sourceTree = ""; };
7588011D2BAAB37C00F02934 /* AppDeeplink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDeeplink.swift; sourceTree = ""; };
7588011F2BAAC70200F02934 /* URL+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Helpers.swift"; sourceTree = ""; };
+ 758A4E6B2BE38A5E005E7B5A /* CryptData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptData.swift; sourceTree = ""; };
+ 758A4E6C2BE38A5E005E7B5A /* String+sha512.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+sha512.swift"; sourceTree = ""; };
+ 758A4E6D2BE38A5E005E7B5A /* String+PemCert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+PemCert.swift"; sourceTree = ""; };
+ 758A4E712BE38A7E005E7B5A /* NetceteraThreeDSController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetceteraThreeDSController.swift; sourceTree = ""; };
+ 758A4E722BE38A7E005E7B5A /* NetceteraConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetceteraConfig.swift; sourceTree = ""; };
+ 758A4E762BE38A88005E7B5A /* ThreeDSButtonCustomization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeDSButtonCustomization.swift; sourceTree = ""; };
+ 758A4E772BE38A89005E7B5A /* ThreeDSLabelCustomization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeDSLabelCustomization.swift; sourceTree = ""; };
+ 758A4E782BE38A89005E7B5A /* ThreeDSToolbarCustomization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeDSToolbarCustomization.swift; sourceTree = ""; };
+ 758A4E792BE38A89005E7B5A /* ThreeDSCustomization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeDSCustomization.swift; sourceTree = ""; };
+ 758A4E7A2BE38A89005E7B5A /* ThreeDSUICustomization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeDSUICustomization.swift; sourceTree = ""; };
+ 758A4E7B2BE38A89005E7B5A /* ThreeDSTextBoxCustomization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeDSTextBoxCustomization.swift; sourceTree = ""; };
+ 758A4E882BE38AFD005E7B5A /* NetceteraThreeDSControllerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetceteraThreeDSControllerTests.swift; sourceTree = ""; };
+ 758A4E8A2BE38AFD005E7B5A /* UICustomizationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UICustomizationTests.swift; sourceTree = ""; };
+ 758A4E8B2BE38AFD005E7B5A /* AuthorizingPaymentTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizingPaymentTests.swift; sourceTree = ""; };
+ 758A4E8E2BE38AFD005E7B5A /* ThreeDSButtonCustomizationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeDSButtonCustomizationTests.swift; sourceTree = ""; };
+ 758A4E8F2BE38AFD005E7B5A /* SHA512Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SHA512Tests.swift; sourceTree = ""; };
+ 758A4E9E2BE3DFDF005E7B5A /* URL+PathComponentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+PathComponentsTests.swift"; sourceTree = ""; };
+ 758A4EA52BE9D5C0005E7B5A /* Closures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Closures.swift; sourceTree = ""; };
+ 758A4EA72BE9D722005E7B5A /* AuthorizingPaymentDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizingPaymentDelegate.swift; sourceTree = ""; };
759856232A286C880087B605 /* UIScrollView+Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Helpers.swift"; sourceTree = ""; };
759C7EBF2B7DC7F50029D555 /* SourceType+MobileBanking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceType+MobileBanking.swift"; sourceTree = ""; };
759C7EC12B7DC80A0029D555 /* SourceType+InternetBanking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SourceType+InternetBanking.swift"; sourceTree = ""; };
@@ -421,7 +460,7 @@
75F2A0C02A1A84900038FA54 /* UIColor+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Helpers.swift"; sourceTree = ""; };
75F8C0B62B1F78E300AE78D9 /* PaymentChooserViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentChooserViewControllerTests.swift; sourceTree = ""; };
8A00FEC21F1DE88600246078 /* README.md */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; tabWidth = 2; };
- 8A188BAF1D951E3C00FFF467 /* AuthorizingPaymentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizingPaymentViewController.swift; sourceTree = ""; };
+ 8A188BAF1D951E3C00FFF467 /* AuthorizingPaymentWebViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizingPaymentWebViewController.swift; sourceTree = ""; };
8A25E929213CF6F300D48252 /* EContextPaymentFormController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EContextPaymentFormController.swift; sourceTree = ""; };
8A275CBB21241E4500C5716F /* MainActionButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainActionButton.swift; sourceTree = ""; };
8A30FB7C214929C900DC09DC /* PaymentCreatorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentCreatorController.swift; sourceTree = ""; };
@@ -461,6 +500,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 758A4E842BE38AC3005E7B5A /* ThreeDS_SDK in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -486,6 +526,7 @@
files = (
987A02CE1CE35D5E0035417A /* OmiseSDK.framework in Frameworks */,
757B0A742AD71E3100DEAE8F /* OmiseUnitTestKit in Frameworks */,
+ 758A4E862BE38AD0005E7B5A /* ThreeDS_SDK in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -521,6 +562,7 @@
2259311A1CE3210700841B86 /* OmiseSDK */ = {
isa = PBXGroup;
children = (
+ 75348D4C29C3FC8F0008C8A3 /* ToolKit */,
75E0EB872B7BCEA000E3198A /* Sources */,
8A00FEC21F1DE88600246078 /* README.md */,
2259311D1CE3210700841B86 /* Info.plist */,
@@ -662,6 +704,7 @@
75348D4C29C3FC8F0008C8A3 /* ToolKit */ = {
isa = PBXGroup;
children = (
+ 758A4EA42BE9D599005E7B5A /* Typedefs */,
75F2A0AE2A1A5BE30038FA54 /* Property Wrappers */,
75405A852A26340E008C21F6 /* UI Extensions */,
75405A7E2A262B14008C21F6 /* Extensions */,
@@ -707,6 +750,60 @@
path = ViewModels;
sourceTree = "";
};
+ 758A4E682BE389B2005E7B5A /* 3DS */ = {
+ isa = PBXGroup;
+ children = (
+ 758A4E752BE38A84005E7B5A /* UICustomization */,
+ 758A4E722BE38A7E005E7B5A /* NetceteraConfig.swift */,
+ 758A4E712BE38A7E005E7B5A /* NetceteraThreeDSController.swift */,
+ 758A4E6A2BE38A49005E7B5A /* Crypto */,
+ );
+ path = 3DS;
+ sourceTree = "";
+ };
+ 758A4E6A2BE38A49005E7B5A /* Crypto */ = {
+ isa = PBXGroup;
+ children = (
+ 758A4E6B2BE38A5E005E7B5A /* CryptData.swift */,
+ 758A4E6D2BE38A5E005E7B5A /* String+PemCert.swift */,
+ 758A4E6C2BE38A5E005E7B5A /* String+sha512.swift */,
+ );
+ path = Crypto;
+ sourceTree = "";
+ };
+ 758A4E752BE38A84005E7B5A /* UICustomization */ = {
+ isa = PBXGroup;
+ children = (
+ 758A4E762BE38A88005E7B5A /* ThreeDSButtonCustomization.swift */,
+ 758A4E792BE38A89005E7B5A /* ThreeDSCustomization.swift */,
+ 758A4E772BE38A89005E7B5A /* ThreeDSLabelCustomization.swift */,
+ 758A4E7B2BE38A89005E7B5A /* ThreeDSTextBoxCustomization.swift */,
+ 758A4E782BE38A89005E7B5A /* ThreeDSToolbarCustomization.swift */,
+ 758A4E7A2BE38A89005E7B5A /* ThreeDSUICustomization.swift */,
+ );
+ path = UICustomization;
+ sourceTree = "";
+ };
+ 758A4E872BE38AE8005E7B5A /* 3DS */ = {
+ isa = PBXGroup;
+ children = (
+ 758A4E8B2BE38AFD005E7B5A /* AuthorizingPaymentTests.swift */,
+ 758A4E882BE38AFD005E7B5A /* NetceteraThreeDSControllerTests.swift */,
+ 758A4E8F2BE38AFD005E7B5A /* SHA512Tests.swift */,
+ 758A4E8E2BE38AFD005E7B5A /* ThreeDSButtonCustomizationTests.swift */,
+ 758A4E8A2BE38AFD005E7B5A /* UICustomizationTests.swift */,
+ );
+ path = 3DS;
+ sourceTree = "";
+ };
+ 758A4EA42BE9D599005E7B5A /* Typedefs */ = {
+ isa = PBXGroup;
+ children = (
+ 758A4EA52BE9D5C0005E7B5A /* Closures.swift */,
+ );
+ path = Typedefs;
+ sourceTree = "";
+ };
75CFC4EF2B753AB900422A8F /* JSON Models */ = {
isa = PBXGroup;
children = (
@@ -893,8 +990,8 @@
75D13DFA2B866CF40073A831 /* Authorizing Payment */ = {
isa = PBXGroup;
children = (
- 8A188BAF1D951E3C00FFF467 /* AuthorizingPaymentViewController.swift */,
- 750126782B95A34000ED5E14 /* AuthorizingPaymentViewController.xib */,
+ 8A188BAF1D951E3C00FFF467 /* AuthorizingPaymentWebViewController.swift */,
+ 750126782B95A34000ED5E14 /* AuthorizingPaymentWebViewController.xib */,
);
path = "Authorizing Payment";
sourceTree = "";
@@ -976,13 +1073,14 @@
75E0EB872B7BCEA000E3198A /* Sources */ = {
isa = PBXGroup;
children = (
- 75348D4C29C3FC8F0008C8A3 /* ToolKit */,
+ 758A4E682BE389B2005E7B5A /* 3DS */,
75E0EBBD2B7D0DB600E3198A /* Components */,
75E0EB942B7BD00F00E3198A /* Models */,
75E0EBBF2B7D13D200E3198A /* OmiseAPI */,
75D13DEE2B866BCA0073A831 /* Views */,
75CFC4D12B73AFD500422A8F /* OmiseSDK.swift */,
757E14342BD61DFE005F08AD /* ChoosePaymentMethodDelegate.swift */,
+ 758A4EA72BE9D722005E7B5A /* AuthorizingPaymentDelegate.swift */,
755CD9222BA2000600E8FCDA /* ClientProtocol.swift */,
);
path = Sources;
@@ -1103,6 +1201,8 @@
987A02CA1CE35D5E0035417A /* OmiseSDKTests */ = {
isa = PBXGroup;
children = (
+ 758A4E9E2BE3DFDF005E7B5A /* URL+PathComponentsTests.swift */,
+ 758A4E872BE38AE8005E7B5A /* 3DS */,
757B0A722AD71DCD00DEAE8F /* OmiseUnitTestKit */,
987A02CD1CE35D5E0035417A /* Info.plist */,
987A03AC1CE5CD450035417A /* Fixtures */,
@@ -1167,6 +1267,7 @@
);
name = OmiseSDK;
packageProductDependencies = (
+ 758A4E832BE38AC3005E7B5A /* ThreeDS_SDK */,
);
productName = OmiseSDK;
productReference = 225931181CE3210700841B86 /* OmiseSDK.framework */;
@@ -1180,6 +1281,7 @@
22D480991D0EB29C00544CE1 /* Sources */,
22D4809A1D0EB29C00544CE1 /* Frameworks */,
8A0928211D545455008D393F /* Embed Frameworks */,
+ 752970442BF4A8F100DE1AA5 /* Skip Config file changes */,
);
buildRules = (
);
@@ -1232,6 +1334,7 @@
name = OmiseSDKTests;
packageProductDependencies = (
757B0A732AD71E3100DEAE8F /* OmiseUnitTestKit */,
+ 758A4E852BE38AD0005E7B5A /* ThreeDS_SDK */,
);
productName = OmiseSDKTests;
productReference = 987A02C91CE35D5E0035417A /* OmiseSDKTests.xctest */;
@@ -1279,6 +1382,9 @@
Base,
);
mainGroup = 2259310E1CE3210700841B86;
+ packageReferences = (
+ 758A4E822BE38AC3005E7B5A /* XCRemoteSwiftPackageReference "SPM" */,
+ );
productRefGroup = 225931191CE3210700841B86 /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -1301,7 +1407,7 @@
75D13E1B2B86FD2D0073A831 /* CCVInfoController.xib in Resources */,
75405AA82A27F97E008C21F6 /* Localizable.strings in Resources */,
7501266E2B8A411C00ED5E14 /* FPXPaymentFormController.xib in Resources */,
- 750126792B95A34000ED5E14 /* AuthorizingPaymentViewController.xib in Resources */,
+ 750126792B95A34000ED5E14 /* AuthorizingPaymentWebViewController.xib in Resources */,
8A776647214A5FC60029D166 /* NoticeView.xib in Resources */,
2225B3541D0FDD63003EB396 /* Assets.xcassets in Resources */,
8AF5517A213D4C0100D61C98 /* Localizable.stringsdict in Resources */,
@@ -1342,6 +1448,24 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
+ 752970442BF4A8F100DE1AA5 /* Skip Config file changes */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ name = "Skip Config file changes";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "if git --version >/dev/null 2>&1; then\n git update-index --assume-unchanged ExampleApp/Config.local.plist\nelse\n echo \"Git is not installed, skipping command.\"\nfi\n";
+ };
75341FF329A716FD003027E6 /* Swiftlint */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -1384,6 +1508,7 @@
73F9BA4B25E6053700F55CA8 /* FPXPaymentFormController.swift in Sources */,
98FF99BB2577789800476487 /* Configuration.swift in Sources */,
759C7EC02B7DC7F50029D555 /* SourceType+MobileBanking.swift in Sources */,
+ 758A4EA82BE9D722005E7B5A /* AuthorizingPaymentDelegate.swift in Sources */,
75B420612B776D8A0036134D /* APIProtocol.swift in Sources */,
75CFC4D22B73AFD500422A8F /* OmiseSDK.swift in Sources */,
75131F862AC2BD9D008BCB17 /* UIViewController+NavigationBarStyle.swift in Sources */,
@@ -1406,6 +1531,7 @@
75CFC4EE2B7537BC00422A8F /* Client+URLRequest.swift in Sources */,
75D13DAC2B83B58B0073A831 /* Source.Payment.EContext.swift in Sources */,
75D13D8C2B8385820073A831 /* UIBarButtonItem+Helpers.swift in Sources */,
+ 758A4E7C2BE38A89005E7B5A /* ThreeDSButtonCustomization.swift in Sources */,
75E0EBA22B7BD02200E3198A /* Token.Card.swift in Sources */,
75405A8B2A26340E008C21F6 /* UIStackView+Helpers.swift in Sources */,
75D13DA82B83B58B0073A831 /* Source.Payment.DuitNowOBW.swift in Sources */,
@@ -1419,9 +1545,10 @@
8AF9857121479F38007E18ED /* CCVInfoController.swift in Sources */,
75E0EB7C2B7B410600E3198A /* Client+NetworkService.swift in Sources */,
75D13D602B807AC90073A831 /* ChoosePaymentCoordinator.swift in Sources */,
+ 758A4E7D2BE38A89005E7B5A /* ThreeDSLabelCustomization.swift in Sources */,
75D13DB42B83B71D0073A831 /* DuitNowOBW.Bank+ViewPresentable.swift in Sources */,
75405A7C2A25EC88008C21F6 /* Array+Helpers.swift in Sources */,
- 8A188BB01D951E3C00FFF467 /* AuthorizingPaymentViewController.swift in Sources */,
+ 8A188BB01D951E3C00FFF467 /* AuthorizingPaymentWebViewController.swift in Sources */,
75D13DFC2B866DFE0073A831 /* PaymentCreatorController.swift in Sources */,
14ACEF831D02D641001C5319 /* CardExpiryDatePicker.swift in Sources */,
75CFC4F32B753BCD00422A8F /* Capability.PaymentMethod.swift in Sources */,
@@ -1439,29 +1566,37 @@
986406301CFEA7E4004BCF51 /* CardCVVTextField.swift in Sources */,
75D13DAA2B83B58B0073A831 /* Source.Payment.Installment.swift in Sources */,
75D13DD62B8653360073A831 /* UIControl.State+Hashable.swift in Sources */,
+ 758A4E7F2BE38A89005E7B5A /* ThreeDSCustomization.swift in Sources */,
751E7BEC2B7F7CE50031B4A2 /* ViewPresentable.swift in Sources */,
751E7BEA2B7F77120031B4A2 /* Localized+Omise.swift in Sources */,
75D13DA52B83B58B0073A831 /* Source.swift in Sources */,
22445FFC1CF41DD300801D0F /* Globals.swift in Sources */,
+ 758A4E702BE38A5F005E7B5A /* String+PemCert.swift in Sources */,
7501265F2B8893C900ED5E14 /* PaymentFormController.swift in Sources */,
756C8F222A40694F00D53059 /* CreditCardPaymentViewContext.swift in Sources */,
751E7BF02B7FBA760031B4A2 /* UIColor+Omise.swift in Sources */,
751E7BF22B7FC90C0031B4A2 /* SourceType+Collections.swift in Sources */,
750126722B8A49BC00ED5E14 /* FPX.Bank+ViewPresentable.swift in Sources */,
+ 758A4E812BE38A89005E7B5A /* ThreeDSTextBoxCustomization.swift in Sources */,
75D13DE42B8666CC0073A831 /* SelectSourceTypeDelegate.swift in Sources */,
75CFC4DD2B73F75C00422A8F /* OmiseAPI.swift in Sources */,
75348D4E29C3FCC70008C8A3 /* Bundle+OmiseSDK.swift in Sources */,
755CD9232BA2000600E8FCDA /* ClientProtocol.swift in Sources */,
757E14352BD61DFE005F08AD /* ChoosePaymentMethodDelegate.swift in Sources */,
+ 758A4E802BE38A89005E7B5A /* ThreeDSUICustomization.swift in Sources */,
75E0EB9D2B7BD00F00E3198A /* CardBrand.swift in Sources */,
+ 758A4EA62BE9D5C0005E7B5A /* Closures.swift in Sources */,
+ 758A4E6E2BE38A5F005E7B5A /* CryptData.swift in Sources */,
75E0EB9A2B7BD00F00E3198A /* Currency.swift in Sources */,
75405A9C2A266F50008C21F6 /* AtomePaymentFormViewModel.swift in Sources */,
75D13DA72B83B58B0073A831 /* Source.Payment.BarcodeAlipay.swift in Sources */,
75CFC4DB2B73F5AF00422A8F /* NetworkService.swift in Sources */,
+ 758A4E6F2BE38A5F005E7B5A /* String+sha512.swift in Sources */,
75E0EBA32B7BD02200E3198A /* Token.swift in Sources */,
75405A932A26340E008C21F6 /* UIButton+Helpers.swift in Sources */,
751E7BD92B7E5D130031B4A2 /* PaymentMethod.swift in Sources */,
754B78182B8741CD00973B71 /* TapGestureHandler.swift in Sources */,
+ 758A4E742BE38A7E005E7B5A /* NetceteraConfig.swift in Sources */,
75D13DCB2B8531D20073A831 /* Assets.swift in Sources */,
986406321CFEA7E4004BCF51 /* CardNumberTextField.swift in Sources */,
750126742B8A580500ED5E14 /* SelectDuitNowOBWBankViewModel.swift in Sources */,
@@ -1482,6 +1617,7 @@
75E0EBBB2B7BD02F00E3198A /* SourceType.swift in Sources */,
75D13DAF2B83B58B0073A831 /* Source.Payment.Atome.swift in Sources */,
75CFC4F12B753B0F00422A8F /* Capability.swift in Sources */,
+ 758A4E7E2BE38A89005E7B5A /* ThreeDSToolbarCustomization.swift in Sources */,
75CFC4CE2B73AF0E00422A8F /* Client.swift in Sources */,
7502CA622A31A7E100766E7D /* CountryListViewModelProtocol.swift in Sources */,
75D13DC12B8400610073A831 /* SelectPaymentMethodViewModel.swift in Sources */,
@@ -1491,6 +1627,7 @@
755CD9212BA1C81600E8FCDA /* UIViewController+KeyboardAppearance.swift in Sources */,
8AEF7804224CABF800787470 /* EContextPaymentFormController.swift in Sources */,
75D13DC92B8529790073A831 /* SelectSourceTypePaymentViewModel.swift in Sources */,
+ 758A4E732BE38A7E005E7B5A /* NetceteraThreeDSController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1539,18 +1676,23 @@
buildActionMask = 2147483647;
files = (
7509D4E02A1AA1450050AB38 /* AtomeFormViewControllerTests.swift in Sources */,
+ 758A4E962BE38AFD005E7B5A /* ThreeDSButtonCustomizationTests.swift in Sources */,
+ 758A4E902BE38AFD005E7B5A /* NetceteraThreeDSControllerTests.swift in Sources */,
7509D4E42A1C89F10050AB38 /* AtomeFormViewModelMockup.swift in Sources */,
750708F42B7A765500A48DD0 /* OmiseSDKTests.swift in Sources */,
75E0EB7E2B7B5EE400E3198A /* CreateTokenPayloadTests.swift in Sources */,
75E0EB712B7A904100E3198A /* SourceFlowTests.swift in Sources */,
75E0EB832B7B79E700E3198A /* CapabilityTests.swift in Sources */,
F615CBF8261565D600E1A2D9 /* CardExpiryDatePickerTests.swift in Sources */,
+ 758A4E9F2BE3DFDF005E7B5A /* URL+PathComponentsTests.swift in Sources */,
+ 758A4E922BE38AFD005E7B5A /* UICustomizationTests.swift in Sources */,
75E0EB722B7A962600E3198A /* CreateSourcePayloadTests.swift in Sources */,
753279382A31B40F008048AD /* CountryListViewModelMockup.swift in Sources */,
7509D4E72A1C8E3D0050AB38 /* AtomeFormViewModelTests.swift in Sources */,
7509D4E22A1C876B0050AB38 /* AtomeFormViewContextMockup.swift in Sources */,
750708E32B790B8300A48DD0 /* String+JSON.swift in Sources */,
750708E52B790BD600A48DD0 /* SampleData.swift in Sources */,
+ 758A4E972BE38AFD005E7B5A /* SHA512Tests.swift in Sources */,
750708E02B7909BB00A48DD0 /* SourceTests.swift in Sources */,
75CFC4D42B73B21100422A8F /* ClientTests.swift in Sources */,
);
@@ -2049,6 +2191,17 @@
};
/* End XCConfigurationList section */
+/* Begin XCRemoteSwiftPackageReference section */
+ 758A4E822BE38AC3005E7B5A /* XCRemoteSwiftPackageReference "SPM" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/ios-3ds-sdk/SPM.git";
+ requirement = {
+ kind = exactVersion;
+ version = 2.4.0;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
/* Begin XCSwiftPackageProductDependency section */
7509D4ED2A1D1F9F0050AB38 /* OmiseTestSDK */ = {
isa = XCSwiftPackageProductDependency;
@@ -2058,6 +2211,16 @@
isa = XCSwiftPackageProductDependency;
productName = OmiseUnitTestKit;
};
+ 758A4E832BE38AC3005E7B5A /* ThreeDS_SDK */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 758A4E822BE38AC3005E7B5A /* XCRemoteSwiftPackageReference "SPM" */;
+ productName = ThreeDS_SDK;
+ };
+ 758A4E852BE38AD0005E7B5A /* ThreeDS_SDK */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 758A4E822BE38AC3005E7B5A /* XCRemoteSwiftPackageReference "SPM" */;
+ productName = ThreeDS_SDK;
+ };
75D13E0E2B8678530073A831 /* OmiseSwiftUIKit */ = {
isa = XCSwiftPackageProductDependency;
productName = OmiseSwiftUIKit;
diff --git a/dev.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/dev.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
new file mode 100644
index 00000000..2d52cade
--- /dev/null
+++ b/dev.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -0,0 +1,16 @@
+{
+ "object": {
+ "pins": [
+ {
+ "package": "ThreeDS_SDK",
+ "repositoryURL": "https://github.com/ios-3ds-sdk/SPM.git",
+ "state": {
+ "branch": null,
+ "revision": "a4e55d7f10294ea81abd1db393ae05606a5efa46",
+ "version": "2.4.0"
+ }
+ }
+ ]
+ },
+ "version": 1
+}
diff --git a/sonar-project.properties b/sonar-project.properties
index d4e62d10..eab5e4e5 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -10,7 +10,7 @@ sonar.tests=OmiseSDKTests
sonar.swift.simulator=platform=iOS Simulator,name=iPhone 12,OS=latest
sonar.swift.excludedPathsFromCoverage=.*Tests.*
-sonar.swift.project=OmiseSDK.xcodeproj
+sonar.swift.project=dev.xcodeproj
sonar.swift.appScheme=OmiseSDK
sonar.language=swift