m2pfintech
SDK IntegrationPPI SDK

PPI Android SDK Integration

M2P Headless UPI SDK for Android — complete technical integration guide for PPI partners covering onboarding, payments, mandates, disputes, and security.

Module: UpiSdkWithoutUi
Package: m2p.upi.sdk.android
SDK Version: v1.1.4 Min SDK: 26 (Android 8.0)
Target SDK: 36
Language: Kotlin (JVM 17)


Table of Contents

  1. Introduction
  2. Prerequisites
  3. SDK Setup & Initialization
  4. Core Concepts
  5. Key Exchange (Encryption Setup)
  6. Onboarding Flow
  7. Payment Flow
  8. Transaction Management
  9. Mandate Management
  10. Dispute Management
  1. Profile & Account Management
  2. Utility Functions
  3. Error Handling & Retry Mechanism
  4. Security Features
  5. Enums & Constants Reference

1. Introduction

What is M2P UPI Headless SDK?

The M2P UPI Headless SDK for Android is a lightweight, UI-less library that enables PPI (Prepaid Payment Instruments) partners to integrate UPI functionality into their mobile applications. As a PPI issuer, your transactions are pre-approved from the PPI app itself — the SDK handles UPI transaction processing, security, and device binding while giving you complete control over the user interface.

This SDK does not use the NPCI Common Library (NPCI CL). NPCI CL is only required for TPAP/PSP integrations where UPI PIN entry and credentials management happen on-device. In the PPI model, transactions are pre-approved by the PPI issuer, so NPCI CL is not needed.

Who Should Use This SDK?

This SDK is designed for:

  • Digital Wallet Providers integrating UPI functionality
  • Prepaid Instruments adding UPI capability to closed-loop systems
  • Financial Apps requiring headless UPI integration
  • Neo-banks building custom UPI experiences

Key Features

Headless Design - No UI components, full flexibility
Complete UPI Coverage - P2P, P2M, Collect, Mandate support
Pre-Approved Transactions - PPI issuer-approved flow, no NPCI CL dependency
End-to-End Encryption - ECDH key exchange + AES-256-GCM
SIM Binding Support - Secure device verification
Comprehensive APIs - 50+ methods covering all UPI operations
Kotlin First - Modern, coroutine-based APIs
Small Footprint - Optimized for size and performance

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                 Your Application (PPI App)                  │
│  ┌───────────────────────────────────────────────────────┐  │
│  │            Your UI Layer (Activities/Fragments)       │  │
│  └────────────────────┬──────────────────────────────────┘  │
│                       │                                     │
│  ┌────────────────────▼──────────────────────────────────┐  │
│  │        M2P UPI Headless SDK (M2PUPIModule)            │  │
│  │  ┌──────────────┐  ┌──────────────┐  ┌─────────────┐  │  │
│  │  │ Transaction  │  │   Profile    │  │   Mandate   │  │  │
│  │  │   Manager    │  │   Manager    │  │   Manager   │  │  │
│  │  └──────────────┘  └──────────────┘  └─────────────┘  │  │
│  │  ┌──────────────────────────────────────────────────┐ │  │
│  │  │        Security Layer (Encryption/ECDH)          │ │  │
│  │  └──────────────────────────────────────────────────┘ │  │
│  │  ┌──────────────────────────────────────────────────┐ │  │
│  │  │     Network Layer (Retrofit/OkHttp/SSL Pinning)  │ │  │
│  │  └──────────────────────────────────────────────────┘ │  │
│  └───────────────────────┬──────────────────────────────┘   │
└────────────────────────┬─┴──────────────────────────────────┘


              ┌────────────────────────┐
              │  M2P UPI Switch/APIs   │
              └────────────────────────┘


              ┌────────────────────────┐
              │      NPCI Network      │
              └────────────────────────┘

2. Prerequisites

System Requirements

RequirementMinimumRecommended
Android OSAPI 26 (Android 8.0 Oreo)API 33+ (Android 13+)
JDKJava 11Java 17
Kotlin1.7.01.9.0+
Gradle7.08.0+
Android Gradle Plugin7.2.08.0.0+
Storage50 MB free space100 MB
NetworkTLS 1.2+TLS 1.3

Required Permissions

Add the following permissions to your AndroidManifest.xml:

<!-- Mandatory Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<!-- Required for SIM Binding -->
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

SEND_SMS is required for the SIM binding verification flow (the SDK sends a silent SMS to verify the device SIM). READ_PHONE_STATE is needed to read SIM/device information. The READ_PHONE_NUMBERS permission is not required and should not be requested — it is restricted on modern Android versions.

Hardware Requirements

  • Device with SIM Card - Required for UPI authentication and SIM binding
  • Internet Connectivity - Stable internet connection (Wi-Fi or Mobile Data)

Developer Prerequisites

  • Understanding of Kotlin Coroutines and suspend functions
  • Familiarity with MVVM or MVI architecture patterns
  • Basic knowledge of REST APIs and JSON
  • Experience with Android lifecycle management

3. SDK Setup & Initialization

3.1: Add AAR File

The M2P UPI PPI SDK is distributed as an .aar file. The M2P Implementation team will share the UpiSdkWithoutUi.aar file directly.

  1. Create a libs directory in your app module if it doesn't already exist.
  2. Copy the UpiSdkWithoutUi.aar file into app/libs/.

3.2: Add SDK Dependencies

Add the SDK and its required dependencies to your app-level build.gradle:

dependencies {
    // M2P UPI PPI SDK (AAR)
    implementation files('libs/UpiSdkWithoutUi.aar')

    // Required Dependencies
    implementation 'com.squareup.retrofit2:retrofit:3.0.0'
    implementation 'com.squareup.retrofit2:converter-gson:3.0.0'
    implementation 'com.squareup.retrofit2:converter-scalars:3.0.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:5.3.0'
    implementation 'com.google.code.gson:gson:2.13.2'
    implementation 'androidx.security:security-crypto:1.1.0'

    api 'com.madgag.spongycastle:core:1.58.0.0'
    api 'com.madgag.spongycastle:prov:1.58.0.0'
    api 'com.madgag.spongycastle:pkix:1.54.0.0'
    api 'com.madgag.spongycastle:pg:1.54.0.0'
}

3.3: Enable Java 17

Update your build.gradle (app module):

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }
    
    kotlinOptions {
        jvmTarget = '17'
    }
}

3.4: Add Proguard Rule

Add below rules in your proguard.rule.pro:

-keep class java.security.** { *; }
-keep class org.spongycastle.** { *; }
-keep class pci.org.spongycastle.** { *; }
-keep class com.google.gson.reflect.TypeToken
-keep class * extends com.google.gson.reflect.TypeToken
-keep public class * implements java.lang.reflect.Type

3.5: Sync Project

Click "Sync Now" in Android Studio or run:

./gradlew clean build

3.6: Verify Installation

Create a test class to verify SDK import:

import m2p.upi.sdk.android.utils.M2PUPIModule

class SdkTest {
    fun testSdkVersion() {
        val version = M2PUPIModule.getSDKVersion()
        println("SDK Version: $version")
    }
}

3.7: SDK SetUp

Before making any API call, configure the SDK in your Application class or launch Activity:

M2PUPIModule.setupUpiSDKValues(
    context = context,
    baseUrl = "https://your-base-url.com/",       // Provided by M2P
    endPointUrl = "middleware-endpoint/",            // Provided by M2P
    sslPinningKey = "YOUR_SSL_PINNING_KEY",         // Provided by M2P
    environment = M2PUpiEnvironmentEnum.UAT,        // UAT or PROD
    isLogRequired = true,                           // Enable logs in debug
    isRootDetectionRequired = false                 // Enable root/VPN/proxy detection
)

