Android
Android is the only platform where the UPI app hands your app something back. Used properly it is the strongest confirmation signal in the product, because it does not depend on the amount at all.
The Kotlin SDK is a single AAR with no dependencies of its own — JSON comes
from the framework’s org.json, HTTP from HttpURLConnection — so there is
nothing else to add to your build.
Download moneylanded-sdk.aar, drop
it into app/libs/, and add one line:
dependencies { implementation(files("libs/moneylanded-sdk.aar"))}There is no registry coordinate to depend on instead — when there is one, this paragraph will say so. Everything below also works without the library, against the same endpoints.
The flow
Section titled “The flow”- Your backend creates the order and returns
page_tokento the app. - The app reserves through
POST /p/{token}/reserve. - The app launches the UPI intent with
startActivityForResult. - The UPI app returns a result containing
ApprovalRefNo— the UTR. - The app sends that UTR to your backend, which posts it to
POST /v1/intents/{id}/receipt. - When the bank’s credit alert arrives carrying that reference, the order is
confirmed with evidence
receipt.
Your API key stays on your server at every step. The app only ever holds the page token, which is good for one order.
Reserving
Section titled “Reserving”val res = client.newCall( Request.Builder() .url("https://moneylanded.com/p/$pageToken/reserve") .post("""{"app":"phonepe"}""".toRequestBody("application/json".toMediaType())) .build()).execute().use { JSONObject(it.body!!.string()) }
val upiUri = res.getString("upi_uri")val paid = res.getInt("paid_paise") // what to show the customerval off = res.getInt("discount_paise") // 0 most of the timeLaunching, and reading the result
Section titled “Launching, and reading the result”private val upi = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { r -> // The UPI app also returns a Status field. We accept it and never trust it: // only the bank's own alert confirms a payment. Read the reference and let // the server decide. val utr = r.data?.getStringExtra("response") ?.split("&") ?.map { it.split("=", limit = 2) } ?.firstOrNull { it.size == 2 && it[0].equals("ApprovalRefNo", ignoreCase = true) } ?.get(1)
if (utr != null && utr.matches(Regex("""\d{12}"""))) { // Your own backend, which holds the API key. reportReceiptToYourServer(intentId, utr) } // No UTR? Nothing is lost. The amount path confirms it the moment the // bank alert lands, exactly as it does on every other platform.}
fun pay(upiUri: String) { val i = Intent(Intent.ACTION_VIEW, Uri.parse(upiUri)) upi.launch(Intent.createChooser(i, "Pay with"))}Then on your server:
curl -sS -X POST https://moneylanded.com/v1/intents/int_9f2c1a77b40e6d3a5c81/receipt \ -H "Authorization: Bearer $ML_KEY" \ -H "Content-Type: application/json" \ -d '{"utr":"530112345678"}'{ "status": "pending" }pending means we have stored the reference and are waiting for the bank.
confirmed means a credit with that reference had already arrived and was
sitting unplaced — it has now been attached to the order.
Why the receipt is worth the work
Section titled “Why the receipt is worth the work”A receipt confirmation matches on the bank’s own reference number, not on the
amount. So it survives the case the amount path cannot: the customer changed
the amount, or paid from somewhere the amount walk did not anticipate. It is
also immediate rather than waiting on an amount comparison.
Not every UPI app returns a UTR for a personal VPA. When none comes back, nothing degrades — the amount path confirms the order the same way it does for every web and iOS customer.
Do not
Section titled “Do not”- Do not ask the customer to read a reference off their screen. The number a payer’s app shows them is not always the number the payee’s bank reports, and asking is the kind of friction this product exists to remove. The UTR here comes from the UPI app to your app, machine to machine.
- Do not trust the
Statusfield. We accept it on the endpoint and throw it away. A UPI app reporting SUCCESS is not money in your account. - Do not ship your API key in the APK. It can be extracted from any installed app in about a minute.