Android · Maven Central · Kotlin & Java
Overview
The official Android SDK wraps the SabPaisa Payment Gateway 2.0 API for both Kotlin and Java apps. It handles HMAC checksum signing, launches the checkout page via Chrome Custom Tabs, WebView, or an external browser, and returns the result through standard Android lifecycle callbacks.
Kotlin & Java
Coroutines for Kotlin, CompletableFuture for Java
Chrome Custom Tabs
Best-UX checkout via androidx.browser
WebView Mode
Embedded checkout with onActivityResult callback
Auto Checksum
verifyReturnUrl validates HMAC for you
Idempotency Keys
Safe refund retries with no duplicates
Typed Exceptions
ValidationException, ApiException, ChecksumException
Requirements
CompletableFuture for async return types. Kotlin apps can target API 21+ via coroutines.Installation
Add the Maven Central repository (usually already present) and the dependency to your app's build file.
1repositories {
2 mavenCentral()
3}
4
5dependencies {
6 implementation("in.sabpaisa:sabpaisa-pg:1.1.0")
7}1dependencies {
2 implementation 'in.sabpaisa:sabpaisa-pg:1.1.0'
3}Kotlin vs Java
The SDK is written in Kotlin. Kotlin projects use SabPaisaClient directly with suspend functions. Java projects use SabPaisaClientJava, a wrapper exposing the same API with CompletableFuture<T> return types. Both share the same models, exceptions, and config.
| Kotlin | Java | |
|---|---|---|
| Client class | SabPaisaClient | SabPaisaClientJava |
| Min SDK | 21 | 24 |
| Async model | suspend / coroutines | CompletableFuture<T> |
| Cleanup | Cancel coroutine scope | client.close() |
SabPaisaClientJava methods use @JvmOverloads, so optional parameters can be omitted. However, model data classes (CreatePaymentRequest, RefundListParams, etc.) do not have @JvmOverloads — pass all constructor arguments, using null for optional fields.Setup
Create the client once (e.g. in your Application subclass) and reuse it across your app. SabPaisaConfig throws SabPaisaException immediately if any required field is blank or env is invalid.
1import `in`.sabpaisa.pg.SabPaisaClient
2import `in`.sabpaisa.pg.SabPaisaConfig
3
4val config = SabPaisaConfig(
5 apiKey = "your-api-key",
6 merchantId = "your-merchant-id",
7 secretKey = "your-secret-key",
8 clientCode = "your-client-code",
9 env = "staging" // or "production"
10)
11
12val sabpaisa = SabPaisaClient(config)1import in.sabpaisa.pg.SabPaisaClientJava;
2import in.sabpaisa.pg.SabPaisaConfig;
3
4SabPaisaConfig config = new SabPaisaConfig(
5 "your-api-key", // apiKey
6 "your-merchant-id", // merchantId
7 "your-secret-key", // secretKey
8 "your-client-code", // clientCode
9 "staging", // env — or "production"
10 null // baseUrl (optional)
11);
12
13SabPaisaClientJava sabpaisa = new SabPaisaClientJava(config);
14
15// When done:
16sabpaisa.close();in is a Kotlin keyword, so imports require backtick escaping: import `in`.sabpaisa.pg.*. Java does not need this.Accept a Payment
50000 for ₹500.AGet URL and redirect yourself
Create a session, then launch the returned URL however you like.
1import `in`.sabpaisa.pg.models.CreatePaymentRequest
2
3viewModelScope.launch {
4 val response = sabpaisa.payments.createSession(
5 CreatePaymentRequest(
6 merchantTxnId = "ORDER-$orderId",
7 amount = 50000,
8 customerName = "Ravi Kumar",
9 customerEmail = "[email protected]",
10 customerMobile = "9876543210",
11 udfFields = mapOf("udf1" to "campus-north", "udf2" to "semester-6")
12 )
13 )
14 val checkoutUrl = sabpaisa.payments.getCheckoutUrl(response)
15 startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(checkoutUrl)))
16}1import in.sabpaisa.pg.models.CreatePaymentRequest;
2
3// Data class — pass all 10 args; last 5 (currency, returnUrl, paymentMode, metadata, udfFields) are optional
4Map<String, String> udfFields = new HashMap<>();
5udfFields.put("udf1", "campus-north");
6udfFields.put("udf2", "semester-6");
7
8CreatePaymentRequest params = new CreatePaymentRequest(
9 "ORDER-" + orderId, 50000, "Ravi Kumar", "[email protected]", "9876543210",
10 "INR", null, null, null, udfFields
11);
12
13sabpaisa.createSession(params)
14 .thenAccept(response -> {
15 String url = sabpaisa.getCheckoutUrl(response);
16 runOnUiThread(() -> startActivity(
17 new Intent(Intent.ACTION_VIEW, Uri.parse(url))
18 ));
19 })
20 .exceptionally(e -> { handleError(e.getCause()); return null; });BChrome Custom Tabs Recommended
Best UX — opens checkout in an in-app Chrome tab. CheckoutMode.CUSTOM_TABS is the default.
1import `in`.sabpaisa.pg.CheckoutMode
2
3viewModelScope.launch {
4 sabpaisa.payments.redirectToCheckout(params, activity = this@MyActivity)
5}1sabpaisa.redirectToCheckout(params, this /* Activity */)
2 .thenAccept(response -> runOnUiThread(() ->
3 Log.d("SabPaisa", "Checkout launched: " + response.getPaymentId())
4 ))
5 .exceptionally(e -> { handleError(e.getCause()); return null; });CIn-App WebView
Embedded WebView. Result is delivered via onActivityResult using Payments.WEBVIEW_REQUEST_CODE.
1viewModelScope.launch {
2 sabpaisa.payments.redirectToCheckout(
3 params,
4 activity = this@MyActivity,
5 mode = CheckoutMode.IN_APP_WEBVIEW,
6 returnUrl = "https://yourapp.com/callback"
7 )
8}
9
10// In onActivityResult:
11override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
12 super.onActivityResult(requestCode, resultCode, data)
13 if (requestCode == Payments.WEBVIEW_REQUEST_CODE && resultCode == RESULT_OK) {
14 val callbackUrl = data?.getStringExtra(Payments.EXTRA_CALLBACK_URL)
15 // verify with sabpaisa.payments.verifyReturnUrl(callbackUrl!!)
16 }
17}1sabpaisa.redirectToCheckout(
2 params, this, CheckoutMode.IN_APP_WEBVIEW, "https://yourapp.com/callback"
3).thenAccept(response ->
4 Log.d("SabPaisa", "WebView launched")
5).exceptionally(e -> { handleError(e.getCause()); return null; });
6
7@Override
8protected void onActivityResult(int requestCode, int resultCode, Intent data) {
9 super.onActivityResult(requestCode, resultCode, data);
10 if (requestCode == Payments.WEBVIEW_REQUEST_CODE && resultCode == RESULT_OK) {
11 String callbackUrl = data.getStringExtra(Payments.EXTRA_CALLBACK_URL);
12 // verify with sabpaisa.verifyReturnUrl(callbackUrl)
13 }
14}DLaunch checkout with an existing URL
If you already have a checkout URL (for example, created by your backend), use launchCheckout directly.
1sabpaisa.payments.launchCheckout(
2 activity = this@MyActivity,
3 checkoutUrl = "https://merchant-api.sabpaisa.in/checkout?clientSecret=...",
4 mode = CheckoutMode.CUSTOM_TABS
5)1sabpaisa.launchCheckout(this, checkoutUrl, CheckoutMode.CUSTOM_TABS);User-Defined Fields (UDF)
CreatePaymentRequest supports up to 20 user-defined fields (udf1 through udf20) via the udfFields parameter. Each value can be up to 255 characters. These are returned in the enquiry response as udfData.
1val params = CreatePaymentRequest(
2 merchantTxnId = "ORDER-$orderId",
3 amount = 50000,
4 customerName = "Ravi Kumar",
5 customerEmail = "[email protected]",
6 customerMobile = "9876543210",
7 udfFields = mapOf(
8 "udf1" to "campus-north",
9 "udf2" to "semester-6",
10 "udf3" to "hostel-block-A",
11 "udf4" to "engineering",
12 "udf5" to "tuition-fee"
13 // ... up to udf20
14 )
15)1Map<String, String> udfFields = new HashMap<>();
2udfFields.put("udf1", "campus-north");
3udfFields.put("udf2", "semester-6");
4udfFields.put("udf3", "hostel-block-A");
5udfFields.put("udf4", "engineering");
6udfFields.put("udf5", "tuition-fee");
7// ... up to udf20
8
9CreatePaymentRequest params = new CreatePaymentRequest(
10 "ORDER-" + orderId, 50000, "Ravi Kumar", "[email protected]", "9876543210",
11 "INR", null, null, null, udfFields
12);UDF data is returned in the enquiry response:
1// Kotlin
2enquiry.data.udfData?.forEach { (k, v) -> Log.d("SabPaisa", "$k = $v") }1// Java
2if (r.getData().getUdfData() != null) {
3 r.getData().getUdfData().forEach((k, v) -> Log.d("SabPaisa", k + " = " + v));
4}Verify Payment After Redirect
verifyReturnUrl accepts the full callback URL string and verifies the checksum internally. It extracts all query parameters, sorts them, and validates the HMAC signature.
1val callbackUrl = data?.getStringExtra(Payments.EXTRA_CALLBACK_URL) ?: return
2
3if (sabpaisa.payments.verifyReturnUrl(callbackUrl)) {
4 // Signature valid — parse query params for status, merchantTxnId, etc.
5 val uri = Uri.parse(callbackUrl)
6 val status = uri.getQueryParameter("status") // "SUCCESS" or "FAILED"
7} else {
8 // Signature invalid — reject
9}1String callbackUrl = data.getStringExtra(Payments.EXTRA_CALLBACK_URL);
2
3if (sabpaisa.verifyReturnUrl(callbackUrl)) {
4 // Signature valid — parse query params
5 Uri uri = Uri.parse(callbackUrl);
6 String status = uri.getQueryParameter("status");
7} else {
8 // Signature invalid — reject
9}Transaction Enquiry
Look up the authoritative status of any transaction using the merchant transaction ID.
1viewModelScope.launch {
2 val enquiry = sabpaisa.transactions.enquiry(merchantTxnId = "ORDER-$orderId")
3 Log.d("SabPaisa", "Status: ${enquiry.data.status}")
4 Log.d("SabPaisa", "TxnId: ${enquiry.data.txnId}")
5 Log.d("SabPaisa", "Mode: ${enquiry.data.paymentMode}")
6 enquiry.data.udfData?.forEach { (k, v) -> Log.d("SabPaisa", "UDF $k = $v") }
7}1sabpaisa.enquiry("ORDER-" + orderId)
2 .thenAccept(r -> runOnUiThread(() -> {
3 Log.d("SabPaisa", "Status: " + r.getData().getStatus());
4 Log.d("SabPaisa", "TxnId: " + r.getData().getTxnId());
5 Log.d("SabPaisa", "Mode: " + r.getData().getPaymentMode());
6 if (r.getData().getUdfData() != null) {
7 r.getData().getUdfData().forEach((k, v) -> Log.d("SabPaisa", "UDF " + k + " = " + v));
8 }
9 }))
10 .exceptionally(e -> { handleError(e.getCause()); return null; });Refunds
Initiate refunds, check refund status, and list refunds with optional filters.
1Create a refund
Pass the SabPaisa txnId from your enquiry, the amount in paise, and an optional reason. Always include an idempotency key in production.
1import `in`.sabpaisa.pg.models.CreateRefundRequest
2import `in`.sabpaisa.pg.models.RequestOptions
3
4viewModelScope.launch {
5 val refund = sabpaisa.refunds.create(
6 params = CreateRefundRequest(
7 txnId = enquiry.data.txnId,
8 amount = 50000,
9 reason = "Customer request"
10 ),
11 options = RequestOptions(idempotencyKey = "refund-ORDER-$orderId-50000")
12 )
13 Log.d("SabPaisa", "Refund ID: ${refund.data.refundId}")
14}1import in.sabpaisa.pg.models.CreateRefundRequest;
2import in.sabpaisa.pg.models.RequestOptions;
3
4CreateRefundRequest params = new CreateRefundRequest(
5 txnId, 50000, "Customer request" // txnId, amount (paise), reason
6);
7RequestOptions options = new RequestOptions(
8 "refund-ORDER-" + orderId + "-50000", // idempotencyKey
9 null // timeoutMs (use default)
10);
11
12sabpaisa.createRefund(params, options)
13 .thenAccept(r -> runOnUiThread(() ->
14 Log.d("SabPaisa", "Refund ID: " + r.getData().getRefundId())
15 ))
16 .exceptionally(e -> { handleError(e.getCause()); return null; });2Check refund status
1val status = sabpaisa.refunds.getStatus("refund-id")
2Log.d("SabPaisa", "Refund status: ${status.data.status}")1sabpaisa.getRefundStatus("refund-id")
2 .thenAccept(r -> Log.d("SabPaisa", "Status: " + r.getData().getStatus()))
3 .exceptionally(e -> { handleError(e.getCause()); return null; });3List refunds
Filter by transaction, status, date range, or paginate the full list.
1import `in`.sabpaisa.pg.models.RefundListParams
2
3val list = sabpaisa.refunds.list(RefundListParams(txnId = enquiry.data.txnId, page = 0, size = 20))
4list.data.forEach { Log.d("SabPaisa", "${it.refundId} — ${it.status}") }
5Log.d("SabPaisa", "Total: ${list.pagination.total}, Page: ${list.pagination.page + 1}")1import in.sabpaisa.pg.models.RefundListParams;
2
3// Data class — pass all 6 args
4RefundListParams filters = new RefundListParams(txnId, null, null, null, 0, 20);
5
6sabpaisa.listRefunds(filters)
7 .thenAccept(r -> {
8 r.getData().forEach(item ->
9 Log.d("SabPaisa", item.getRefundId() + " — " + item.getStatus())
10 );
11 Log.d("SabPaisa", "Total: " + r.getPagination().getTotal());
12 })
13 .exceptionally(e -> { handleError(e.getCause()); return null; });Checkout Modes
The SDK supports three ways to open the payment page.
| Mode | Enum | Description |
|---|---|---|
| Chrome Custom Tabs Default | CheckoutMode.CUSTOM_TABS | Opens in an in-app Chrome tab. Best UX. |
| In-App WebView | CheckoutMode.IN_APP_WEBVIEW | Embedded WebView. Result delivered via onActivityResult. |
| External Browser | CheckoutMode.EXTERNAL_BROWSER | Opens the device default browser. |
Error Handling
CompletableFuture wraps exceptions in CompletionException — always unwrap with e.getCause(). Kotlin coroutines propagate the typed exception directly.
| Exception | When it occurs |
|---|---|
| ValidationException | Missing/invalid input — check field / getField() |
| ApiException | API returned an error; retryable / isRetryable() is true for 429/5xx |
| ChecksumException | Return URL or webhook signature verification failed |
| SabPaisaException | Base class — network errors, timeouts, config errors |
1import `in`.sabpaisa.pg.exceptions.*
2
3viewModelScope.launch {
4 try {
5 val response = sabpaisa.payments.createSession(params)
6 } catch (e: ValidationException) {
7 Log.e("SabPaisa", "Invalid ${e.field}: ${e.message}")
8 } catch (e: ApiException) {
9 Log.e("SabPaisa", "API error ${e.code}: ${e.message} (HTTP ${e.statusCode})")
10 if (e.retryable) { /* safe to retry — 429 or 5xx */ }
11 } catch (e: ChecksumException) {
12 Log.e("SabPaisa", "Checksum failed: ${e.message}")
13 } catch (e: SabPaisaException) {
14 Log.e("SabPaisa", "SDK error [${e.code}]: ${e.message}")
15 }
16}1import in.sabpaisa.pg.exceptions.*;
2
3private void handleError(Throwable cause) {
4 if (cause instanceof ValidationException) {
5 ValidationException e = (ValidationException) cause;
6 Log.e("SabPaisa", "Invalid " + e.getField() + ": " + e.getMessage());
7 } else if (cause instanceof ApiException) {
8 ApiException e = (ApiException) cause;
9 Log.e("SabPaisa", "API error [" + e.getCode() + "] HTTP "
10 + e.getStatusCode() + ": " + e.getMessage());
11 if (e.isRetryable()) { /* safe to retry — 429 or 5xx */ }
12 } else if (cause instanceof ChecksumException) {
13 Log.e("SabPaisa", "Checksum failed: " + cause.getMessage());
14 } else if (cause instanceof SabPaisaException) {
15 SabPaisaException e = (SabPaisaException) cause;
16 Log.e("SabPaisa", "SDK error [" + e.getCode() + "]: " + e.getMessage());
17 }
18}
19
20// Usage:
21sabpaisa.createSession(params)
22 .thenAccept(response -> { /* ... */ })
23 .exceptionally(e -> { handleError(e.getCause()); return null; });CreatePaymentRequest Fields
| Field | Type | Required | Description |
|---|---|---|---|
| merchantTxnId | String | Yes | Unique transaction ID from your system |
| amount | Int | Yes | Amount in paise (100 = ₹1) |
| customerName | String | Yes | Customer's full name |
| customerEmail | String | Yes | Customer's email address |
| customerMobile | String | Yes | Customer's mobile number |
| currency | String | No | Currency code (default: "INR") |
| returnUrl | String? | No | Redirect URL after payment |
| paymentMode | String? | No | Filter by mode (e.g. "UPI", "CARD") |
| metadata | Map<String, String>? | No | Custom key-value metadata |
| udfFields | Map<String, String>? | No | User-defined fields udf1–udf20 (max 255 chars each) |
Configuration Reference
| Field | Required | Description |
|---|---|---|
| apiKey | Yes | API key from SabPaisa dashboard |
| merchantId | Yes | Merchant ID for X-Merchant-Id auth header |
| secretKey | Yes | HMAC secret for checksum signing |
| clientCode | Yes | Client/merchant code for API request bodies |
| env | Yes | "staging" or "production" |
| baseUrl | No | Custom URL override for proxies or local testing |
Dependency Graph
| Dependency | Version | Purpose |
|---|---|---|
| com.squareup.okhttp3:okhttp | 4.12.0 | HTTP client |
| com.google.code.gson:gson | 2.10.1 | JSON serialization |
| org.jetbrains.kotlinx:kotlinx-coroutines-android | 1.7.3 | Async/suspend support (Kotlin) |
| org.jetbrains.kotlinx:kotlinx-coroutines-jdk8 | 1.7.3 | CompletableFuture bridge (Java) |
| androidx.browser:browser | 1.7.0 | Chrome Custom Tabs for checkout redirect |
Need Help?
For integration support, contact the SabPaisa technical team with:
- •Your merchant ID
- •The trace ID from the error response (
ApiException.getTraceId()) - •The SDK version (
1.1.0)
Was this page helpful?
Related Pages
SDKs & Libraries
Choose your platform — Flutter, Java, Python, Node.js
Flutter SDK
Official Flutter SDK — Android, iOS, Web
Java SDK
Official Java SDK — Spring Boot, Jakarta EE, Quarkus
Quick Start
Get up and running in 30 minutes
API Reference
Complete endpoint documentation
Webhooks
Real-time payment notifications