Android SDK

Present the Qasimha hosted checkout from an Android app with com.qasimha:checkout-android and handle the result.

com.qasimha:checkout-android opens a Qasimha hosted checkout from your Android app and reports how it ended. Your server creates the session and hands the app a paymentUrl; the SDK opens it in a Custom Tab (default) or a hardened WebView, watches for the return to your app and gives you a typed result. It holds no secret key, stores nothing and tracks nothing.

Requirements

ItemRequirement
LanguageKotlin (Java callers supported)
AndroidminSdk 24, compileSdk 35
DependenciesAndroidX only: androidx.browser, androidx.appcompat, androidx.core
Permissionsandroid.permission.INTERNET only
ServerA Qasimha server SDK that creates the checkout session. See Checkout flows.

Install

dependencies {
    implementation("com.qasimha:checkout-android:0.1.0")
}

Add the return-scheme manifest placeholder to your app module. The library manifest needs it to build, even if you only use App Links:

android {
    defaultConfig {
        manifestPlaceholders["qasimhaReturnScheme"] = "myshop"
    }
}

If you only use App Links, set it to something unique, for example your application id.

Set up the return into your app

Only Presentation.CUSTOM_TAB needs this: the browser has to bring the shopper back into your app. In WEB_VIEW mode the SDK intercepts the return URLs inside the WebView, so no manifest work is needed.

Your return URLs are https pages on your own domain, verified as App Links into your app. Publish assetlinks.json for the domain as usual.

Either let the library's receiver handle the link by adding an intent filter to it in your manifest (the manifest merger merges by activity name):

<activity android:name="com.qasimha.checkout.QasimhaReturnActivity">
    <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="shop.example" android:pathPrefix="/qasimha/return" />
    </intent-filter>
</activity>

or keep the link on your own activity and forward it:

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    val data = intent.data ?: return
    if (!QasimhaCheckout.handleReturn(data)) handleMyOwnDeepLink(data)
}

handleReturn returns false and does nothing when no checkout is open or the URL is not one of its return URLs, so your other deep links keep working.

Start a checkout

Get paymentUrl from your server, then launch the checkout with the same three return URLs your server sent when creating the session. Use the Activity Result contract: it survives process death.

class CheckoutActivity : AppCompatActivity() {
    private val checkout = registerForActivityResult(QasimhaCheckoutContract()) { result ->
        when (result) {
            is CheckoutResult.Success -> confirmWithMyServer(result.orderId)
            is CheckoutResult.Failure -> showFailure(result.status)
            CheckoutResult.Cancelled -> showCart()
        }
    }

    fun pay(paymentUrl: String) {
        checkout.launch(
            CheckoutOptions(
                paymentUrl = paymentUrl,
                merchantUrl = MerchantUrl(
                    success = "https://shop.example/qasimha/return?order=1001",
                    failure = "https://shop.example/qasimha/return?order=1001",
                    cancel = "https://shop.example/cart",
                ),
                presentation = Presentation.CUSTOM_TAB,
                language = "ar",
            ),
        )
    }
}

A callback API also exists: QasimhaCheckout.start(activity, options) { result -> ... } (from Java: QasimhaCheckout.start(this, options, result -> { ...; return Unit.INSTANCE; })). The callback lives in memory: if Android kills the process while the checkout is open, it is lost. Prefer the contract.

CheckoutOptions

ParameterRule
paymentUrlAbsolute https URL from your server. The SDK appends lang= from language.
merchantUrl.success / .failure / .cancelAbsolute, no fragment, at most 1024 characters. https with a host, or a custom app scheme of at least 3 characters. http, javascript, data, file, blob, vbscript, intent, content, android-app and about are refused.
presentationCUSTOM_TAB (default) or WEB_VIEW.
language"ar" (default) or "en".
webViewAllowedHostsWEB_VIEW only. Extra hosts the page may visit (payment provider, Nafath). Exact host names, or *.example.com for every subdomain (never the apex). Default empty.

An invalid value throws IllegalArgumentException from the constructor.

Handle the result and confirm on your server

ResultMeaning
Success(orderId, orderReferenceId)The return URL said status=confirmed and carried an orderId.
Failure(status, orderId, orderReferenceId)declined, expired or failed from the checkout, or an SDK status: ssl_error, network_error (WebView), no_browser (no browser for the Custom Tab), malformed (confirmed without an orderId), unknown (no status). Other statuses pass through lowercased.
Cancelledstatus=cancelled, the cancel URL without a status, the shopper closed the Custom Tab, or pressed back out of the WebView.

