Skip to content
Open
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
5 changes: 2 additions & 3 deletions drop-in/api/drop-in.api
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,9 @@ public abstract interface class com/adyen/checkout/dropin/DropInResultCallback {
public abstract class com/adyen/checkout/dropin/DropInService : androidx/lifecycle/LifecycleService {
public static final field $stable I
public fun <init> ()V
protected abstract fun onAdditionalDetails (Lcom/adyen/checkout/core/action/data/ActionComponentData;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun onBind (Landroid/content/Intent;)Landroid/os/IBinder;
public fun onCreate ()V
public fun onDestroy ()V
public fun onStartCommand (Landroid/content/Intent;II)I
protected abstract fun onSubmit (Lcom/adyen/checkout/core/components/paymentmethod/PaymentComponentState;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class com/adyen/checkout/dropin/internal/ui/ComposableSingletons$ConfirmationDialogKt {
Expand Down
30 changes: 14 additions & 16 deletions drop-in/src/main/java/com/adyen/checkout/dropin/DropInService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,34 @@
package com.adyen.checkout.dropin

import android.content.Intent
import android.os.IBinder
import androidx.lifecycle.LifecycleService
import com.adyen.checkout.core.action.data.ActionComponentData
import com.adyen.checkout.core.common.AdyenLogLevel
import com.adyen.checkout.core.common.internal.helper.adyenLog
import com.adyen.checkout.core.components.CheckoutResult
import com.adyen.checkout.core.components.paymentmethod.PaymentComponentState
import com.adyen.checkout.dropin.internal.service.DropInBinder
import com.adyen.checkout.dropin.internal.service.DropInServiceRegistry

abstract class DropInService : LifecycleService() {

private val binder = object : DropInBinder() {
override suspend fun requestOnSubmit(state: PaymentComponentState<*>): CheckoutResult {
return onSubmit(state)
}
override fun onCreate() {
super.onCreate()
adyenLog(AdyenLogLevel.DEBUG) { "onCreate" }
DropInServiceRegistry.register(this)
}

override suspend fun requestOnAdditionalDetails(data: ActionComponentData): CheckoutResult {
return onAdditionalDetails(data)
}
override fun onDestroy() {
DropInServiceRegistry.unregister()
adyenLog(AdyenLogLevel.DEBUG) { "onDestroy" }
super.onDestroy()
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
return START_NOT_STICKY
}

override fun onBind(intent: Intent): IBinder {
super.onBind(intent)
return binder
}

protected abstract suspend fun onSubmit(state: PaymentComponentState<*>): CheckoutResult
abstract suspend fun onSubmit(state: PaymentComponentState<*>): CheckoutResult

protected abstract suspend fun onAdditionalDetails(data: ActionComponentData): CheckoutResult
abstract suspend fun onAdditionalDetails(data: ActionComponentData): CheckoutResult
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,10 @@

package com.adyen.checkout.dropin.internal.service

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import com.adyen.checkout.core.action.data.ActionComponentData
import com.adyen.checkout.core.common.AdyenLogLevel
import com.adyen.checkout.core.common.PaymentResult
import com.adyen.checkout.core.common.internal.helper.adyenLog
import com.adyen.checkout.core.components.CheckoutResult
import com.adyen.checkout.core.components.paymentmethod.PaymentComponentState
import com.adyen.checkout.core.error.CheckoutError
Expand All @@ -29,55 +24,28 @@ internal class DropInServiceManager(
private val serviceClass: Class<out DropInService>,
) {

private var binder: DropInBinder? = null

private val _paymentResultFlow = MutableSharedFlow<PaymentResult>()
val paymentResultFlow: SharedFlow<PaymentResult> = _paymentResultFlow.asSharedFlow()

private val _errorFlow = MutableSharedFlow<CheckoutError>()
val errorFlow: SharedFlow<CheckoutError> = _errorFlow.asSharedFlow()

private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val localBinder = service as? DropInBinder
if (localBinder != null) {
binder = localBinder
}
}

override fun onServiceDisconnected(name: ComponentName?) {
binder = null
}
}

fun startAndBind(context: Context) {
fun start(context: Context) {
val intent = Intent(context, serviceClass)
context.startService(intent)
context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
}

fun unbind(context: Context) {
try {
context.unbindService(connection)
} catch (e: IllegalArgumentException) {
adyenLog(AdyenLogLevel.WARN, e) { "Failed to unbind service" }
}
binder = null
}

fun stop(context: Context) {
val intent = Intent(context, serviceClass)
context.stopService(intent)
}

// TODO - Binder could be null when service is not connected yet.
// Implement a queue similar in functionality to one in v5 DropInActivity.
suspend fun requestOnSubmit(state: PaymentComponentState<*>): CheckoutResult {
return binder?.requestOnSubmit(state) ?: error("Service is not bound")
return DropInServiceRegistry.get()?.onSubmit(state) ?: error("Service is not available")
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Calling error() will crash the application if the service is not available. This could happen if this method is called before the service is fully initialized. A more robust approach is to return a CheckoutResult.Error. This prevents a crash and allows the UI to handle the error gracefully.

Suggested change
return DropInServiceRegistry.get()?.onSubmit(state) ?: error("Service is not available")
return DropInServiceRegistry.get()?.onSubmit(state) ?: CheckoutResult.Error("Service is not available")

}

suspend fun requestOnAdditionalDetails(data: ActionComponentData): CheckoutResult {
return binder?.requestOnAdditionalDetails(data) ?: error("Service is not bound")
return DropInServiceRegistry.get()?.onAdditionalDetails(data) ?: error("Service is not available")
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Calling error() will crash the application if the service is not available. This could happen if this method is called before the service is fully initialized. A more robust approach is to return a CheckoutResult.Error. This prevents a crash and allows the UI to handle the error gracefully.

Suggested change
return DropInServiceRegistry.get()?.onAdditionalDetails(data) ?: error("Service is not available")
return DropInServiceRegistry.get()?.onAdditionalDetails(data) ?: CheckoutResult.Error("Service is not available")

}

suspend fun onPaymentFinished(paymentResult: PaymentResult) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2026 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by oscars on 16/3/2026.
*/

package com.adyen.checkout.dropin.internal.service

import com.adyen.checkout.dropin.DropInService

internal object DropInServiceRegistry {
private var service: DropInService? = null
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The service property can be accessed from multiple threads. register() and unregister() are called on the main thread, but get() can be called from background threads. To ensure visibility of changes to service across threads and prevent potential race conditions, it should be marked as @Volatile.

Suggested change
private var service: DropInService? = null
@Volatile private var service: DropInService? = null


fun register(service: DropInService) {
this.service = service
}

fun unregister() {
service = null
}

fun get(): DropInService? = service
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ class DropInActivity : ComponentActivity() {
}

override fun onDestroy() {
viewModel.unbindDropInService(this)
if (isFinishing) {
viewModel.stopDropInService(this)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,7 @@ internal class DropInViewModel(
}

fun startDropInService(context: Context) {
dropInServiceManager.startAndBind(context)
}

fun unbindDropInService(context: Context) {
dropInServiceManager.unbind(context)
dropInServiceManager.start(context)
}

fun stopDropInService(context: Context) {
Expand Down
Loading