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
- Introduction
- Prerequisites
- SDK Setup & Initialization
- Core Concepts
- Key Exchange (Encryption Setup)
- Onboarding Flow
- Payment Flow
- Transaction Management
- Mandate Management
- Dispute Management
- 10.1 Raise Dispute
- 10.2 Fetch User Dispute List
- Profile & Account Management
- Utility Functions
- Error Handling & Retry Mechanism
- Security Features
- 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
| Requirement | Minimum | Recommended |
|---|---|---|
| Android OS | API 26 (Android 8.0 Oreo) | API 33+ (Android 13+) |
| JDK | Java 11 | Java 17 |
| Kotlin | 1.7.0 | 1.9.0+ |
| Gradle | 7.0 | 8.0+ |
| Android Gradle Plugin | 7.2.0 | 8.0.0+ |
| Storage | 50 MB free space | 100 MB |
| Network | TLS 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
suspendfunctions - 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.
- Create a
libsdirectory in your app module if it doesn't already exist. - Copy the
UpiSdkWithoutUi.aarfile intoapp/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.Type3.5: Sync Project
Click "Sync Now" in Android Studio or run:
./gradlew clean build3.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:
| Enum | Description |
|---|---|
M2PUpiEnvironmentEnum.UAT | Testing / Sandbox |
M2PUpiEnvironmentEnum.PROD | Production |
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
typeparameter 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:
| Field | Type | Description |
|---|---|---|
deviceId | String | Unique device identifier (auto-generated) |
deviceType | String | Always "MOB" |
os | String | e.g., "Android 14" |
telecom | String | SIM provider name |
geoCode | String | Latitude,Longitude (e.g., "13.01,80.21") |
appId | String | Host app package name |
ipAddress | String | Device IP address |
location | String | Locality name (max 40 chars) |
mobile | String | User 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_EXCHANGEAPI call). - The SDK internally manages the session lifecycle and stores the session reference securely.
- A session is established after a successful key exchange (
-
Session Reference:
- The
requestReferenceIdreturned from theKEY_EXCHANGEresponse 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.
- The
-
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 UpiPairPublicKeyResponseResponse: UpiPairPublicKeyResponse
| Field | Type | Description |
|---|---|---|
code | Int? | HTTP status code |
success | String? | "TRUE" on success |
detailMessage | String? | Error detail if failed |
requestReferenceId | String? | Reference ID for encrypted session |
result.publicKey | String? | Server's public key |
result.sslPinningKey | String? | 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 UpiSimBindingResponseModelResponse: UpiSimBindingResponseModel
| Field | Type | Description |
|---|---|---|
status | String? | "SUCCESS" or "FAILURE" |
result.data | String? | SMS data payload to send |
result.to | String? | SMS destination number |
error | String? | Error message |
exception | UpiExceptionResponseModel? | 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 UpiCheckSimBindStatusResponseModelResponse: UpiCheckSimBindStatusResponseModel
| Field | Type | Description |
|---|---|---|
status | String? | "SUCCESS" if SIM is bound |
result.id | String? | Profile ID |
result.name | String? | User name |
result.key | String? | Key value |
result.ifsc | String? | IFSC code |
result.bankName | String? | Bank name |
result.ipCode | String? | IP code (used for transaction ID generation) |
result.accounts | List? | Linked accounts |
result.vpas | List? | Existing VPAs |
result.upiNo | List? | 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 UpiFetchProfileResponseModelGet 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 UpiFetchProfileResponseModelResponse: UpiFetchProfileResponseModel
| Field | Type | Description |
|---|---|---|
status | String? | "SUCCESS" or "FAILURE" |
result.id | String? | Profile ID (store this — needed for all subsequent calls) |
result.mobile | String? | Registered mobile number |
result.name | String? | User name |
result.channelCode | String? | Channel code |
result.profileStatus | String? | Profile status |
result.accounts | List<UpiFetchProfileAccountModel>? | Linked bank accounts |
result.vpas | List<UpiFetchProfileVPAModel>? | Created VPAs (UPI IDs) |
result.upiNo | List<UpiFetchProfileUpiNumberModel>? | UPI numbers |
result.ipCode | String? | IP Code (needed for generating transaction IDs) |
result.type | String? | Profile type ("PERSON") |
result.delegateDetails | List<UpiDelegateDetailsModel>? | UPI Circle delegate details |
Key Data to Store:
result.id→profileId(used in all subsequent API calls)result.ipCode→ipCode(used forgenerateTransactionId())result.accounts→ Linked accounts listresult.vpas→ User's VPA addressesresult.upiNo→ User's UPI Number Detailresult.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 UpiCommonResponseModelStep B: Create VPA
val response = M2PUPIModule.m2pApiCallService(
context = context,
header = hashMapOf("tenant" to tenant),
type = M2PUpiServiceName.CREATE_VPA,
inputType = request, // Same UpiCreateVPARequest
) as UpiCommonResponseModelStep 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 UpiCommonResponseModelResponse: UpiCommonResponseModel
| Field | Type | Description |
|---|---|---|
status | String? | "SUCCESS" or "FAILURE" |
message | String? | Success/failure message |
exception | UpiExceptionResponseModel? | Error details |
Link Account (Fetch & Add Account)
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 UpiFetchProfileAccountResponseModelAdd 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 UpiCommonResponseModelSet 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 UpiCommonResponseModelSet 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 UpiCommonResponseModel7. Payment Flow
Amount Limits (Configurable)
| Limit | Default 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 UpiValidateUpiIdResponseModelSample 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 UpiPayResponseModelResponse: UpiPayResponseModel
| Field | Type | Description |
|---|---|---|
status | String? | "SUCCESS" or "FAILURE" |
result.status | String? | Transaction status |
result.txnId | String? | Transaction ID |
result.custRef | String? | Customer reference (RRN) |
result.approvalNum | String? | Approval number |
result.errCode | String? | Error code |
result.errDescription | String? | Error description |
result.ref | List<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 PAYERScan & Pay (QR Code)
Parse QR Code Data
val payModel: UpiTransferPayModel = M2PUPIModule.validateUpiData(scannedQRString)The returned UpiTransferPayModel is auto-populated with:
payeeId,payeeName,amount,transactionId,descriptionqrCodeType— one of:BHARAT_PAY_QR,NORMAL_QR,SIGN_QR,MANDATE_QR,INTERNATIONAL_QR,ATM_WITHDRAWAL_QRisQRExpired,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 UpiCheckBalanceResponseModelResponse Fields:
| Field | Type | Description |
|---|---|---|
result.bal | String? | Account balance |
result.currency | String? | 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 UpiCommonResponseModelFetch 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 UpiTransactionResponseModelApprove / 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 UpiCommonResponseModel8. 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 UpiTransactionResponseModelUser 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 UpiTransactionResponseModelResponse: UpiTransactionResponseModel
| Field | Type | Description |
|---|---|---|
status | String? | "SUCCESS" or "FAILURE" |
pagination.pageNo | Int? | Current page number |
pagination.pageSize | Int? | Items per page |
pagination.totalPages | Int? | Total pages |
pagination.totalElements | Int? | Total transaction count |
result | List<UpiTransactionResultModel>? | Transaction list |
UpiTransactionResultModel Fields:
| Field | Type | Description |
|---|---|---|
txnId | String? | Transaction ID |
orgTxnId | String? | Original transaction ID |
rrn | String? | Retrieval Reference Number |
amount | String? | Transaction amount |
txnType | String? | Transaction type (PAY, COLLECT) |
txnSubType | String? | Sub-type (DEBIT, CREDIT) |
status | String? | Transaction status |
createdDate | Long? | Unix timestamp of creation |
payeeName | String? | Payee name |
payeeAddr | String? | Payee VPA address |
payerName | String? | Payer name |
payerAddr | String? | Payer VPA address |
remarks | String? | Transaction remarks |
description | String? | Description |
isComplaintRaised | Boolean? | Whether a dispute exists |
dispute | UpiTransactionDisputeModel? | 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 UpiPayResponseModel9. Mandate Management
Purpose: UPI Mandates allow recurring/auto-debit payments. The SDK supports creation, listing, and lifecycle actions on mandates.
Mandate Payment Types
| Enum | Description |
|---|---|
M2PUpiPaymentType.CREATE_MANDATE | Create a new mandate |
M2PUpiPaymentType.APPROVE_MANDATE | Approve a mandate request |
M2PUpiPaymentType.REJECT_MANDATE | Reject a mandate request |
M2PUpiPaymentType.MODIFY_APPROVE_MANDATE | Approve a modification |
M2PUpiPaymentType.MODIFY_REJECT_MANDATE | Reject a modification |
M2PUpiPaymentType.PAUSE_MANDATE | Pause an active mandate |
M2PUpiPaymentType.UNPAUSE_MANDATE | Resume a paused mandate |
M2PUpiPaymentType.REVOKE_MANDATE | Revoke/cancel a mandate |
M2PUpiPaymentType.UPDATE_MANDATE | Update 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 UpiMandateCallBackResponseModelResponse: UpiMandateCallBackResponseModel
| Field | Type | Description |
|---|---|---|
status | String? | "SUCCESS" or "FAILURE" |
message | String? | Result message |
result.reqMsgId | String? | Request message ID |
result.result | String? | Result status |
result.txnId | String? | Transaction ID |
result.refId | String? | 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 UpiMandateResponseModelResponse: UpiMandateResponseModel
| Field | Type | Description |
|---|---|---|
status | String? | "SUCCESS" or "FAILURE" |
pagination | UpiPaginationResponseModel? | Pagination details |
result | List<UpiMandateResultModel>? | List of mandates |
UpiMandateResultModel Key Fields:
| Field | Type | Description |
|---|---|---|
mandateStatus | String? | Current status (ACTIVE, PAUSED, REVOKED, etc.) |
mandate | UpiMandateResultMandateModel? | Mandate details (amount, dates, recurrence) |
payee | UpiMandatePayeeModel? | Payee details |
payer | UpiMandatePayerModel? | Payer details |
head | UpiMandateHeadModel? | Header info |
txn | UpiMandateTransactionModel? | Transaction details |
pauseFlag | Boolean? | Whether mandate is paused |
pauseMandateStartDate | String? | Pause start date |
pauseMandateEndDate | String? | Pause end date |
lastPaidOn | ArrayList<String>? | Last payment dates |
paymentDetails | List<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 UpiMandateCallBackResponseModelAction-to-PaymentType Mapping:
| Action | M2PUpiPaymentType |
|---|---|
| Approve | APPROVE_MANDATE |
| Reject | REJECT_MANDATE |
| Modify + Approve | MODIFY_APPROVE_MANDATE |
| Modify + Reject | MODIFY_REJECT_MANDATE |
| Pause | PAUSE_MANDATE |
| Unpause | UNPAUSE_MANDATE |
| Revoke | REVOKE_MANDATE |
| Update | UPDATE_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 UpiApproveOrRejectResponseModelResponse: UpiApproveOrRejectResponseModel
| Field | Type | Description |
|---|---|---|
status | String? | "SUCCESS" or "FAILURE" |
result.txnId | String? | Dispute transaction ID |
result.txnRef | String? | Dispute transaction reference |
result.crn | String? | Complaint Reference Number (CRN) |
exception | UpiExceptionResponseModel? | 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 UpiTransactionResponseModelThe response contains UpiTransactionResultModel entries with dispute field populated:
UpiTransactionDisputeModel Fields:
| Field | Type | Description |
|---|---|---|
orgTxnId | String? | Original transaction ID |
orgRrn | String? | Original RRN |
description | String? | Dispute description |
profileId | String? | User's profile ID |
crn | String? | Complaint Reference Number |
status | String? | Current dispute status |
statusDescription | String? | Status description |
statusUpdated | Boolean? | Whether status was recently updated |
createdDate | Long? | Dispute creation timestamp |
modifiedDate | Long? | 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 UpiCommonResponseModelDelete 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 UpiCommonResponseModelEnable / Disable UPI ID
// Enable
type = M2PUpiServiceName.ENABLE_UPI_ID
// Disable
type = M2PUpiServiceName.DISABLE_UPI_IDRemove 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 UpiCommonResponseModelManage User Account List
val response = M2PUPIModule.m2pApiCallService(
context = context,
header = hashMapOf("tenant" to tenant),
type = M2PUpiServiceName.MANAGE_USER_ACCOUNT_LIST,
inputType = commonRequest,
) as UpiUserAccountListResponseModelBlock / 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 UpiCommonResponseModelGet Blocked User List
val response = M2PUPIModule.m2pApiCallService(
context = context,
header = hashMapOf("tenant" to tenant),
type = M2PUpiServiceName.GET_BLOCKED_USER_LIST,
inputType = commonRequest,
) as UpiBlockedUserResponseModelDe-Register User Profile
val response = M2PUPIModule.m2pApiCallService(
context = context,
header = hashMapOf("tenant" to tenant),
type = M2PUpiServiceName.DE_REGISTER_USER_PROFILE,
inputType = commonRequest,
) as UpiCommonResponseModelBeneficiary Management
Add Beneficiary
val response = M2PUPIModule.m2pApiCallService(
context = context,
header = hashMapOf("tenant" to tenant),
type = M2PUpiServiceName.ADD_BENEFICIARY,
inputType = UpiAddBeneficiaryRequest(/* ... */),
) as UpiAddBeneficiaryResponseModelView Beneficiaries
val response = M2PUPIModule.m2pApiCallService(
context = context,
header = hashMapOf("tenant" to tenant),
type = M2PUpiServiceName.VIEW_BENEFICIARY,
inputType = UpiViewBeneficiaryRequest(/* ... */),
) as UpiBeneficiariesResponseModel12. 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
}
}| Function | Description | Return Type |
|---|---|---|
M2PUPIModule.getSDKVersion() | Get current SDK version | String ("v1.1.2") |
M2PUPIModule.getDeviceID(context) | Get unique device identifier | String |
M2PUPIModule.generateTransactionId(ipCode) | Generate unique transaction ID | String |
M2PUPIModule.getAppId(context) | Get host app package name | String |
M2PUPIModule.getOsDetail() | Get OS version string | String (e.g., "Android 14") |
M2PUPIModule.validateUpiData(upiUri) | Parse QR code/UPI URI data | UpiTransferPayModel |
M2PUPIModule.verifySignedQR(text, publicKey) | Verify signed QR code | Boolean |
M2PUPIModule.isValidDate(date, format, isGmt, isStart) | Validate date string | Boolean |
M2PUPIModule.generateQRCode(upiId, notes, name, amount, orgId, key) | Generate QR Bitmap | Bitmap? |
M2PUPIModule.createDeviceInfoRequest(address, context, mobile, sim) | Create device info object | UpiDeviceInfoRequest |
M2PUPIModule.clearSDKValues(context) | Clear all SDK preferences | Unit |
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):
| Constant | Value | Description |
|---|---|---|
UPI_API_RETRY_COUNT | 7 | Maximum retry attempts |
UPI_API_TIME_DELAY | 3000ms | Delay between retries |
Retryable Error Codes:
| Error Code | Description |
|---|---|
UPI_007 | Transaction pending (will retry) |
UPI_115 | Awaiting callback response (will retry) |
UPI_149 | Used in list key validation |
UPI_203 | Used 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:
| Check | Description |
|---|---|
| Rooted Device | Detects if device is rooted |
| VPN Detection | Detects active VPN connections |
| Proxy Detection | Detects HTTP proxy configurations |
| SSL Pin Validation | Validates 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 Value | Description | Request Model | Response Model |
|---|---|---|---|
KEY_EXCHANGE | Exchange encryption keys | — (empty string) | UpiPairPublicKeyResponse |
GET_SIM_BIND_TOKEN | Get SIM binding token | UpiSimBindingRequest | UpiSimBindingResponseModel |
VALIDATE_SIM_BIND | Validate SIM binding | UpiSimBindingRequest | UpiCheckSimBindStatusResponseModel |
REGISTER_PROFILE | Register new profile | UpiGenerateProfileRequest | UpiFetchProfileResponseModel |
GET_PROFILE | Get existing profile | UpiUserCommonRequest | UpiFetchProfileResponseModel |
GET_LIST_KEY | list keys | UpiListKeyRequest | UpiListKeyApiResponseModel |
CHECK_VPA_AVAILABILITY | Check VPA availability | UpiCreateVPARequest | UpiCommonResponseModel |
CHECK_AND_CREATE_VPA | Check + create VPA | UpiCreateVPARequest | UpiCommonResponseModel |
CREATE_VPA | Create VPA | UpiCreateVPARequest | UpiCommonResponseModel |
GET_ACCOUNT_LIST | Fetch bank accounts | UpiFetchProfileAccountListRequest | UpiFetchProfileAccountResponseModel |
ADD_USER_ACCOUNT | Add bank account | UpiAddProfileAccountRequest | UpiCommonResponseModel |
UPDATE_USER_PROFILE | Update profile | UpiUserCommonRequest | UpiCommonResponseModel |
SET_PRIMARY_ACCOUNT | Set primary account | UpiAccountActionRequest | UpiCommonResponseModel |
SET_PRIMARY_UPI_ID | Set primary UPI ID | UpiUserCommonRequest | UpiCommonResponseModel |
DELETE_UPI_ID | Delete a UPI ID | UpiUserCommonRequest | UpiCommonResponseModel |
DISABLE_UPI_ID | Disable a UPI ID | UpiUserCommonRequest | UpiCommonResponseModel |
ENABLE_UPI_ID | Enable a UPI ID | UpiUserCommonRequest | UpiCommonResponseModel |
REMOVE_ACCOUNT | Remove bank account | UpiAccountActionRequest | UpiCommonResponseModel |
GET_BLOCKED_USER_LIST | Get blocked users | UpiUserCommonRequest | UpiBlockedUserResponseModel |
BLOCK_UNBLOCK_USER | Block/unblock user | UpiBlockOrUnblockRequest | UpiCommonResponseModel |
DE_REGISTER_USER_PROFILE | De-register profile | UpiUserCommonRequest | UpiCommonResponseModel |
MANAGE_USER_ACCOUNT_LIST | List all accounts | UpiUserCommonRequest | UpiUserAccountListResponseModel |
CHECK_UPI_NUMBER_AVAILABILITY | Check UPI number | UpiNumberRequest | UpiNumberResponseModel |
CHECK_AND_CREATE_UPI_NUMBER | Check + create UPI number | UpiNumberRequest | UpiCommonResponseModel |
CREATE_UPI_NUMBER | Create UPI number | UpiNumberRequest | UpiCommonResponseModel |
PORT_UPI_NUMBER | Port UPI number | UpiNumberRequest | UpiCommonResponseModel |
UPI_NUMBER_ACTION | UPI number actions | UpiNumberRequest | UpiCommonResponseModel |
CHECK_BALANCE | Check account balance | UpiPinRequest | UpiCheckBalanceResponseModel |
VERIFY_VPA | Validate a VPA/UPI ID | UpiValidateUpiIDRequest | UpiValidateUpiIdResponseModel |
ADD_BENEFICIARY | Add beneficiary | UpiAddBeneficiaryRequest | UpiAddBeneficiaryResponseModel |
VIEW_BENEFICIARY | View beneficiaries | UpiViewBeneficiaryRequest | UpiBeneficiariesResponseModel |
RECENT_TRANSACTION_LIST | Recent transactions | UpiUserCommonRequest | UpiTransactionResponseModel |
USER_TRANSACTION_LIST | User transaction history | UpiUserCommonRequest | UpiTransactionResponseModel |
MAKE_PAYMENT | Make UPI payment | UpiPaymentRequestModel | UpiPayResponseModel |
MONEY_REQUEST_SENDER_LIST | Sent collect requests | UpiUserCommonRequest | UpiTransactionResponseModel |
MONEY_REQUEST_RECEIVER_LIST | Received collect requests | UpiUserCommonRequest | UpiTransactionResponseModel |
MONEY_REQUEST_ACTION | Approve/reject collect | UpiCollectRequestModel | UpiCommonResponseModel |
SEND_MONEY_REQUEST | Send collect request | UpiPaymentRequestModel | UpiCommonResponseModel |
CREATE_MANDATE | Create mandate | UpiPaymentRequestModel | UpiMandateCallBackResponseModel |
GET_MANDATE_LIST | Fetch mandates | UpiFetchMandateRequest | UpiMandateResponseModel |
MANDATE_ACTION | Mandate lifecycle actions | UpiPaymentRequestModel | UpiMandateCallBackResponseModel |
DISPUTE_LIST | Fetch disputes | UpiUserDisputeListRequest | UpiTransactionResponseModel |
RAISE_DISPUTE | Raise dispute | UpiRaiseDisputeRequest | UpiApproveOrRejectResponseModel |
CHECK_UPI_CURRENT_STATUS | Check transaction status | UpiCheckCurrentStatusRequest | UpiPayResponseModel |
UPI_VERSION_ENABLED_CHECK | Check version support | UpiUserCommonRequest | UpiVersionEnableResponseModel |
M2PUpiPaymentType — Payment Type Variants
| Enum | Use Case |
|---|---|
DEFAULT | Standard P2P/P2M payment |
SCAN_AND_PAY | QR code scan payment |
ATM_WITHDRAWAL_SCAN_AND_PAY | ATM withdrawal via QR |
PAY_COLLECT_REQUEST | Pay a collect request |
SEND_COLLECT_REQUEST | Send a collect request |
CREATE_MANDATE | Create mandate |
APPROVE_MANDATE | Approve mandate |
REJECT_MANDATE | Reject mandate |
MODIFY_APPROVE_MANDATE | Approve modified mandate |
MODIFY_REJECT_MANDATE | Reject modified mandate |
PAUSE_MANDATE | Pause mandate |
UNPAUSE_MANDATE | Unpause mandate |
REVOKE_MANDATE | Revoke mandate |
UPDATE_MANDATE | Update mandate |
SIGNED_QR | Signed QR code payment |
APPROVE_CIRCLE_PAYMENT | Approve UPI Circle payment |
REJECT_CIRCLE_PAYMENT | Reject UPI Circle payment |
ACTIVATE_INTERNATIONAL_QR | Activate international QR |
DEACTIVATE_INTERNATIONAL_QR | Deactivate international QR |
CHECK_BALANCE | Check balance |
M2PUpiQRCodeType — QR Code Types
| Enum | Description |
|---|---|
BHARAT_PAY_QR | BharatPay standard QR |
NORMAL_QR | Regular UPI QR |
SIGN_QR | Signed/secure QR |
MANDATE_QR | Mandate creation QR |
INTERNATIONAL_QR | International QR |
ATM_WITHDRAWAL_QR | ATM 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)
