Card Vault widgets integrate functionalities like wallets and tokenization without processing the payment.
DEUNA's Payment Vault contains different types of widgets that allow you to:
- Save credit and debit cards securely in the Vault.
- Make payments with Click To Pay using the Click To Pay widget.

Initialize the widget
Before integrating the Payment widget, complete the Getting Started - Android.
1. Display the widget
Display in a dialog
To display the widget in a DialogFragment, call the initElements function passing the following data:
val callbacks = ElementsCallbacks().apply {
onSuccess = { response ->
deunaSDK.close() // Close the widget
}
onError = { error ->
// handle the error
deunaSDK.close() // Close the widget
}
onClosed = { action ->
// The VAULT widget was closed
}
onEventDispatch = { type, response ->
// Listen to events
}
}
// Display the VAULT widget
deunaSDK.initElements(
context = YOUR_ACTIVITY_CONTEXT,
userToken = "<DEUNA user token>", // optional
userInfo = DeunaSDK.UserInfo(
firstName = "Esteban", // Optional if userToken is provided
lastName = "Posada", // Optional if userToken is provided
email = "[email protected]" // required
), // optional
callbacks = callbacks,
types = ..., // optional, If this value is not passed, the VAULT widget will be shown by default.
widgetExperience = ElementsWidgetExperience( // optional
userExperience = ElementsWidgetExperience.UserExperience(
showSavedCardFlow = false, // optional
defaultCardFlow = false // optional
)
)
)Display embedded (Jetpack Compose)
To display the widget embedded in your application with Compose, use the AndroidView composable and the DeunaWidget view.
DeunaWidgetdoes not support dynamic dimensions so theAndroidViewcontainer must define the dimensions that the DEUNA widget uses:
@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 (for example, .height(400.dp) or .weight(1f) in parent Column)
Box(
modifier = yourModifier
) {
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
DeunaWidget(context).apply {
this.widgetConfiguration = ElementsWidgetConfiguration(
sdkInstance = deunaSDK,
hidePayButton = true, // (optional) Hide the pay button in the embedded widget
orderToken = orderToken,
userToken = userToken,
userInfo = UserInfo(
firstName = "Darwin",
lastName = "Morocho",
email = "[email protected]"// required
),
callbacks = ElementsCallbacks().apply {
this.onSuccess = { data ->
val savedCard = (data["metadata"] as Json)["createdCard"] as Json
}
},
)
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")
}
}
}DeunaWidget Auto-Resize Integration Guide — Vault Widget (Android)
Same behavior as PaymentWidget: AutoResizeConfig switches the widget from fixed height to dynamic height, and setOnScrollByCallback wires keyboard-scroll handling.
<activity
android:name=".YourActivity"
android:windowSoftInputMode="adjustResize" />@Composable
fun YourScreen(
deunaSDK: DeunaSDK,
orderToken: String,
userToken: String,
) {
val scrollState = rememberScrollState()
val coroutineScope = rememberCoroutineScope()
val widgetConfig = remember {
ElementsWidgetConfiguration(
sdkInstance = deunaSDK,
orderToken = orderToken,
userToken = userToken,
userInfo = UserInfo(
firstName = "Darwin",
lastName = "Morocho",
email = "[email protected]"
),
autoResizeConfig = AutoResizeConfig(initialHeightDp = 100),
callbacks = ElementsCallbacks().apply {
onSuccess = { data ->
val savedCard = (data["metadata"] as Json)["createdCard"] as Json
}
}
)
}
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollState) // Main scrollable container
) {
// ... Your content above the widget ...
AndroidView(
modifier = Modifier.fillMaxWidth(),
factory = { context ->
DeunaWidget(context).apply {
widgetConfiguration = widgetConfig
build()
}
},
update = { widget ->
// Wire the SDK's scroll request to the Compose ScrollState
widget.setOnScrollByCallback { amountPx ->
coroutineScope.launch {
scrollState.animateScrollBy(amountPx)
}
}
}
)
// ... Your content below the widget ...
}
DisposableEffect(Unit) {
onDispose {
// Clean up WebView resources
}
}
}Parameters
| Parameter | Dialog | Embedded | Description |
|---|---|---|---|
callbacks | ✅ | ✅ | An instance of the ElementsCallbacks class, which contains callbacks that will be called in case of success, error, or when the widget is closed. |
userToken (Optional) | ✅ | ✅ | The token of a user obtained using DEUNA's API https://docs.deuna.com/reference/users-register https://docs.deuna.com/reference/request-otp https://docs.deuna.com/reference/login-with-otp |
orderToken(optional) | ✅ | ✅ | 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. User information available in billing_address can be extracted to use within the widget. |
userInfo | ✅ | ✅ | Instance of the Note: The |
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 vault widget will use the UI configuration provided by the theme configuration that matches the provided UUID. |
types(optional) | ✅ | ✅ | An instance of type Allowed values are: Example: If this parameter is not passed, DEUNA's Vault Widget for saving credit and debit cards will be shown by default. |
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,
|
widgetExperience(optional) | ✅ | ✅ | Overrides merchant configurations. Currently supported by the widget are the following: userExperience.showSavedCardFlow: Shows card saving toggle. |
domain (Optional) | ✅ | ✅ | This optional parameter is used to override the base host when the vault widget URL is loaded. It replaces the default host (elements.deuna.com) with the domain you provide. Example: If you pass the domain |
autoResizeEnabled (Optional) | ✅ | It allows the widget to dynamically adapt to the height of the container where it is embedded. |
Click To Pay widget
Using the types parameter of the initElements function you can make a payment with Click To Pay.
TheuserInfoparameter is required to be able to display the ClickToPay widget.
The following code snippet shows how to display the ClickToPay widget:
deunaSDK.initElements(
context = this,
userInfo = UserInfo(// Required for click_to_pay
firstName = "Esteban",
lastName = "Posada",
email = "[email protected]",
),
types = listOf(
mapOf(
"name" to ElementsWidget.CLICK_TO_PAY // Display the ClickToPay widget
)
),
callbacks = ElementsCallbacks().apply {
onSuccess = {
deunaSdk.close()
}
onEventDispatch = ...
onError = ...
onClosed = ...
},
) 2. Listen to widget events
It's crucial to properly handle widget events to provide a smooth experience to users. Define the necessary callbacks to update your application's interface.
Callbacks
| Callback | Dialog | Embedded | When is it triggered? |
|---|---|---|---|
onSuccess | ✅ | ✅ | It's executed when a card is successfully saved or when the Click to Pay payment was successful. This callback contains a parameter of type **Map<String,Any> **. |
onError | ✅ | ✅ | It's executed when an error occurs while processing the operation of the displayed widget. This callback contains a parameter of type ElementsError |
onClosed(Optional) | ✅ | ❌ | It's executed when the payment widget is closed. This callback contains a parameter of type enumCloseAction with the following values:
|
onEventDispatch (optional) | ✅ | ✅ | It's executed when specific events are detected in the widget. This callback contains a parameter of type ElementsEvent. |
3. Close the widget
By default, Element widgets only close when the user presses the widget's close button or when they press the "back" button on Android.
To close the modal when an operation is successful or when an error occurs, you must call the close function.
The following example code shows how to close the widget:
val callbacks = ElementsCallbacks().apply {
onSuccess = { response ->
deunaSDK.close() // Close the Vault Widget
// Your additional code
}
}
// Display the Vault widget
deunaSDK.initElements(
context = YOUR_ACTIVITY_CONTEXT,
userToken = "<DEUNA user token>", // optional
userInfo = DeunaSDK.UserInfo("Esteban", "Posada", "[email protected]"), // optional
callbacks = callbacks
)
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
val callbacks = ElementsCallbacks().apply{
onSuccess = ...,
onError = ...,
onClosed = ...,
onCanceled = ...,
onEventDispatch = { type, response ->
// Listen to events
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()
)
}
}
// Display the VAULT widget
deunaSDK.initElements(
context = YOUR_ACTIVITY_CONTEXT,
userToken = "<DEUNA user token>", // optional
userInfo = DeunaSDK.UserInfo("Esteban", "Posada", "[email protected]"), // optional
callbacks = callbacks
) Hide payment button (embedded widget)
When the widget is displayed embedded, you can use the hidePayButton parameter to hide the payment button of 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 = ElementsWidgetConfiguration(
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{...} | Runs 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's recommended to use
isValid{}before callingsubmit{}to avoid errors in the payment process. - If the payment flow is not yet available, then
submit{}always returns an error with the message"The submit flow is not available"