Android
Integrate Volley's hosted payment page in your Android app with WebView
Embed Volley's hosted payment page in a WebView. Your app opens the page, the customer approves in their bank app, and the page sends a completion message to your app to let you update the UI.
1. Add the WebView
Create a payment request on your server, pass the returned URL to your app, and load it in a WebView.
@Composable
fun ContentView(requestUrl: String) {
var showRequest by remember { mutableStateOf(false) }
if (showPayment) {
RequestWebView(url = requestUrl) { status ->
showRequest = false
}
} else {
Button(onClick = { showRequest = true }) {
Text("Pay by bank")
}
}
}2. Add the bridge
Add a VolleyBridge and attach it to the WebView as VolleyWebview. Copy the whole class in - you only need to provide the onComplete handler.
class VolleyBridge(
private val context: Context,
private val onResult: (String) -> Unit,
) {
private val main = Handler(Looper.getMainLooper())
@JavascriptInterface
fun onMessage(json: String) {
val msg = JSONObject(json)
when (msg.optString("type")) {
"redirect" -> main.post { openBank(msg.getString("url")) }
"complete" -> main.post { onComplete(msg.optString("status")) }
}
}
// Open the bank URL natively so it launches the bank app (a WebView can't).
private fun openBank(url: String) {
val intent = Intent(Intent.ACTION_VIEW, url.toUri()).apply {
addCategory(Intent.CATEGORY_BROWSABLE)
// Launch the bank app as its own task.
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
addFlags(Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER)
}
}
runCatching { context.startActivity(intent) }.onFailure {
// No bank app claims this URL - open it however the device can.
context.startActivity(
Intent(Intent.ACTION_VIEW, url.toUri())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
)
}
}
}Attach the bridge to the WebView:
@SuppressLint("SetJavaScriptEnabled")
@Composable
fun RequestWebView(url: String, onComplete: (String) -> Unit) {
AndroidView(
factory = { context ->
WebView(context).apply {
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
addJavascriptInterface(VolleyBridge(context, onComplete), "VolleyWebview")
loadUrl(url)
}
},
)
}3. Handle redirect back to your app
After approving, the customer is redirected from their bank app to their default browser. From here Volley automatically redirects the customer to your payment request's redirect URL. Use a deep link for this URL to get the customer back to your app.
Development - add an intent filter for a custom scheme and set the request's
redirect URL to yourscheme://return:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourscheme" />
</intent-filter>Production - use an App Link:
- Host
assetlinks.jsonathttps://yourdomain.com/.well-known/assetlinks.json:
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourcompany.yourapp",
"sha256_cert_fingerprints": ["YOUR:APP:SIGNING:SHA256"]
}
}]- Add a verified intent filter, scoped to a path (e.g.
/return) so it doesn't shadow the association file:
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="yourdomain.com"
android:pathPrefix="/return" />
</intent-filter>Remember to add the redirect domain as an allowed domain in the Volley Dashboard.
Test end-to-end
The app-to-app redirect only works for real bank apps on a physical device. An emulator can't reproduce it. We recommend testing your integration against each bank app, including BNZ to test the decoupled flow.
If the bank opens as a web page instead of its app, the hand-off isn't reaching the bank app - confirm openBank adds FLAG_ACTIVITY_REQUIRE_NON_BROWSER. If the bank app opens inside your app rather than as its own screen (easy to miss), you need the FLAG_ACTIVITY_NEW_TASK - without it the bank app (or its consent tab) stacks onto your task and shows your app's name in the toolbar.
If you aren't returned to your app after completing a payment, confirm your App Link is verified (adb shell pm get-app-links <package> shows verified) and that the redirect domain is allow-listed in the Volley Dashboard. Also check the success and failure redirect URLs are set on the payment request you're testing.