> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dodopayments.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Android

> Buka hosted checkout Dodo Payments dari aplikasi Android dalam Custom Tab menggunakan Android SDK resmi, dan dapatkan hasil checkout bertipe dari satu pemanggilan.

<Info>
  Halaman ini membahas Android checkout SDK, `com.dodopayments.api:checkout-android`, yang membuka hosted checkout Dodo Payments di dalam aplikasi Anda. Untuk memanggil API Dodo Payments dari server, gunakan [backend Kotlin SDK](/developer-resources/sdks/kotlin).
</Info>

<CardGroup cols={2}>
  <Card title="Checkout Sessions API" icon="cart-shopping" href="/developer-resources/checkout-session">
    Buat `checkout_url` yang dibuka oleh SDK ini.
  </Card>

  <Card title="Mobile Integration Guide" icon="mobile" href="/developer-resources/mobile-integration">
    Praktik terbaik untuk alur checkout seluler.
  </Card>
</CardGroup>

Android SDK membuka hosted checkout Dodo Payments dalam Custom Tab (`androidx.browser.customtabs`) dan mengembalikan `CheckoutResult` bertipe ketika pelanggan menyelesaikan atau meninggalkan checkout. Backend Anda membuat checkout session dan mengirim `checkout_url` ke aplikasi. SDK tidak berisi kode networking dan tidak menyimpan API key, sehingga tidak pernah memanggil API Dodo Payments.

**Persyaratan:** `minSdk` 23, Kotlin, dan Java 17. SDK hanya bergantung pada `androidx.activity`, `androidx.browser`, dan `kotlinx-coroutines-android`.

## Instalasi