Environments Available:

EnumDescription
M2PUpiEnvironmentEnum.UATTesting / Sandbox
M2PUpiEnvironmentEnum.PRODProduction

4. Core Concepts

Central API Entry Point

All SDK operations go through a single function:

suspend fun M2PUPIModule.m2pApiCallService(
    context: Context,
    type: M2PUpiServiceName,       // The operation to perform
    header: HashMap<String, String>, // Must include "tenant"
    inputType: Any,                 // Request model (varies per operation)
    queryParams: HashMap<String, Any> = hashMapOf() // Optional query params
): Any?
  • This is a suspend function — must be called from a coroutine scope.
  • The type parameter determines which API is called.
  • The return type must be cast to the appropriate response model.

Common Header

Every API call requires at minimum:

val header = hashMapOf("tenant" to "YOUR_TENANT_CODE")

DeviceInfo (Required in Most Requests)

Most request models require a UpiDeviceInfoRequest. Use the helper method:

val deviceInfo = M2PUPIModule.createDeviceInfoRequest(
    context = context,
    mobileNumber = "919XXXXXXXXX",  // Full mobile number with country code
    simProviderName = "Airtel",      // SIM provider name
    address = address                // android.location.Address object
)

UpiDeviceInfoRequest Fields:

FieldTypeDescription
deviceIdStringUnique device identifier (auto-generated)
deviceTypeStringAlways "MOB"
osStringe.g., "Android 14"
telecomStringSIM provider name
geoCodeStringLatitude,Longitude (e.g., "13.01,80.21")
appIdStringHost app package name
ipAddressStringDevice IP address
locationStringLocality name (max 40 chars)
mobileStringUser mobile number (max 12 chars)

Session Handling (NEW)

Overview

Session handling is now a critical part of the SDK integration to ensure secure, stateful communication between the client and the UPI backend. Each session is uniquely identified and managed by the SDK, and is required for all sensitive operations (such as onboarding, payments, and mandate management).

Key Points

  • Session Initialization:

    • A session is established after a successful key exchange (KEY_EXCHANGE API call).
    • The SDK internally manages the session lifecycle and stores the session reference securely.
  • Session Reference:

    • The requestReferenceId returned from the KEY_EXCHANGE response is used as the session identifier for all subsequent API calls.
    • This value is managed by the SDK; you do not need to pass it manually.
  • Session Expiry & Renewal:

    • If the session expires or becomes invalid (e.g., due to inactivity or server-side invalidation), the SDK will automatically attempt to re-initiate the key exchange and obtain a new session.
  • Automatic Session Management:

    • The SDK handles session persistence, renewal, and cleanup internally. No manual intervention is required for most use cases.
    • On logout or app uninstall, call M2PUPIModule.clearSDKValues(context) to clear all session and encryption data.

5. Key Exchange (Encryption Setup)

When: First step before any other API call if encryption is required.

The SDK uses ECDH key exchange to establish secure encrypted communication.

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.KEY_EXCHANGE,
    inputType = "",  // No input needed; SDK generates key pair internally
) as UpiPairPublicKeyResponse

Response: UpiPairPublicKeyResponse

FieldTypeDescription
codeInt?HTTP status code
successString?"TRUE" on success
detailMessageString?Error detail if failed
requestReferenceIdString?Reference ID for encrypted session
result.publicKeyString?Server's public key
result.sslPinningKeyString?SSL pinning key

Success Check:

if (response.success.orEmpty().equals("TRUE", ignoreCase = true)) {
    // Encryption keys are auto-stored by SDK. Proceed to onboarding.
} else if (response.detailMessage.orEmpty()
        .equals("Unsupported state or unable to authenticate data", true)) {
    // Retry the key exchange
} else {
    // Handle error: response.exception
}

After successful key exchange, all subsequent API requests are automatically encrypted/decrypted by the SDK internally. You do not need to manage encryption yourself.


6. Onboarding Flow

Complete Onboarding Sequence:

Key Exchange → SIM Binding → Register Profile  → Create VPA → Fetch Account List → Add Account 

SIM Binding

Purpose: Binds the user's SIM card to the device for secure UPI transactions. This is a two-step process.

Step A: Get SIM Bind Token

val request = UpiSimBindingRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    seqNo = "1",
    deviceInfo = deviceInfo,
    callbackRef = null  // Optional callback reference
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.GET_SIM_BIND_TOKEN,
    inputType = request,
) as UpiSimBindingResponseModel

Response: UpiSimBindingResponseModel

FieldTypeDescription
statusString?"SUCCESS" or "FAILURE"
result.dataString?SMS data payload to send
result.toString?SMS destination number
errorString?Error message
exceptionUpiExceptionResponseModel?Detailed exception info

Action Required: After receiving the token, send an SMS with result.data to result.to using the device's SMS manager.

Step B: Validate SIM Bind Status

After the SMS is sent, validate the binding:

val request = UpiSimBindingRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    seqNo = "1",
    deviceInfo = deviceInfo,
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.VALIDATE_SIM_BIND,
    inputType = request,
) as UpiCheckSimBindStatusResponseModel

Response: UpiCheckSimBindStatusResponseModel

FieldTypeDescription
statusString?"SUCCESS" if SIM is bound
result.idString?Profile ID
result.nameString?User name
result.keyString?Key value
result.ifscString?IFSC code
result.bankNameString?Bank name
result.ipCodeString?IP code (used for transaction ID generation)
result.accountsList?Linked accounts
result.vpasList?Existing VPAs
result.upiNoList?Existing UPI Numbers

SIM Utility Helpers:

// Get SIM details from device
val simDetails: UpiSimDetails = M2PUPIModule.getUserSimDetails(context)

// Validate if SIM binding is needed
val isRequired: Boolean = M2PUPIModule.validateSimBinding(
    context = context,
    userSim = "SIM_ICCID",
    userSubscriptionId = subscriptionId
)

Register / Get Profile

Purpose: Creates a new user profile or retrieves an existing one. The profile is the central identity for all UPI operations.

Register Profile (New User)

val request = UpiGenerateProfileRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    businessType = "YOUR_CHANNEL_CODE",
    type = "PERSON",                        // "PERSON" for individual users
    seqNo = "1",
    name = "User Full Name",
    deviceInfo = deviceInfo,
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.REGISTER_PROFILE,
    inputType = request,
) as UpiFetchProfileResponseModel

Get Existing Profile

val request = UpiUserCommonRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    profileId = "PROFILE_ID",              // From registration or SIM bind
    deviceInfo = deviceInfo,
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.GET_PROFILE,
    inputType = request,
) as UpiFetchProfileResponseModel

Response: UpiFetchProfileResponseModel

FieldTypeDescription
statusString?"SUCCESS" or "FAILURE"
result.idString?Profile ID (store this — needed for all subsequent calls)
result.mobileString?Registered mobile number
result.nameString?User name
result.channelCodeString?Channel code
result.profileStatusString?Profile status
result.accountsList<UpiFetchProfileAccountModel>?Linked bank accounts
result.vpasList<UpiFetchProfileVPAModel>?Created VPAs (UPI IDs)
result.upiNoList<UpiFetchProfileUpiNumberModel>?UPI numbers
result.ipCodeString?IP Code (needed for generating transaction IDs)
result.typeString?Profile type ("PERSON")
result.delegateDetailsList<UpiDelegateDetailsModel>?UPI Circle delegate details

