A secure UPI checkout SDK for the PaymentSetu payment gateway. Your backend creates the order (with the secret API key); the app opens the resulting payment URL via Chrome Custom Tabs (with a WebView fallback) and returns a typed result through the Activity Result API.
- ✅ Kotlin,
minSdk 21, published as an AAR - ✅ No API key in the app — the secret key stays on your server
- ✅ Chrome Custom Tabs (best UPI-app compatibility) + WebView fallback
- ✅ Typed
PaymentResultvia the Activity Result API - ✅ Deep-link return handling built in
Your PaymentSetu API key is a secret (it authenticates the API and signs webhooks). Never ship it in an app. This SDK is deliberately a checkout SDK, not an API client:
[Your App] --request order--> [Your Backend] --create_order (secret key)--> [PaymentSetu]
[Your App] <---payment_url--- [Your Backend] <---------payment_url----------
[Your App] --- opens payment_url via this SDK ---> UPI payment
[PaymentSetu] --webhook (payment result)--> [Your Backend] ← source of truth
[Your App] --confirm order status--> [Your Backend]
A PaymentResult.Success from this SDK means the customer returned — not
that money was captured. Always confirm the order with your backend (which
knows the real state from the webhook) before delivering goods/services. Use the
server SDKs (PHP / Node / .NET) for order creation and webhook verification.
Once published:
// app/build.gradle.kts
dependencies {
implementation("com.paymentsetu:checkout:1.0.0")
}To build the AAR from source (this repo ships without the Gradle wrapper jar):
cd android-sdk
gradle wrapper # once, to generate ./gradlew
./gradlew :paymentsetu:assembleRelease
# output: paymentsetu/build/outputs/aar/paymentsetu-release.aarOr just open the android-sdk folder in Android Studio.
The SDK detects the return from payment via a deep link into your app. Pick a unique scheme and set it as a manifest placeholder:
// app/build.gradle.kts
android {
defaultConfig {
manifestPlaceholders["paymentSetuRedirectScheme"] = "myapp"
}
}This wires up the SDK's redirect receiver for URLs like
myapp://paymentsetu/callback. (You do not need to add an intent-filter
yourself — the library manifest does it using this placeholder.)
When your server calls create_order, set redirect_url to the same deep
link you'll pass to the SDK, e.g. myapp://paymentsetu/callback. After
payment, PaymentSetu redirects there, which reopens your app.
import com.paymentsetu.checkout.CheckoutOptions
import com.paymentsetu.checkout.PaymentResult
import com.paymentsetu.checkout.PaymentSetuCheckout
class CheckoutActivity : ComponentActivity() {
private val checkout =
registerForActivityResult(PaymentSetuCheckout.Contract()) { result ->
when (result) {
is PaymentResult.Success -> {
// The customer returned. CONFIRM with your backend before fulfilling.
confirmOrderWithBackend(result.orderId)
}
is PaymentResult.Failed -> showError(result.reason)
is PaymentResult.Cancelled -> showCancelled()
}
}
private fun startPayment() = lifecycleScope.launch {
// 1. Ask YOUR backend to create the order and return a payment_url.
val order = api.createOrder(amountPaisa = 10000) // your API, server uses secret key
// 2. Launch checkout.
checkout.launch(
CheckoutOptions(
paymentUrl = order.paymentUrl,
redirectUrl = "myapp://paymentsetu/callback", // must match order redirect_url
orderId = order.orderId,
)
)
}
}| Field | Required | Notes |
|---|---|---|
paymentUrl |
✅ | The payment_url your backend got from create_order. |
redirectUrl |
✅ | Deep link the SDK watches for; must equal the order's redirect_url. |
orderId |
– | Echoed back in the result. |
toolbarColor |
– | Custom Tab toolbar color (@ColorInt). |
allowWebViewFallback |
– | Default true; use a WebView if no browser supports Custom Tabs. |
Success(orderId, returnUri, data)— customer returned; verify server-side.Failed(orderId, reason, returnUri?, data)— explicit failure or no browser/WebView.Cancelled(orderId?)— customer dismissed the payment screen.
data holds any query parameters present on the redirect URL (e.g. status,
txn_utr) when PaymentSetu includes them.
- SDK opens
paymentUrlin a Custom Tab. - Customer pays (UPI apps launch from the browser).
- PaymentSetu redirects to your
redirect_urldeep link. PaymentSetuRedirectActivity(registered via your scheme) receives it and hands it back to the SDK, which parses it into aPaymentResult.- If the customer just closes the tab, the SDK reports
Cancelled.
On devices with no Custom Tabs-capable browser, the SDK falls back to an in-app
WebView that also routes upi:/intent: links to installed UPI apps.
The framework-free redirect logic has JVM unit tests:
./gradlew :paymentsetu:testDebugUnitTest- Android
minSdk 21+ - AndroidX
MIT — see LICENSE.