From bf3bdc7e84bc1b32be319782eaf90e8aae8ca401 Mon Sep 17 00:00:00 2001 From: laiso Date: Tue, 28 Jan 2025 21:35:29 +0700 Subject: [PATCH 1/5] add workspace files in project root --- PAYJP.xcodeproj/project.pbxproj | 22 ++++++++++++++----- PAYJP.xcworkspace/contents.xcworkspacedata | 13 +++++++++++ .../xcshareddata/WorkspaceSettings.xcsettings | 5 +++++ 3 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 PAYJP.xcworkspace/contents.xcworkspacedata create mode 100644 PAYJP.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/PAYJP.xcodeproj/project.pbxproj b/PAYJP.xcodeproj/project.pbxproj index cc8bfa5..a3a14e9 100644 --- a/PAYJP.xcodeproj/project.pbxproj +++ b/PAYJP.xcodeproj/project.pbxproj @@ -7,13 +7,13 @@ objects = { /* Begin PBXBuildFile section */ + 07BC10742D4625CB00891977 /* PhoneNumberKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 328622622C6C42CB00DEA55B /* PhoneNumberKit.xcframework */; }; 32167E0727B1C55800E4BCD5 /* OHHTTPStubs.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32167E0627B1C55800E4BCD5 /* OHHTTPStubs.xcframework */; }; 322BD1D6225C83CA00D83B5E /* JSONDecoder+PAYJP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322BD1D5225C83CA00D83B5E /* JSONDecoder+PAYJP.swift */; }; 32585BAF2CAC4E7C003F920B /* CardHolderValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32585BAE2CAC4E7C003F920B /* CardHolderValidator.swift */; }; 32585BB22CAC5237003F920B /* CardHolderValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32585BB02CAC522B003F920B /* CardHolderValidatorTests.swift */; }; 325E36B1233484C90037E6BE /* UIColor+PAYJP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 325E36B0233484C90037E6BE /* UIColor+PAYJP.swift */; }; 327C806E2C73D1F700CADF44 /* ExtraAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 327C806D2C73D1F700CADF44 /* ExtraAttribute.swift */; }; - 328622662C6C483F00DEA55B /* PhoneNumberKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 328622622C6C42CB00DEA55B /* PhoneNumberKit.xcframework */; }; 3289C9D123EACB6D008FFBC1 /* ClientInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3289C9D023EACB6D008FFBC1 /* ClientInfo.swift */; }; 3289C9D323EBFC87008FFBC1 /* ClientInfoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3289C9D223EBFC87008FFBC1 /* ClientInfoTests.swift */; }; 32A6F614239608DD00747477 /* ActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32A6F613239608DD00747477 /* ActionButton.swift */; }; @@ -152,8 +152,20 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 07BC10762D4625CB00891977 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ - 320B38E92C6E3886002D1BFB /* PAYJPTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = PAYJPTests.xctestplan; path = Tests/PAYJPTests.xctestplan; sourceTree = ""; }; 32167E0627B1C55800E4BCD5 /* OHHTTPStubs.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OHHTTPStubs.xcframework; path = Carthage/Build/OHHTTPStubs.xcframework; sourceTree = ""; }; 322BD1D5225C83CA00D83B5E /* JSONDecoder+PAYJP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONDecoder+PAYJP.swift"; sourceTree = ""; }; 32585BAE2CAC4E7C003F920B /* CardHolderValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardHolderValidator.swift; sourceTree = ""; }; @@ -225,7 +237,6 @@ C65513BD1DA37AD4009D6DA5 /* APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIError.swift; sourceTree = ""; }; C68681F61E1E21D9001D7F75 /* CardFromTokenTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardFromTokenTests.swift; sourceTree = ""; }; C68681F91E1E2369001D7F75 /* TestFixture.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestFixture.swift; sourceTree = ""; }; - C6E00B0D1E1F505600C101B0 /* OHHTTPStubs.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OHHTTPStubs.framework; path = Carthage/Build/iOS/OHHTTPStubs.framework; sourceTree = ""; }; ED00F8BE24319E1B004DFE2C /* UIApplication+PAYJP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+PAYJP.swift"; sourceTree = ""; }; ED00F8CF243353FC004DFE2C /* ThreeDSecureProcessHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeDSecureProcessHandler.swift; sourceTree = ""; }; ED00F8D0243353FC004DFE2C /* ThreeDSecureURLConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreeDSecureURLConfiguration.swift; sourceTree = ""; }; @@ -299,6 +310,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 07BC10742D4625CB00891977 /* PhoneNumberKit.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -307,7 +319,6 @@ buildActionMask = 2147483647; files = ( 32167E0727B1C55800E4BCD5 /* OHHTTPStubs.xcframework in Frameworks */, - 328622662C6C483F00DEA55B /* PhoneNumberKit.xcframework in Frameworks */, 615AFF661C8C74A1003FB86F /* PAYJP.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -362,7 +373,6 @@ 615AFF511C8C74A1003FB86F = { isa = PBXGroup; children = ( - 320B38E92C6E3886002D1BFB /* PAYJPTests.xctestplan */, C6E00B0C1E1F505600C101B0 /* Frameworks */, 615AFF5C1C8C74A1003FB86F /* Products */, 615AFF5D1C8C74A1003FB86F /* Sources */, @@ -635,7 +645,6 @@ children = ( 328622622C6C42CB00DEA55B /* PhoneNumberKit.xcframework */, 32167E0627B1C55800E4BCD5 /* OHHTTPStubs.xcframework */, - C6E00B0D1E1F505600C101B0 /* OHHTTPStubs.framework */, ); name = Frameworks; sourceTree = ""; @@ -725,6 +734,7 @@ 615AFF581C8C74A1003FB86F /* Headers */, 615AFF591C8C74A1003FB86F /* Resources */, 324792A72342FD9B00C86EFB /* Run SwiftLint */, + 07BC10762D4625CB00891977 /* Embed Frameworks */, ); buildRules = ( ); diff --git a/PAYJP.xcworkspace/contents.xcworkspacedata b/PAYJP.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..e287707 --- /dev/null +++ b/PAYJP.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/PAYJP.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/PAYJP.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/PAYJP.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + + From 3f20027f95ce4d52594584ec258cb2c1d256f496 Mon Sep 17 00:00:00 2001 From: laiso Date: Tue, 28 Jan 2025 22:41:01 +0700 Subject: [PATCH 2/5] Add support for 3D Secure process with resource ID - Introduce new method `startThreeDSecureProcess(viewController:delegate:resourceId:)` in ThreeDSecureProcessHandler - Add `makeTdsEntryUrl(resourceId:)` method to ThreeDSecureURLConfiguration - Mark previous token-based method as deprecated - Update tests to support new resource ID-based 3D Secure process --- PAYJP.xcodeproj/project.pbxproj | 4 +++ .../ThreeDSecureProcessHandler.swift | 26 +++++++++++++- .../ThreeDSecureURLConfiguration.swift | 11 ++++++ Sources/Views/CardFormViewController.swift | 2 +- .../ThreeDSecureProcessHandlerTests.swift | 35 ++++++++++++++----- .../ThreeDSecureURLConfigurationTests.swift | 21 +++++++++++ .../CardFormViewWith3DSViewController.swift | 4 +-- 7 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 Tests/ThreeDSecure/ThreeDSecureURLConfigurationTests.swift diff --git a/PAYJP.xcodeproj/project.pbxproj b/PAYJP.xcodeproj/project.pbxproj index a3a14e9..efe633a 100644 --- a/PAYJP.xcodeproj/project.pbxproj +++ b/PAYJP.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 07AEC1332D492DD80054E345 /* ThreeDSecureURLConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07AEC1322D492DD80054E345 /* ThreeDSecureURLConfigurationTests.swift */; }; 07BC10742D4625CB00891977 /* PhoneNumberKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 328622622C6C42CB00DEA55B /* PhoneNumberKit.xcframework */; }; 32167E0727B1C55800E4BCD5 /* OHHTTPStubs.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32167E0627B1C55800E4BCD5 /* OHHTTPStubs.xcframework */; }; 322BD1D6225C83CA00D83B5E /* JSONDecoder+PAYJP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322BD1D5225C83CA00D83B5E /* JSONDecoder+PAYJP.swift */; }; @@ -166,6 +167,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 07AEC1322D492DD80054E345 /* ThreeDSecureURLConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeDSecureURLConfigurationTests.swift; sourceTree = ""; }; 32167E0627B1C55800E4BCD5 /* OHHTTPStubs.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OHHTTPStubs.xcframework; path = Carthage/Build/OHHTTPStubs.xcframework; sourceTree = ""; }; 322BD1D5225C83CA00D83B5E /* JSONDecoder+PAYJP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONDecoder+PAYJP.swift"; sourceTree = ""; }; 32585BAE2CAC4E7C003F920B /* CardHolderValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardHolderValidator.swift; sourceTree = ""; }; @@ -693,6 +695,7 @@ children = ( EDA0E418243739C00083A5E0 /* ThreeDSecureProcessHandlerTests.swift */, ED3DB9E1243ACD2B0065FC33 /* ThreeDSecureSFSafariViewControllerDriverTests.swift */, + 07AEC1322D492DD80054E345 /* ThreeDSecureURLConfigurationTests.swift */, ED3DB9DF243ACCBE0065FC33 /* Mock.swift */, ); path = ThreeDSecure; @@ -959,6 +962,7 @@ EDA3EE9B22E1CD9900D9A6C9 /* CardBrandTransformerTests.swift in Sources */, ED508383231CC3B600C64A7F /* StringProtocol+PAYJPTests.swift in Sources */, EDA77569238E4CB200162340 /* PublicKeyValidatorTests.swift in Sources */, + 07AEC1332D492DD80054E345 /* ThreeDSecureURLConfigurationTests.swift in Sources */, EDA0E4142435EBA80083A5E0 /* ThreeDSecureStatusTests.swift in Sources */, EDA3EE8122DF1F0300D9A6C9 /* CardNumberFormatterTests.swift in Sources */, BE21781622E8091A0098128D /* PAYJPSDKTests.swift in Sources */, diff --git a/Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift b/Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift index 32e275b..9daba40 100644 --- a/Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift +++ b/Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift @@ -31,16 +31,27 @@ public protocol ThreeDSecureProcessHandlerDelegate: AnyObject { /// Handler for 3DSecure process. public protocol ThreeDSecureProcessHandlerType { - /// Stsart 3DSecure process + /// Start 3DSecure process /// Delegate will be released once the process is finished. /// - Parameters: /// - viewController: the viewController which will present SFSafariViewController. /// - delegate: ThreeDSecureProcessHandlerDelegate /// - token: Token + @available(*, deprecated, message: "Use startThreeDSecureProcess(viewController:delegate:resourceId:) instead.") func startThreeDSecureProcess(viewController: UIViewController, delegate: ThreeDSecureProcessHandlerDelegate, token: Token) + /// Start 3DSecure process with resourceID + /// Delegate will be released once the process is finished. + /// - Parameters: + /// - viewController: the viewController which will present SFSafariViewController. + /// - delegate: ThreeDSecureProcessHandlerDelegate + /// - resourceId: ID of the resource(card, charge, token, etc.) + func startThreeDSecureProcess(viewController: UIViewController, + delegate: ThreeDSecureProcessHandlerDelegate, + resourceId: String) + /// Complete 3DSecure process. /// - Parameters: /// - url: redirect URL @@ -64,6 +75,7 @@ public class ThreeDSecureProcessHandler: NSObject, ThreeDSecureProcessHandlerTyp // MARK: ThreeDSecureProcessHandlerType + @available(*, deprecated, message: "Use startThreeDSecureProcess(viewController:delegate:resourceId:) instead.") public func startThreeDSecureProcess(viewController: UIViewController, delegate: ThreeDSecureProcessHandlerDelegate, token: Token) { @@ -71,6 +83,18 @@ public class ThreeDSecureProcessHandler: NSObject, ThreeDSecureProcessHandlerTyp webDriver.openWebBrowser(host: viewController, url: token.tdsEntryUrl, delegate: self) } + public func startThreeDSecureProcess(viewController: UIViewController, + delegate: ThreeDSecureProcessHandlerDelegate, + resourceId: String) { + self.delegate = delegate + let tdsEntryUrl = PAYJPSDK.threeDSecureURLConfiguration?.makeTdsEntryUrl(resourceId: resourceId) + guard let tdsEntryUrl = tdsEntryUrl else { + delegate.threeDSecureProcessHandlerDidFinish(self, status: .canceled) + return + } + webDriver.openWebBrowser(host: viewController, url: tdsEntryUrl, delegate: self) + } + public func completeThreeDSecureProcess(url: URL) -> Bool { print(debug: "tds redirect url => \(url)") diff --git a/Sources/ThreeDSecure/ThreeDSecureURLConfiguration.swift b/Sources/ThreeDSecure/ThreeDSecureURLConfiguration.swift index 0a3ba69..6ee0bf2 100644 --- a/Sources/ThreeDSecure/ThreeDSecureURLConfiguration.swift +++ b/Sources/ThreeDSecure/ThreeDSecureURLConfiguration.swift @@ -19,4 +19,15 @@ public class ThreeDSecureURLConfiguration: NSObject { self.redirectURL = redirectURL self.redirectURLKey = redirectURLKey } + + public func makeTdsEntryUrl(resourceId: String) -> URL { + let baseUrl = URL(string: "\(PAYJPApiEndpoint)tds/\(resourceId)")! + let url = baseUrl.appendingPathComponent("start") + var components = URLComponents(url: url, resolvingAgainstBaseURL: true)! + components.queryItems = [ + URLQueryItem(name: "publickey", value: PAYJPSDK.publicKey), + URLQueryItem(name: "back", value: redirectURLKey) + ] + return components.url! + } } diff --git a/Sources/Views/CardFormViewController.swift b/Sources/Views/CardFormViewController.swift index 85f8195..897e779 100644 --- a/Sources/Views/CardFormViewController.swift +++ b/Sources/Views/CardFormViewController.swift @@ -332,7 +332,7 @@ extension CardFormViewController: CardFormScreenDelegate { func presentVerificationScreen(token: Token) { ThreeDSecureProcessHandler.shared.startThreeDSecureProcess(viewController: self, delegate: self, - token: token) + resourceId: token.identifer) } func didCompleteCardForm(with result: CardFormResult) { diff --git a/Tests/ThreeDSecure/ThreeDSecureProcessHandlerTests.swift b/Tests/ThreeDSecure/ThreeDSecureProcessHandlerTests.swift index 8b4ead5..8791029 100644 --- a/Tests/ThreeDSecure/ThreeDSecureProcessHandlerTests.swift +++ b/Tests/ThreeDSecure/ThreeDSecureProcessHandlerTests.swift @@ -33,6 +33,11 @@ class ThreeDSecureProcessHandlerTests: XCTestCase { return token } + private func mockResourceID() -> String { + return "ch_123" + } + + @available(*, deprecated) func testStartThreeDSecureProcess() { let mockDriver = MockWebDriver() let handler = ThreeDSecureProcessHandler(webDriver: mockDriver) @@ -46,19 +51,33 @@ class ThreeDSecureProcessHandlerTests: XCTestCase { XCTAssertEqual(mockDriver.openWebBrowserUrl?.absoluteString, token.tdsEntryUrl.absoluteString) } + func testStartThreeDSecureProcessWithResourceID() { + let mockDriver = MockWebDriver() + let handler = ThreeDSecureProcessHandler(webDriver: mockDriver) + let resourceID = self.mockResourceID() + let mockVC = MockViewController() + + handler.startThreeDSecureProcess(viewController: mockVC, + delegate: mockVC, + resourceId: resourceID) + + // Verify the URL contains the resource ID + XCTAssertTrue(mockDriver.openWebBrowserUrl?.absoluteString.contains(resourceID) ?? false) + } + func testCompleteThreeDSecureProcess() { PAYJPSDK.threeDSecureURLConfiguration = ThreeDSecureURLConfiguration(redirectURL: URL(string: "test://")!, redirectURLKey: "test") let mockDriver = MockWebDriver(isSafariVC: true) let handler = ThreeDSecureProcessHandler(webDriver: mockDriver) - let token = self.mockToken(tdsStatus: .unverified) + let resourceID = self.mockResourceID() let mockVC = MockViewController() let url = URL(string: "test://")! handler.startThreeDSecureProcess(viewController: mockVC, delegate: mockVC, - token: token) + resourceId: resourceID) let result = handler.completeThreeDSecureProcess(url: url) @@ -72,13 +91,13 @@ class ThreeDSecureProcessHandlerTests: XCTestCase { let mockDriver = MockWebDriver(isSafariVC: true) let handler = ThreeDSecureProcessHandler(webDriver: mockDriver) - let token = self.mockToken(tdsStatus: .unverified) + let resourceID = self.mockResourceID() let mockVC = MockViewController() let url = URL(string: "unknown://")! handler.startThreeDSecureProcess(viewController: mockVC, delegate: mockVC, - token: token) + resourceId: resourceID) let result = handler.completeThreeDSecureProcess(url: url) @@ -92,13 +111,13 @@ class ThreeDSecureProcessHandlerTests: XCTestCase { let mockDriver = MockWebDriver(isSafariVC: false) let handler = ThreeDSecureProcessHandler(webDriver: mockDriver) - let token = self.mockToken(tdsStatus: .unverified) + let resourceID = self.mockResourceID() let mockVC = MockViewController() let url = URL(string: "test://")! handler.startThreeDSecureProcess(viewController: mockVC, delegate: mockVC, - token: token) + resourceId: resourceID) let result = handler.completeThreeDSecureProcess(url: url) @@ -112,12 +131,12 @@ class ThreeDSecureProcessHandlerTests: XCTestCase { let mockDriver = MockWebDriver(isSafariVC: true) let handler = ThreeDSecureProcessHandler(webDriver: mockDriver) - let token = self.mockToken(tdsStatus: .unverified) + let resourceID = self.mockResourceID() let mockVC = MockViewController() handler.startThreeDSecureProcess(viewController: mockVC, delegate: mockVC, - token: token) + resourceId: resourceID) // Webブラウザを閉じた場合を想定 handler.webBrowseDidFinish(mockDriver) diff --git a/Tests/ThreeDSecure/ThreeDSecureURLConfigurationTests.swift b/Tests/ThreeDSecure/ThreeDSecureURLConfigurationTests.swift new file mode 100644 index 0000000..10a6a5d --- /dev/null +++ b/Tests/ThreeDSecure/ThreeDSecureURLConfigurationTests.swift @@ -0,0 +1,21 @@ +import XCTest +@testable import PAYJP + +class ThreeDSecureURLConfigurationTests: XCTestCase { + override func setUp() { + super.setUp() + + PAYJPSDK.publicKey = "public_key" + } + + func testMakeTdsEntryUrl() { + let redirectURL = URL(string: "test://")! + let redirectURLKey = "test" + let configuration = ThreeDSecureURLConfiguration(redirectURL: redirectURL, redirectURLKey: redirectURLKey) + let resourceId = "ch_123" + let expectedUrlString = "\(PAYJPApiEndpoint)tds/\(resourceId)/start?publickey=\(PAYJPSDK.publicKey!)&back=\(redirectURLKey)" + let tdsEntryUrl = configuration.makeTdsEntryUrl(resourceId: resourceId) + + XCTAssertEqual(tdsEntryUrl.absoluteString, expectedUrlString) + } +} diff --git a/example-swift/example-swift/CardFormViewWith3DSViewController.swift b/example-swift/example-swift/CardFormViewWith3DSViewController.swift index a03ae18..65310fe 100644 --- a/example-swift/example-swift/CardFormViewWith3DSViewController.swift +++ b/example-swift/example-swift/CardFormViewWith3DSViewController.swift @@ -67,8 +67,8 @@ class CardFormViewWith3DSViewController: UIViewController { DispatchQueue.main.async { if let tdsStatus = token.card.threeDSecureStatus, tdsStatus == .unverified { self.pendingToken = token - ThreeDSecureProcessHandler.shared.startThreeDSecureProcess(viewController: self, delegate: self, token: token) - return + ThreeDSecureProcessHandler.shared.startThreeDSecureProcess(viewController: self, delegate: self, resourceId: token.identifer) + return } self.tokenIdLabel.text = token.identifer self.showToken(token: token) From f065bfd2399ac1ba417058c6204332abe903cf75 Mon Sep 17 00:00:00 2001 From: laiso Date: Wed, 29 Jan 2025 19:56:08 +0700 Subject: [PATCH 3/5] Remove deprecated tdsEntryUrl method from Token - Remove `tdsEntryUrl` method from Token model - Update ThreeDSecureProcessHandler to use new URL configuration method - Modify test to use new URL generation approach --- Sources/Networking/Models/Token.swift | 22 ------------------- .../ThreeDSecureProcessHandler.swift | 7 +++++- .../ThreeDSecureProcessHandlerTests.swift | 5 ++++- 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/Sources/Networking/Models/Token.swift b/Sources/Networking/Models/Token.swift index 603b048..117f13b 100644 --- a/Sources/Networking/Models/Token.swift +++ b/Sources/Networking/Models/Token.swift @@ -88,25 +88,3 @@ extension Token { return false } } - -// MARK: - ThreeDSecure - -extension Token { - private var tdsBaseUrl: URL { - return URL(string: "\(PAYJPApiEndpoint)tds/\(identifer)")! - } - - var tdsEntryUrl: URL { - let url = tdsBaseUrl.appendingPathComponent("start") - var components = URLComponents(url: url, resolvingAgainstBaseURL: true)! - components.queryItems = [ - URLQueryItem(name: "publickey", value: PAYJPSDK.publicKey), - URLQueryItem(name: "back", value: PAYJPSDK.threeDSecureURLConfiguration?.redirectURLKey) - ] - return components.url! - } - - var tdsFinishUrl: URL { - return tdsBaseUrl.appendingPathComponent("finish") - } -} diff --git a/Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift b/Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift index 9daba40..3dca0c7 100644 --- a/Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift +++ b/Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift @@ -80,7 +80,12 @@ public class ThreeDSecureProcessHandler: NSObject, ThreeDSecureProcessHandlerTyp delegate: ThreeDSecureProcessHandlerDelegate, token: Token) { self.delegate = delegate - webDriver.openWebBrowser(host: viewController, url: token.tdsEntryUrl, delegate: self) + let tdsEntryUrl = PAYJPSDK.threeDSecureURLConfiguration?.makeTdsEntryUrl(resourceId: token.identifer) + guard let tdsEntryUrl = tdsEntryUrl else { + delegate.threeDSecureProcessHandlerDidFinish(self, status: .canceled) + return + } + webDriver.openWebBrowser(host: viewController, url: tdsEntryUrl, delegate: self) } public func startThreeDSecureProcess(viewController: UIViewController, diff --git a/Tests/ThreeDSecure/ThreeDSecureProcessHandlerTests.swift b/Tests/ThreeDSecure/ThreeDSecureProcessHandlerTests.swift index 8791029..f6ed571 100644 --- a/Tests/ThreeDSecure/ThreeDSecureProcessHandlerTests.swift +++ b/Tests/ThreeDSecure/ThreeDSecureProcessHandlerTests.swift @@ -39,6 +39,8 @@ class ThreeDSecureProcessHandlerTests: XCTestCase { @available(*, deprecated) func testStartThreeDSecureProcess() { + PAYJPSDK.threeDSecureURLConfiguration = ThreeDSecureURLConfiguration(redirectURL: URL(string: "test://")!, + redirectURLKey: "test") let mockDriver = MockWebDriver() let handler = ThreeDSecureProcessHandler(webDriver: mockDriver) let token = self.mockToken(tdsStatus: .unverified) @@ -48,7 +50,8 @@ class ThreeDSecureProcessHandlerTests: XCTestCase { delegate: mockVC, token: token) - XCTAssertEqual(mockDriver.openWebBrowserUrl?.absoluteString, token.tdsEntryUrl.absoluteString) + let expectedUrl = PAYJPSDK.threeDSecureURLConfiguration?.makeTdsEntryUrl(resourceId: token.identifer) + XCTAssertEqual(mockDriver.openWebBrowserUrl?.absoluteString, expectedUrl?.absoluteString) } func testStartThreeDSecureProcessWithResourceID() { From 804c9e469dff6541496740c1bf3e4f57a7fc4779 Mon Sep 17 00:00:00 2001 From: laiso Date: Wed, 29 Jan 2025 20:10:09 +0700 Subject: [PATCH 4/5] Rename 3D Secure URL generation method for clarity - Rename `makeTdsEntryUrl` to `makeThreeDSecureEntryURL` in ThreeDSecureURLConfiguration - Update method calls in ThreeDSecureProcessHandler and tests - Improve method naming for better readability --- .../ThreeDSecure/ThreeDSecureProcessHandler.swift | 12 ++++++------ .../ThreeDSecure/ThreeDSecureURLConfiguration.swift | 2 +- .../ThreeDSecureProcessHandlerTests.swift | 2 +- .../ThreeDSecureURLConfigurationTests.swift | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift b/Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift index 3dca0c7..d365bbe 100644 --- a/Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift +++ b/Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift @@ -80,24 +80,24 @@ public class ThreeDSecureProcessHandler: NSObject, ThreeDSecureProcessHandlerTyp delegate: ThreeDSecureProcessHandlerDelegate, token: Token) { self.delegate = delegate - let tdsEntryUrl = PAYJPSDK.threeDSecureURLConfiguration?.makeTdsEntryUrl(resourceId: token.identifer) - guard let tdsEntryUrl = tdsEntryUrl else { + let threeDSecureEntryURL = PAYJPSDK.threeDSecureURLConfiguration?.makeThreeDSecureEntryURL(resourceId: token.identifer) + guard let threeDSecureEntryURL = threeDSecureEntryURL else { delegate.threeDSecureProcessHandlerDidFinish(self, status: .canceled) return } - webDriver.openWebBrowser(host: viewController, url: tdsEntryUrl, delegate: self) + webDriver.openWebBrowser(host: viewController, url: threeDSecureEntryURL, delegate: self) } public func startThreeDSecureProcess(viewController: UIViewController, delegate: ThreeDSecureProcessHandlerDelegate, resourceId: String) { self.delegate = delegate - let tdsEntryUrl = PAYJPSDK.threeDSecureURLConfiguration?.makeTdsEntryUrl(resourceId: resourceId) - guard let tdsEntryUrl = tdsEntryUrl else { + let threeDSecureEntryURL = PAYJPSDK.threeDSecureURLConfiguration?.makeThreeDSecureEntryURL(resourceId: resourceId) + guard let threeDSecureEntryURL = threeDSecureEntryURL else { delegate.threeDSecureProcessHandlerDidFinish(self, status: .canceled) return } - webDriver.openWebBrowser(host: viewController, url: tdsEntryUrl, delegate: self) + webDriver.openWebBrowser(host: viewController, url: threeDSecureEntryURL, delegate: self) } public func completeThreeDSecureProcess(url: URL) -> Bool { diff --git a/Sources/ThreeDSecure/ThreeDSecureURLConfiguration.swift b/Sources/ThreeDSecure/ThreeDSecureURLConfiguration.swift index 6ee0bf2..66006d0 100644 --- a/Sources/ThreeDSecure/ThreeDSecureURLConfiguration.swift +++ b/Sources/ThreeDSecure/ThreeDSecureURLConfiguration.swift @@ -20,7 +20,7 @@ public class ThreeDSecureURLConfiguration: NSObject { self.redirectURLKey = redirectURLKey } - public func makeTdsEntryUrl(resourceId: String) -> URL { + public func makeThreeDSecureEntryURL(resourceId: String) -> URL { let baseUrl = URL(string: "\(PAYJPApiEndpoint)tds/\(resourceId)")! let url = baseUrl.appendingPathComponent("start") var components = URLComponents(url: url, resolvingAgainstBaseURL: true)! diff --git a/Tests/ThreeDSecure/ThreeDSecureProcessHandlerTests.swift b/Tests/ThreeDSecure/ThreeDSecureProcessHandlerTests.swift index f6ed571..c60f37d 100644 --- a/Tests/ThreeDSecure/ThreeDSecureProcessHandlerTests.swift +++ b/Tests/ThreeDSecure/ThreeDSecureProcessHandlerTests.swift @@ -50,7 +50,7 @@ class ThreeDSecureProcessHandlerTests: XCTestCase { delegate: mockVC, token: token) - let expectedUrl = PAYJPSDK.threeDSecureURLConfiguration?.makeTdsEntryUrl(resourceId: token.identifer) + let expectedUrl = PAYJPSDK.threeDSecureURLConfiguration?.makeThreeDSecureEntryURL(resourceId: token.identifer) XCTAssertEqual(mockDriver.openWebBrowserUrl?.absoluteString, expectedUrl?.absoluteString) } diff --git a/Tests/ThreeDSecure/ThreeDSecureURLConfigurationTests.swift b/Tests/ThreeDSecure/ThreeDSecureURLConfigurationTests.swift index 10a6a5d..2465488 100644 --- a/Tests/ThreeDSecure/ThreeDSecureURLConfigurationTests.swift +++ b/Tests/ThreeDSecure/ThreeDSecureURLConfigurationTests.swift @@ -8,14 +8,14 @@ class ThreeDSecureURLConfigurationTests: XCTestCase { PAYJPSDK.publicKey = "public_key" } - func testMakeTdsEntryUrl() { + func testMakeThreeDSecureEntryURL() { let redirectURL = URL(string: "test://")! let redirectURLKey = "test" let configuration = ThreeDSecureURLConfiguration(redirectURL: redirectURL, redirectURLKey: redirectURLKey) let resourceId = "ch_123" let expectedUrlString = "\(PAYJPApiEndpoint)tds/\(resourceId)/start?publickey=\(PAYJPSDK.publicKey!)&back=\(redirectURLKey)" - let tdsEntryUrl = configuration.makeTdsEntryUrl(resourceId: resourceId) + let threeDSecureEntryURL = configuration.makeThreeDSecureEntryURL(resourceId: resourceId) - XCTAssertEqual(tdsEntryUrl.absoluteString, expectedUrlString) + XCTAssertEqual(threeDSecureEntryURL.absoluteString, expectedUrlString) } } From 06e1175fd8c3d312abda1e7898209e856332670a Mon Sep 17 00:00:00 2001 From: laiso Date: Wed, 29 Jan 2025 21:44:43 +0700 Subject: [PATCH 5/5] Add Objective-C support for 3D Secure process - Add Objective-C compatibility annotations to ThreeDSecureProcessHandler - Update example Objective-C app to handle 3D Secure verification flow - Implement 3D Secure delegate methods in CardFormViewScrollViewController - Add pending token handling for 3D Secure verification --- .../ThreeDSecureProcessHandler.swift | 1 + .../example-objc.xcodeproj/project.pbxproj | 98 +++++++++++++++++++ .../CardFormViewScrollViewController.h | 10 +- .../CardFormViewScrollViewController.m | 56 +++++++++++ 4 files changed, 161 insertions(+), 4 deletions(-) diff --git a/Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift b/Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift index d365bbe..9719f15 100644 --- a/Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift +++ b/Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift @@ -18,6 +18,7 @@ import SafariServices } /// 3DSecure handler delegate. +@objc(PAYJPThreeDSecureProcessHandlerDelegate) public protocol ThreeDSecureProcessHandlerDelegate: AnyObject { /// Tells the delegate that 3DSecure process is finished. diff --git a/example-objc/example-objc.xcodeproj/project.pbxproj b/example-objc/example-objc.xcodeproj/project.pbxproj index f5f133a..3b54913 100644 --- a/example-objc/example-objc.xcodeproj/project.pbxproj +++ b/example-objc/example-objc.xcodeproj/project.pbxproj @@ -22,7 +22,46 @@ EDADA25E238B9DE60057FDF3 /* SampleService.m in Sources */ = {isa = PBXBuildFile; fileRef = EDADA25D238B9DE60057FDF3 /* SampleService.m */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 0777C9172D4A746B00D90CFD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0777C90F2D4A746A00D90CFD /* Pods.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 5D5DCB4D5FD7F25FA86BF45E27546AC6; + remoteInfo = PAYJP; + }; + 0777C9192D4A746B00D90CFD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0777C90F2D4A746A00D90CFD /* Pods.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9B9004C28064E22540CFEB2BBE5D815D; + remoteInfo = "PAYJP-PAYJP"; + }; + 0777C91B2D4A746B00D90CFD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0777C90F2D4A746A00D90CFD /* Pods.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 69681285C13FB58876DD5727BCB2DC85; + remoteInfo = PhoneNumberKit; + }; + 0777C91D2D4A746B00D90CFD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0777C90F2D4A746A00D90CFD /* Pods.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 45D025E797F561FE08B0D339D021BCCD; + remoteInfo = "PhoneNumberKit-PhoneNumberKitPrivacy"; + }; + 0777C91F2D4A746B00D90CFD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0777C90F2D4A746A00D90CFD /* Pods.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 6FDD3DCEA8973F11FFD8BD6C6C9B89E7; + remoteInfo = "Pods-example-objc"; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ + 0777C90F2D4A746A00D90CFD /* Pods.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = Pods.xcodeproj; sourceTree = ""; }; 0C330AD1C4F19804D88FF08A /* Pods-example-objc.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-objc.release.xcconfig"; path = "Target Support Files/Pods-example-objc/Pods-example-objc.release.xcconfig"; sourceTree = ""; }; 32CB11641FDA97A1007AD8F5 /* example-objc.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example-objc.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 32CB11671FDA97A1007AD8F5 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -71,6 +110,18 @@ name = Frameworks; sourceTree = ""; }; + 0777C9102D4A746A00D90CFD /* Products */ = { + isa = PBXGroup; + children = ( + 0777C9182D4A746B00D90CFD /* PAYJP.framework */, + 0777C91A2D4A746B00D90CFD /* PAYJP.bundle */, + 0777C91C2D4A746B00D90CFD /* PhoneNumberKit.framework */, + 0777C91E2D4A746B00D90CFD /* PhoneNumberKitPrivacy.bundle */, + 0777C9202D4A746B00D90CFD /* Pods_example_objc.framework */, + ); + name = Products; + sourceTree = ""; + }; 32CB115B1FDA97A1007AD8F5 = { isa = PBXGroup; children = ( @@ -79,7 +130,9 @@ 7D1E977C41291B2B0E664566 /* Pods */, 076A1CE0860082B360166A12 /* Frameworks */, ); + indentWidth = 4; sourceTree = ""; + tabWidth = 4; }; 32CB11651FDA97A1007AD8F5 /* Products */ = { isa = PBXGroup; @@ -120,6 +173,7 @@ 7D1E977C41291B2B0E664566 /* Pods */ = { isa = PBXGroup; children = ( + 0777C90F2D4A746A00D90CFD /* Pods.xcodeproj */, 50CBE7A2314A4BA81D8496FE /* Pods-example-objc.debug.xcconfig */, 0C330AD1C4F19804D88FF08A /* Pods-example-objc.release.xcconfig */, ); @@ -175,6 +229,12 @@ mainGroup = 32CB115B1FDA97A1007AD8F5; productRefGroup = 32CB11651FDA97A1007AD8F5 /* Products */; projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 0777C9102D4A746A00D90CFD /* Products */; + ProjectRef = 0777C90F2D4A746A00D90CFD /* Pods.xcodeproj */; + }, + ); projectRoot = ""; targets = ( 32CB11631FDA97A1007AD8F5 /* example-objc */, @@ -182,6 +242,44 @@ }; /* End PBXProject section */ +/* Begin PBXReferenceProxy section */ + 0777C9182D4A746B00D90CFD /* PAYJP.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PAYJP.framework; + remoteRef = 0777C9172D4A746B00D90CFD /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 0777C91A2D4A746B00D90CFD /* PAYJP.bundle */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PAYJP.bundle; + remoteRef = 0777C9192D4A746B00D90CFD /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 0777C91C2D4A746B00D90CFD /* PhoneNumberKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = PhoneNumberKit.framework; + remoteRef = 0777C91B2D4A746B00D90CFD /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 0777C91E2D4A746B00D90CFD /* PhoneNumberKitPrivacy.bundle */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = PhoneNumberKitPrivacy.bundle; + remoteRef = 0777C91D2D4A746B00D90CFD /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 0777C9202D4A746B00D90CFD /* Pods_example_objc.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = Pods_example_objc.framework; + remoteRef = 0777C91F2D4A746B00D90CFD /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + /* Begin PBXResourcesBuildPhase section */ 32CB11621FDA97A1007AD8F5 /* Resources */ = { isa = PBXResourcesBuildPhase; diff --git a/example-objc/example-objc/CardFormViewScrollViewController.h b/example-objc/example-objc/CardFormViewScrollViewController.h index 9d2eff5..1bdbea0 100644 --- a/example-objc/example-objc/CardFormViewScrollViewController.h +++ b/example-objc/example-objc/CardFormViewScrollViewController.h @@ -11,10 +11,12 @@ NS_ASSUME_NONNULL_BEGIN -@interface CardFormViewScrollViewController : UIViewController +@interface CardFormViewScrollViewController + : UIViewController @end diff --git a/example-objc/example-objc/CardFormViewScrollViewController.m b/example-objc/example-objc/CardFormViewScrollViewController.m index f4dc893..e9027e5 100644 --- a/example-objc/example-objc/CardFormViewScrollViewController.m +++ b/example-objc/example-objc/CardFormViewScrollViewController.m @@ -25,6 +25,7 @@ - (IBAction)validateAndCreateToken:(id)sender; @property(strong, nonatomic) NSArray *list; @property(strong, nonatomic) UIPickerView *pickerView; @property(assign, nonatomic) PAYTokenOperationStatus tokenOperationStatus; +@property(nonatomic, strong) PAYToken *pendingToken; @end @@ -205,6 +206,14 @@ - (void)createToken { NSLog(@"token = %@", [wself displayToken:token]); dispatch_async(dispatch_get_main_queue(), ^{ + if (token.card.threeDSecureStatus == PAYThreeDSecureStatusUnverified) { + wself.pendingToken = token; + [[PAYJPThreeDSecureProcessHandler sharedHandler] + startThreeDSecureProcessWithViewController:wself + delegate:wself + resourceId:token.identifer]; + return; + } wself.tokenIdLabel.text = token.identifer; [wself showToken:token]; }); @@ -230,4 +239,51 @@ - (void)fetchBrands { }]; } +- (void)completeTokenTds { + if (!self.pendingToken) { + return; + } + + __weak typeof(self) wself = self; + [[PAYAPIClient sharedClient] + finishTokenThreeDSecureWith:self.pendingToken.identifer + completionHandler:^(PAYToken *token, NSError *error) { + if (error) { + if ([error.domain isEqualToString:PAYErrorDomain]) { + id errorResponse = + error.userInfo[PAYErrorServiceErrorObject]; + NSLog(@"[errorResponse] %@", errorResponse.description); + } + + dispatch_async(dispatch_get_main_queue(), ^{ + wself.tokenIdLabel.text = nil; + [wself showError:error]; + }); + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + wself.pendingToken = nil; + wself.tokenIdLabel.text = token.identifer; + [wself showToken:token]; + }); + }]; +} + +#pragma mark - PAYThreeDSecureProcessHandlerDelegate + +- (void)threeDSecureProcessHandlerDidFinish:(PAYJPThreeDSecureProcessHandler *)handler + status:(enum ThreeDSecureProcessStatus)status { + switch (status) { + case ThreeDSecureProcessStatusCompleted: + [self completeTokenTds]; + break; + case ThreeDSecureProcessStatusCanceled: + // UI更新など + break; + default: + break; + } +} + @end