Key Data to Store:

  • result.idprofileId (used in all subsequent API calls)
  • result.ipCodeipCode (used for generateTransactionId())
  • result.accounts → Linked accounts list
  • result.vpas → User's VPA addresses
  • result.upiNo → User's UPI Number Detail
  • result.delegateDetails → User's Delegate(Circle) Detail

Create VPA (UPI ID)

Purpose: Creates a Virtual Payment Address (VPA) — the user's UPI ID (e.g., user@bank).

Step A: Check VPA Availability

val request = UpiCreateVPARequest(
    channelCode = "YOUR_CHANNEL_CODE",
    vpaId = "desired-upi-id",           // Without the @handle suffix
    profileId = profileId,
    deviceInfo = deviceInfo,
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.CHECK_VPA_AVAILABILITY,
    inputType = request,
) as UpiCommonResponseModel

Step B: Create VPA

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.CREATE_VPA,
    inputType = request,   // Same UpiCreateVPARequest
) as UpiCommonResponseModel

Step B (Alternative): Check & Create VPA in One Call

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.CHECK_AND_CREATE_VPA,
    inputType = request,
) as UpiCommonResponseModel

Response: UpiCommonResponseModel

FieldTypeDescription
statusString?"SUCCESS" or "FAILURE"
messageString?Success/failure message
exceptionUpiExceptionResponseModel?Error details

Purpose: Fetches bank accounts associated with the user's mobile number and links them to the UPI profile.

Fetch Account List

val request = UpiFetchProfileAccountListRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    profileId = profileId,
    ifsc = "IFSC_CODE",                  // Bank's IFSC code
    bankName = "BANK_NAME",              // Bank name
    deviceInfo = deviceInfo,
    seqNo = "1",
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.GET_ACCOUNT_LIST,
    inputType = request,
) as UpiFetchProfileAccountResponseModel

Add Account to Profile

val request = UpiAddProfileAccountRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    profileId = profileId,
    ifsc = "BANK_IFSC_CODE",
    vpaId = "user-upi-id",
    accRefNo = "ACCOUNT_REFERENCE_NUMBER",   // From account fetch response
    maskedAccnumber = "XXXX1234",            // Masked account number
    accountType = "SAVINGS",                  // Account type
    name = "Account Holder Name",
    bank = "Bank Name",
    deviceInfo = deviceInfo,
    seqNo = "1",
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.ADD_USER_ACCOUNT,
    inputType = request,
) as UpiCommonResponseModel

Set Primary Account & UPI ID

Set Primary Account

val request = UpiAccountActionRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    seqNo = "1",
    profileId = profileId,
    accRefNo = "ACCOUNT_REFERENCE_NUMBER",
    vpaId = "user-upi-id",
    deviceInfo = deviceInfo,
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.SET_PRIMARY_ACCOUNT,
    inputType = request,
) as UpiCommonResponseModel

Set Primary UPI ID

val request = UpiUserCommonRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    profileId = profileId,
    vpaId = "user-upi-id",
    deviceInfo = deviceInfo,
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.SET_PRIMARY_UPI_ID,
    inputType = request,
) as UpiCommonResponseModel

7. Payment Flow

Amount Limits (Configurable)

LimitDefault Value
p2POfflineLimit₹2,000
collectRequestMaxAmountLimit₹2,000
peerToPeerMaxAmountLimit₹1,00,000
merchantMaxAmountLimit₹2,00,000
specialMerchantMaxAmountLimit₹5,00,000

Verify VPA (Validate Payee)

Purpose: Validates a payee's UPI ID before initiating a payment.

val request = UpiValidateUpiIDRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    profileId = profileId,
    type = "VPA",                        // "VPA" or "ACCOUNT" or "UPI_NUMBER"
    id = "payee@upihandle",             // Payee's UPI ID to validate
    deviceInfo = deviceInfo,
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.VERIFY_VPA,
    inputType = request,
) as UpiValidateUpiIdResponseModel

Sample Response for Person VPA: 

{
  "status": "SUCCESS",
  "exception": null,
  "seqNo": "1",
  "callbackRef": null,
  "message": null,
  "pagination": null,
  "result": {
    "merchant": null,
    "result": "SUCCESS",
    "maskName": "Narayanan",
    "type": "PERSON",
    "code": "0000",
    "ifsc": "AABC0009009",
    "addr": "gift@mypsp",
    "cmid": null,
    "accType": "SAVINGS",
    "merchantKey": null,
    "featureSupportedValue": null,
    "ptype": "UPIMANDATE"
  }
}

Sample Response for Merchant VPA: 

{
  "status": "SUCCESS",
  "exception": null,
  "seqNo": "1",
  "callbackRef": null,
  "message": null,
  "pagination": null,
  "result": {
    "merchant": {
      "identifier": {
        "subCode": "1234",
        "mid": "8394",
        "sid": "2212",
        "tid": "0101",
        "merchantType": "SMALL",
        "merchantGenre": "ONLINE",
        "onBoardingType": null,
        "regId": null,
        "pinCode": null,
        "tier": null,
        "merchantLoc": null,
        "merchantInstCode": null,
        "gstin": null,
        "mcc": null
      },
      "name": {
        "brand": "AllianceFranche",
        "legal": "Reliance",
        "franchise": "LearnFrench"
      },
      "ownership": {
        "type": "PRIVATE"
      },
      "invoice": null,
      "verifiedMerchant": false
    },
    "result": "SUCCESS",
    "maskName": "Narayanan",
    "type": "ENTITY",
    "code": "6012",
    "ifsc": "AABF0009009",
    "addr": "gift@mypsp2",
    "cmid": null,
    "accType": "SAVINGS",
    "merchantKey": null,
    "featureSupportedValue": null,
    "ptype": "UPIMANDATE"
  }
}

Make Payment (Pay)

Purpose: Initiates a UPI payment to a payee.

val payModel = UpiTransferPayModel()

payModel =payModel.copy(
        payeeName = response.validateResultModel?.maskName,
        payeeId = response.validateResultModel?.addr,
        payeeType = response.validateResultModel?.type,
        payeeCode = response.validateResultModel?.code,
        accountType = response.validateResultModel?.accType,
        ifscCode = response.validateResultModel?.ifsc,
        cmid = response.validateResultModel?.cmid,
        transactionType = UpiPayRequestTypeEnum.PAY.name,
        payType = "SEND-AS-ACCOUNT-WHILE-DOING-BANK-TRANSACTION-OTHERWISE-SEND-AS-VPA",
        isVerifiedMerchant = response.validateResultModel?.merchant?.verifiedMerchant,
        merchantGenre = response.validateResultModel?.merchant?.identifier?.merchantGenre,
        merchantKey = response.validateResultModel?.merchantKey,
    )

Whenever user changed their amount /description you need to update like below

payModel = payModel.copy(
        amount = "USER-ENTERED-AMOUNT",
        description ="USER-ENTERED-DESCRIPTION",
    )

You can Initiates the Payment API Call like below

val request = UpiPaymentRequestModel(
    profileId = profileId,
    seqNo = "1",
    channelCode = "YOUR_CHANNEL_CODE",
    businessType = "YOUR_CHANNEL_CODE",
    deviceInfo = deviceInfo,
    payModel = payModel,
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.MAKE_PAYMENT,
    inputType = request,
) as UpiPayResponseModel

Response: UpiPayResponseModel

FieldTypeDescription
statusString?"SUCCESS" or "FAILURE"
result.statusString?Transaction status
result.txnIdString?Transaction ID
result.custRefString?Customer reference (RRN)
result.approvalNumString?Approval number
result.errCodeString?Error code
result.errDescriptionString?Error description
result.refList<UpiPayResultRefModel>?Payer/Payee reference details

