# Android SDK

SimPay Checkout Android SDK displays a payment sheet in your app. It loads channels, creates a transaction, handles BLIK Level 0 and redirect channels in a Custom Tab, then checks the status through the API. This is an Android UI library, not the [Java SDK for backend integration](https://github.com/SimPaypl/SimPay-API-java).

Source code and the demo app are in [simpay-android](https://github.com/SimPaypl/simpay-android). This SDK does not expose OneClick, recurrent BLIK payments, refunds, SMS or DirectBilling.

## Prerequisites

1. Open the service settings in the merchant panel and enable support in the "Mobilne SDK" tab.
2. Copy the short, 8-character service ID, for example `a1b2c3d4`. This is the first segment of the service UUID, not the full UUID. Use the same value on your backend and in the app.
3. Register a return scheme, for example `pl.example.shop`. It must match the merchant panel, Android manifest and `TransactionReturns` exactly.
4. Prepare your backend to issue sessions and verify IPN notifications. Keep the merchant API key on the backend only. Never put it in the APK, resources or `BuildConfig`.


The SDK requires `minSdk` 21, `compileSdk` at least 34 and Java 11 source and target compatibility. The JDK that runs Gradle depends on your Android Gradle Plugin, not on the Java 11 compatibility setting. The payment sheet requires a `FragmentActivity` or a subclass such as `AppCompatActivity`.

## Gradle dependency

In your app's `settings.gradle.kts`:


```kotlin
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}
```

In the app module's `build.gradle.kts`:


```kotlin
android {
    compileSdk = 34
    defaultConfig { minSdk = 21 }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
}

dependencies {
    implementation("pl.simpay:checkout-android:1.0.0")
}
```

Version `1.0.0` is available on [Maven Central](https://central.sonatype.com/artifact/pl.simpay/checkout-android/1.0.0). The package includes the AAR, sources and Javadoc. No additional Maven repository is needed beyond `mavenCentral()`.

## Session from your backend

The app requests a session from your authenticated backend. The backend calls `POST https://api.simpay.pl/payment/{serviceId}/mobile-sdk/session` with the merchant key in `Authorization: Bearer <API key>`. It returns a short-lived token, usually about 15 minutes, its expiry if available, and the payer's public IP observed on the request. Behind a proxy, use only trusted IP forwarding configuration, not an arbitrary client-supplied header.

`MyBackendClient` and `BackendSessionResponse` below are integrator-supplied abstractions, not SDK classes. Implement the HTTP client for your own backend. Do not call the session endpoint with a merchant key from the app.


```kotlin
import pl.simpay.android.checkout.mobile.MobileSdkSession
import pl.simpay.android.checkout.mobile.MobileSessionProvider

data class BackendSessionResponse(
    val token: String,
    val expiresAt: String?,
    val payerIp: String
)

interface MyBackendClient {
    suspend fun createPaymentSession(): BackendSessionResponse
}

class BackendSessionProvider(private val client: MyBackendClient) : MobileSessionProvider {
    override suspend fun provideSession(): MobileSdkSession {
        val response = client.createPaymentSession()
        return MobileSdkSession(
            token = response.token,
            expiresAt = response.expiresAt,
            payerIp = response.payerIp
        )
    }
}
```

Pass the full token including `mobsdk_`. The SDK adds `Bearer` and uses the fixed host `https://api.simpay.pl`. The provider should return a usable session on subsequent calls too. Do not log tokens. `payerIp` must be the payer's public address, not your backend server's address or a phone's local interface address. The order's `payerIpOverride` is only a fallback when the session has no IP and follows the same rules.

## Order and payment sheet

This function accepts your backend client. Call it from an active `FragmentActivity`, for example when the payer taps the payment button. Use `onResult` to update the app UI, not to fulfill the order.


```kotlin
import androidx.fragment.app.FragmentActivity
import java.math.BigDecimal
import pl.simpay.android.checkout.mobile.CheckoutResult
import pl.simpay.android.checkout.mobile.SimPayMobileCheckout
import pl.simpay.android.checkout.mobile.SimPayMobileConfiguration
import pl.simpay.android.checkout.mobile.TransactionOrder
import pl.simpay.android.checkout.mobile.TransactionReturns

fun pay(
    activity: FragmentActivity,
    client: MyBackendClient,
    onResult: (CheckoutResult) -> Unit
) {
    val checkout = SimPayMobileCheckout(
        configuration = SimPayMobileConfiguration(
            serviceId = "a1b2c3d4",
            merchantDisplayName = "Example Shop"
        ),
        sessionProvider = BackendSessionProvider(client)
    )
    val order = TransactionOrder(
        amount = BigDecimal("49.99"),
        currency = "PLN",
        description = "Order 2451",
        control = "order-2451",
        customerEmail = "buyer@example.com",
        customerCountryCode = "PL",
        returns = TransactionReturns(
            success = "pl.example.shop://payment-return?result=success",
            failure = "pl.example.shop://payment-return?result=failure"
        )
    )
    checkout.present(activity, order, onResult)
}
```

The amount must be at least `1`. Construct `BigDecimal` from a string, not a `Double`. The description limit is 64 characters; `control` has a limit of 255. Set `control` to a reference to an order stored on your backend. Email is required. The country code must be two uppercase ISO 3166-1 alpha-2 letters matching the currency. The SDK can derive the country from the currency, but you must supply it for ambiguous currencies such as EUR.

## Return from the browser

Add these entries inside `<application>` in `AndroidManifest.xml`. Replace class names and the scheme with your app's values. `CheckoutActivity` must extend `FragmentActivity`.


```xml
<activity
    android:name=".CheckoutActivity"
    android:exported="false"
    android:launchMode="singleTop" />
<activity
    android:name=".PaymentReturnActivity"
    android:exported="true"
    android:theme="@android:style/Theme.Translucent.NoTitleBar">
    <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="pl.example.shop" android:host="payment-return" />
    </intent-filter>
</activity>
```


```kotlin
import android.app.Activity
import android.content.Intent
import android.os.Bundle

class PaymentReturnActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        startActivity(
            Intent(this, CheckoutActivity::class.java)
                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
        )
        finish()
    }
}
```

Do not treat `result=success` as proof of payment. Custom schemes do not guarantee exclusive ownership by your app. The return only brings the screen back; the SDK checks the transaction status again. Do not assume that the callback and full checkout can be restored after process death. On restart, fetch the order state from your backend instead of automatically starting another payment.

## Results and order fulfillment

| `CheckoutResult` | App action |
|  --- | --- |
| `Succeeded` | The SDK read `transaction_paid`. Show the result and fetch the order state from your backend. |
| `Pending` | The outcome remains open, for example during verification or after the polling budget expires. Do not mark it paid or definitively declined. `transactionId` may be `null`. |
| `Failed` | Handle `error`; `message` contains technical details. `transactionId` and `rawStatus` may be `null`. Check the order state before retrying. |
| `Cancelled` | The payer dismissed the sheet. This does not prove the backend order was cancelled. |


No SDK result authorizes fulfillment. A modified app can change the amount, currency and `control`, and the session token does not bind these values to your order.

Your backend must verify the [IPN signature](/en/notifications/payment/signature) and handle [transaction status changes](/en/notifications/payment/events/transaction_status_changed). Before fulfillment, compare the paid status, service ID, `control` and transaction-to-order association, amount and currency against your own order record. Handle duplicate IPN notifications idempotently. Neither the browser nor the app is a source of payment confirmation. See the [IPN introduction](/en/notifications/payment).

## Troubleshooting

| Symptom | Check |
|  --- | --- |
| Gradle cannot find `1.0.0` | Check the coordinates `pl.simpay:checkout-android:1.0.0` and that `mavenCentral()` is included in your repositories. |
| AAR metadata error | `compileSdk` must be at least 34. |
| `present` rejects your Activity | Use `FragmentActivity`, not a plain `ComponentActivity`. |
| `SessionFailed` or `Unauthorized` | Mobile SDK opt-in, short service ID, matching service and backend key, token expiry and full `mobsdk_` prefix. |
| `MissingPayerIp` | Your backend must return the payer's public IP in the session. |
| `MissingCountryCode` | Supply `customerCountryCode`, especially for EUR. |
| Payer stays in the browser | Compare the scheme in the panel, manifest and both `TransactionReturns` URLs, and check the intent filter host. |
| `RateLimited` | Wait before retrying; do not request sessions in a loop. |
| `BrowserUnavailable` | The device needs a browser that can handle the redirect. |
| `Indeterminate`, timeout or `Pending` | Check the backend state. Do not automatically repeat an operation that may already have created a transaction. |


Use `CheckoutResult.Failed.error` and `message` for diagnostics, without tokens or payer data in logs. The fake API demo tests the UI, not a real payment. Before release, use a prepared service to test BLIK, redirect and return, interrupted payments, delayed IPN and backend order-data matching.