App-to-app on Android

Launch the Scan to Pay wallet from your Android app via Intent and receive the return via your registered URL scheme.

On Android, app-to-app uses standard Intent.ACTION_VIEW with a URL. You build the URL with the wallet's scheme + the 10-digit code + a URL-encoded return URL, and Android's intent system routes it to the wallet app.


Prerequisites

  1. Register your own URL scheme in your AndroidManifest.xml. This is what the wallet uses to return to your app.

    <activity android:name=".PaymentReturnActivity"
              android:exported="true">
      <intent-filter android:label="Return from Scan to Pay">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="your.app.scheme" android:host="merchant.com" />
      </intent-filter>
    </activity>

    Replace your.app.scheme and merchant.com with your own values. You'll receive the wallet's return via this activity.

  2. Your backend creates a code via POST /code/create — see Dynamic QR for the request shape. Your Android app receives the 10-digit code from your backend and uses it to launch the wallet.


Launch the wallet

Use Intent.ACTION_VIEW with the appropriate wallet scheme. This MUST be called from an Activity:

fun launchScanToPayWallet(activity: Activity, code: String, walletScheme: String) {
    val returnUrl = URLEncoder.encode("your.app.scheme://merchant.com", "UTF-8")
    val deepLink = "$walletScheme://masterpass.oltio.co.za/$code/$returnUrl"

    val intent = Intent(Intent.ACTION_VIEW).apply {
        data = Uri.parse(deepLink)
        flags = Intent.FLAG_ACTIVITY_NEW_TASK
    }
    activity.startActivity(intent)
}

// Standalone Scan to Pay app on Android
launchScanToPayWallet(this, "0123456789", "masterpass.app.scheme")
public static void launchScanToPayWallet(Activity activity, String code, String walletScheme)
        throws UnsupportedEncodingException {
    String returnUrl = URLEncoder.encode("your.app.scheme://merchant.com", "UTF-8");
    String deepLink = walletScheme + "://masterpass.oltio.co.za/" + code + "/" + returnUrl;

    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setData(Uri.parse(deepLink));
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    activity.startActivity(intent);
}

Three things to get right:

DetailWhy it matters
URL-encode the return URLIf you skip this, the wallet won't be able to construct a working return URL when the customer finishes.
Use FLAG_ACTIVITY_NEW_TASKOtherwise the back stack can become confused when control returns to your app.
Call from an ActivityBackground services can't launch other apps without further plumbing.

Picking the right wallet scheme

The customer might have any combination of wallets installed. Use one of these strategies:

Strategy 1 — let the customer pick at the start

Show your customer a list of wallet logos in your app, let them pick the one they want, and then launch the corresponding scheme. Maintain the wallet-scheme map in your app's resources.

val walletSchemes = mapOf(
    "Scan to Pay"   to "masterpass.app.scheme",
    "ABSA"          to "masterpass.absa.scheme",
    "Standard Bank" to "masterpass.sbsa.scheme",
    "Nedbank"       to "masterpass.nedbank.scheme",
    "Capitec"       to "masterpass.capitec.scheme",
    "VodaPay"       to "masterpass.vodapay.scheme",
    "Spenda"        to "masterpass.spenda.scheme"
)

Strategy 2 — query installed packages

Use PackageManager.queryIntentActivities to check which wallets are actually installed before showing the picker:

fun installedWallets(context: Context): List<String> {
    val pm = context.packageManager
    return walletSchemes.filter { (_, scheme) ->
        val intent = Intent(Intent.ACTION_VIEW, Uri.parse("$scheme://test"))
        pm.queryIntentActivities(intent, 0).isNotEmpty()
    }.keys.toList()
}

Note: this requires the wallet schemes to be declared in <queries> in AndroidManifest.xml from Android 11 (API 30):

<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <data android:scheme="masterpass.app.scheme" />
    </intent>
    <!-- repeat for each wallet -->
</queries>

FNB / RMB universal link

FNB and RMB use an HTTPS universal link instead of the wallet-scheme path. Supply the payment code and return URL using the exact query parameters below:

https://www.online.fnb.co.za/banking/mobileservices?codetype=masterpassdeeplink&urlencodedqr={URL-encoded 10-digit code}&urlencodedcallbackurl={URL-encoded return URL}
Query parameterRequired value
codetypeThe fixed value masterpassdeeplink
urlencodedqrThe URL-encoded 10-digit code returned by POST /code/create
urlencodedcallbackurlYour app's URL-encoded return URL

Example:

https://www.online.fnb.co.za/banking/mobileservices?codetype=masterpassdeeplink&urlencodedqr=0123456789&urlencodedcallbackurl=your.app.scheme%3A%2F%2Fmerchant.com

Percent-encode each value once. Detect FNB / RMB separately and open this HTTPS URL when the customer selects either wallet.


Receive the return

When the customer finishes in the wallet app, it opens your your.app.scheme://merchant.com?status=SUCCESS&ref=... URL. Android routes it to your registered activity:

class PaymentReturnActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val data = intent?.data
        if (data != null) {
            val status = data.getQueryParameter("status")
            val ref    = data.getQueryParameter("ref")
            handlePaymentReturn(status, ref)
        }
    }
}

The status values and what they mean are documented on Handling the response.

⚠️

The return URL is not proof of payment. It tells you the customer's flow ended; it doesn't tell you the bank approved the transaction. Always verify via your backend webhook before releasing goods. See App-to-app overview.


What's next


Did this page help you?