The Payment widget is an integrated tool that allows you to offer multiple payment methods to end customers.
The Payment widget supports:
- Alternative payment methods (APMs): Such as OXXO, KueskiPay, Aplazo, among others.
- Credit and debit cards: Such as MasterCard, Visa, American Express, among others.
Initialize the widget
Before integrating the Payment widget, complete the Getting Started - Android.
1. Display the widget
Display the widget within your application using one of two options:
- Display the widget in a
DialogFragment
dialog - Display the widget embedded in Jetpack Compose
Display the widget in a dialog
To display the Payment Widget in a DialogFragment, call the initPaymentWidget function passing the following data:

let deunaSDK: DeunaSDK = ....
deunaSDK.initPaymentWidget(
context = YOUR_ACTIVITY_CONTEXT,
orderToken = "<DEUNA order token>",
userToken = "<DEUNA user token>", // Optional
styleFile: "<DEUNA theme ID to customize the look and feel of the widget>", // Optional
callbacks = PaymentWidgetCallbacks().apply {
onSuccess = { data ->
deunaSDK.close() // Close the DialogFragment of the payment widget
// Your additional code
}
onCardBinDetected = { metadata, refetchOrder ->
if (metadata != null) {
// Your code here
}
}
onInstallmentSelected = { metadata, refetchOrder ->
// Your code here
}
onError = { error ->
// Error handling
when (error.type) {
PaymentsError.Type.PAYMENT_ERROR -> {
// YOUR CODE HERE
}
else -> {}
}
}
onClosed = { action ->
// Widget closed
}
},
)
Display the widget embedded (Jetpack Compose)
To display the payment widget embedded in your application with Compose, use the AndroidView
composable and the DeunaWidget
view.
DeunaWidget
does not support dynamic dimensions so theAndroidView
container must define the dimensions that the DEUNA widget will use.