<Steps>
  <Step title="Add the Dependency">
    Tambahkan SDK dari Maven Central ke `build.gradle.kts` modul aplikasi Anda:

    ```kotlin build.gradle.kts theme={null}
    dependencies {
        implementation("com.dodopayments.api:checkout-android:1.1.0")
    }
    ```

    [Kustomisasi tampilan](#appearance-customization) memerlukan versi 1.1.0 atau yang lebih baru.
  </Step>

  <Step title="Register a Callback URL Scheme">
    Tetapkan callback scheme Anda sebagai manifest placeholder Gradle. Manifest milik SDK sendiri mendeklarasikan intent filter redirect activity dengan placeholder `${dodoCallbackScheme}`, sehingga properti ini adalah satu-satunya langkah penyiapan. Anda tidak perlu menambahkan XML manifest apa pun:

    ```kotlin build.gradle.kts theme={null}
    android {
        defaultConfig {
            manifestPlaceholders["dodoCallbackScheme"] = "myapp"
        }
    }
    ```

    Gunakan scheme yang sama dalam `CheckoutParams.returnUrl`, misalnya `myapp://checkout/return`, dan tetapkan URL yang sama sebagai `return_url` checkout session saat backend Anda membuat session tersebut. SDK mencocokkan return URL berdasarkan scheme, host, dan path, serta mengabaikan query string. URL tersebut tidak perlu memuat halaman nyata.

    <Note>
      Jika Anda menghilangkan placeholder, build akan gagal dengan unresolved-placeholder error. Jika placeholder tidak cocok dengan scheme dari `returnUrl`, SDK akan melempar `PLATFORM_ERROR` sebelum membuka checkout.
    </Note>
  </Step>
</Steps>

## Penggunaan

SDK memiliki dua cara untuk memulai checkout: activity result launcher dan suspend function. Keduanya mengembalikan `CheckoutResult` yang sama.

<Tabs>
  <Tab title="Launcher (Recommended)">
    Daftarkan contract dengan `registerForActivityResult`, lalu jalankan:

    ```kotlin theme={null}
    import com.dodopayments.checkout.CheckoutParams
    import com.dodopayments.checkout.CheckoutStatus
    import com.dodopayments.checkout.DodoCheckout

    // showSuccess(), showFailure(), showExpired(), and reconcileAbandonedSession()
    // are your own functions. See Abandoned Sessions for what to reconcile.
    private val checkoutLauncher =
        registerForActivityResult(DodoCheckout.contract()) { result ->
            when (result.status) {
                CheckoutStatus.SUCCEEDED -> showSuccess(result.paymentId)
                CheckoutStatus.FAILED -> showFailure()
                CheckoutStatus.CANCELLED -> reconcileAbandonedSession() // outcome unknown, not a failure
                CheckoutStatus.PENDING -> reconcileAbandonedSession()
                CheckoutStatus.EXPIRED -> showExpired()
            }
        }

    checkoutLauncher.launch(
        CheckoutParams(
            checkoutUrl = checkoutUrl, // from your backend's checkout session
            returnUrl = "myapp://checkout/return"
        )
    )
    ```

    <Tip>
      Gunakan gaya ini jika memungkinkan. Android mengirimkan hasil melalui `ActivityResultRegistry` miliknya, sehingga hasil tetap tersedia setelah process death.
    </Tip>
  </Tab>

  <Tab title="Suspend Function">
    Panggil `DodoCheckout.start` dari coroutine scope:

    ```kotlin theme={null}
    import androidx.lifecycle.lifecycleScope
    import com.dodopayments.checkout.CheckoutParams
    import com.dodopayments.checkout.CheckoutStatus
    import com.dodopayments.checkout.DodoCheckout
    import kotlinx.coroutines.launch

    // showSuccess(), showFailure(), showExpired(), and reconcileAbandonedSession()
    // are your own functions. See Abandoned Sessions for what to reconcile.
    lifecycleScope.launch {
        val result = DodoCheckout.start(
            activity = this@MyActivity,
            params = CheckoutParams(
                checkoutUrl = checkoutUrl, // from your backend's checkout session
                returnUrl = "myapp://checkout/return"
            ),
            onEvent = { event -> println(event.name) } // logging only
        )

        when (result.status) {
            CheckoutStatus.SUCCEEDED -> showSuccess(result.paymentId)
            CheckoutStatus.FAILED -> showFailure()
            CheckoutStatus.CANCELLED -> reconcileAbandonedSession() // outcome unknown, not a failure
            CheckoutStatus.PENDING -> reconcileAbandonedSession()
            CheckoutStatus.EXPIRED -> showExpired()
        }
    }
    ```

    `onEvent` menerima event `checkout.opened`, `checkout.return_received`, dan `checkout.closed`. Gunakan event tersebut hanya untuk logging, bukan untuk menentukan hasil.

    <Warning>
      Gaya ini menunggu `CompletableDeferred` dalam memori, sehingga hasil **tidak** tetap tersedia setelah process death. `onEvent` hanya tersedia dalam gaya ini, bukan dengan launcher.
    </Warning>
  </Tab>
</Tabs>

## Arti Hasil

SDK membuat `CheckoutResult` dari query parameter pada return URL.

<Warning>
  Field `status` adalah petunjuk UI, bukan bukti pembayaran. Sebelum memberikan akses, konfirmasikan pembayaran di backend Anda menggunakan webhook atau endpoint Get Payment Detail.
</Warning>

<ParamField body="status" type="CheckoutStatus" required>
  Salah satu dari lima nilai berikut:

  * `SUCCEEDED`: return URL memiliki `status=succeeded` (pembayaran satu kali) atau `status=active` (subscription).
  * `FAILED`: pembayaran ditolak (`status=failed`).
  * `CANCELLED`: pelanggan menutup Custom Tab sebelum return URL diterima. SDK tidak mengetahui hasilnya, dan pembayaran mungkin berhasil, jadi jangan tampilkan layar kegagalan. Rekonsiliasikan [abandoned session](#abandoned-sessions) sebagai gantinya.
  * `PENDING`: pembayaran diselesaikan kemudian (`status=processing` atau nilai `requires_*` apa pun), atau parameter `status` tidak ada atau tidak dikenali. Rekonsiliasikan seperti `CANCELLED`.
  * `EXPIRED`: checkout session kedaluwarsa (`status=expired`).
</ParamField>

<ParamField body="paymentId" type="String?">
  Query parameter `payment_id`, jika return URL menyertakannya. Tampilkan parameter ini di UI Anda, tetapi jangan gunakan untuk memberikan akses. Lihat [Verify the Payment](#verify-the-payment).
</ParamField>

<ParamField body="subscriptionId" type="String?">
  Query parameter `subscription_id`. Tetapkan untuk subscription checkout.
</ParamField>

<ParamField body="licenseKeys" type="List<String>?">
  Query parameter `license_key`. Tetapkan saat checkout menyertakan produk license key.
</ParamField>

<ParamField body="customerEmail" type="String?">
  Query parameter `email`. Tetapkan saat checkout mengambil alamat email.
</ParamField>

<ParamField body="raw" type="Map<String, String>">
  Setiap query parameter dari return URL, apa adanya.
</ParamField>

## Verifikasi Pembayaran

<CardGroup cols={2}>
  <Card title="Webhooks" icon="webhook" href="/developer-resources/webhooks">
    Dengarkan payment event secara real time.
  </Card>

  <Card title="Get Payment Detail" icon="magnifying-glass" href="/api-reference/payments/get-payments-1">
    Query status pembayaran sesuai kebutuhan.
  </Card>
</CardGroup>

Berikan akses hanya setelah salah satu metode tersebut mengonfirmasi pembayaran, misalnya melalui webhook `payment.succeeded` atau `subscription.active`. Jangan hanya mengandalkan `CheckoutResult.status`.

## Kustomisasi Tampilan

Untuk mengubah toolbar, tombol, dan skema warna Custom Tab, teruskan `BrowserCustomization` sebagai `customization` pada `CheckoutParams`. Setiap field bersifat opsional dan secara default menggunakan `null`. Untuk field `null`, SDK tidak menetapkan opsi tersebut, sehingga browser yang menjadi host Custom Tab akan menerapkan default-nya sendiri.

<ParamField body="toolbarColor" type="Int?">
  Warna latar belakang toolbar, sebagai integer ARGB `Color`.
</ParamField>

<ParamField body="navigationBarColor" type="Int?">
  Warna navigation bar, sebagai integer ARGB `Color`.
</ParamField>

<ParamField body="navigationBarDividerColor" type="Int?">
  Warna pemisah di atas navigation bar, sebagai integer ARGB `Color`.
</ParamField>

<ParamField body="closeButtonStyle" type="CloseButtonStyle?">
  `DEFAULT` menampilkan ikon sistem "X". `BACK` menampilkan panah kembali yang digambar oleh SDK.
</ParamField>

<ParamField body="closeButtonPosition" type="CloseButtonPosition?">
  Sisi toolbar tempat tombol tutup muncul: `START` atau `END`.
</ParamField>

<ParamField body="shareButtonEnabled" type="Boolean?">
  Menampilkan ikon berbagi pada toolbar. `false` menyembunyikannya.
</ParamField>

<ParamField body="showTitleEnabled" type="Boolean?">
  Menampilkan judul halaman di bawah URL pada toolbar.
</ParamField>

<ParamField body="urlBarHidingEnabled" type="Boolean?">
  Menyembunyikan toolbar secara otomatis saat halaman digulir.
</ParamField>

<ParamField body="bookmarksButtonEnabled" type="Boolean?">
  Menampilkan "Bookmark this page" dalam menu overflow.
</ParamField>

<ParamField body="downloadsButtonEnabled" type="Boolean?">
  Menampilkan "Download page" dalam menu overflow.
</ParamField>

<ParamField body="colorScheme" type="ColorScheme?">
  `LIGHT` atau `DARK` memaksa tampilan tersebut terlepas dari pengaturan sistem perangkat. `SYSTEM` mengikuti pengaturan sistem.
</ParamField>

Contoh ini menggunakan kembali `checkoutLauncher` dari [Usage](#usage):

```kotlin theme={null}
import android.graphics.Color
import com.dodopayments.checkout.BrowserCustomization
import com.dodopayments.checkout.CheckoutParams

checkoutLauncher.launch(
    CheckoutParams(
        checkoutUrl = checkoutUrl,
        returnUrl = "myapp://checkout/return",
        customization = BrowserCustomization(
            toolbarColor = Color.parseColor("#6366F1"),
            closeButtonStyle = BrowserCustomization.CloseButtonStyle.BACK,
            colorScheme = BrowserCustomization.ColorScheme.DARK,
        )
    )
)
```

## Error

`DodoCheckout.start` hanya melempar `CheckoutError` untuk penggunaan yang salah atau kegagalan platform. Baca alasannya dari `CheckoutError.code`:

* `INVALID_CHECKOUT_URL`: `checkoutUrl` bukan URL checkout session `https` (path yang diawali `/session/`) pada `checkout.dodopayments.com` atau `test.checkout.dodopayments.com`.
* `INVALID_RETURN_URL`: `returnUrl` bukan URL absolut dengan scheme dan host.
* `ALREADY_IN_PROGRESS`: checkout lain sedang berjalan. Hanya satu checkout yang dapat berjalan pada satu waktu.
* `PLATFORM_ERROR`: kegagalan platform yang tidak terduga, termasuk scheme `returnUrl` yang tidak cocok dengan placeholder `dodoCallbackScheme` Anda.

Pelanggan yang membatalkan, atau pembayaran yang ditolak, selalu menghasilkan result (`CANCELLED` atau `FAILED`), bukan error yang dilempar. Dengan launcher, error validasi dilempar dari `launcher.launch(...)`. Kegagalan platform setelah peluncuran tidak dapat dilempar melalui activity result callback, sehingga launcher mengembalikan `CANCELLED` dengan error code di `raw["error"]`.

## Session yang Ditinggalkan

SDK mencatat checkout session saat checkout dimulai, dan menghapus catatan tersebut hanya saat checkout berakhir dengan `SUCCEEDED`, `FAILED`, atau `EXPIRED`. Catatan tetap ada saat aplikasi dihentikan selama checkout, serta setelah result `CANCELLED` atau `PENDING`, karena dalam kasus tersebut SDK tidak mengetahui hasilnya. Periksa catatan tersebut saat aplikasi diluncurkan berikutnya dan setelah setiap result `CANCELLED` atau `PENDING`:

```kotlin theme={null}
// context is any Context, such as your Activity.
DodoCheckout.getAbandonedSession(context)?.let { abandoned ->
    // Ask your backend for the outcome of abandoned.sessionId, and show it.
    // Clear the record only after the outcome is final:
    DodoCheckout.clearAbandonedSession(context)
}
```

`abandoned.sessionId` adalah ID checkout session, yang diawali dengan `cks_`. `abandoned.createdAt` adalah waktu checkout dimulai, sebagai timestamp epoch dalam milidetik. Backend Anda dapat mencari session tersebut dengan [Get Checkout Session](/api-reference/checkout-sessions/get-checkouts), yang mengembalikan `payment_id` dan `payment_status` miliknya. Hingga pembayaran mencapai status final, perlakukan sebagai pending, bukan failed.

## Terkait

<CardGroup cols={2}>
  <Card title="Mobile Integration Guide" icon="mobile" href="/developer-resources/mobile-integration">
    Praktik terbaik untuk alur checkout seluler.
  </Card>

  <Card title="Kotlin SDK" icon="code" href="/developer-resources/sdks/kotlin">
    Backend SDK untuk operasi sisi server.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.