Getting Payer/Payee Details:

val payeeInfo = response.result?.getPayeeInfo()  // Returns UpiPayResultRefModel for PAYEE
val payerInfo = response.result?.getPayerInfo()  // Returns UpiPayResultRefModel for PAYER

Scan & Pay (QR Code)

Parse QR Code Data

val payModel: UpiTransferPayModel = M2PUPIModule.validateUpiData(scannedQRString)

The returned UpiTransferPayModel is auto-populated with:

  • payeeId, payeeName, amount, transactionId, description
  • qrCodeType — one of: BHARAT_PAY_QR, NORMAL_QR, SIGN_QR, MANDATE_QR, INTERNATIONAL_QR, ATM_WITHDRAWAL_QR
  • isQRExpired, isInValidQR, isVerifiedMerchant, etc.

Handle QR Types

when {
    payModel.isQRExpired == true -> { /* Show QR expired error */ }
    payModel.isInValidQR == true -> { /* Show invalid QR error */ }
    payModel.qrCodeType == M2PUpiQRCodeType.MANDATE_QR.name -> {
        if (payModel.isValidStartDate == true && payModel.isValidEndDate == true) {
            // Proceed with Create Mandate flow
        } else { /* Show QR expired error */ }
    }
    payModel.qrCodeType == M2PUpiQRCodeType.INTERNATIONAL_QR.name -> {
        // Show international QR not supported
    }
    payModel.scannedUpiId.orEmpty().isNotEmpty() -> {
        // Validate payee UPI ID then proceed to payment
    }
    else -> { /* Show invalid QR error */ }
}

Verify Signed QR

val isValid: Boolean = M2PUPIModule.verifySignedQR(
    originalText = qrString,
    publicKey = "PUBLIC_KEY_FOR_VERIFICATION"
)

Generate QR Code

val bitmap: Bitmap? = M2PUPIModule.generateQRCode(
    upiId = "user@upihandle",
    upiNotes = "Payment for order",
    upiName = "User Name",
    amountValue = "100.00",
    upiOrgId = "ORG_ID",          // Optional
    key = "KEY"                    // Optional
)

Payment Types for Scan & Pay

Use M2PUpiPaymentType.SCAN_AND_PAY in UpiTransferPayModel:

val payModel = UpiTransferPayModel(
    paymentType = M2PUpiPaymentType.SCAN_AND_PAY,
    // ... other fields from QR data
)

Check Balance

val request = UpiPinRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    seqNo = "1",
    deviceInfo = deviceInfo,
    profileId = profileId,
    payModel = UpiTransferPayModel(
        paymentType = M2PUpiPaymentType.CHECK_BALANCE,
        selectedAccount = "ACCOUNT_REF_NUMBER",
        selectedIfscCode = "IFSC_CODE",
        payerVPAAddress = "user@upihandle"
    ),
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.CHECK_BALANCE,
    inputType = request,
) as UpiCheckBalanceResponseModel

Response Fields:

FieldTypeDescription
result.balString?Account balance
result.currencyString?Currency code (e.g., "INR")

Collect Request (Money Request)

Send a Collect Request

val payModel = UpiTransferPayModel(
    transactionType = "COLLECT",
    paymentType = M2PUpiPaymentType.SEND_COLLECT_REQUEST,
    payeeId = "payer-to-collect-from@upi",
    amount = "500.00",
    description = "Payment request",
    transactionId = M2PUPIModule.generateTransactionId(ipCode),
    // ... other fields
)

val request = UpiPaymentRequestModel(
    profileId = profileId,
    seqNo = "1",
    channelCode = "YOUR_CHANNEL_CODE",
    businessType = "YOUR_CHANNEL_CODE",
    deviceInfo = deviceInfo,
    payModel = payModel,
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.SEND_MONEY_REQUEST,
    inputType = request,
) as UpiCommonResponseModel

Fetch Collect Requests (Sent / Received)

// Sent requests (I requested money from others)
val sentResponse = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.MONEY_REQUEST_SENDER_LIST,
    inputType = commonRequest,
    queryParams = hashMapOf("pageNo" to 0, "pageSize" to 20),
) as UpiTransactionResponseModel

// Received requests (Others requested money from me)
val receivedResponse = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.MONEY_REQUEST_RECEIVER_LIST,
    inputType = commonRequest,
    queryParams = hashMapOf("pageNo" to 0, "pageSize" to 20),
) as UpiTransactionResponseModel

Approve / Reject a Collect Request

val request = UpiCollectRequestModel(
    channelCode = "YOUR_CHANNEL_CODE",
    action = "APPROVE",                    // "APPROVE" or "REJECT"
    profileId = profileId,
    deviceInfo = deviceInfo,
    payModel = UpiTransferPayModel(
        paymentType = M2PUpiPaymentType.PAY_COLLECT_REQUEST,
        transactionId = "ORIGINAL_TXN_ID",
        // ... payer/payee details, amount, credentials
    ),
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.MONEY_REQUEST_ACTION,
    inputType = request,
) as UpiCommonResponseModel

8. Transaction Management

Recent Transaction List

val request = UpiUserCommonRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    profileId = profileId,
    deviceInfo = deviceInfo,
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.RECENT_TRANSACTION_LIST,
    inputType = request,
) as UpiTransactionResponseModel

User Transaction List (Paginated)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.USER_TRANSACTION_LIST,
    inputType = request,
    queryParams = hashMapOf(
        "pageNo" to 0,
        "pageSize" to 20
    ),
) as UpiTransactionResponseModel

Response: UpiTransactionResponseModel

FieldTypeDescription
statusString?"SUCCESS" or "FAILURE"
pagination.pageNoInt?Current page number
pagination.pageSizeInt?Items per page
pagination.totalPagesInt?Total pages
pagination.totalElementsInt?Total transaction count
resultList<UpiTransactionResultModel>?Transaction list

UpiTransactionResultModel Fields:

FieldTypeDescription
txnIdString?Transaction ID
orgTxnIdString?Original transaction ID
rrnString?Retrieval Reference Number
amountString?Transaction amount
txnTypeString?Transaction type (PAY, COLLECT)
txnSubTypeString?Sub-type (DEBIT, CREDIT)
statusString?Transaction status
createdDateLong?Unix timestamp of creation
payeeNameString?Payee name
payeeAddrString?Payee VPA address
payerNameString?Payer name
payerAddrString?Payer VPA address
remarksString?Transaction remarks
descriptionString?Description
isComplaintRaisedBoolean?Whether a dispute exists
disputeUpiTransactionDisputeModel?Dispute details if raised

Check UPI Current Status

Purpose: Check the status of a pending or in-progress UPI transaction.

val request = UpiCheckCurrentStatusRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    seqNo = "1",
    deviceInfo = deviceInfo,
    profileId = profileId,
    txnType = "PAY",                        // Transaction type
    extTxnId = "ORIGINAL_TRANSACTION_ID",   // Transaction ID to check
    orgRrn = "ORIGINAL_RRN",               // Optional: Original RRN
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.CHECK_UPI_CURRENT_STATUS,
    inputType = request,
) as UpiPayResponseModel

9. Mandate Management

Purpose: UPI Mandates allow recurring/auto-debit payments. The SDK supports creation, listing, and lifecycle actions on mandates.

Mandate Payment Types