@Composable
fun YourScreen(
deunaSDK: DeunaSDK,
orderToken: String,
userToken: String,
) {
// Maintains the Deuna WebView instance across recompositions
val deunaWidget = remember { mutableStateOf<DeunaWidget?>(null) }
Column(modifier = Modifier.padding(16.dp)) {
// Container for the embedded payment widget
// MUST specify dimensions (e.g., .height(400.dp) or .weight(1f) in parent Column)
Box(
modifier = yourModifier
) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
DeunaWidget(context).apply {
this.widgetConfiguration = PaymentWidgetConfiguration(
sdkInstance = deunaSDK,
hidePayButton = true, // (Optional) Hide the pay button in the embedded widget
orderToken = orderToken,
userToken = userToken,
callbacks = PaymentWidgetCallbacks().apply {
onEventDispatch = { event, data ->
// Handle event dispatch
}
onSuccess = { data ->
// Handle success
}
onError = { error ->
// Handle error
}
},
)
this.build() // Render the DEUNA widget
// Store reference for later submission
deunaWidget.value = this
}
}
)
}
Spacer(modifier = Modifier.height(16.dp))
// Custom payment submission button
// Only needed when hidePayButton = true
Button(
onClick = {
// Programmatically submit payment details to DEUNA
// Required when hidePayButton = true
deunaWidget.value?.submit { result ->
// Handle submission result
// result.status: "success"|"error"
// result.message: Detailed status message
}
},
modifier = Modifier.fillMaxWidth()
) {
Text("Complete Payment")
}
}
// Critical: Clean up DeunaWidget resources when composable leaves composition
DisposableEffect(Unit) {
onDispose {
deunaWidget.value?.destroy()
Log.d("DeunaWidget", "WebView resources cleaned up")
}
}
}
Parameters
Depending on how you display the payment widget, you should use one of the following options:
- Dialog: Through calling the
initPaymentWidget
function. - Embedded: Through an instance of the
PaymentWidgetConfiguration
class.
Attributes | Dialog | Embedded | Description |
---|---|---|---|
context | ✅ | ❌ | The context is the context of your Activity in Android. It provides access to application-specific resources and information about the environment in which the application is running. |
orderToken | ✅ | ✅ | The orderToken is a unique token generated for the payment order. This token is generated through DEUNA's API and you must implement the corresponding endpoint in your backend to obtain this information. * When creating the order with DEUNA's API, a redirect URL (redirect_url) should not be defined so that the onSuccess callback runs correctly. |
callbacks | ✅ | ✅ | Callbacks are return functions that are responsible for listening to and handling payment widget events. These events allow managing specific actions based on the payment status. The main callbacks include: onSuccess , onError , onClosed , onCanceled , onCardBinDetected , onInstallmentSelected If DEUNA's widget is displayed embedded, the callbacks must be passed within an instance of the PaymentWidgetBridge class, as shown in the Compose example code. |
userToken (optional) | ✅ | ✅ | The DEUNA user's bearer token. When this is sent, all actions within the widget will be performed on this DEUNA user. For this userToken to be used and saved cards to be shown to the customer, the email associated with that userToken must be the same as the one sent when creating the order in billing_address.email . If both emails don't match, then the flow without showing cards is used for security. |
styleFile (optional) | ✅ | ✅ | UUID provided by DEUNA. This applies if you want to configure a custom custom styles file (change colors, texts, logo, and so on). If a valid value is provided for styleFile the Payment widget will use the UI configuration provided by the theme configuration that matches the provided UUID. |
paymentMethods (optional) | ✅ | ✅ | A list of allowed payment methods. This parameter determines what type of widget should be rendered. |
language (optional) | ✅ | ✅ | This parameter allows you to specify the language in which the widget interface will be displayed. It must be provided as a valid language code (for example, "es" for Spanish, "en" for English, "pt" for Portuguese). Behavior: - If provided: The widget uses the language specified in this parameter, regardless of the merchant's configuration. - If not provided: The widget uses the language configured by the merchant. |
behavior (optional) | ✅ | ✅ | Use this parameter to configure the widget's behavior. |
behavior
parameter (optional)
behavior
parameter (optional)The initPaymentWidget
function and the PaymentWidgetConfiguration
class accept the behavior
parameter, which must be an instance of the WidgetBehavior
class.
This allows you to customize the Payment widget's behavior, including:
- Enable payments with multiple cards.
- Among other configuration options.
These configurations apply to all payment methods enabled in the widget.
2. Configure payment methods
Payment methods behavior paymentMethods
paymentMethods
The paymentMethods
parameter within behavior
allows configuring global behaviors for all payment methods enabled in the widget.
-
flowType (flow type)
Values (string):
twoStep
orsingleStep
Current compatibility: Exclusive for PayPal.
Controls the display flow for specific payment methods that require showing information prior to the payment form.
/// DIALOG
deunaSDK.initPaymentWidget(
orderToken = "TOKEN",
...
behavior = WidgetBehavior(
paymentMethods = mapOf(
"flowType" to "twoStep"
)
)
);
/// EMBEDDED
PaymentWidgetConfiguration(
sdkInstance = deunaSDK,
orderToken = "TOKEN",
...
behavior = WidgetBehavior(
paymentMethods = mapOf(
"flowType" to "twoStep"
)
)
);
Split payments with multiple cards
The Split Payments feature allows customers to split the payment of a purchase between multiple credit/debit cards.
Requisites:
- The option must be enabled in the merchant configuration.
- Currently, only splitting between a maximum of two cards is supported.
/// DIALOG
deunaSDK.initPaymentWidget(
orderToken = "TOKEN",
...
behavior = WidgetBehavior(
paymentMethods = mapOf(
"creditCard" to mapOf(
"splitPayments" to mapOf(
"maxCards" to 2 // Maximum number of cards allowed
)
)
)
)
);
/// EMBEDDED
PaymentWidgetConfiguration(
sdkInstance = deunaSDK,
orderToken = "TOKEN",
...
behavior = WidgetBehavior(
paymentMethods = mapOf(
"creditCard" to mapOf(
"splitPayments" to mapOf(
"maxCards" to 2
)
)
)
)
);
Auto purchase configuration for PayPal
The auto purchase (automatic purchase) feature for PayPal allows processing payments instantly when:
- The customer has previously linked their PayPal account.
- Has authorized quick payments in their account.
deunaSDK.initPaymentWidget(
orderToken = "<DEUNA order token>",
paymentMethods =
listOf(
mapOf(
"paymentMethod" to "wallet",
"processors" to listOf("paypal_wallet"),
"configuration" to mapOf("express" to true)
)
)
)
Configuration Parameters
Parameter | Type | Default value | Description |
---|---|---|---|
express | Boolean | true | When true , processes payment automatically if the customer has PayPal linked. When false , allows selecting or confirming the account. |
The express configuration will only work if only the PayPal configuration is passed in the
paymentMethods
parameter.
Show or hide payment methods
The Payment widget can display various payment methods available for an order, without the merchant having to individually add each APM button in their frontend.
When invoking the initPaymentWidget
function, the paymentMethods
parameter defines the payment methods that the widget shows:
-
If only one method is passed in
paymentMethods
, then the widget automatically opens the payment method form without showing buttons for other APMs.
// DIALOG
deunaSDK.initPaymentWidget(
orderToken = "<DEUNA order token>",
userToken = ..., // optional
styleFile = ..., // optional
callbacks = ...,
paymentMethods = listOf(
mapOf(
"paymentMethod" to "voucher",
"processors" to listOf("payu_oxxo_cash")
)
)
)
// EMBEDDED
PaymentWidgetConfiguration(
sdkInstance = deunaSDK,
orderToken ="<DEUNA order token>",
userToken = ..., // optional
paymentMethods = listOf(
mapOf(
"paymentMethod" to "voucher",
"processors" to listOf("payu_oxxo_cash")
)
)
)

