Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
22b501d
feat: add CardView Turbo module
descorp Oct 29, 2025
d6d634c
feat: render Card component on Android
descorp Nov 4, 2025
b13d731
chore: dynamic layouting on Android
descorp Nov 4, 2025
03f0bea
chore: trying to pass events
descorp Nov 5, 2025
e11a811
chore: add MessageBus
descorp Nov 6, 2025
5eb838b
chore: receving events on android
descorp Nov 6, 2025
a2b5521
chore: correct typo in binLookup
descorp Nov 26, 2025
66ccec6
chore: improv tests files
descorp Nov 26, 2025
5859150
chore: adding standalone card page
descorp Nov 28, 2025
d1ee809
chore: refactor navigation system
descorp Dec 2, 2025
70fc175
chore: fix Sessions DropIn
descorp Dec 2, 2025
d3a0be3
chore: refactor kotlin
descorp Dec 3, 2025
24a057f
chore: rename IfNotNull.kt
descorp Dec 3, 2025
7608979
chore: align hide signature
descorp Dec 3, 2025
808f853
chore: refactor MessageBus context
descorp Dec 4, 2025
c5ef27d
chore: correct events subscriptions
descorp Dec 9, 2025
9cd1532
chore: refactor Android messageBus
descorp Dec 10, 2025
9889dd6
chore: navigate back on sessions
descorp Dec 10, 2025
a1d84eb
chore: minor JS fixes
descorp Dec 10, 2025
f907743
chore: simplify Android code
descorp Dec 10, 2025
07b87a0
fix: add debug logs on AdyenCheckout live cycle
descorp Dec 11, 2025
75747d1
chore: improve examle app
descorp Dec 11, 2025
e8000d3
chore: revert android/build.gradle
descorp Dec 11, 2025
1604b0c
chore: improve Android code
descorp Dec 11, 2025
e8d6ec6
chore: refactor JS Native modules to be generic
descorp Dec 12, 2025
c105abd
chore: finalize android cardView
descorp Dec 16, 2025
af97fad
chore: fix advanced partial payments
descorp Dec 16, 2025
3abda40
chore: add 2 separate sessions examples
descorp Dec 16, 2025
a3bdff9
chore: finished sessions separation
descorp Dec 16, 2025
6815295
chore: introduce tabs
descorp Dec 16, 2025
224e17e
chore: make separate stored card examples
descorp Dec 16, 2025
94f7cdc
chore: make ios CardView
descorp Dec 18, 2025
822aebf
chore: make swift format only check ios folder
descorp Dec 18, 2025
743913b
feat: iOS embeded card component
descorp Dec 23, 2025
d163b04
chore: make Session events standalone
descorp Dec 24, 2025
412f9c9
chore: add Architecture.md
descorp Dec 29, 2025
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
9 changes: 5 additions & 4 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
yarn test
yarn lint
yarn typecheck
swiftformat --lint .
yarn test > /dev/null 2>&1 || yarn test
yarn lint > /dev/null 2>&1 || yarn lint
yarn typecheck > /dev/null 2>&1 || yarn typecheck
swiftformat --lint ios > /dev/null 2>&1 || swiftformat --lint ios
ktlint "!**node_modules**" > /dev/null 2>&1 || ktlint "!**node_modules**"
Comment on lines +1 to +5
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current approach of running commands twice on failure (e.g., yarn test > /dev/null 2>&1 || yarn test) can be inefficient for long-running tasks like tests. A more efficient pattern would be to capture the command's output and only print it if the command fails, thus avoiding a second execution. For example:

if ! output=$(yarn test 2>&1); then
  echo "$output"
  exit 1
fi

This would improve the developer experience by speeding up the pre-commit hook when failures occur.

384 changes: 384 additions & 0 deletions Architecture.md

Large diffs are not rendered by default.

32 changes: 1 addition & 31 deletions android/src/main/java/com/adyenreactnativesdk/AdyenCheckout.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ import com.adyen.checkout.components.core.internal.ActivityResultHandlingCompone
import com.adyen.checkout.components.core.internal.Component
import com.adyen.checkout.dropin.DropIn
import com.adyen.checkout.dropin.DropInCallback
import com.adyen.checkout.dropin.DropInResult
import com.adyen.checkout.dropin.SessionDropInCallback
import com.adyen.checkout.dropin.SessionDropInResult
import com.adyen.checkout.dropin.internal.ui.model.DropInResultContractParams
import com.adyen.checkout.dropin.internal.ui.model.SessionDropInResultContractParams
import com.adyenreactnativesdk.component.dropin.DropInCallbackListener
import com.adyenreactnativesdk.component.dropin.ReactDropInCallback
import com.adyenreactnativesdk.component.googlepay.GooglePayModule
import java.lang.ref.WeakReference
Expand Down Expand Up @@ -117,32 +116,3 @@ object AdyenCheckout {

private const val TAG = "AdyenCheckout"
}