EnumDescription
M2PUpiPaymentType.CREATE_MANDATECreate a new mandate
M2PUpiPaymentType.APPROVE_MANDATEApprove a mandate request
M2PUpiPaymentType.REJECT_MANDATEReject a mandate request
M2PUpiPaymentType.MODIFY_APPROVE_MANDATEApprove a modification
M2PUpiPaymentType.MODIFY_REJECT_MANDATEReject a modification
M2PUpiPaymentType.PAUSE_MANDATEPause an active mandate
M2PUpiPaymentType.UNPAUSE_MANDATEResume a paused mandate
M2PUpiPaymentType.REVOKE_MANDATERevoke/cancel a mandate
M2PUpiPaymentType.UPDATE_MANDATEUpdate mandate details

Create Mandate

val payModel = UpiTransferPayModel(
    paymentType = M2PUpiPaymentType.CREATE_MANDATE,
    payeeName = "Merchant Name",
    payeeId = "merchant@upi",
    payeeCode = "MERCHANT_CODE",
    payerName = "Payer Name",
    payerVPAAddress = "payer@upi",
    amount = "1000.00",                          // Maximum amount
    minimumAmount = "100.00",                     // Minimum amount (optional)
    description = "Monthly subscription",
    transactionId = M2PUPIModule.generateTransactionId(ipCode),
    selectedAccount = "ACCOUNT_REF_NUMBER",
    selectedIfscCode = "IFSC_CODE",
    mandatesModel = UpiMandatesModel(
        startDate = "01012026",                   // ddMMyyyy format
        endDate = "01012027",                     // ddMMyyyy format
        expiryDate = "01012027",
        frequency = "MONTHLY",                    // DAILY, WEEKLY, FORTNIGHTLY, MONTHLY, BIMONTHLY, QUARTERLY, HALFYEARLY, YEARLY, ASPRESENTED, ONETIME
        debitRule = "MAX",                        // MAX or EXACT
        share = "Y",                              // Share to payee
        block = "N",                              // Block fund
        remove = "Y",                             // Revokable
        initiatedBy = "PAYER",                    // "PAYER" or "PAYEE"
        name = "Mandate Name",
    )
)

val request = UpiPaymentRequestModel(
    profileId = profileId,
    seqNo = "1",
    channelCode = "YOUR_CHANNEL_CODE",
    businessType = "YOUR_CHANNEL_CODE",
    startDate = "01012026",                       // ddMMyyyy
    expiryDate = "01012027",                      // ddMMyyyy
    deviceInfo = deviceInfo,
    payModel = payModel,
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.CREATE_MANDATE,
    inputType = request,
) as UpiMandateCallBackResponseModel

Response: UpiMandateCallBackResponseModel

FieldTypeDescription
statusString?"SUCCESS" or "FAILURE"
messageString?Result message
result.reqMsgIdString?Request message ID
result.resultString?Result status
result.txnIdString?Transaction ID
result.refIdString?Reference ID

Fetch Mandate List

val request = UpiFetchMandateRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    profileId = profileId,
    seqNo = "1",
    deviceInfo = deviceInfo,
    mandateStatus = listOf("ACTIVE", "PAUSED", "REVOKED"),  // Filter by status
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.GET_MANDATE_LIST,
    inputType = request,
    queryParams = hashMapOf("pageNo" to 0, "pageSize" to 20),
) as UpiMandateResponseModel

Response: UpiMandateResponseModel

FieldTypeDescription
statusString?"SUCCESS" or "FAILURE"
paginationUpiPaginationResponseModel?Pagination details
resultList<UpiMandateResultModel>?List of mandates

UpiMandateResultModel Key Fields:

FieldTypeDescription
mandateStatusString?Current status (ACTIVE, PAUSED, REVOKED, etc.)
mandateUpiMandateResultMandateModel?Mandate details (amount, dates, recurrence)
payeeUpiMandatePayeeModel?Payee details
payerUpiMandatePayerModel?Payer details
headUpiMandateHeadModel?Header info
txnUpiMandateTransactionModel?Transaction details
pauseFlagBoolean?Whether mandate is paused
pauseMandateStartDateString?Pause start date
pauseMandateEndDateString?Pause end date
lastPaidOnArrayList<String>?Last payment dates
paymentDetailsList<UpiMandatePaymentModel>?Payment history

Mandate Actions

Purpose: Perform lifecycle actions on mandates — approve, reject, pause, unpause, revoke, update.

val payModel = UpiTransferPayModel(
    paymentType = M2PUpiPaymentType.APPROVE_MANDATE,  // or REJECT_MANDATE, PAUSE_MANDATE, etc.
    transactionId = M2PUPIModule.generateTransactionId(ipCode),
    transactionReferenceId = "MANDATE_TXN_REF_ID",    // From mandate list response
    payeeId = "merchant@upi",
    payerVPAAddress = "payer@upi",
    selectedAccount = "ACCOUNT_REF_NUMBER",
    amount = "1000.00",
    setOrResetPinBlock = account.credsAllowed,
    // For Pause: mandatesModel with pauseStartDate, pauseEndDate
    // For Update: mandatesModel with new endDate, amount
)

val request = UpiPaymentRequestModel(
    profileId = profileId,
    seqNo = "1",
    channelCode = "YOUR_CHANNEL_CODE",
    businessType = "YOUR_CHANNEL_CODE",
    action = "APPROVE",    // "APPROVE", "REJECT", "PAUSE", "UNPAUSE", "REVOKE", "UPDATE"
    deviceInfo = deviceInfo,
    payModel = payModel,
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.MANDATE_ACTION,
    inputType = request,
) as UpiMandateCallBackResponseModel

Action-to-PaymentType Mapping:

ActionM2PUpiPaymentType
ApproveAPPROVE_MANDATE
RejectREJECT_MANDATE
Modify + ApproveMODIFY_APPROVE_MANDATE
Modify + RejectMODIFY_REJECT_MANDATE
PausePAUSE_MANDATE
UnpauseUNPAUSE_MANDATE
RevokeREVOKE_MANDATE
UpdateUPDATE_MANDATE

10. Step 6 — Dispute Management

Raise Dispute

Purpose: Raises a complaint/dispute on a completed transaction.

val request = UpiRaiseDisputeRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    seqNo = "1",
    profileId = profileId,
    orgRrn = "ORIGINAL_RRN",                    // RRN from transaction
    orgTxnId = "ORIGINAL_TRANSACTION_ID",        // Transaction ID from transaction
    description = "Amount debited but not received",  // Dispute reason
    disputeType = "COMPLAINT",                   // Dispute type
    deviceInfo = deviceInfo,
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.RAISE_DISPUTE,
    inputType = request,
) as UpiApproveOrRejectResponseModel

Response: UpiApproveOrRejectResponseModel

FieldTypeDescription
statusString?"SUCCESS" or "FAILURE"
result.txnIdString?Dispute transaction ID
result.txnRefString?Dispute transaction reference
result.crnString?Complaint Reference Number (CRN)
exceptionUpiExceptionResponseModel?Error details

Fetch User Dispute List

val request = UpiUserDisputeListRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    status = "ALL",                              // "ALL", "OPEN", "CLOSED", etc.
    seqNo = "1",
    fromDate = "2025-01-01",                     // Start date filter
    toDate = "2026-03-11",                       // End date filter
    profileId = profileId,
    deviceInfo = deviceInfo,
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.DISPUTE_LIST,
    inputType = request,
    queryParams = hashMapOf("pageNo" to 0, "pageSize" to 20),
) as UpiTransactionResponseModel

The response contains UpiTransactionResultModel entries with dispute field populated:

UpiTransactionDisputeModel Fields:

FieldTypeDescription
orgTxnIdString?Original transaction ID
orgRrnString?Original RRN
descriptionString?Dispute description
profileIdString?User's profile ID
crnString?Complaint Reference Number
statusString?Current dispute status
statusDescriptionString?Status description
statusUpdatedBoolean?Whether status was recently updated
createdDateLong?Dispute creation timestamp
modifiedDateLong?Last modification timestamp