// DIALOG
deunaSDK.initPaymentWidget(
orderToken = "<DEUNA order token>",
userToken = ..., // optional
styleFile = ..., // optional
callbacks = ...,
paymentMethods = listOf(
mapOf(
"paymentMethod" to "voucher",
"processors" to listOf("daviplata")
)
)
)
// EMBEDDED
PaymentWidgetConfiguration(
sdkInstance = deunaSDK,
orderToken ="<DEUNA order token>",
userToken = ..., // optional
paymentMethods = listOf(
mapOf(
"paymentMethod" to "voucher",
"processors" to listOf("daviplata")
)
)
)

// DIALOG
deunaSDK.initPaymentWidget(
orderToken = "<DEUNA order token>",
userToken = ..., // optional
styleFile = ..., // optional
callbacks = ...,
paymentMethods = listOf(
mapOf(
"paymentMethod" to "voucher",
"processors" to listOf("nequi_push_voucher")
)
)
)
// EMBEDDED
PaymentWidgetConfiguration(
sdkInstance = deunaSDK,
orderToken ="<DEUNA order token>",
userToken = ..., // optional
paymentMethods = listOf(
mapOf(
"paymentMethod" to "voucher",
"processors" to listOf("nequi_push_voucher")
)
)
)
If more than one payment method is specified in the paymentMethods
parameter, then the widget only shows those enabled methods that must be configured at the merchant level.
For credit card processing, there is no need to pass the processors's array. You can have multiple processors configured for the use of routing.

// DIALOG
deunaSDK.initPaymentWidget(
orderToken = "<DEUNA order token>",
userToken = ..., // optional
styleFile = ..., // optional
callbacks = ...,
paymentMethods = listOf(
mapOf(
"paymentMethod" to "credit_card"
),
mapOf(
"paymentMethod" to "voucher",
"processors" to listOf("payu_oxxo_cash")
),
mapOf(
"paymentMethod" to "bnpl",
"processors" to listOf("kueski")
)
)
)
// EMBEDDED
PaymentWidgetConfiguration(
sdkInstance = deunaSDK,
orderToken ="<DEUNA order token>",
userToken = ..., // optional
paymentMethods = listOf(
mapOf(
"paymentMethod" to "credit_card"
),
mapOf(
"paymentMethod" to "voucher",
"processors" to listOf("payu_oxxo_cash")
),
mapOf(
"paymentMethod" to "bnpl",
"processors" to listOf("kueski")
)
)
)
If no method list is specified in paymentMethods
and the include_payment_options
parameter was not used when creating the order, then the widget shows all payment methods configured for the merchant.