private class DropInCallbackListener :
DropInCallback,
SessionDropInCallback {
var callback: WeakReference<ReactDropInCallback> =
WeakReference(null)

override fun onDropInResult(dropInResult: DropInResult?) {
callback.get()?.let {
when (dropInResult) {
is DropInResult.CancelledByUser -> it.onCancel()
is DropInResult.Error -> it.onError(dropInResult.reason)
is DropInResult.Finished -> it.onCompleted(dropInResult.result)
null -> return
}
}
}

override fun onDropInResult(sessionDropInResult: SessionDropInResult?) {
callback.get()?.let {
when (sessionDropInResult) {
is SessionDropInResult.CancelledByUser -> it.onCancel()
is SessionDropInResult.Error -> it.onError(sessionDropInResult.reason)
is SessionDropInResult.Finished -> it.onFinished(sessionDropInResult.result)
null -> return
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,49 @@
package com.adyenreactnativesdk

import android.annotation.SuppressLint
import com.adyen.checkout.components.core.AddressData
import com.adyen.checkout.components.core.internal.analytics.AnalyticsPlatform
import com.adyen.checkout.components.core.internal.analytics.AnalyticsPlatformParams
import com.adyenreactnativesdk.component.MessageBusModule
import com.adyenreactnativesdk.component.SessionHelperModule
import com.adyenreactnativesdk.component.applepay.ApplePayModuleMock
import com.adyenreactnativesdk.component.dropin.DropInModule
import com.adyenreactnativesdk.component.googlepay.GooglePayModule
import com.adyenreactnativesdk.component.instant.InstantModule
import com.adyenreactnativesdk.component.model.AddressDataAdapter
import com.adyenreactnativesdk.cse.ActionModule
import com.adyenreactnativesdk.cse.AdyenCSEModule
import com.adyenreactnativesdk.react.CardViewManager
import com.adyenreactnativesdk.react.PlatformPayViewManager
import com.adyenreactnativesdk.util.messaging.MessageBus
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
import com.google.gson.GsonBuilder

class AdyenPaymentPackage : ReactPackage {
override fun createViewManagers(reactContext: ReactApplicationContext) = listOf(PlatformPayViewManager())
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<in Nothing, in Nothing>> {
val messageBus = getOrCreateMessageBus(reactContext)
val cardView = CardViewManager(messageBus)
MessageBusModule.consumers[CardViewManager.NAME] = cardView

return listOf(
PlatformPayViewManager(),
cardView,
)
}

override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
configureAnalytics()
val messageBus = getOrCreateMessageBus(reactContext)

return listOf(
DropInModule(reactContext),
InstantModule(reactContext),
GooglePayModule(reactContext),
ApplePayModuleMock(reactContext),
DropInModule(reactContext, messageBus, gson),
InstantModule(reactContext, messageBus),
GooglePayModule(reactContext, messageBus),
ApplePayModuleMock(reactContext, messageBus),
MessageBusModule(reactContext, messageBus),
AdyenCSEModule(reactContext),
SessionHelperModule(reactContext),
ActionModule(reactContext),
Expand All @@ -43,4 +62,25 @@ class AdyenPaymentPackage : ReactPackage {
val version = BuildConfig.CHECKOUT_VERSION
AnalyticsPlatformParams.overrideForCrossPlatform(AnalyticsPlatform.REACT_NATIVE, version)
}

companion object {
public val messageBus: MessageBus
get() {
return _messageBus ?: throw Exception("AdyenCheckout MessageBus is not initialized")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Throwing a generic java.lang.Exception is not ideal. It's better to use a more specific exception, like IllegalStateException, to indicate that a component is being used in an invalid state. This provides more clarity to developers using the SDK and allows for more specific catch blocks.

Suggested change
return _messageBus ?: throw Exception("AdyenCheckout MessageBus is not initialized")
return _messageBus ?: throw IllegalStateException("AdyenCheckout MessageBus is not initialized")

}

@Volatile
private var _messageBus: MessageBus? = null
private val lock = Any()

private fun getOrCreateMessageBus(context: ReactApplicationContext): MessageBus =
_messageBus ?: synchronized(lock) {
_messageBus ?: MessageBus(context, gson).also { _messageBus = it }
}

private val gson =
GsonBuilder()
.registerTypeAdapter(AddressData::class.java, AddressDataAdapter())
.create()
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.adyenreactnativesdk.component

import com.adyen.checkout.components.core.action.Action
import com.adyenreactnativesdk.component.base.BaseModule
import com.adyenreactnativesdk.component.base.ModuleException
import com.adyenreactnativesdk.react.ComponentContract
import com.adyenreactnativesdk.util.ReactNativeJson
import com.adyenreactnativesdk.util.messaging.MessageBus
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableMap
import org.json.JSONException

class MessageBusModule(
val context: ReactApplicationContext?,
val messageBus: MessageBus,
) : BaseModule(context) {
override fun getName(): String = COMPONENT_NAME

@ReactMethod
fun addListener(eventName: String?) {
}

@ReactMethod
fun removeListeners(count: Int?) {
}

@ReactMethod
fun hide(
success: Boolean,
message: ReadableMap?,
) {
cleanup()
}

@ReactMethod
fun handle(actionMap: ReadableMap?) {
val name =
currentComponent ?: return messageBus.sendErrorEvent(ModuleException.NoPaymentRegistered())

val component =
consumers[name]
?: return messageBus.sendErrorEvent(ModuleException.NoConsumer(name))

try {
val jsonObject = ReactNativeJson.convertMapToJson(actionMap)
val action = Action.Companion.SERIALIZER.deserialize(jsonObject)
component.onAction(action)
} catch (e: JSONException) {
messageBus.sendErrorEvent(ModuleException.InvalidAction(e))
}
}

companion object {
private const val TAG = "MessageBusModule"
private const val COMPONENT_NAME = "AdyenMessageBus"
var consumers: MutableMap<String, ComponentContract> = mutableMapOf()
var currentComponent: String? = null
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package com.adyenreactnativesdk.component

import androidx.lifecycle.lifecycleScope
import com.adyen.checkout.sessions.core.SessionPaymentResult
import com.adyen.checkout.components.core.CheckoutConfiguration
import com.adyen.checkout.sessions.core.CheckoutSessionProvider
import com.adyen.checkout.sessions.core.CheckoutSessionResult
import com.adyen.checkout.sessions.core.SessionModel
import com.adyen.checkout.sessions.core.SessionSetupResponse
import com.adyenreactnativesdk.component.base.BaseModule
import com.adyenreactnativesdk.component.base.ModuleException
import com.adyenreactnativesdk.configuration.CheckoutConfigurationFactory
import com.adyenreactnativesdk.util.ReactNativeJson
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactMethod
Expand All @@ -14,42 +21,74 @@ class SessionHelperModule(
context: ReactApplicationContext?,
) : BaseModule(context) {
@ReactMethod
fun addListener(eventName: String?) { // No JS events expected
fun addListener(eventName: String?) {
// Required for NativeEventEmitter
}

@ReactMethod
fun removeListeners(count: Int?) { // No JS events expected
}

@ReactMethod
fun open(
paymentMethodsData: ReadableMap?,
configuration: ReadableMap,
) { // No UI
fun removeListeners(count: Int?) {
// Required for NativeEventEmitter
}

@ReactMethod
fun hide(
success: Boolean,
message: ReadableMap?,
) { // No UI
cleanup()
}

override fun getName(): String = COMPONENT_NAME

override fun onFinished(result: SessionPaymentResult): Unit = throw NotImplementedError("This Module have no events")

@ReactMethod
fun createSession(
sessionModelJSON: ReadableMap,
configurationJSON: ReadableMap,
promise: Promise,
) {
appCompatActivity.lifecycleScope.launch(Dispatchers.IO) {
super.createSessionAsync(sessionModelJSON, configurationJSON, promise)
createSessionAsync(sessionModelJSON, configurationJSON, promise)
}
}

suspend fun createSessionAsync(
sessionModelJSON: ReadableMap,
configurationJSON: ReadableMap,
promise: Promise,
) {
val sessionModel: SessionModel
val configuration: CheckoutConfiguration
try {
sessionModel = parseSessionModel(sessionModelJSON)
configuration = CheckoutConfigurationFactory.get(configurationJSON)
} catch (e: java.lang.Exception) {
promise.reject(ModuleException.SessionError(e))
return
}

val session =
when (val result = CheckoutSessionProvider.createSession(sessionModel, configuration)) {
is CheckoutSessionResult.Success -> {
result.checkoutSession
}

is CheckoutSessionResult.Error -> {
promise.reject(ModuleException.SessionError(result.exception))
return
}
}

val json = SessionSetupResponse.SERIALIZER.serialize(session.sessionSetupResponse)
val map = ReactNativeJson.convertJsonToMap(json)
setSession(session)
promise.resolve(map)
}

private fun parseSessionModel(json: ReadableMap): SessionModel {
val sessionModelJSON = ReactNativeJson.convertMapToJson(json)
return SessionModel.SERIALIZER.deserialize(sessionModelJSON)
}

companion object {
private const val COMPONENT_NAME = "SessionHelper"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ package com.adyenreactnativesdk.component.applepay

import com.adyenreactnativesdk.component.base.BaseModule
import com.adyenreactnativesdk.component.base.ModuleException
import com.adyenreactnativesdk.util.messaging.MessageBus
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableMap

class ApplePayModuleMock(
context: ReactApplicationContext?,
val messageBus: MessageBus,
) : BaseModule(context) {
override fun getName(): String = COMPONENT_NAME

Expand All @@ -27,7 +29,7 @@ class ApplePayModuleMock(
paymentMethodsData: ReadableMap,
configuration: ReadableMap,
) {
sendErrorEvent(ModuleException.NotSupported())
messageBus.sendErrorEvent(ModuleException.NotSupported())
}

@ReactMethod
Expand Down
Loading
Loading