11. Profile & Account Management

Update User Profile

val request = UpiUserCommonRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    profileId = profileId,
    deviceInfo = deviceInfo,
    pushKey = "FCM_PUSH_TOKEN",              // Optional: for push notifications
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.UPDATE_USER_PROFILE,
    inputType = request,
) as UpiCommonResponseModel

Delete UPI ID

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.DELETE_UPI_ID,
    inputType = UpiUserCommonRequest(
        channelCode = "YOUR_CHANNEL_CODE",
        profileId = profileId,
        vpaId = "upi-id-to-delete",
        deviceInfo = deviceInfo,
    ),
) as UpiCommonResponseModel

Enable / Disable UPI ID

// Enable
type = M2PUpiServiceName.ENABLE_UPI_ID

// Disable
type = M2PUpiServiceName.DISABLE_UPI_ID

Remove Account

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.REMOVE_ACCOUNT,
    inputType = UpiAccountActionRequest(
        channelCode = "YOUR_CHANNEL_CODE",
        seqNo = "1",
        profileId = profileId,
        accRefNo = "ACCOUNT_REF_NUMBER",
        deviceInfo = deviceInfo,
    ),
) as UpiCommonResponseModel

Manage User Account List

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.MANAGE_USER_ACCOUNT_LIST,
    inputType = commonRequest,
) as UpiUserAccountListResponseModel

Block / Unblock User

val request = UpiBlockOrUnblockRequest(
    channelCode = "YOUR_CHANNEL_CODE",
    profileId = profileId,
    txnId = "TRANSACTION_ID",
    custRef = "CUSTOMER_REFERENCE",
    vpaId = "vpa-to-block@upi",
    action = "BLOCK",                    // "BLOCK" or "UNBLOCK"
    deviceInfo = deviceInfo,
)

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.BLOCK_UNBLOCK_USER,
    inputType = request,
) as UpiCommonResponseModel

Get Blocked User List

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.GET_BLOCKED_USER_LIST,
    inputType = commonRequest,
) as UpiBlockedUserResponseModel

De-Register User Profile

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.DE_REGISTER_USER_PROFILE,
    inputType = commonRequest,
) as UpiCommonResponseModel

Beneficiary Management

Add Beneficiary

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.ADD_BENEFICIARY,
    inputType = UpiAddBeneficiaryRequest(/* ... */),
) as UpiAddBeneficiaryResponseModel

View Beneficiaries

val response = M2PUPIModule.m2pApiCallService(
    context = context,
    header = hashMapOf("tenant" to tenant),
    type = M2PUpiServiceName.VIEW_BENEFICIARY,
    inputType = UpiViewBeneficiaryRequest(/* ... */),
) as UpiBeneficiariesResponseModel

12. Utility Functions

QR Code Generation

import android.graphics.Bitmap
import m2p.upi.sdk.android.utils.M2PUPIModule

fun generateQRCode(
    upiId: String,
    name: String,
    amount: String? = null,
    note: String? = null,
    orgId: String? = null,
    signKey: String? = null
): Bitmap? {
    return M2PUPIModule.generateQRCode(
        upiId = upiId,
        upiName = name,
        amountValue = amount ?: "",
        upiNotes = note ?: "",
        upiOrgId = orgId,
        key = signKey
    )
}

// Usage
val qrBitmap = generateQRCode(
    upiId = "johndoe@mywallet",
    name = "John Doe",
    amount = "500.00",
    note = "Payment for invoice #1234"
)

// Display in ImageView
qrBitmap?.let {
    imageView.setImageBitmap(it)
}

QR Code Validation and Parsing

import m2p.upi.sdk.android.model.UpiTransferPayModel
import m2p.upi.sdk.android.utils.M2PUPIModule

fun parseUpiQRCode(scannedData: String): UpiTransferPayModel {
    return M2PUPIModule.validateUpiData(scannedData)
}

// Usage with QR scanner result
val scannedQR = "upi://pay?pa=merchant@paytm&pn=ABC Store&am=500.00&cu=INR"

val payModel = parseUpiQRCode(scannedQR)

println("Payee VPA: ${payModel.payeeVpa}")
println("Payee Name: ${payModel.payeeName}")
println("Amount: ${payModel.amount}")
println("Currency: ${payModel.currency}")

Verify Signed QR Code

fun verifySignedQR(
    qrData: String,
    publicKey: String
): Boolean {
    return M2PUPIModule.verifySignedQR(
        originalText = qrData,
        publicKey = publicKey
    )
}

// Usage
val isValid = verifySignedQR(
    qrData = scannedQRData,
    publicKey = merchantPublicKey
)

if (isValid) {
    // Proceed with payment
} else {
    // Show error: QR code is tampered or invalid
}

Generate Transaction ID


val txnId = M2PUPIModule.generateTransactionId("MYWALLET")
println(txnId) // e.g., "MYWALLETa1b2c3d4e5f67890..."

Date Validation

fun isValidDate(
    dateString: String,
    format: String = "yyyy-MM-dd",
    requireGmt: Boolean = false,
    isStartDate: Boolean = false
): Boolean {
    return M2PUPIModule.isValidDate(
        expireDateString = dateString,
        dateFormat = format,
        isRequiredGmt = requireGmt,
        isStartDate = isStartDate
    )
}

// Usage
val isValid = isValidDate(
    dateString = "2026-12-31",
    format = "yyyy-MM-dd"
)

Transaction Limits Configuration

// Set transaction limits
M2PUPIModule.p2POfflineLimit = 2000.0              // ₹2000
M2PUPIModule.peerToPeerMaxAmountLimit = 100000.0   // ₹1 Lakh
M2PUPIModule.merchantMaxAmountLimit = 200000.0      // ₹2 Lakhs
M2PUPIModule.specialMerchantMaxAmountLimit = 500000.0 // ₹5 Lakhs
M2PUPIModule.collectRequestMaxAmountLimit = 2000.0   // ₹2000

// Usage in validation
fun validateTransactionAmount(amount: Double, type: String): Boolean {
    return when (type) {
        "P2P" -> amount <= M2PUPIModule.peerToPeerMaxAmountLimit
        "P2M" -> amount <= M2PUPIModule.merchantMaxAmountLimit
        "COLLECT" -> amount <= M2PUPIModule.collectRequestMaxAmountLimit
        else -> false
    }
}
FunctionDescriptionReturn Type
M2PUPIModule.getSDKVersion()Get current SDK versionString ("v1.1.2")
M2PUPIModule.getDeviceID(context)Get unique device identifierString
M2PUPIModule.generateTransactionId(ipCode)Generate unique transaction IDString
M2PUPIModule.getAppId(context)Get host app package nameString
M2PUPIModule.getOsDetail()Get OS version stringString (e.g., "Android 14")
M2PUPIModule.validateUpiData(upiUri)Parse QR code/UPI URI dataUpiTransferPayModel
M2PUPIModule.verifySignedQR(text, publicKey)Verify signed QR codeBoolean
M2PUPIModule.isValidDate(date, format, isGmt, isStart)Validate date stringBoolean
M2PUPIModule.generateQRCode(upiId, notes, name, amount, orgId, key)Generate QR BitmapBitmap?
M2PUPIModule.createDeviceInfoRequest(address, context, mobile, sim)Create device info objectUpiDeviceInfoRequest
M2PUPIModule.clearSDKValues(context)Clear all SDK preferencesUnit

13. Error Handling & Retry Mechanism

