UPI Autopay SDK - Android

Autopay android SDK integration guide

Overview

The Decentro UPI AutoPay Android SDK enables seamless integration of UPI recurring payments into your Android applications. Built with modern Android technologies including Jetpack Compose, the SDK handles the complete mandate creation flow from mobile number entry to UPI app selection and transaction processing.


Key Features

  • Recurring Payments: Support for Onetime, Monthly, Weekly, Daily, Quarterly, Bimonthly, Fortnightly, Halfyearly, Yearly, Aspresented mandates.
  • PSP Support: Works with all UPI apps (Google Pay, PhonePe, Paytm, BHIM, etc.)
  • Customizable UI: Jetpack Compose-based UI with full theming support.
  • Secure Authentication: JWT-based authentication with automatic token refresh.
  • Easy Integration: Simple callback-based integration.
  • Modern Architecture: Built with Kotlin, Coroutines, and Jetpack Compose.
  • Production Ready: Comprehensive error handling and validation
  • Flexible Configuration: Multiple flow options and customizable parameters

Prerequisites

Before integrating the SDK, ensure your development environment meets the following requirements:

System Requirements

  • Android Studio: Arctic Fox (2020.3.1) or newer
  • Minimum SDK: Android 7.0 (API level 24)
  • Target SDK: Android 14 (API level 34)
  • Kotlin: 1.9.0 or higher
  • Gradle: 7.4 or higher
  • Java: JDK 17 or higher

Business Requirements

  • Merchants should be onboarded on Decentro
  • UPI AutoPay feature enabled for your account
  • Valid API credentials (Client ID, Client Secret, Consumer URN)
    Callback URLs to be configured
  • IP whitelisting

Compliance Requirements

  • Your application must comply with Decentro's integrity standards
  • Proper handling of sensitive user data
  • Implementation of security best practices

Installation

Step 1: Add Repository Configuration

Add the Decentro SDK repository to your project's settings.gradle file:

dependencyResolutionManagement {  
    repositories {  
        google()  
        mavenCentral()
// Decentro SDK Repository
    maven {
        url = uri("https://gitlab.com/api/v4/projects/XXXX/packages/maven")
        credentials {
            username = project.findProperty("decentro.sdk.username")
            password = project.findProperty("decentro.sdk.password")
        }
        authentication {
            basic(BasicAuthentication)
        }
    }
    }
}


Step 2: Add Credentials

Add your SDK access credentials to gradle.properties: (Note : We will share the username and password for the sdk)

Decentro SDK Credentials (provided by Decentro team)  
decentro.sdk.username=your-deploy-token-username  
decentro.sdk.password=your-deploy-token

Security Note: Never commit gradle.properties with actual credentials to version control. Add it to your .gitignore file.


Step 3: Add SDK Dependency

Add the Decentro SDK dependency to your app's build.gradle file:

dependencies {  
    implementation 'com.decentro.sdk:upi-autopay-sdk:1.X.X'
// Required dependencies (if not already included)
implementation 'androidx.activity:activity-compose:1.8.2'
implementation 'androidx.compose.material3:material3:1.1.2' }

Step 4: Update AndroidManifest.xml

Add the following permissions and configurations to your AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="upi" />
    </intent>
</queries>

<application android:allowBackup="true" android:theme="@style/Theme.AppCompat" ... >
    
  </application>
</manifest>

Authentication Setup

Step 1: Obtain JWT Token

Before using the SDK, you need to obtain a JWT access token from your app. The SDK requires this token for authentication.

Backend API Call Example:

curl --location 'https://api.decentro.tech/sdk_exc/v2/auth/token' \
--header 'Content-Type: application/json' \
--header 'User-Agent: DecentroSDK/1.0' \
--data '{
    "grant_type": "client_credentials",
    "client_id": "xxxxxxxx",
    "client_secret": "xxxxxxxx",
    "ttl": 3600,
      "apis": [
      {
        "endpoint": "/v3/payments/upi/autopay/mandate/status",
        "query_parameters": null,
        "request_payload": null
      },
      {
        "endpoint": "/v3/banking/mobile_to_account",
        "query_parameters": null,
        "request_payload": null
      },
      {
        "endpoint": "/v3/payments/upi/autopay/mandate/link",
        "query_parameters": null,
        "request_payload": null
      },
      {
        "endpoint": "/v3/banking/account_validation/status",
        "query_parameters": null,
        "request_payload": null
      },
      {
        "endpoint": "/decentro/read/get_upi_psp_mapping",
        "query_parameters": null,
        "request_payload": null
      }
    ]
    
}'


Step 2: Initialize SDK Configuration

import com.decentro.sdk.DecentroUPIAutoPaySDK
 
class MyActivity : ComponentActivity() {
 
    private fun initializeSDK() {
        val config = DecentroUPIAutoPaySDK.Configuration(
            accessToken = "your-jwt-access-token",
            refreshToken = "your-jwt-refresh-token", // Optional but recommended
            environment = DecentroUPIAutoPaySDK.Environment.PRODUCTION, // or SANDBOX/QA
            enableLogging = BuildConfig.DEBUG // Enable logging in debug mode
        )
        
        DecentroUPIAutoPaySDK.initialize(this, config)
    }
}


