Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
apply plugin: "com.android.library"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "kotlin-parcelize"

ext.set("react.internal.libraryModeAppModule", "true")

apply plugin: "com.facebook.react"

group = "io.hyperswitch"
Expand Down Expand Up @@ -99,6 +102,15 @@ android {
}
}

tasks.matching { it.name.startsWith("bundle") && it.name.endsWith("Aar") }.configureEach {
exclude "**/libc++_shared.so"
exclude "**/libfbjni.so"
exclude "**/libjsi.so"
exclude "**/libreactnative.so"
exclude "**/libhermes.so"
exclude "**/libhermestooling.so"
}

dependencies {
// The version of react-native is set by the React Native Gradle Plugin
api("com.facebook.react:react-android")
Expand Down
5 changes: 4 additions & 1 deletion app/consumer-rules.pro
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
-keep class com.reactnativehyperswitchscancard.** { *; }
-keep class com.reactnativehyperswitchnetcetera3ds.** { *; }
-keep class com.juspaytech.reactnativehyperswitchpaypal.** { *; }
-keep class com.proyecto26.inappbrowser.** { *; }
-keep class io.sentry.react.** { *; }
-dontwarn com.horcrux.svg.**
-keep class com.horcrux.svg.** { *; }
-dontwarn com.horcrux.svg.**
3,037 changes: 1,544 additions & 1,493 deletions app/src/main/assets/hyperswitch.bundle

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import io.hyperswitch.model.HyperswitchBaseConfiguration
import io.hyperswitch.model.PaymentSessionConfiguration
import io.hyperswitch.paymentsheet.PaymentSheet
import io.hyperswitch.paymentsheet.PaymentResult
import io.hyperswitch.react.HyperEventEmitter
import io.hyperswitch.react.ReactNativeController
import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine

Expand All @@ -34,25 +34,22 @@ class DefaultPaymentSessionLauncher(

override fun initPaymentSession(sessionConfig: PaymentSessionConfiguration) {
super.initPaymentSession(sessionConfig)
// Keep companion copy for PaymentSessionReactLauncher.invokeStartTask which
// needs static access from a listener lambda.
Companion.sessionConfig = sessionConfig
paymentSessionReactLauncher.sessionConfig = sessionConfig
}

private fun applySubscription(subscribe: (PaymentEventSubscriptionBuilder.() -> Unit)?) {
subscribe ?: return
val builder = PaymentEventSubscriptionBuilder()
builder.subscribe()
val (subscription, listener) = builder.build()
HyperEventEmitter.setEventListener(listener, subscription)
ReactNativeController.eventEmitter.setEventListener(listener, subscription)
}

override fun presentPaymentSheet(
configuration: PaymentSheet.Configuration?,
subscribe: (PaymentEventSubscriptionBuilder.() -> Unit)?,
resultCallback: (PaymentResult) -> Unit
) {
isPresented = true
applySubscription(subscribe)
val isFragment =
paymentSessionReactLauncher.presentSheet(sessionConfig, configuration)
Expand All @@ -64,7 +61,6 @@ class DefaultPaymentSessionLauncher(
subscribe: (PaymentEventSubscriptionBuilder.() -> Unit)?,
resultCallback: (PaymentResult) -> Unit
) {
isPresented = true
applySubscription(subscribe)
val isFragment = paymentSessionReactLauncher.presentSheet(configurationMap)
PaymentSheetCallbackManager.setCallback(resultCallback, isFragment)
Expand All @@ -74,8 +70,7 @@ class DefaultPaymentSessionLauncher(
configuration: SavedPaymentMethodsConfiguration?,
savedPaymentMethodCallback: ((PaymentSessionHandler) -> Unit),
) {
isPresented = false
GetPaymentSessionCallBackManager.setCallback(sessionConfig?.sdkAuthorization, savedPaymentMethodCallback)
ReactNativeController.sessionRouter.setSessionCallback(sessionConfig?.sdkAuthorization, savedPaymentMethodCallback)
paymentSessionReactLauncher.recreateReactContext(configuration)
}

Expand All @@ -89,19 +84,13 @@ class DefaultPaymentSessionLauncher(
configuration: SavedPaymentMethodsConfiguration?,
): PaymentSessionHandler =
suspendCancellableCoroutine { continuation ->
isPresented = false
GetPaymentSessionCallBackManager.setCallback(sessionConfig?.sdkAuthorization) { handler ->
ReactNativeController.sessionRouter.setSessionCallback(sessionConfig?.sdkAuthorization) { handler ->
if (continuation.isActive) continuation.resume(handler)
}
continuation.invokeOnCancellation {
GetPaymentSessionCallBackManager.setCallback(sessionConfig?.sdkAuthorization, null)
ReactNativeController.sessionRouter.setSessionCallback(sessionConfig?.sdkAuthorization, null)
}
paymentSessionReactLauncher.recreateReactContext(configuration)
}


companion object {
var isPresented: Boolean = false
var sessionConfig: PaymentSessionConfiguration? = null
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal class PaymentSessionHandlerImpl(
private val lastUsedMethodData: ReadableMap,
private val allMethodsData: ReadableArray,
private val jsCallback: Callback,
private val sessionRouter: PaymentSessionRouter,
) : PaymentSessionHandler {

override fun getCustomerDefaultSavedPaymentMethodData(): Result<PaymentMethod> =
Expand Down Expand Up @@ -60,7 +61,7 @@ internal class PaymentSessionHandlerImpl(
paymentToken: String, cvc: String?, resultHandler: (PaymentResult) -> Unit
) {
try {
val registered = ExitHeadlessCallBackManager.tryRegisterCallback(-1, resultHandler)
val registered = sessionRouter.tryRegisterExitCallback(-1, resultHandler)
if (!registered) {
resultHandler(PaymentResult.Failed(
Throwable("Payment confirmation already in progress for this handler").apply {
Expand All @@ -74,7 +75,7 @@ internal class PaymentSessionHandlerImpl(
putString("cvc", cvc)
})
} catch (ex: Exception) {
ExitHeadlessCallBackManager.clearCallback(-1)
sessionRouter.clearExitCallback(-1)
resultHandler(PaymentResult.Failed(Throwable("Not Initialised").apply {
initCause(Throwable("Not Initialised"))
}))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import androidx.activity.addCallback
import androidx.fragment.app.FragmentActivity
import com.facebook.react.ReactHost
import com.facebook.react.ReactInstanceEventListener
import com.facebook.react.ReactNativeHost
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.UiThreadUtil
Expand All @@ -21,41 +20,32 @@ import io.hyperswitch.BuildConfig
import io.hyperswitch.model.HyperswitchBaseConfiguration
import io.hyperswitch.model.PaymentSessionConfiguration
import io.hyperswitch.react.ReactNativeController
import io.hyperswitch.paymentsession.DefaultPaymentSessionLauncher.Companion.sessionConfig
import io.hyperswitch.paymentsheet.PaymentSheet
import io.hyperswitch.react.HyperActivity
import io.hyperswitch.react.HyperFragment
import io.hyperswitch.react.HyperEventEmitter

class PaymentSessionReactLauncher(
private val activity: Activity,
hsConfig: HyperswitchBaseConfiguration? = null,
) : SDKInterface {

override var sessionConfig: PaymentSessionConfiguration? = null

private var reactHost: ReactHost? = null
private var reactNativeHost: ReactNativeHost? = null
private var reactContext: ReactContext? = null
private var headlessTaskId: Int? = null
private val launchOptions = LaunchOptions(activity, BuildConfig.VERSION_NAME, hsConfig)

@SuppressLint("VisibleForTests")
override fun initializeReactNativeInstance() {
reactContext = try {
// Get ReactNativeHost from ReactNativeController singleton instead of casting Application to ReactApplication
// This allows merchants to use their own Application class without extending MainApplication
if (!ReactNativeController.getIsInitialized()){
ReactNativeController.initialize(activity.application)
}
reactNativeHost = ReactNativeController.getReactNativeHost()
reactHost = ReactNativeController.getReactHost()

if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
val reactHost = checkNotNull(reactHost) { "ReactHost is not initialized in New Architecture" }
reactHost.currentReactContext
} else {
val reactInstanceManager = reactNativeHost?.reactInstanceManager
reactInstanceManager?.currentReactContext
}
checkNotNull(reactHost) { "ReactHost is not initialized" }.currentReactContext
} catch (ex: IllegalStateException) {
throw IllegalStateException(
"HyperSDK not initialized. Please call HyperSDK.initialize() in your Application.onCreate()",
Expand All @@ -73,37 +63,24 @@ class PaymentSessionReactLauncher(
activity.runOnUiThread {
val context = reactContext
if (context == null) {
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
val reactHost = checkNotNull(reactHost)
reactHost.addReactInstanceEventListener(
object : ReactInstanceEventListener {
override fun onReactContextInitialized(context: ReactContext) {
invokeStartTask(context, configuration)
reactHost.removeReactInstanceEventListener(this)
}
}
)
reactHost.start()
} else {
val reactInstanceManager = reactNativeHost?.reactInstanceManager
reactInstanceManager?.addReactInstanceEventListener(
object : ReactInstanceEventListener {
override fun onReactContextInitialized(context: ReactContext) {
invokeStartTask(context, configuration)
reactInstanceManager.removeReactInstanceEventListener(this)
}
val reactHost = checkNotNull(reactHost)
reactHost.addReactInstanceEventListener(
object : ReactInstanceEventListener {
override fun onReactContextInitialized(context: ReactContext) {
invokeStartTask(context, configuration)
reactHost.removeReactInstanceEventListener(this)
}
)
reactInstanceManager?.createReactContextInBackground()
}
}
)
reactHost.start()
} else {
invokeStartTask(context, configuration)
}
}
}

private fun getSubscribedEventsSafely(): List<String> =
try { HyperEventEmitter.getSubscribedEvents() } catch (_: Exception) { emptyList() }
try { ReactNativeController.eventEmitter.getSubscribedEvents() } catch (_: Exception) { emptyList() }

private fun invokeStartTask(reactContext: ReactContext, configuration: SavedPaymentMethodsConfiguration? = null) {
val subscribedEvents = getSubscribedEventsSafely()
Expand Down Expand Up @@ -155,7 +132,7 @@ class PaymentSessionReactLauncher(
if (activity is DefaultHardwareBackBtnHandler && activity is FragmentActivity) {
val newReactNativeFragmentSheet =
HyperFragment.Builder().setComponentName("hyperSwitch").setLaunchOptions(bundle)
.setFabricEnabled(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED).build()
.setFabricEnabled(true).build()

val activity2 = activity as FragmentActivity

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.hyperswitch.paymentsession

import io.hyperswitch.paymentsheet.PaymentResult
import org.json.JSONObject
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicReference

class PaymentSessionRouter {

private val sessionCallbackRef = AtomicReference<((PaymentSessionHandler) -> Unit)?>(null)
private val sdkAuthorizationRef = AtomicReference<String?>(null)
private val exitCallbacks = ConcurrentHashMap<Int, (PaymentResult) -> Unit>()

fun setSessionCallback(sdkAuthorization: String?, newCallback: ((PaymentSessionHandler) -> Unit)?) {
sessionCallbackRef.set(newCallback)
sdkAuthorizationRef.set(sdkAuthorization)
}

fun getSdkAuthorization(): String = sdkAuthorizationRef.get() ?: ""

fun executeSessionCallback(data: PaymentSessionHandler) {
sessionCallbackRef.getAndSet(null)?.invoke(data)
?: println("No callback set")
}

fun tryRegisterExitCallback(rootTag: Int, callback: (PaymentResult) -> Unit): Boolean {
return if (rootTag == -1) {
exitCallbacks[-1] = callback
true
} else {
exitCallbacks.putIfAbsent(rootTag, callback) == null
}
}

fun executeExitCallback(rootTag: Int, data: String): Boolean {
val cb = exitCallbacks.remove(rootTag) ?: exitCallbacks.remove(-1)

val result = parseResult(data)
cb?.invoke(result)
return true
}

fun clearExitCallback(rootTag: Int) {
exitCallbacks.remove(rootTag)
}

private fun parseResult(data: String): PaymentResult {
val message = JSONObject(data)
return when (val status = message.getString("status")) {
"cancelled" -> PaymentResult.Canceled(status)
"failed", "requires_payment_method" -> {
val throwable = Throwable(message.getString("message"))
throwable.initCause(Throwable(message.getString("code")))
PaymentResult.Failed(throwable)
}

else -> PaymentResult.Completed(status ?: "default")
}
}
}
Loading