Exception Response Model

All API responses include an optional exception field:

data class UpiExceptionResponseModel(
    val detailMessage: String?,
    val shortMessage: String?,
    val cause: String?,
    val errorCode: String?,
    val message: String?,
    val languageCode: String?,
    val errors: List<String>?,
    val fieldError: String?,
    val localizedMessage: String?,
)

Status Checking Pattern

val response = M2PUPIModule.m2pApiCallService(/* ... */) as UpiCommonResponseModel

when {
    response.status.orEmpty().equals("SUCCESS", ignoreCase = true) -> {
        // Handle success
    }
    response.error.orEmpty().isNotEmpty() -> {
        // Handle SDK-level error (e.g., root detection, VPN detected)
    }
    response.exception != null -> {
        val errorCode = response.exception?.errorCode
        val errorMessage = response.exception?.detailMessage
        // Handle API-level error
    }
}

Built-in Retry Mechanism

The SDK implements automatic retry logic for callback-based APIs (payment, mandate, dispute):

ConstantValueDescription
UPI_API_RETRY_COUNT7Maximum retry attempts
UPI_API_TIME_DELAY3000msDelay between retries

Retryable Error Codes:

Error CodeDescription
UPI_007Transaction pending (will retry)
UPI_115Awaiting callback response (will retry)
UPI_149Used in list key validation
UPI_203Used in SIM bind status validation

The SDK automatically retries up to 7 times with a 3-second delay when these error codes are received. After exhausting retries, the last response is returned.

Root Detection Errors

When root detection is enabled and a security issue is detected, the SDK returns a response with status = "FAILURE" and a descriptive error message for:

  • Rooted device detection
  • VPN connection detection
  • Proxy connection detection
  • SSL pinning mismatch

14. Security Features

Encryption (ECDH)

  • SDK uses Elliptic Curve Diffie-Hellman (ECDH) key exchange
  • After KEY_EXCHANGE, all request/response payloads are automatically encrypted
  • Encryption keys are stored securely via AndroidX Security Crypto (EncryptedSharedPreferences)

Root Detection

When enabled (isRootDetectionRequired = true), the SDK checks for:

CheckDescription
Rooted DeviceDetects if device is rooted
VPN DetectionDetects active VPN connections
Proxy DetectionDetects HTTP proxy configurations
SSL Pin ValidationValidates server SSL certificate pin

SSL Pinning

The SDK validates the server's SSL certificate public key against the configured sslPinningKey.

Device Fingerprinting

KEY_EXCHANGE sends the app's SHA signature fingerprint for additional verification:

UpiPairPublicKeyRequest(
    publicKey = generatedPublicKey,
    fingerPrintKey = context.getAppSignatureShaKey().firstOrNull().orEmpty()
)

15. Enums & Constants Reference

M2PUpiServiceName — All API Operations

Enum ValueDescriptionRequest ModelResponse Model
KEY_EXCHANGEExchange encryption keys— (empty string)UpiPairPublicKeyResponse
GET_SIM_BIND_TOKENGet SIM binding tokenUpiSimBindingRequestUpiSimBindingResponseModel
VALIDATE_SIM_BINDValidate SIM bindingUpiSimBindingRequestUpiCheckSimBindStatusResponseModel
REGISTER_PROFILERegister new profileUpiGenerateProfileRequestUpiFetchProfileResponseModel
GET_PROFILEGet existing profileUpiUserCommonRequestUpiFetchProfileResponseModel
GET_LIST_KEYlist keysUpiListKeyRequestUpiListKeyApiResponseModel
CHECK_VPA_AVAILABILITYCheck VPA availabilityUpiCreateVPARequestUpiCommonResponseModel
CHECK_AND_CREATE_VPACheck + create VPAUpiCreateVPARequestUpiCommonResponseModel
CREATE_VPACreate VPAUpiCreateVPARequestUpiCommonResponseModel
GET_ACCOUNT_LISTFetch bank accountsUpiFetchProfileAccountListRequestUpiFetchProfileAccountResponseModel
ADD_USER_ACCOUNTAdd bank accountUpiAddProfileAccountRequestUpiCommonResponseModel
UPDATE_USER_PROFILEUpdate profileUpiUserCommonRequestUpiCommonResponseModel
SET_PRIMARY_ACCOUNTSet primary accountUpiAccountActionRequestUpiCommonResponseModel
SET_PRIMARY_UPI_IDSet primary UPI IDUpiUserCommonRequestUpiCommonResponseModel
DELETE_UPI_IDDelete a UPI IDUpiUserCommonRequestUpiCommonResponseModel
DISABLE_UPI_IDDisable a UPI IDUpiUserCommonRequestUpiCommonResponseModel
ENABLE_UPI_IDEnable a UPI IDUpiUserCommonRequestUpiCommonResponseModel
REMOVE_ACCOUNTRemove bank accountUpiAccountActionRequestUpiCommonResponseModel
GET_BLOCKED_USER_LISTGet blocked usersUpiUserCommonRequestUpiBlockedUserResponseModel
BLOCK_UNBLOCK_USERBlock/unblock userUpiBlockOrUnblockRequestUpiCommonResponseModel
DE_REGISTER_USER_PROFILEDe-register profileUpiUserCommonRequestUpiCommonResponseModel
MANAGE_USER_ACCOUNT_LISTList all accountsUpiUserCommonRequestUpiUserAccountListResponseModel
CHECK_UPI_NUMBER_AVAILABILITYCheck UPI numberUpiNumberRequestUpiNumberResponseModel
CHECK_AND_CREATE_UPI_NUMBERCheck + create UPI numberUpiNumberRequestUpiCommonResponseModel
CREATE_UPI_NUMBERCreate UPI numberUpiNumberRequestUpiCommonResponseModel
PORT_UPI_NUMBERPort UPI numberUpiNumberRequestUpiCommonResponseModel
UPI_NUMBER_ACTIONUPI number actionsUpiNumberRequestUpiCommonResponseModel
CHECK_BALANCECheck account balanceUpiPinRequestUpiCheckBalanceResponseModel
VERIFY_VPAValidate a VPA/UPI IDUpiValidateUpiIDRequestUpiValidateUpiIdResponseModel
ADD_BENEFICIARYAdd beneficiaryUpiAddBeneficiaryRequestUpiAddBeneficiaryResponseModel
VIEW_BENEFICIARYView beneficiariesUpiViewBeneficiaryRequestUpiBeneficiariesResponseModel
RECENT_TRANSACTION_LISTRecent transactionsUpiUserCommonRequestUpiTransactionResponseModel
USER_TRANSACTION_LISTUser transaction historyUpiUserCommonRequestUpiTransactionResponseModel
MAKE_PAYMENTMake UPI paymentUpiPaymentRequestModelUpiPayResponseModel
MONEY_REQUEST_SENDER_LISTSent collect requestsUpiUserCommonRequestUpiTransactionResponseModel
MONEY_REQUEST_RECEIVER_LISTReceived collect requestsUpiUserCommonRequestUpiTransactionResponseModel
MONEY_REQUEST_ACTIONApprove/reject collectUpiCollectRequestModelUpiCommonResponseModel
SEND_MONEY_REQUESTSend collect requestUpiPaymentRequestModelUpiCommonResponseModel
CREATE_MANDATECreate mandateUpiPaymentRequestModelUpiMandateCallBackResponseModel
GET_MANDATE_LISTFetch mandatesUpiFetchMandateRequestUpiMandateResponseModel
MANDATE_ACTIONMandate lifecycle actionsUpiPaymentRequestModelUpiMandateCallBackResponseModel
DISPUTE_LISTFetch disputesUpiUserDisputeListRequestUpiTransactionResponseModel
RAISE_DISPUTERaise disputeUpiRaiseDisputeRequestUpiApproveOrRejectResponseModel
CHECK_UPI_CURRENT_STATUSCheck transaction statusUpiCheckCurrentStatusRequestUpiPayResponseModel
UPI_VERSION_ENABLED_CHECKCheck version supportUpiUserCommonRequestUpiVersionEnableResponseModel

