diff --git a/app/build.gradle b/app/build.gradle
index 5fedc5e9..24cda459 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -129,6 +129,7 @@ dependencies {
implementation("org.greenrobot:eventbus")
implementation project(':hyperswitch-sdk-android-lite')
implementation('com.squareup.okhttp3:okhttp')
+ compileOnly project(':react-native-hyperswitch-unified-3ds')
constraints {
implementation("com.squareup.okhttp3:okhttp") {
@@ -136,7 +137,7 @@ dependencies {
prefer '3.3.0'
}
}
-
+
implementation("androidx.appcompat:appcompat") {
version {
prefer '1.6.1'
diff --git a/app/src/main/java/io/hyperswitch/authentication/AuthenticationSession.kt b/app/src/main/java/io/hyperswitch/authentication/AuthenticationSession.kt
new file mode 100644
index 00000000..3dc74111
--- /dev/null
+++ b/app/src/main/java/io/hyperswitch/authentication/AuthenticationSession.kt
@@ -0,0 +1,62 @@
+package io.hyperswitch.authentication
+
+import android.app.Activity
+import io.hyperswitch.modular_3ds.api.ThreeDSAuthenticationSession
+
+
+class AuthenticationSession(
+ private val activity: Activity,
+ private val publishKey: String
+) {
+ companion object {
+ /**
+ * Check if the 3DS library is available at runtime
+ *
+ * @return true if modular_3ds library is available, false otherwise
+ */
+ @JvmStatic
+ fun isAvailable(): Boolean {
+ return try {
+ Class.forName("io.hyperswitch.modular_3ds.api.ThreeDSAuthenticationSession")
+ true
+ } catch (e: ClassNotFoundException) {
+ false
+ }
+ }
+ }
+
+ private val internalAuthSession: ThreeDSAuthenticationSession
+
+ init {
+ if (!isAvailable()) {
+ throw IllegalStateException(
+ "3DS library not found. Please add the following dependency to your build.gradle:\n" +
+ "implementation 'io.hyperswitch:modular-3ds-api:x.x.x'\n" +
+ "You can check availability using AuthenticationSession.isAvailable() before instantiation."
+ )
+ }
+ internalAuthSession = ThreeDSAuthenticationSession(activity, publishKey)
+ }
+
+ /**
+ * Initialize a 3DS session
+ *
+ * @param authIntentClientSecret The authentication intent client secret
+ * @param configuration Authentication configuration (use AuthenticationConfiguration from this package)
+ * @param callback Callback to receive authentication result (use AuthenticationResult from this package)
+ * @return Session object if initialization is successful, null otherwise
+ */
+ fun initThreeDsSession(
+ authIntentClientSecret: String,
+ configuration: AuthenticationConfiguration,
+ callback: (AuthenticationResult) -> Unit
+ ): Session? {
+ return internalAuthSession.initThreeDsSession(
+ authIntentClientSecret = authIntentClientSecret,
+ configuration = configuration
+ ) { modularResult ->
+ // Convert modular_3ds result to wrapper result
+ callback(AuthenticationResult.from(modularResult))
+ }
+ }
+}
diff --git a/app/src/main/java/io/hyperswitch/authentication/TypeAliases.kt b/app/src/main/java/io/hyperswitch/authentication/TypeAliases.kt
new file mode 100644
index 00000000..df03c3dd
--- /dev/null
+++ b/app/src/main/java/io/hyperswitch/authentication/TypeAliases.kt
@@ -0,0 +1,58 @@
+package io.hyperswitch.authentication
+
+/**
+ * Type aliases and wrappers for 3DS authentication types
+ *
+ * These allow merchants to use io.hyperswitch.authentication.* namespace
+ * while the actual implementation comes from io.hyperswitch.modular_3ds
+ */
+
+// Session and Transaction types
+typealias Session = io.hyperswitch.modular_3ds.api.ThreeDSSession
+typealias Transaction = io.hyperswitch.modular_3ds.api.ThreeDSTransaction
+
+// Configuration and Parameters
+typealias AuthenticationConfiguration = io.hyperswitch.modular_3ds.api.AuthenticationConfiguration
+typealias AuthenticationRequestParameters = io.hyperswitch.modular_3ds.api.AuthenticationRequestParameters
+typealias ChallengeParameters = io.hyperswitch.modular_3ds.models.ChallengeParameters
+
+// Callbacks
+typealias ChallengeStatusReceiver = io.hyperswitch.modular_3ds.api.ChallengeStatusReceiver
+
+// Events
+typealias CompletionEvent = io.hyperswitch.modular_3ds.api.CompletionEvent
+typealias ProtocolErrorEvent = io.hyperswitch.modular_3ds.api.ProtocolErrorEvent
+typealias RuntimeErrorEvent = io.hyperswitch.modular_3ds.api.RuntimeErrorEvent
+typealias ErrorMessage = io.hyperswitch.modular_3ds.api.ErrorMessage
+
+// Environment and Customization
+typealias ThreeDSEnvironment = io.hyperswitch.modular_3ds.models.ThreeDSEnvironment
+typealias UiCustomization = io.hyperswitch.modular_3ds.models.UiCustomization
+typealias ToolbarCustomization = io.hyperswitch.modular_3ds.models.ToolbarCustomization
+
+// Provider Registration
+typealias ProviderRegistry = io.hyperswitch.modular_3ds.provider.ProviderRegistry
+typealias ProviderFactory = io.hyperswitch.modular_3ds.provider.ProviderFactory
+
+/**
+ * Wrapper for AuthenticationResult sealed class
+ * This is needed because type aliases don't work well with sealed classes and their nested types
+ */
+sealed class AuthenticationResult {
+ object Success : AuthenticationResult()
+ data class Error(val message: String) : AuthenticationResult()
+ object Challenge : AuthenticationResult()
+
+ companion object {
+ /**
+ * Convert from modular_3ds AuthenticationResult to wrapper AuthenticationResult
+ */
+ internal fun from(result: io.hyperswitch.modular_3ds.api.AuthenticationResult): AuthenticationResult {
+ return when (result) {
+ is io.hyperswitch.modular_3ds.api.AuthenticationResult.Success -> Success
+ is io.hyperswitch.modular_3ds.api.AuthenticationResult.Error -> Error(result.message)
+ is io.hyperswitch.modular_3ds.api.AuthenticationResult.Challenge -> Challenge
+ }
+ }
+ }
+}
diff --git a/demo-app/build.gradle b/demo-app/build.gradle
index bf3b9578..382793ee 100644
--- a/demo-app/build.gradle
+++ b/demo-app/build.gradle
@@ -84,12 +84,13 @@ dependencies {
implementation 'com.github.kittinunf.fuel:fuel-json:2.3.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
- implementation project(':app')
+ implementation project(':app')
implementation ('com.wix:detox:+')
androidTestImplementation('com.wix:detox:+')
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation project(':hyperswitch-sdk-android-lite')
- implementation(project(":hyperswitch-scan-card-lite"))
+ implementation project(':react-native-hyperswitch-unified-3ds')
+ implementation ('io.hyperswitch:modular-3ds-trident:1.0.0')
compileOnly 'com.facebook.react:react-android'
implementation 'org.slf4j:slf4j-nop:2.0.9'
}
\ No newline at end of file
diff --git a/demo-app/src/main/AndroidManifest.xml b/demo-app/src/main/AndroidManifest.xml
index f500f4d4..4bb1e07a 100644
--- a/demo-app/src/main/AndroidManifest.xml
+++ b/demo-app/src/main/AndroidManifest.xml
@@ -1,6 +1,10 @@
+
+
+
+
-
+
+
+
\ No newline at end of file
diff --git a/demo-app/src/main/java/io/hyperswitch/demoapp/MainActivity.kt b/demo-app/src/main/java/io/hyperswitch/demoapp/MainActivity.kt
index 620f58f4..4425acf9 100644
--- a/demo-app/src/main/java/io/hyperswitch/demoapp/MainActivity.kt
+++ b/demo-app/src/main/java/io/hyperswitch/demoapp/MainActivity.kt
@@ -312,6 +312,12 @@ class MainActivity : Activity() {
startActivity(intent)
}
+ findViewById(R.id.launchAuthenticationButton).setOnClickListener{
+ val intent = Intent(this, ThreeDSTestActivity::class.java)
+ intent.putExtra("publishKey", publishKey)
+ intent.putExtra("clientSecret", paymentIntentClientSecret)
+ startActivity(intent)
+ }
}
private fun setStatus(error: String) {
diff --git a/demo-app/src/main/java/io/hyperswitch/demoapp/ThreeDSTestActivity.kt b/demo-app/src/main/java/io/hyperswitch/demoapp/ThreeDSTestActivity.kt
new file mode 100644
index 00000000..19ad1636
--- /dev/null
+++ b/demo-app/src/main/java/io/hyperswitch/demoapp/ThreeDSTestActivity.kt
@@ -0,0 +1,496 @@
+package io.hyperswitch.demoapp
+
+import android.app.Activity
+import android.os.Bundle
+import android.util.Log
+import android.widget.Button
+import android.widget.TextView
+import io.hyperswitch.authentication.*
+import io.hyperswitch.modular_3ds.trident.TridentProviderFactory
+import okhttp3.*
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.RequestBody.Companion.toRequestBody
+import org.json.JSONObject
+import java.io.IOException
+
+class ThreeDSTestActivity : Activity() {
+
+ private lateinit var statusText: TextView
+ private var publishKey: String = ""
+ private var paymentIntentClientSecret: String = ""
+ // New merchant-facing API objects
+ private lateinit var authenticationSession: AuthenticationSession
+ private var session: Session? = null
+ private var transaction: Transaction? = null
+ private var aReqParams: AuthenticationRequestParameters? = null
+ private var challengeParameters: ChallengeParameters? = null
+ // API credentials and data
+ private val apiKey = ""
+ private val profileId = ""
+ private val baseUrl = "https://sandbox.hyperswitch.io"
+ private var authenticationId: String? = null
+ // Eligibility response data
+ private var threeDsServerTransactionId: String? = null
+ private var messageVersion: String? = null
+ private var directoryServerId: String? = null
+ // HTTP client - use singleton to avoid creating multiple instances
+ private val httpClient by lazy {
+ OkHttpClient.Builder()
+ .connectTimeout(15, java.util.concurrent.TimeUnit.SECONDS)
+ .readTimeout(15, java.util.concurrent.TimeUnit.SECONDS)
+ .writeTimeout(15, java.util.concurrent.TimeUnit.SECONDS)
+ .build()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.three_ds_test_activity)
+
+ // Get data from intent
+ publishKey = intent.getStringExtra("publishKey") ?: ""
+ paymentIntentClientSecret = intent.getStringExtra("clientSecret") ?: ""
+
+ statusText = findViewById(R.id.statusText)
+
+ // Register Trident 3DS provider
+ ProviderRegistry.registerProvider(TridentProviderFactory())
+
+ // Initialize AuthenticationSession
+ authenticationSession = AuthenticationSession(this, publishKey)
+
+ setupButtons()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ // Clean up resources to prevent memory leaks
+ session = null
+ transaction = null
+ aReqParams = null
+ challengeParameters = null
+
+ // Cancel any pending HTTP calls
+ try {
+ httpClient.dispatcher.executorService.shutdown()
+ } catch (e: Exception) {
+ Log.w("ThreeDSTest", "Error shutting down HTTP client: ${e.message}")
+ }
+ }
+
+ private fun setupButtons() {
+ findViewById