Skip to content

Commit

Permalink
Add support for 3D Secure process with resource ID
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
laiso committed Jan 29, 2025
1 parent bf3bdc7 commit 3f20027
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 12 deletions.
4 changes: 4 additions & 0 deletions PAYJP.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -166,6 +167,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
07AEC1322D492DD80054E345 /* ThreeDSecureURLConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeDSecureURLConfigurationTests.swift; sourceTree = "<group>"; };
32167E0627B1C55800E4BCD5 /* OHHTTPStubs.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OHHTTPStubs.xcframework; path = Carthage/Build/OHHTTPStubs.xcframework; sourceTree = "<group>"; };
322BD1D5225C83CA00D83B5E /* JSONDecoder+PAYJP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONDecoder+PAYJP.swift"; sourceTree = "<group>"; };
32585BAE2CAC4E7C003F920B /* CardHolderValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardHolderValidator.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -693,6 +695,7 @@
children = (
EDA0E418243739C00083A5E0 /* ThreeDSecureProcessHandlerTests.swift */,
ED3DB9E1243ACD2B0065FC33 /* ThreeDSecureSFSafariViewControllerDriverTests.swift */,
07AEC1322D492DD80054E345 /* ThreeDSecureURLConfigurationTests.swift */,
ED3DB9DF243ACCBE0065FC33 /* Mock.swift */,
);
path = ThreeDSecure;
Expand Down Expand Up @@ -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 */,
Expand Down
26 changes: 25 additions & 1 deletion Sources/ThreeDSecure/ThreeDSecureProcessHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -64,13 +75,26 @@ 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) {
self.delegate = delegate
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)")

Expand Down
11 changes: 11 additions & 0 deletions Sources/ThreeDSecure/ThreeDSecureURLConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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!
}
}
2 changes: 1 addition & 1 deletion Sources/Views/CardFormViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
35 changes: 27 additions & 8 deletions Tests/ThreeDSecure/ThreeDSecureProcessHandlerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand Down
21 changes: 21 additions & 0 deletions Tests/ThreeDSecure/ThreeDSecureURLConfigurationTests.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 3f20027

Please sign in to comment.