# Android SDK

SimPay Checkout Android SDK wyświetla arkusz płatności w aplikacji. Pobiera kanały, tworzy transakcję, obsługuje BLIK Level 0 i kanały z przekierowaniem w Custom Tab, a następnie sprawdza status przez API. To biblioteka interfejsu Android, nie [Java SDK do integracji backendowej](https://github.com/SimPaypl/SimPay-API-java).

Kod i aplikacja demonstracyjna są w repozytorium [simpay-android](https://github.com/SimPaypl/simpay-android). Ten SDK nie udostępnia obsługi OneClick, płatności cyklicznych BLIK, zwrotów, SMS ani DirectBilling.

## Przygotowanie

1. W panelu akceptanta otwórz ustawienia usługi i włącz obsługę w zakładce "Mobilne SDK".
2. Zapisz krótki, 8-znakowy identyfikator usługi, np. `a1b2c3d4`. To pierwszy segment UUID usługi, nie cały UUID. Backend i aplikacja muszą używać tej samej wartości.
3. Zarejestruj schemat powrotu, np. `pl.example.shop`. Musi być identyczny w panelu, manifeście Androida i `TransactionReturns`.
4. Przygotuj backend do wydawania sesji i weryfikacji IPN. Klucz API akceptanta pozostaje wyłącznie na backendzie. Nie umieszczaj go w APK, zasobach ani `BuildConfig`.


SDK wymaga `minSdk` 21, `compileSdk` co najmniej 34 i zgodności kodu Java 11. Wersja JDK uruchamiająca Gradle zależy od używanego Android Gradle Plugin, nie od ustawienia zgodności Java 11. Hostem arkusza musi być `FragmentActivity` lub jej podklasa, np. `AppCompatActivity`.

## Zależność Gradle

W `settings.gradle.kts` aplikacji:


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

W `build.gradle.kts` modułu aplikacji:


```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")
}
```

Wersja `1.0.0` jest dostępna w [Maven Central](https://central.sonatype.com/artifact/pl.simpay/checkout-android/1.0.0). Pakiet zawiera AAR, źródła i dokumentację Javadoc. Nie wymaga dodatkowego repozytorium Maven poza `mavenCentral()`.

## Sesja z backendu

Aplikacja prosi Twój uwierzytelniony backend o sesję. Backend wywołuje `POST https://api.simpay.pl/payment/{serviceId}/mobile-sdk/session` z kluczem akceptanta w `Authorization: Bearer <klucz API>`. Zwraca aplikacji krótko ważny token, zwykle około 15 minut, jego termin ważności, jeśli dostępny, oraz publiczny IP płatnika zaobserwowany przy żądaniu. Za proxy korzystaj wyłącznie z zaufanej konfiguracji przekazywania IP, nie z dowolnego nagłówka od klienta.

`MyBackendClient` i `BackendSessionResponse` poniżej to abstrakcje dostarczane przez integratora, nie klasy SDK. Zaimplementuj klienta HTTP swojego backendu. Nie wywołuj endpointu sesji z kluczem akceptanta z aplikacji.


```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
        )
    }
}
```

Przekaż cały token z prefiksem `mobsdk_`. SDK dodaje `Bearer` i używa stałego hosta `https://api.simpay.pl`. Provider powinien zwracać aktualną sesję, także przy ponownym wywołaniu. Nie loguj tokenów. `payerIp` to publiczny adres płatnika, nie adres serwera backendu ani lokalnego interfejsu telefonu. `payerIpOverride` w zamówieniu jest tylko wartością zapasową, gdy sesja nie zawiera IP, i podlega tym samym zasadom.

## Zamówienie i otwarcie arkusza

Poniższa funkcja przyjmuje klienta Twojego backendu. Wywołaj ją z aktywnej `FragmentActivity`, np. po naciśnięciu przycisku płatności. `onResult` aktualizuje interfejs aplikacji, nie realizuje zamówienia.


```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)
}
```

Kwota musi wynosić co najmniej `1`. Używaj `BigDecimal` z tekstu, nie z `Double`. Opis ma limit 64 znaków, `control` 255. `control` powinno wskazywać zamówienie zapisane w Twoim backendzie. E-mail jest wymagany. Kraj to dwie wielkie litery ISO 3166-1 alpha-2 zgodne z walutą. SDK może ustalić kraj z waluty, ale dla niejednoznacznej waluty, np. EUR, podaj go jawnie.

## Powrót z przeglądarki

Dodaj do `<application>` w `AndroidManifest.xml`. Zastąp nazwy klas i schemat wartościami swojej aplikacji. `CheckoutActivity` musi dziedziczyć po `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()
    }
}
```

Nie traktuj parametru `result=success` jako potwierdzenia płatności. Schematy niestandardowe nie gwarantują wyłączności aplikacji. Powrót ma jedynie przywrócić ekran, a SDK ponownie sprawdza status transakcji. Nie zakładaj odtworzenia callbacku i całego checkoutu po zabiciu procesu. Po ponownym uruchomieniu pobierz stan zamówienia z backendu zamiast automatycznie tworzyć nową płatność.

## Wynik i realizacja zamówienia

| `CheckoutResult` | Działanie aplikacji |
|  --- | --- |
| `Succeeded` | SDK odczytał `transaction_paid`. Pokaż wynik i pobierz stan zamówienia z backendu. |
| `Pending` | Wynik pozostaje otwarty, np. trwa weryfikacja lub skończył się czas odpytywania. Nie oznaczaj jako opłacone ani definitywnie odrzucone. `transactionId` może być `null`. |
| `Failed` | Obsłuż `error`; `message` zawiera szczegóły techniczne. `transactionId` i `rawStatus` mogą być `null`. Sprawdź stan zamówienia przed ponowieniem. |
| `Cancelled` | Użytkownik zamknął arkusz. Nie jest to dowód anulowania zamówienia na backendzie. |


Żaden wynik SDK nie uprawnia do wydania towaru. Zmodyfikowana aplikacja może zmienić kwotę, walutę i `control`, a token sesji nie wiąże tych wartości z zamówieniem.

Backend musi zweryfikować [sygnaturę IPN](/notifications/payment/signature) i obsłużyć [zmianę statusu transakcji](/notifications/payment/events/transaction_status_changed). Przed realizacją porównaj status opłacenia, identyfikator usługi, `control` i powiązanie transakcji z zamówieniem, kwotę oraz walutę z własnym rekordem zamówienia. Obsłuż powtórzone IPN idempotentnie. Przeglądarka i aplikacja nie są źródłem potwierdzenia. Zobacz [wprowadzenie do IPN](/notifications/payment).

## Rozwiązywanie problemów

| Objaw | Co sprawdzić |
|  --- | --- |
| Gradle nie znajduje `1.0.0` | Sprawdź współrzędne `pl.simpay:checkout-android:1.0.0` i obecność `mavenCentral()` w repozytoriach. |
| Błąd metadanych AAR | `compileSdk` musi wynosić co najmniej 34. |
| `present` nie przyjmuje Activity | Wymagana jest `FragmentActivity`, nie sama `ComponentActivity`. |
| `SessionFailed` lub `Unauthorized` | Włączenie Mobilnego SDK, krótki service ID, zgodność usługi i klucza na backendzie, ważność tokenu i pełny prefiks `mobsdk_`. |
| `MissingPayerIp` | Backend musi zwrócić publiczny IP płatnika w sesji. |
| `MissingCountryCode` | Podaj `customerCountryCode`, zwłaszcza dla EUR. |
| Płatnik zostaje w przeglądarce | Porównaj schemat w panelu, manifeście i obu adresach `TransactionReturns`, a także host filtra. |
| `RateLimited` | Odczekaj przed ponowieniem, nie pobieraj sesji w pętli. |
| `BrowserUnavailable` | Na urządzeniu musi być przeglądarka obsługująca przekierowanie. |
| `Indeterminate`, timeout lub `Pending` | Sprawdź stan na backendzie. Nie ponawiaj automatycznie operacji, która mogła już utworzyć transakcję. |


Do diagnostyki używaj `CheckoutResult.Failed.error` i `message`, bez tokenów i danych płatnika w logach. Demo z fałszywym API sprawdza interfejs, nie rzeczywistą płatność. Przed wdrożeniem sprawdź na przygotowanej usłudze BLIK, przekierowanie i powrót, przerwanie płatności, opóźnione IPN i zgodność danych zamówienia na backendzie.