An Android example app that demonstrates the integration of the Yuno Payments SDK, including enrollment, checkout, payment flows, and render mode (advanced integration).
| Requirement | Minimum version |
|---|---|
minSdkVersion |
21 |
| Java | 17 |
| Android Gradle Plugin | 8.0.0 |
| Kotlin Gradle Plugin | 1.8.0 |
| AndroidX | Required |
First, add the repository source using the following code line:
maven { url "https://yunopayments.jfrog.io/artifactory/snapshots-libs-release" }After that, include the code snippet below in the "build.gradle" file to add the Yuno SDK dependency to the application.
dependencies {
implementation 'com.yuno.payments:android-sdk:{last_version}'
}We have already included the INTERNET permission by default as we need it to make network requests.
<uses-permission android:name="android.permission.INTERNET" />To initialize the Yuno SDK, first, you need to get your public API keys from the Yuno dashboard. Then, if you have not implemented a custom application yet, you will need to create one and call the initialize function in the onCreate() method of your application class.
The following code snippet includes an example of a custom application:
class CustomApplication : Application() {
override fun onCreate() {
super.onCreate()
Yuno.initialize(
this,
"your api key",
config: YunoConfig, // This is a data class to use custom configs in the SDK.
)
}
}Please use the YunoConfig data class presented as follows:
data class YunoConfig(
val saveCardEnabled: Boolean = false,
val cardFormDeployed: Boolean = false,
val language: YunoLanguage? = null,
val styles: YunoStyles? = null,
)The following table describes each customization option:
| Customization option | Description |
|---|---|
saveCardEnabled |
Enables the Save card checkbox on card flows. |
cardFormDeployed |
This is only for SDK FULL. Choose if show card form deployed on payment methods list (TRUE) or if show normal card form in another screen (FALSE). |
language |
Defines the language to be used in the payment forms. You can set it to one of the available language options (see Available Languages below). If you send null or don't send it, Yuno SDK will take device language. |
styles |
Enables SDK-wide UI customization. Use it to define global visual styles like font family and button appearance (color, padding, radius, typography) through a YunoStyles object. For more information, see the Styles section below. |
Since 2.11.0: The
cardFlowparameter (CardFormType) has been removed fromYunoConfig. Card form configuration is now managed through the Checkout Builder.
The following languages are supported:
enum class YunoLanguage {
SPANISH, // es - Spanish
ENGLISH, // en - English
PORTUGUESE, // pt - Portuguese
INDONESIAN, // id - Indonesian
MALAYSIAN, // ms - Malay
FRENCH, // fr - French
POLISH, // pl - Polish
ITALIAN, // it - Italian
GERMAN, // de - German
RUSSIAN, // ru - Russian
TURKISH, // tr - Turkish
DUTCH, // nl - Dutch
SWEDISH, // sv - Swedish
THAI, // th - Thai
FILIPINO, // fil - Filipino
VIETNAMESE, // vi - Vietnamese
CHINESE_SIMPLIFIED, // zh-CN - Chinese (Simplified, China)
CHINESE_TRADITIONAL // zh-TW - Chinese (Traditional, Taiwan)
}With the styles customization option, you can define global visual styles through a YunoStyles object. It lets you apply consistent branding across the SDK by customizing button appearance and typography.
data class YunoStyles(
val buttonStyles: YunoButtonStyles? = null,
val fontFamily: FontFamily? = null
)| Parameter | Description |
|---|---|
buttonStyles |
Customizes the primary buttons displayed in the SDK. |
fontFamily |
Sets the font family used across all SDK text elements. |
The YunoButtonStyles object lets you define specific settings for button appearance:
data class YunoButtonStyles(
val backgroundColor: Color? = null,
val contentColor: Color? = null,
val cornerRadius: Dp? = null,
val elevation: Dp? = null,
val padding: Dp? = null,
val fontFamily: FontFamily? = null,
val fontSize: TextUnit? = null,
val fontStyle: FontStyle? = null
)In addition, you need to update your manifest to use your application:
<application android:name=".CustomApplication"></application>If your project has minifyEnabled = true, add these rules to your proguard-rules.pro:
# Preserve generic type signatures and annotations
-keepattributes Signature
-keepattributes *Annotation*
# Yuno SDK
-keep class com.yuno.** { *; }
-dontwarn com.yuno.**
# Gson — R8 full mode compatibility
-keep class com.google.gson.** { *; }
-keep,allowobfuscation,allowshrinking,allowoptimization class * extends com.google.gson.reflect.TypeToken
-dontwarn com.google.gson.**
# Retrofit — R8 full mode compatibility
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
-keep,allowobfuscation,allowshrinking class retrofit2.Response
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
-dontwarn retrofit2.**
# OkHttp + Okio — Suppress warnings
-dontwarn okhttp3.**
-dontwarn okio.**
Why are these rules necessary? AGP 8.x+ enables R8 full mode by default, which aggressively strips generic type signatures. Without
-keepattributes Signature, Gson'sTypeToken<T>fails at runtime withjava.lang.Class cannot be cast to java.lang.reflect.ParameterizedType.
To start an enrollment flow is a must to call initEnrollment method on the onCreate method of your activity, this because we use it to register the contract to give you the final enrollment state.
fun ComponentActivity.initEnrollment(
callbackEnrollmentState: ((String?) -> Unit)? = null, //Default null | To register this callback is a must to call ```initEnrollment``` method on the onCreate method of activity.
)Call the following method from your activity to start an enrollment flow of a new payment method.
fun Activity.startEnrollment(
customerSession: String,
countryCode: String,
showEnrollmentStatus: Boolean = true, //Optional - Default true
callbackEnrollmentState: ((String?) -> Unit)? = null, //Optional - You can send again another callback that is gonna override the one you sent on initEnrollment function.
)To register a callback to get the final enrollment state, it is necessary call the initEnrollment
method on the onCreate method of activity.
To only get the final enrollment state, it is necessary call the initEnrollment
method on the onCreate method of activity, in that method you register a callback function
called callbackEnrollmentState, we are gonna give you the status in that callback.
IMPORTANT: This function is not gonna start the enrollment process, is only to check a previous enrollment state this based on customer session.
fun AppCompatActivity.enrollmentStatus(
customerSession: String,
countryCode: String,
showEnrollmentStatus: Boolean = false, //Optional - Default false
callbackEnrollmentState: ((String?) -> Unit)? = null, //Optional - You can send again another callback that is gonna override the one you sent on initEnrollment function.
)For developers requiring advanced UI control, the SDK supports Render Mode integration. This mode provides fragment-based UI components that you can integrate into custom layouts, offering more flexibility while maintaining SDK functionality.
Step 1: Call startEnrollment() in your activity's onCreate() method. This initializes the enrollment flow and injects necessary dependencies:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_enrollment)
// IMPORTANT: Must be called before startEnrollmentRender
startEnrollment()
initViews()
initListeners()
}Step 2: Implement YunoEnrollmentRenderListener in your activity:
class EnrollmentActivity : AppCompatActivity(), YunoEnrollmentRenderListener {
private lateinit var fragmentController: YunoEnrollmentFragmentController
private fun startEnrollmentFlow() {
fragmentController = startEnrollmentRender(
customerSession = "customer_session_id",
countryCode = "CO",
coroutineScope = lifecycleScope,
listener = this
)
}
// Implement listener methods below
}showView() - Display the enrollment form:
override fun showView(fragment: Fragment, needSubmit: Boolean) {
// Insert the SDK fragment into your layout
supportFragmentManager.beginTransaction()
.replace(R.id.enrollment_fragment_container, fragment)
.commit()
// Show/hide custom submit button based on needSubmit
if (needSubmit) {
customSubmitButton.visibility = View.VISIBLE
customSubmitButton.setOnClickListener {
fragmentController.submitForm()
}
}
}returnStatus() - Handle enrollment completion:
override fun returnStatus(resultCode: Int, paymentStatus: String) {
when (paymentStatus) {
"SUCCEEDED" -> {
// Payment method enrolled successfully
// Remove fragment and show success message
}
"FAIL" -> {
// Enrollment failed
// Show error and allow retry
}
"CANCELED" -> {
// User canceled enrollment
}
}
}loadingListener() - Handle loading states:
override fun loadingListener(isLoading: Boolean) {
progressBar.isVisible = isLoading
if (needsSubmit) {
customSubmitButton.isEnabled = !isLoading
}
}- Custom UI Integration: Embed enrollment components in your existing layouts
- Fragment Compatibility: Works with both XML and Jetpack Compose
- Flow Control: Manage form submission manually with custom buttons
<FrameLayout
android:id="@+id/enrollment_fragment_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/custom_submit_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Submit Enrollment"
android:visibility="gone" />To start a new payment process, you need to call the following method on the onCreate method of activity that calls the SDK:
startCheckout(
checkoutSession: "checkout_session",
countryCode: "country_code_iso",
callbackOTT: (String?) -> Unit,
callbackPaymentState: ((String?, String?) -> Unit)?,
merchantSessionId: String? = null //Optional - Default null
)The callbackOTT parameter is a function that returns an OTT needed to complete the payment back to
back. This function is mandatory.
The callbackPaymentState parameter is a function that returns the current payment process state and sub-state. Sending
this function is not mandatory if you do not need the result.
The callback receives two parameters:
- paymentState (String?): The main payment state
- paymentSubState (String?): Additional sub-state information providing more details about the payment status
The possible payment states are:
const val PAYMENT_STATE_SUCCEEDED = "SUCCEEDED"
const val PAYMENT_STATE_FAIL = "FAIL"
const val PAYMENT_STATE_PROCESSING = "PROCESSING"
const val PAYMENT_STATE_REJECT = "REJECT"
const val PAYMENT_STATE_INTERNAL_ERROR = "INTERNAL_ERROR"
const val PAYMENT_STATE_STATE_CANCELED_BY_USER = "CANCELED"When implementing the Full SDK version, use PaymentMethodListViewComponent to display the
available payment methods list in your Compose screen:
PaymentMethodListViewComponent(
activity = activity,
onPaymentSelected = { isSelected, paymentSelected ->
// isSelected: true when the user picks a method, false when deselected
// paymentSelected: contains the chosen method details (available since SDK 2.10.2)
},
)| Parameter | Type | Description |
|---|---|---|
activity |
Activity |
The host Activity. Must be an AppCompatActivity instance. |
onPaymentSelected |
(Boolean, PaymentSelected?) -> Unit |
Callback fired when the user selects or deselects a payment method. |
The onPaymentSelected callback provides:
| Parameter | Type | Description |
|---|---|---|
isSelected |
Boolean |
true when a payment method is selected, false when deselected. Use this to enable/disable your Pay button. |
paymentSelected |
PaymentSelected? |
The selected payment method details. Pass this directly to startPaymentLite() if using the Lite flow. |
PaymentSelected fields:
| Field | Type | Description |
|---|---|---|
paymentMethodType |
String |
The payment method type identifier (e.g. "CARD", "BANCOLOMBIA_TRANSFER"). |
vaultedToken |
String? |
A vaulted token for previously saved payment methods. null for new methods. |
Note:
PaymentMethodListViewComponentmust not be placed inside averticalScrollcontainer. Give it a bounded height (e.g.Modifier.weight(1f)) to avoid a runtime crash if the component uses a scrollable layout internally.
To start a payment process, you have to call the method startPayment. However, if you are using
the lite version of the SDK, you must call the method startPaymentLite.
startPayment(
showStatusYuno: Boolean = true, //Optional - Default true
callbackOTT: (String?) -> Unit, //Optional - Default null
callBackTokenWithInformation: (OneTimeTokenModel?) -> Unit //Optional - Default null
)Configure the payment with the following options:
| Parameter | Description |
|---|---|
showStatusYuno |
A boolean that specifies whether the payment status should be displayed within the Yuno interface. Default is true. |
callbackOTT |
A function that returns the updated one-time token (OTT) needed to complete the payment process. This token is required to complete the payment. |
callBackTokenWithInformation |
A function that supplies detailed information about the one-time token, wrapped in a OneTimeTokenModel object, allowing for comprehensive handling of token details. |
startPaymentLite(
paymentSelected: PaymentSelected,
showPaymentStatus: Boolean = true, //Optional - Default true
callbackOTT: (String?) -> Unit, //Optional - Default null
callBackTokenWithInformation: (OneTimeTokenModel?) -> Unit //Optional - Default null
)The following table describes the required parameters to start the payment:
| Parameter | Description |
|---|---|
paymentSelected |
Inform the payment method selected by the buyer. |
showPaymentStatus |
A boolean that specifies whether the payment status should be displayed within the Yuno interface. Default is true. |
callbackOTT |
A function that returns the updated one-time token needed to complete the payment process. |
callBackTokenWithInformation |
A function that supplies detailed information about the one-time token, wrapped in a OneTimeTokenModel object, allowing for comprehensive handling of token details. |
For the Lite version, you need to send an additional parameter, which is the vaulted token and/or the payment type that the user selected to make the payment.
PaymentSelected(
vaultedToken: String? = null, //Optional - The vaulted token for saved payment methods
paymentMethodType: String //Required - The payment method type (e.g., "CARD", "NEQUI", etc.)
)At the end of this process, you will obtain the OTT, which is a required parameter to create a payment via the Payments API POST/payments.
The callBackTokenWithInformation callback provides detailed information about the one-time token through the OneTimeTokenModel object:
@Parcelize
data class OneTimeTokenModel(
val token: String? = null,
val vaultedToken: String? = null,
val vaultOnSuccess: Boolean? = null,
val type: String? = null,
val cardData: CardInformationModel? = null,
val customer: CustomerPayerInformationModel? = null,
) : ParcelableCard Information Model:
@Parcelize
data class CardInformationModel(
val holderName: String? = null,
val iin: String? = null,
val lfd: String? = null,
val numberLength: Int? = null,
val securityCodeLength: Int? = null,
val brand: String? = null,
val type: String? = null,
val category: String? = null,
val issuerName: String? = null,
val issuerCode: String? = null,
val countryCode: String? = null,
) : ParcelableCustomer Payer Information Model:
@Parcelize
data class CustomerPayerInformationModel(
val name: String? = null,
val lastName: String? = null,
val email: String? = null,
val document: Document? = null,
val phone: Phone? = null,
val address: Address? = null,
val deviceFingerPrint: String? = null,
val thirdPartySessionId: String? = null,
) : ParcelableSupporting Models:
@Parcelize
data class Document(
val documentNumber: String? = null,
val documentType: String? = null,
) : Parcelable
@Parcelize
data class Phone(
val number: String,
val countryCode: String,
) : Parcelable
@Parcelize
data class Address(
val addressLine1: String? = null,
val addressLine2: String? = null,
val country: String? = null,
val city: String? = null,
val state: String? = null,
val zipCode: String? = null,
val neighborhood: String? = null,
) : ParcelableIf in the create_payment response the sdk_action_required parameter is true you need to call the following method:
continuePayment(
showPaymentStatus: Boolean, //Optional - Default true
callbackPaymentState:((String?, String?) -> Unit)?, //Optional - Default null
)To show your own payment status screens, you should send false in the showPaymentStatus
parameter and then get the payment state by callback.
For developers requiring advanced UI control, the SDK supports Payment Render Mode. This mode provides fragment-based UI components that you can integrate into custom layouts, offering complete control over the payment flow presentation.
Step 1: Call startCheckout() in your activity's onCreate() method. This initializes the payment flow and injects necessary dependencies:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_payment)
// IMPORTANT: Must be called before startPaymentRender
startCheckout()
initViews()
initListeners()
}Step 2: Update the checkout session and country before starting the render:
private fun startPaymentFlow() {
// Update session configuration
updateCheckoutSession(
checkoutSession = "checkout_session_id",
countryCode = "CO"
)
// Start payment render
fragmentController = startPaymentRender(
checkoutSession = "checkout_session_id",
countryCode = "CO",
coroutineScope = lifecycleScope,
paymentSelected = PaymentSelected(
vaultedToken = null,
paymentMethodType = "CARD"
),
listener = this
)
}Step 3: Implement YunoPaymentRenderListener in your activity:
class PaymentActivity : AppCompatActivity(), YunoPaymentRenderListener {
private lateinit var fragmentController: YunoPaymentFragmentController
// Implement listener methods below
}showView() - Display the payment form:
override fun showView(fragment: Fragment) {
// Insert the SDK fragment into your layout
supportFragmentManager.beginTransaction()
.replace(R.id.payment_fragment_container, fragment)
.commit()
// Hide configuration fields and show submit button
configurationContainer.visibility = View.GONE
customSubmitButton.visibility = View.VISIBLE
customSubmitButton.setOnClickListener {
fragmentController.submitForm()
}
}returnOneTimeToken() - Receive the OTT after form submission:
override fun returnOneTimeToken(oneTimeToken: String, additionalData: OneTimeTokenModel?) {
// Remove the SDK form fragment
supportFragmentManager.findFragmentById(R.id.payment_fragment_container)?.let {
supportFragmentManager.beginTransaction().remove(it).commit()
}
// Display the OTT (e.g., in a TextView for testing/debugging)
ottTextView.text = "One-Time Token: $oneTimeToken"
ottTextView.visibility = View.VISIBLE
// Show continue button
continuePaymentButton.visibility = View.VISIBLE
// Here you would call your backend to create the payment with this OTT
// After creating the payment, user clicks "Continue Payment"
}continuePayment() - Continue with payment after backend creation:
continuePaymentButton.setOnClickListener {
if (::fragmentController.isInitialized) {
// This handles 3DS, redirects, and other payment method requirements
fragmentController.continuePayment()
}
}returnStatus() - Handle payment completion:
override fun returnStatus(resultCode: Int, paymentStatus: String, paymentSubStatus: String?) {
when (paymentStatus) {
"SUCCEEDED" -> {
// Payment completed successfully
// Remove fragment and show success
}
"FAIL" -> {
// Payment failed
// Show error and allow retry
}
"PROCESSING" -> {
// Payment is being processed
// Show processing message, can include paymentSubStatus for details
}
"REJECT" -> {
// Payment rejected
}
"CANCELED" -> {
// User canceled payment
}
}
}loadingListener() - Handle loading states:
override fun loadingListener(isLoading: Boolean) {
progressBar.isVisible = isLoading
}- User fills form: SDK fragment displays payment form
- Submit form: Custom button calls
fragmentController.submitForm() - Receive OTT: SDK calls
returnOneTimeToken()with the token - Remove form: Remove SDK fragment, display OTT
- Create payment: Call your backend with the OTT
- Continue: User clicks continue, call
fragmentController.continuePayment() - Complete: SDK handles 3DS/redirects and calls
returnStatus()with final result
- Custom UI Integration: Embed payment components in your existing layouts
- Complete Control: Manage when to show/hide form, display OTT, continue payment
- Fragment Compatibility: Works with both XML and Jetpack Compose
- Flow Control: Manually control form submission and payment continuation
<!-- Configuration inputs (hidden after form loads) -->
<LinearLayout
android:id="@+id/configuration_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:id="@+id/checkout_session_input"
android:hint="Checkout Session" />
<Button
android:id="@+id/start_payment_button"
android:text="Start Payment" />
</LinearLayout>
<!-- SDK payment form container -->
<FrameLayout
android:id="@+id/payment_fragment_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<!-- Custom submit button (shown below form) -->
<Button
android:id="@+id/submit_button"
android:text="Submit Payment"
android:visibility="gone" />
<!-- OTT display (shown after submission) -->
<TextView
android:id="@+id/ott_text_view"
android:text="One-Time Token: "
android:clickable="true"
android:visibility="gone" />
<!-- Continue button (shown after OTT) -->
<Button
android:id="@+id/continue_payment_button"
android:text="Continue Payment"
android:visibility="gone" />- Always call
startCheckout()inonCreate()before using render functions - Call
updateCheckoutSession()beforestartPaymentRender() - Remove the SDK fragment after receiving the OTT for cleaner UI
- Create the payment in your backend after receiving the OTT
- Call
continuePayment()after payment creation for 3DS/redirect handling - Handle all payment states in
returnStatus()(SUCCEEDED, FAIL, PROCESSING, etc.)