Do not show "paid" on any result. On Success, send orderId to your server. Your server calls GET /api/v1/merchant/orders/{orderId} (orders.get in the server SDKs) with its secret key and fulfils only if the order is paid. Your server also receives the webhook for the same order.

If the process was killed while the Custom Tab was open, there is no pending checkout. The App Link then arrives at your activity as a normal deep link with orderId, orderReferenceId and status in the query, and your server can still confirm the order.

Custom Tab or WebView

  • CUSTOM_TAB (default). The system browser sheet. The shopper gets the platform's TLS, autofill and cookie isolation, and the journey's third-party pages (payment provider, Nafath app switch, App Links into the Qasimha app) work as in a browser. Use this unless you have a reason not to.
  • WEB_VIEW (opt-in). QasimhaCheckoutActivity hosts a hardened WebView: no file or content access, no mixed content, no addJavascriptInterface, Safe Browsing on, third-party cookies off, permission prompts denied. Only https hosts on the allow-list load (the paymentUrl host, your return hosts and webViewAllowedHosts); everything else, including intent:// and app-switch schemes, is dropped. A Nafath app hand-off does not work here; the Nafath web flow does when its host is allow-listed. A TLS error ends with Failure("ssl_error").

Promo view

QasimhaPromoView shows "Split into 4 monthly payments of [riyal symbol] 300" on product and cart screens. It uses your publishable key only.

<com.qasimha.checkout.QasimhaPromoView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:qasimhaPublicKey="pk_test_..."
    app:qasimhaAmount="1200"
    app:qasimhaLang="ar" />
promo.publicKey = "pk_test_..."
promo.amount = "300.50"
promo.lang = "en"
promo.textView.textSize = 16f
promo.refresh()
  • Calls GET /api/v1/widget/plans with the X-Public-Key header. pk_test_ keys go to UAT, pk_live_ keys to production.
  • Fails closed: the view is GONE when the key or amount is invalid, the request fails, or the amount is outside the plan range. It never invents a number.
  • Nothing is cached; every attribute change re-fetches. Style it through textView, and call refresh() after changing the text size.

Release builds

  • R8 / ProGuard: the AAR ships consumer-rules.pro. It keeps the public API (com.qasimha.checkout.* and the CheckoutResult subclasses) and lets R8 shrink and obfuscate the internals with your app. You do not need to add rules.
  • The SDK uses no reflection. No mapping file or crash report leaves your app through the SDK.

Security

  • No secret on the device. Keep sk_test_... / sk_live_... on your server. The app only ever sees paymentUrl and, for the promo view, a pk_ key.
  • No storage, analytics, advertising id or fingerprinting. No certificate pinning; TLS validation is the platform's.
  • WebView cookies: the WebView shares your app's WebView cookie jar, as any WebView does. The SDK does not read or persist it.
  • PCI DSS: no card data passes through the SDK or your app. You stay in SAQ-A scope.
  • App hardening: root detection, anti-tamper, anti-hooking and RASP are recommended for apps that take payments. The SDK does not perform them.

Troubleshooting

Build fails: manifest placeholder qasimhaReturnScheme is missing

Add manifestPlaceholders["qasimhaReturnScheme"] to defaultConfig. It is required even when you only use App Links.

The checkout ends as Cancelled after the shopper paid

With App Links, an unverified link opens in the browser instead of your app, and the checkout ends as Cancelled when the shopper closes the sheet. Check that assetlinks.json is published and that the intent filter has android:autoVerify="true". Then confirm the order on your server anyway: the payment may have succeeded.

IllegalArgumentException when building CheckoutOptions

A return URL breaks a rule: http, a fragment, more than 1024 characters, or a refused scheme such as intent. Use https or a custom scheme of at least 3 characters.

Failure with status no_browser

The device has no browser to open the Custom Tab. Ask the shopper to install or enable one.

The WebView journey stops at the payment provider or Nafath

The WebView only loads allow-listed https hosts. Add the hosts Qasimha confirms for your journey to webViewAllowedHosts, or switch to CUSTOM_TAB. Nafath app hand-off never works in the WebView.

Failure with status ssl_error in WebView mode

The WebView hit a TLS error, cancelled the request and ended the checkout. This is deliberate: there is no option to ignore TLS errors. Confirm the order on your server before retrying with a new session.

The promo view is not shown

It fails closed. Check that the key matches pk_test_ or pk_live_, the amount is a positive number with at most two decimals, and the amount is within your plan's minimum and maximum.

Related


Did this page help you?