Integration Steps

Step 1: Create Mandate Details

Configure the mandate parameters according to your business requirements:
Decentro Create Mandate API - Link

val mandateDetails = DecentroUPIAutoPaySDK.MandateDetails(
    referenceId = "MERCHANT_REF_${System.currentTimeMillis()}",
    consumerUrn = "your-consumer-urn",
    mandateName = "Monthly Subscription",
    amount = 99.00,
    frequency = "monthly", // daily, weekly, monthly, quarterly, yearly
    purposeMessage = "Subscription Payment",
    ruleType = "after", // after, before, On
    amountRule = "MAX", // MAX, EXACT
    startDate = "2026-02-01", // YYYY-MM-DD format
    endDate = "2027-02-01", // YYYY-MM-DD format
    expiryTime = 10, // Mandate creation timeout in minutes
    defaultAmount = 99.00,
    isManagedByDecentro = false,
    isQrRequested = false,
    isBlockFunds = false,
    isRevokable = true,
    isCollectRequest = false,
    isTpv = false,
    generatePspUri = true,
    isDownpayment = false,
    isFirstTxnAmount = false,
    mandateActionCallbackSubscriberData = DecentroUPIAutoPaySDK.CallbackSubscriberData(
        url = "https://your-webhook-url.com/callback",
        method = "POST",
        headers = mapOf("content-type" to "application/json")
    )
)


Step 2: Configure UI Settings

Customize the SDK's appearance and behavior:

val uiConfig = DecentroUPIAutoPaySDK.SDKUIConfig(
    isMobileToAccountEnabled = true, // Show mobile number screen
    isMobileToAccountSkippable = true, // Allow users to skip mobile entry
    mobileToAccountReferenceId = null, // Pre-fetched M2A reference (optional)
    prefillMobileNumber = "+9198765432XX", // Pre-fill mobile number (optional)
    isAccountDetailsEditable = true, // Allow editing account details
    themeColor = "#2962FF", // Primary color (hex format)
    fontColor = "#000000", // Text color (hex format)
    logoUrl = null, // Your logo URL (optional)
    brandLogo = "https://your-domain.com/logo.svg", // Brand logo for header
    merchantName = "Your Company Name",
)


Step 3: Create Mandate Request

Combine all configurations into a single request object:

val mandateRequest = DecentroUPIAutoPaySDK.MandateRequest(
    mandateDetails = mandateDetails,
    uiConfig = uiConfig,
    mobileToAccountConfig = DecentroUPIAutoPaySDK.MobileToAccountConfig(
        purposeMessage = "auth-validation",
        fetchBranchDetails = false
    )
)


Step 4: Implement SDK Callback

Handle SDK responses with appropriate callbacks:

class MainActivity : ComponentActivity() {
 
    private val sdkCallback = object : DecentroUPIAutoPaySDK.UPIAutoPayCallback {
        override fun onSuccess(response: DecentroUPIAutoPaySDK.MandateResponse) {
            // Mandate created successfully
            Log.d(TAG, "Mandate ID: ${response.mandateId}")
            Log.d(TAG, "Status: ${response.status}")
            Log.d(TAG, "UPI ID: ${response.upiId}")
            Log.d(TAG, "Transaction ID: ${response.transactionId}")
 
            // Update your UI
            showSuccessMessage("Mandate created successfully!")
            
            // Store mandate ID for future reference
            saveMandateId(response.mandateId)
            
            // Navigate to success screen
            navigateToSuccessScreen(response)
        }
 
        override fun onFailure(error: String, errorCode: String?) {
            // Handle failure
            Log.e(TAG, "SDK Error: $error, Code: $errorCode")
            
            // Show error message to user
            showErrorMessage(error)
            
            // Handle specific error codes
            when (errorCode) {
                "SDK_NOT_INITIALIZED" -> initializeSDK()
                "INVALID_TOKEN" -> refreshTokenAndRetry()
                "NETWORK_ERROR" -> showNetworkErrorDialog()
                else -> showGenericErrorDialog(error)
            }
        }
 
        override fun onCancel() {
            // User cancelled the flow
            Log.d(TAG, "User cancelled mandate creation")
            showMessage("Mandate creation cancelled")
        }}}

Step 5: Launch SDK Flow

Start the UPI AutoPay mandate creation flow:

private fun startMandateCreation() {
    try {
        // Ensure SDK is initialized
        if (!DecentroUPIAutoPaySDK.isInitialized()) {
            initializeSDK()
        }
 
        // Launch mandate creation flow
        DecentroUPIAutoPaySDK.startMandateCreation(
            activity = this,
            mandateRequest = mandateRequest,
            callback = sdkCallback
        )
 
    } catch (e: Exception) {
        Log.e(TAG, "Failed to start mandate creation", e)
        showErrorMessage("Failed to start mandate creation: ${e.message}")
    }
}