M2PUpiPaymentType — Payment Type Variants

EnumUse Case
DEFAULTStandard P2P/P2M payment
SCAN_AND_PAYQR code scan payment
ATM_WITHDRAWAL_SCAN_AND_PAYATM withdrawal via QR
PAY_COLLECT_REQUESTPay a collect request
SEND_COLLECT_REQUESTSend a collect request
CREATE_MANDATECreate mandate
APPROVE_MANDATEApprove mandate
REJECT_MANDATEReject mandate
MODIFY_APPROVE_MANDATEApprove modified mandate
MODIFY_REJECT_MANDATEReject modified mandate
PAUSE_MANDATEPause mandate
UNPAUSE_MANDATEUnpause mandate
REVOKE_MANDATERevoke mandate
UPDATE_MANDATEUpdate mandate
SIGNED_QRSigned QR code payment
APPROVE_CIRCLE_PAYMENTApprove UPI Circle payment
REJECT_CIRCLE_PAYMENTReject UPI Circle payment
ACTIVATE_INTERNATIONAL_QRActivate international QR
DEACTIVATE_INTERNATIONAL_QRDeactivate international QR
CHECK_BALANCECheck balance

M2PUpiQRCodeType — QR Code Types

EnumDescription
BHARAT_PAY_QRBharatPay standard QR
NORMAL_QRRegular UPI QR
SIGN_QRSigned/secure QR
MANDATE_QRMandate creation QR
INTERNATIONAL_QRInternational QR
ATM_WITHDRAWAL_QRATM withdrawal QR

Integration Flow Summary

┌─────────────────────────────────────────────────────────┐
│                    SDK INITIALIZATION                   │
│              M2PUPIModule.setupUpiSDKValues()           │
└────────────────────────┬────────────────────────────────┘


┌─────────────────────────────────────────────────────────┐
│               KEY EXCHANGE (if encryption)              │
│            M2PUpiServiceName.KEY_EXCHANGE               │
└────────────────────────┬────────────────────────────────┘


       ┌────────────────────────────────────┐   ┌────────────────────────────────────┐   
       │          ONBOARDING                │   │          PROFILE MANAGEMENT        │  
       │                                    │   │   1. SET_PRIMARY_ACCOUNT           │   
       │ 1. GET_SIM_BIND_TOKEN              │   │   2. UPDATE_USER_PROFILE           │   
       │ 2. VALIDATE_SIM_BIND               │   │   3. SET_PRIMARY_UPI_ID            │   
       │ 3. REGISTER_PROFILE/GET_PROFILE    │   │   4. REMOVE_ACCOUNT                │                          
       │ 4. CREATE_VPA                      │   │   5. DELETE_UPI_ID                 │   
       │ 5. GET_ACCOUNT_LIST                │   │   6. DISABLE_UPI_ID                │   
       │ 6. ADD_USER_ACCOUNT                │   │   7. ENABLE_UPI_ID                 │                                           
       └────────────────────────────────────┘   └────────────────────────────────────┘



┌─────────────────────────────────────────────────────────┐
│                   USER IS READY                         │
├──────────┬──────────┬───────────┬──────────┬────────────┤
│ PAYMENT  │ COLLECT  │ MANDATE   │ HISTORY  │  DISPUTE   │
│          │ REQUEST  │           │          │            │
│          │          │           │          │            │
│ VERIFY_  │ SEND_    │ CREATE_   │ RECENT_  │ RAISE_     │
│ VPA      │ MONEY_   │ MANDATE   │ TXN_LIST │ DISPUTE    │
│          │ REQUEST  │           │          │            │
│          │          │           │          │            │
│ MAKE_    │ MONEY_   │ GET_      │ USER_    │ DISPUTE_   │
│ PAYMENT  │ REQUEST_ │ MANDATE_  │ TXN_LIST │ LIST       │
│          │ ACTION   │ LIST      │          │            │
│ CHECK_   │          │           │ CHECK_   │            │
│ BALANCE  │          │ MANDATE_  │ UPI_     │            │
│          │          │ ACTION    │ STATUS   │            │
└──────────┴──────────┴───────────┴──────────┴────────────┘

Document generated from source analysis of UpiSdkWithoutUi module (SDK v1.1.2)

On this page

Table of Contents
1. Introduction
What is M2P UPI Headless SDK?
Who Should Use This SDK?
Key Features
Architecture Overview
2. Prerequisites
System Requirements
Required Permissions
Hardware Requirements
Developer Prerequisites
3. SDK Setup & Initialization
3.1: Add AAR File
3.2: Add SDK Dependencies
3.3: Enable Java 17
3.4: Add Proguard Rule
3.5: Sync Project
3.6: Verify Installation
3.7: SDK SetUp
4. Core Concepts
Central API Entry Point
Common Header
DeviceInfo (Required in Most Requests)
Session Handling (NEW)
Overview
Key Points
5. Key Exchange (Encryption Setup)
6. Onboarding Flow
Complete Onboarding Sequence:
SIM Binding
Step A: Get SIM Bind Token
Step B: Validate SIM Bind Status
SIM Utility Helpers:
Register / Get Profile
Register Profile (New User)
Get Existing Profile
Create VPA (UPI ID)
Step A: Check VPA Availability
Step B: Create VPA
Step B (Alternative): Check & Create VPA in One Call
Link Account (Fetch & Add Account)
Fetch Account List
Add Account to Profile
Set Primary Account & UPI ID
Set Primary Account
Set Primary UPI ID
7. Payment Flow
Amount Limits (Configurable)
Verify VPA (Validate Payee)
Sample Response for Person VPA: 
Sample Response for Merchant VPA: 
Make Payment (Pay)
Scan & Pay (QR Code)
Parse QR Code Data
Handle QR Types
Verify Signed QR
Generate QR Code
Payment Types for Scan & Pay
Check Balance
Collect Request (Money Request)
Send a Collect Request
Fetch Collect Requests (Sent / Received)
Approve / Reject a Collect Request
8. Transaction Management
Recent Transaction List
User Transaction List (Paginated)
Check UPI Current Status
9. Mandate Management
Mandate Payment Types
Create Mandate
Fetch Mandate List
Mandate Actions
10. Step 6 — Dispute Management
Raise Dispute
Fetch User Dispute List
11. Profile & Account Management
Update User Profile
Delete UPI ID
Enable / Disable UPI ID
Remove Account
Manage User Account List
Block / Unblock User
Get Blocked User List
De-Register User Profile
Beneficiary Management
Add Beneficiary
View Beneficiaries
12. Utility Functions
QR Code Generation
QR Code Validation and Parsing
Verify Signed QR Code
Generate Transaction ID
Date Validation
Transaction Limits Configuration
13. Error Handling & Retry Mechanism
Exception Response Model
Status Checking Pattern
Built-in Retry Mechanism
Root Detection Errors
14. Security Features
Encryption (ECDH)
Root Detection
SSL Pinning
Device Fingerprinting
15. Enums & Constants Reference
M2PUpiServiceName — All API Operations
M2PUpiPaymentType — Payment Type Variants
M2PUpiQRCodeType — QR Code Types
Integration Flow Summary