Skip to content

Mobile SDKs

Futa’s Android and iOS SDKs open a payment’s hosted checkout full screen in the platform’s own web view inside your app. The web view loads the checkout’s origin and nothing else. They are not yet distributed: build them from source, as the last section says.

Your server creates the payment with embedding for an app, and hands your app its checkout_url; the app never holds your secret key.

{
"amount": 2500000,
"currency": "CDF",
"merchant_reference": "order-10482",
"description": "Order 10482",
"return_url": "https://shop.example/thanks",
"expires_at": "2026-10-20T12:00:00Z",
"embedding": { "mode": "app" }
}

An app payment’s pages are never framed by a web page. The Idempotency-Key binds embedding with the other terms, and direct mode (method and msisdn) refuses it. Compute expires_at once per order, within the next 24 hours, as the quick start says: the instant above is an example.

Kotlin, Android 8.0 (API 26) and later, with the platform’s own WebView and no other library:

val session = FutaCheckout.open(
activity,
CheckoutOptions(
checkoutOrigin = "https://checkout.sandbox.futa-app.com",
checkoutUrl = checkoutUrl,
returnUrl = "https://shop.example/thanks",
onState = { state -> showProgress(state.payment, state.state, state.final) },
onClose = { closed -> readOrderFromServer() },
),
)

checkoutOrigin is the checkout’s origin your app trusts: an https origin, or plain HTTP only in a debuggable app, and only for the developer’s loopback with a port: 127.0.0.1, localhost or the emulator’s 10.0.2.2, as http://10.0.2.2:18140 reaches the local sandbox’s checkout on the emulator’s host. The hosted sandbox’s checkout, https://checkout.sandbox.futa-app.com, needs none of that: an emulator and a device reach it alike. checkoutUrl is the payment’s checkout_url exactly as Futa issued it, and returnUrl, optional, its return_url, so going back to your shop reads as returned. open returns a CheckoutSession, whose close() ends it as the back button does. The session lives no longer than the activity it opened over: when that activity is destroyed, finishing or recreated by a configuration change your app does not handle, such as a rotation, the session ends as closed; open a new one if the payer is still to pay.

The Swift package FutaCheckout, iOS 16 and later, with no dependency:

let checkout = try FutaCheckout.open(
from: viewController,
options: CheckoutOptions(
checkoutOrigin: "https://checkout.sandbox.futa-app.com",
checkoutURL: checkoutURL,
returnURL: "https://shop.example/thanks",
onState: { state in showProgress(state) },
onClose: { closed in readOrderFromServer() }
)
)

checkoutOrigin is an https origin, such as the hosted sandbox’s checkout above, or plain HTTP only for 127.0.0.1 or localhost with a port, which a simulator reaches and a device does not: http://127.0.0.1:18140 for the local sandbox. It presents a CheckoutViewController full screen under the platform’s navigation bar; its close() ends the session. The web view uses your app’s default website data store, never Safari’s. openOutside, optional, decides how another site opens, UIApplication.open by default.

onState gets the payment’s id, the state the checkout’s page shows and final, as the embedded checkout describes them: advisory, so confirm the payment on your server by its signed webhook or GET /v1/payments/{id}. onClose gets final as the checkout last said it, false until then, and why the session ended: closed, returned or left. closed means the payer closed it or your app did; returned, that the payer went back to your return_url (its origin and path); left, that they went to any other site, the bank’s included.

Only the checkout’s origin loads in the web view. Every other address opens outside it and ends the session: the bank’s handoff, so bank credentials are never typed inside your app, your return address and any other site. On Android it opens in the system browser, on iOS with UIApplication.open or your openOutside. A receipt opens outside without ending the session: in the system browser on Android, in SFSafariViewController on iOS, or through your openOutside when you set it. The payer’s method is never remembered on the device. Your app’s own policy must allow plain HTTP too: its network security configuration on Android, App Transport Security on iOS.

Your app owns the web view, so it can read what the payer types there, their phone number included, and inject script into the page, which a redirect to the hosted checkout never allowed. Treat what the payer types as you would your own form’s, and never record it. The bank’s handoff always opens outside the app for that reason.

For sandbox testing, by those Futa has given access to Futa-payer-web: both live there, each its own build, outside the checkout’s:

  • Android, sdks/android. The library module checkout (com.futa.checkout.android). It needs JDK 17 or later, the Gradle 9.8.1 wrapper, the Android Gradle plugin 9.4.1 and Kotlin 2.4.21, which Gradle fetches, and an Android SDK with platform 35 and build-tools 36.0.0, named by ANDROID_HOME or a local.properties. Every module Gradle resolves is checked against its SHA-256 in gradle/verification-metadata.xml. ./gradlew :checkout:testDebugUnitTest runs its tests.
  • iOS, sdks/ios. The Swift package whose product is FutaCheckout, with no dependency, built with Xcode 26. swift test runs its tests.