// DIALOG
deunaSDK.initPaymentWidget(
orderToken = "<DEUNA order token>",
userToken = ..., // optional
styleFile = ..., // optional
callbacks = ...,
paymentMethods = null
)
// EMBEDDED
PaymentWidgetConfiguration(
sdkInstance = deunaSDK,
orderToken ="<DEUNA order token>",
userToken = ..., // optional
callbacks = ...,
paymentMethods = null
)
Payment method configuration priorities
The following table shows how the Payment widget decides which payment forms to display when the paymentMethods
parameter is not passed.
Priority | Configuration Source | Description | Behavior in case of a single payment method |
---|---|---|---|
1 | Payment methods passed when executing the .initPaymentWidget function or PaymentWidgetConfiguration | Payment methods are shown according to those passed when starting the widget, as long as they are activated and configured at the merchant level. | If only one payment method is passed, then the widget automatically opens the payment method form without showing buttons. |
2 | Order and include_payment_options | The order is checked to verify if include_payment_options has payment methods that are configured and activated at the merchant level. Unconfigured methods are not shown. | If only one payment method is passed, then the widget automatically opens the payment method form without showing buttons. |
3 | Methods configured at merchant level (API /payment-methods ) | If no payment method is passed either in the .initPaymentWidget function or in include_payment_options when creating the order, then the methods configured at the merchant level are taken. | If only one payment method is passed, then the widget automatically opens the payment method form without showing buttons. |
3. Listen to widget events
When a transaction is successful or fails, it's important to update your interface to notify users about the transaction result. You can do this by listening to the Payment widget events through callbacks.
The instance of the PaymentWidgetCallbacks
class passed to the initPaymentWidget
function or to the PaymentWidgetConfiguration
class allows you to listen to widget events through callbacks.
Define the respective callbacks to update your app's interface.
Callbacks
Callback | Modal | Embedded | When is it triggered? |
---|---|---|---|
onSuccess | ✅ | ✅ | It's run when the payment is completed. This callback contains a parameter of type Map<String,Any> with the order information. |
onError | ✅ | ✅ | It's run when an error occurs. This callback contains a parameter of type PaymentsError, which identifies the type of error produced. See an example of the callback response here. |
onClosed (optional) | ❌ | It's run when the dialog containing the Payment widget is closed. This callback contains a parameter of type enum CloseAction with the following values:- userAction : When the widget was manually closed by the user, pressing the close button (X) or the back button on Android without the operation being completed.- systemAction : When the widget is closed due to the execution of the close function. NOTE: For embedded implementation the onClosed callback does not execute. | |
onCardBinDetected (Optional) | ✅ | ✅ | It's run when the Payment widget detects the BIN of an entered credit or debit card or when the user deletes the entered card number. This callback contains a parameter of type Map<String,Any> with the bin information and brand of the entered card. NOTE: The parameter of type Map<String,Any> is null when the user deletes the text entered in the card number field. |
onInstallmentSelected (optional) | ✅ | ✅ | If the order can be deferred, this callback will execute when the user selects the months to defer. This callback contains a parameter of type Map<String,Any> with the information of the months to defer selected by the user. NOTE: The parameter of type Map<String,Any> is null when the user selects current payment (without installments). |
onPaymentProcessing (optional) | ✅ | ✅ | This callback will execute when the user presses the pay button and the payment is being processed. If there is any incorrect field in the payment form this event will not run. |
onEventDispatch (Optional) | ✅ | ✅ | It's run on all events that the widget can produce. This callback contains a parameter of type CheckoutEvent and the data associated with that event |
Use the
onError
to identify if the widget could not be displayed or if an error occurred while processing the payment.
onError = { error ->
when (error.type) {
// The widget could not be loaded
PaymentsError.Type.INITIALIZATION_FAILED -> {
deunaSDK.close()
}
// The payment failed
PaymentsError.Type.PAYMENT_ERROR -> {
// YOUR CODE HERE
}
else -> {}
}
}
4. Close the widget
By default, when the widget is displayed in a dialog through the initPaymentWidget
function, the Payment widget only closes when the user presses the widget's close button or when they press the "back" button on Android.
To close the dialog when a payment is successful or when an error occurs, you must call the close
function.
The following example code shows how to close the widget when a payment is successful:
deunaSDK.initPaymentWidget(
context = YOUR_ACTIVITY_CONTEXT,
orderToken = "<DEUNA order token>",
userToken = "<DEUNA user token>", // optional
callbacks = PaymentWidgetCallbacks().apply {
onSuccess = { order ->
deunaSDK.close() // Close the payment widget
// Your additional code
}
}
)
5. Free widget resources
If DEUNA's widget is displayed embedded, then it's important to free the widget's resources when it's no longer needed.
Example
val deunaWidget = remember { mutableStateOf<DeunaWidget?>(null) }
.
.
.
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
DeunaWidget(context).apply {
.
.
.
// Store reference for later submission
deunaWidget.value = this
}
}
)
DisposableEffect(Unit) {
onDispose {
deunaWidget.value?.destroy()
}
}
Optional features
In addition to the mandatory steps to operate the widget, you have the following customization options.
Customize widget appearance
Use the setCustomStyle
function to customize the widget appearance:
await DeunaSDK.setCustomStyle({...});
For more information, refer to Style Customization.
Example
// DIALOG
deunaSDK.initPaymentWidget(
context = YOUR_ACTIVITY_CONTEXT,
orderToken = "<DEUNA order token>",
userToken = "<DEUNA user token>", // optional
callbacks = PaymentWidgetCallbacks().apply {
onCardBinDetected = { cardBinMetadata ->
this.deunaSDK.setCustomStyle(
data = JSONObject(
"""
{
"theme": {
"colors": {
"primaryTextColor": "#023047",
"backgroundSecondary": "#8ECAE6",
"backgroundPrimary": "#F2F2F2",
"buttonPrimaryFill": "#FFB703",
"buttonPrimaryHover": "#FFB703",
"buttonPrimaryText": "#000000",
"buttonPrimaryActive": "#FFB703"
}
},
"HeaderPattern": {
"overrides": {
"Logo": {
"props": {
"url": "https://images-staging.getduna.com/ema/fc78ef09-ffc7-4d04-aec3-4c2a2023b336/test2.png"
}
}
}
}
}
"""
).toMap()
)
}
...
}
)
// EMBEDDED JETPACK COMPOSE
// Maintains the Deuna WebView instance across recompositions
val deunaWidget = remember { mutableStateOf<DeunaWidget?>(null) }
.
.
.
deunaWidget.value.setCustomStyle(data = ... )
Refresh Payment widget
The refetchOrder
function updates the Payment widget and returns the updated order data.
This function is useful, for example, when the merchant offers promotions. A common case is a "20% promotion when paying with Mastercard".
In this scenario:
he merchant listens to the onCardBinDetected event to identify the card brand.
The merchant updates the order in DEUNA (being responsible for calculating
In this scenario, the merchant listens to the onCardBinDetected event to identify the card brand. Then, the merchant updates the order in DEUNA (being responsible for calculating the promotions) and, through this function, notifies the widget to update, since the order amount might have changed.
Below is an example of using the refetchOrder
function.
// DIALOG
deunaSDK.initPaymentWidget(
context = YOUR_ACTIVITY_CONTEXT,
orderToken = "<DEUNA order token>",
userToken = "<DEUNA user token>", // optional
callbacks = PaymentWidgetCallbacks().apply {
onCardBinDetected = { metadata ->
val cardBrand = metadata["cardBrand"] as String?
if (cardBrand != null && cardBrand == "Mastercard"){
deunaSDK.refetchOrder { order ->
print("ORDER: $order")
}
}
}
...
}
)
// EMBEDDED JETPACK COMPOSE
// Maintains the Deuna WebView instance across recompositions
val deunaWidget = remember { mutableStateOf<DeunaWidget?>(null) }
.
.
.
deunaWidget.value.refetchOrder { order ->
}
Hide the payment button (Embedded Widget)
When the payment widget is displayed in embedded mode, you can use the hidePayButton
parameter to hide the payment button from the DeunaWidget
.
// Maintains the Deuna WebView instance across recompositions
val deunaWidget = remember { mutableStateOf<DeunaWidget?>(null) }
.
.
.
DeunaWidget(context).apply {
// Set true to handle payment submission manually via submit()
// Set false to use Deuna's built-in payment button
this.widgetConfiguration = PaymentWidgetConfiguration(
.
.
.
hidePayButton = true,
.
.
.
)
.
.
.
}
deunaWidget.value?.isValid { it -> }
deunaWidget.value?.submit {result -> }
You can use the following functions to validate and execute the payment.
Method | Description | Response |
---|---|---|
.isValid{...} | Validates if the entered information is correct and if the payment can be processed. | true if the information is valid, false otherwise. |
.submit{...} | Executes the payment process, equivalent to pressing the pay button. Performs the same internal validations. | { status: "success", message: "Payment processed successfully" } or { status: "error", message: "The submit flow is not available" } |
Considerations
- It is recommended to use
isValid{}
before callingsubmit{}
to avoid errors in the payment process. - If the payment flow is not yet available,
submit{}
will always return an error with the message"The submit flow is not available"