# Tích hợp Mobile SDK (/vi/in-store/integration/sdk_integration)





Tapaya Accept SDK tích hợp việc xử lý thanh toán bằng thẻ trực tiếp vào ứng dụng của bạn. SDK được cung cấp cho &#x2A;*Android
(Kotlin)** và cho **React Native / Expo**, cả hai đều chạy trên thiết bị Android. Gói React Native là một
lớp bọc mỏng bên ngoài Android SDK, nên các bề mặt API, kiểu dữ liệu và trường hợp lỗi bên dưới đều giống nhau trên cả hai.

Mọi mẫu mã trên trang này đều được chia tab theo ngôn ngữ. Chọn một ngôn ngữ và toàn bộ trang sẽ chuyển theo.

## Cài đặt [#installation]

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin title="build.gradle.kts"
    dependencies {
        implementation("com.tapaya:accept:1.15.0")
    }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```json title="app.json"
    {
      "expo": {
        "plugins": ["@tapayadot/accept-react-native"]
      }
    }
    ```
  </CodeBlockTab>
</CodeBlockTabs>

**Android:** thêm dependency vào `build.gradle.kts` ở cấp module, sau đó đồng bộ dự án.

**React Native:** cài đặt bằng `npx expo install @tapayadot/accept-react-native`, thêm config plugin như
minh họa, sau đó tạo lại dự án native bằng `npx expo prebuild --clean` và chạy dự án bằng
`npx expo run:android`.

## Khởi tạo [#initialization]

`Accept` là điểm truy cập cho mọi chức năng của SDK. Khởi tạo nó một lần khi ứng dụng khởi động (trên Android,
thường là trong lớp `Application` của bạn), sau đó xác thực riêng bằng token của merchant.

### Bước 1: Khởi tạo [#step-1-initialize]

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    // Sandbox is the default (isProduction = false)
    Accept.initialize(context = this)

    // Target production explicitly
    Accept.initialize(context = this, isProduction = true)

    // Or drive it from your build type
    Accept.initialize(context = this, isProduction = !BuildConfig.DEBUG)
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    import Accept from '@tapayadot/accept-react-native';

    // Sandbox is the default
    Accept.initialize();

    // Target production explicitly
    Accept.initialize({ isProduction: true });

    // Or drive it from your build
    Accept.initialize({ isProduction: !__DEV__ });
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Trên React Native không có `context`; native module giữ nó thay cho bạn.

### Bước 2: Xác thực [#step-2-authenticate]

Gọi `Accept.auth.authenticate()` sau khi khởi tạo để đăng nhập merchant bằng token lấy từ
backend của bạn. Hàm này là bất đồng bộ và thất bại với một lỗi SDK. Khi thành công, trạng thái SDK chuyển thành *authenticated* (đã đăng nhập).

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    // suspend; call from a coroutine
    try {
        val sdkToken = myBackendApi.getSdkToken()
        Accept.auth.authenticate(sdkToken)
        // Accept.state now emits SdkState.Authenticated
    } catch (e: AcceptException) {
        // handle authentication failure
    }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    try {
        const sdkToken = await myBackendApi.getSdkToken();
        await Accept.auth.authenticate(sdkToken);
        // Accept.state is now 'authenticated'
    } catch (error) {
        // handle authentication failure
    }
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Theo dõi vòng đời để phản hồi các chuyển đổi trạng thái sau: `idle → initialized → authenticated`.

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    // Accept.state is a StateFlow<SdkState>
    Accept.state.collect { state ->
        if (state == SdkState.Authenticated) loadMerchant()
    }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    // Accept.state is a plain getter; subscribe for changes
    const subscription = Accept.addStateListener((state) => {
        if (state === 'authenticated') loadMerchant();
    });

    // later
    subscription.remove();
    ```
  </CodeBlockTab>
</CodeBlockTabs>

## Khái niệm cốt lõi [#core-concepts]

Đối tượng `Accept` cung cấp SDK thông qua một nhóm nhỏ các bề mặt API:

| Bề mặt API        | Mục đích                                                                                                        |
| :---------------- | :-------------------------------------------------------------------------------------------------------------- |
| `Accept.auth`     | Xác thực phiên của merchant.                                                                                    |
| `Accept.merchant` | Hồ sơ merchant, cấu hình, trạng thái onboarding.                                                                |
| `Accept.payments` | Tạo và quản lý các khoản thanh toán bằng thẻ.                                                                   |
| `Accept.plugin`   | Cài đặt và kích hoạt máy thanh toán (ứng dụng plugin).                                                          |
| `Accept.displays` | Thông tin màn hình thiết bị cho máy thanh toán hai mặt (“double-sided”).                                        |
| `Accept.sdk`      | Phiên bản SDK, môi trường, device id, số tiền tối thiểu.                                                        |
| `Accept.state`    | Trạng thái vòng đời: `StateFlow<SdkState>` trên Kotlin, một getter cùng `addStateListener()` trên React Native. |

### Nhận thanh toán [#taking-a-payment]

Gọi `Accept.payments.pay()` với số tiền (theo đơn vị nhỏ nhất của loại tiền tệ) và mã tiền tệ ISO 4217, sau đó
theo dõi luồng các sự kiện `PaymentEvent` mà nó báo về: đầu tiên là tiến trình, sau đó là kết quả cuối cùng. `pay()` không bao giờ
ném ngoại lệ; thay vào đó, việc không tạo được khoản thanh toán được báo về dưới dạng sự kiện `CreationFailed`.

Điểm khác biệt duy nhất là cách bạn theo dõi luồng đó: Kotlin trả về một `Flow<PaymentEvent>` để bạn collect
từ một coroutine, còn React Native nhận một listener và trả về một subscription mà bạn có thể `remove()`.

`amount` và `currency` là bắt buộc. Với các tham số tùy chọn (`paymentToken`, `receiptConfig`, `nfcPosition`,
`metadata`, `tip`, `transactionTimeout`, `display`), xem [Tạo khoản thanh toán](./card_terminal#create-a-payment).

<Callout title="Trên React Native" type="info">
  Các tham số tương tự được truyền dưới dạng một đối tượng `PaymentRequest` duy nhất, `pay({ amount, currency, tip, … })`, với
  `number` thay cho `Long` và `Record<string, string>` thay cho `Map<String, String>`. Có một tham số được đổi tên:
  timeout là `transactionTimeoutMs` và nhận giá trị mili giây thuần (`0`…`120000`) thay vì một `Duration`.
</Callout>

<div className="bg-background relative h-auto w-full overflow-hidden rounded-lg border px-10">
    <img alt="Luồng thanh toán" src="__img0" />
</div>

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    Accept.payments.pay(amount = 15000, currency = "CZK") // 150.00 Kč
        .collect { event ->
            when (event) {
                PaymentEvent.Creating -> Log.d("pay", "creating")
                is PaymentEvent.Created -> Log.d("pay", "created ${event.paymentToken}")
                PaymentEvent.Launched -> Log.d("pay", "launched")
                is PaymentEvent.Result -> Log.d("pay", event.payResult.toString())
                is PaymentEvent.CreationFailed -> Log.e("pay", event.cause.toString())
            }
        }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    const subscription = Accept.payments.pay(
        { amount: 15000, currency: 'CZK' }, // 150.00 Kč
        (event) => {
            switch (event.type) {
                case 'creating':
                    setStatus('Creating payment…');
                    break;
                case 'created':
                    // The payment token and receipt URL exist before the plugin is launched.
                    setReceiptUrl(event.receiptUrl);
                    break;
                case 'launched':
                    setStatus('Waiting for the card…');
                    break;
                case 'result':
                    setResult(event.payResult);
                    break;
                case 'creationFailed':
                    setError(event.error); // the payment never started
                    break;
            }
        },
    );

    // The stream ends itself after `result` or `creationFailed`.
    // Call subscription.remove() only to stop listening early.
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Sự kiện kết quả cuối cùng mang một `PayResult`: thành công (với `paymentToken`, `metadata`, `authCode`,
`receiptDetails`), bị từ chối (`metadata`, `receiptDetails`), bị hủy (`metadata`) hoặc thất bại (`reason`,
`metadata`). Kotlin mô hình hóa chúng thành các lớp con `PayResult.Success` / `Declined` / `Canceled` / `Failed`;
React Native mô hình hóa chúng thành một discriminated union mà bạn rẽ nhánh bằng `payResult.type === 'success'`. Xem
[Kết quả thanh toán](./card_terminal#payment-outcome) để biết chi tiết đầy đủ. Truy vấn khoản thanh toán sau đó bằng
`Accept.payments.status(paymentToken)`, dừng nó bằng `Accept.payments.cancel(paymentToken)`, hoặc hoàn tiền bằng
`Accept.payments.refund(paymentToken, reason)`.

#### Chỉ lấy kết quả (React Native) [#just-the-outcome-react-native]

Nếu UI của bạn không hiển thị các trạng thái trung gian, `payAsync()` rút gọn toàn bộ luồng thành `PayResult`
cuối cùng và reject với một `AcceptError` cho mọi trường hợp mà `pay()` lẽ ra đã báo là
`creationFailed`. Hàm này không có phiên bản tương ứng trên Kotlin; ở đó hãy collect `Flow`, hoặc chỉ lấy sự kiện cuối cùng của nó.

```typescript
const result = await Accept.payments.payAsync({ amount: 15000, currency: 'CZK' });
if (result.type === 'success') {
    console.log(result.paymentToken, result.receiptUrl);
}
```

## Thông tin merchant và SDK [#merchant--sdk-info]

`Accept.merchant` cung cấp hồ sơ và cấu hình thanh toán của merchant đã đăng nhập. `Accept.sdk` cung cấp
thông tin về bản build SDK và thiết bị này. Mọi thành phần bên dưới đều yêu cầu gọi `Accept.initialize()` trước (ngoại trừ
`Accept.sdk.version`), và mọi thành phần của `Accept.merchant` còn yêu cầu thêm `Accept.auth.authenticate()`
đã hoàn tất, nếu không lời gọi sẽ ném `NotInitialized` hoặc `NotAuthenticated`.

### Merchant [#merchant]

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    val info = Accept.merchant.info()                      // MerchantInformation?
    val config = Accept.merchant.config()                  // MerchantConfig
    val currencies = Accept.merchant.availableCurrencies() // List<String>
    val status = Accept.merchant.onboardingStatus()        // OnboardingStatus
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    const info = await Accept.merchant.info();                      // MerchantInformation | null
    const config = await Accept.merchant.config();                  // MerchantConfig
    const currencies = await Accept.merchant.availableCurrencies(); // string[]
    const status = await Accept.merchant.onboardingStatus();        // OnboardingStatus
    ```
  </CodeBlockTab>
</CodeBlockTabs>

| Thành phần              | Trả về                 | Mô tả                                                                                          |
| :---------------------- | :--------------------- | :--------------------------------------------------------------------------------------------- |
| `info()`                | `MerchantInformation?` | Hồ sơ doanh nghiệp đã đăng ký của merchant. `null` nếu hồ sơ chưa có sẵn.                      |
| `config()`              | `MerchantConfig`       | Cấu hình thanh toán của merchant.                                                              |
| `availableCurrencies()` | `List<String>`         | Các mã ISO 4217 mà merchant có thể chấp nhận. Dạng rút gọn của `config().availableCurrencies`. |
| `onboardingStatus()`    | `OnboardingStatus`     | Merchant đã được phép nhận thanh toán hay chưa.                                                |

Cả bốn đều là hàm `suspend` và ném `NotAuthenticated`, `NoInternetConnection` hoặc `Unknown` khi thất bại.

**`MerchantInformation`** chứa hồ sơ doanh nghiệp đã đăng ký. Luôn có: `merchantId` (`UUID`),
`email`, `businessType` (`BusinessType`), `legalName`, `registrationNumber`, `mcc` và địa chỉ đã đăng ký
(`addressLine1`, `city`, `country`). Tùy chọn (`null` trừ khi merchant đã cung cấp): `vatNumber`,
`incorporationDate`, `addressLine2`, `postalCode`, `state`, `phone`, `averageItemValue`,
`expectedMonthlyTurnover`, `businessModelDescription`, `numberOfEmployees`, `annualTurnover`,
`balanceSheetTotal`, `statementDescriptor`, `termsVersion`.

`BusinessType` là một trong các giá trị `INDIVIDUAL`, `COMPANY`, `NONPROFIT_ORGANIZATION`, `GOVERNMENT_ENTITY` hoặc
`PARTNERSHIP`.

**`MerchantConfig`** chứa `availableCurrencies: List<String>` và `currency: String?` mặc định của merchant.

**`OnboardingStatus`** là một sealed interface: hoặc là `ReadyForPayments`, hoặc là `RequiresAction(state)`, trong đó
`state` là một `OnboardingState`: `NOT_STARTED`, `PENDING`, `ACTIVE`, `ATTENTION_NEEDED`, `ACTION_REQUIRED`,
`BLOCKED`, `WAITING_FOR_KYB`, `WAITING_FOR_STAKEHOLDERS` hoặc `PENDING_ACQUIRER_APPROVAL`. Xem
[Onboarding merchant](/merchants/onboarding) để biết ý nghĩa của từng trạng thái.

### SDK [#sdk]

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    Accept.sdk.version            // String, e.g. "1.15.0"
    Accept.sdk.isProduction       // Boolean
    Accept.sdk.deviceId()         // String?  (suspend)
    Accept.sdk.minimumAmounts()   // List<MinimumAmount>  (suspend)
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    Accept.sdk.version                  // string, e.g. "1.15.0"
    Accept.sdk.isProduction             // boolean
    await Accept.sdk.deviceId()         // string | null
    await Accept.sdk.minimumAmounts()   // MinimumAmount[]
    ```
  </CodeBlockTab>
</CodeBlockTabs>

| Thành phần         | Trả về                | Mô tả                                                                                                                                                                                                |
| :----------------- | :-------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `version`          | `String`              | Phiên bản của SDK. Có thể đọc trước khi gọi `initialize()`.                                                                                                                                          |
| `isProduction`     | `Boolean`             | `initialize()` đã nhắm đến production (`true`) hay sandbox (`false`).                                                                                                                                |
| `deviceId()`       | `String?`             | Id cố định của thiết bị này, được gán ở lần `authenticate()` thành công đầu tiên và được giữ cho đến khi gỡ cài đặt ứng dụng. `null` trước thời điểm đó. Hãy cung cấp id này khi gửi yêu cầu hỗ trợ. |
| `minimumAmounts()` | `List<MinimumAmount>` | Số tiền thanh toán tối thiểu cho từng loại tiền tệ được hỗ trợ. Mỗi mục là `currency: String, amount: Long` theo đơn vị nhỏ nhất của loại tiền tệ.                                                   |

## Ứng dụng plugin [#plugin-app]

Mọi khoản thanh toán bằng thẻ đều được chuyển cho **Tapaya Terminal**, ứng dụng plugin của Tapaya; ứng dụng này luôn bắt buộc, không có
luồng thanh toán bằng thẻ nào bỏ qua nó. Kiểm tra xem ứng dụng đã được cài đặt chưa, sau đó kích hoạt máy thanh toán, hoặc mở trang ứng dụng
trên cửa hàng để merchant có thể cài đặt.

Dùng `Accept.plugin.isInstalled()`, `Accept.plugin.activateTerminal()` và `Accept.plugin.install()` cho việc này. Xem
[Cài đặt và kích hoạt máy thanh toán](./card_terminal#install-and-activate-the-terminal) để biết mã và mọi
kết quả kích hoạt.

Đọc trạng thái hiện tại của plugin bằng `Accept.plugin.status()`, hoặc đăng xuất merchant khỏi plugin bằng
`Accept.plugin.logout()`.

## Quyền truy cập [#permissions]

Thanh toán bằng thẻ yêu cầu ứng dụng chủ có quyền truy cập vị trí (`ACCESS_FINE_LOCATION`). SDK không
yêu cầu quyền này thay bạn; hãy cấp quyền bằng các API quyền tiêu chuẩn của Android trước khi gọi `pay()`.

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    class PaymentActivity : AppCompatActivity() {
        private val requestLocation = registerForActivityResult(
            ActivityResultContracts.RequestPermission()
        ) { granted ->
            if (granted) launchPaymentFlow() else showPermissionRationale()
        }

        fun startPayment() {
            requestLocation.launch(Manifest.permission.ACCESS_FINE_LOCATION)
        }
    }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    import { PermissionsAndroid } from 'react-native';

    const granted = await PermissionsAndroid.request(
        PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
    );

    if (granted === PermissionsAndroid.RESULTS.GRANTED) launchPaymentFlow();
    else showPermissionRationale();
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Nếu thiếu quyền vị trí hoặc không xác định được vị trí khi `pay()` chạy, flow sẽ phát ra `PaymentEvent.CreationFailed`
với nguyên nhân là `LocationPermissionRequired`, `LocationUnavailable` hoặc `LocationTimeout`.

## Xử lý lỗi [#error-handling]

Mọi lỗi được kiểm soát đều mang một trong các tên dưới đây. Trên Kotlin, mỗi lỗi là một lớp con của sealed class
`AcceptException`; trên React Native, tất cả đều là một lớp `AcceptError` duy nhất có `code` chứa tên lỗi, còn các
trường payload (`minimum`, `currency`, `state`, …) nằm ngay trên cùng đối tượng đó.

Các lời gọi bất đồng bộ (`authenticate`, `merchant.*`, `payments.status`/`cancel`/`refund`,
`plugin.activateTerminal`/`status`/`logout`/`updateInfo`, `clear`) thất bại với một trong các lỗi này. `payments.pay()` là
ngoại lệ: hàm này không bao giờ ném lỗi, và báo các lỗi tương tự dưới dạng sự kiện `CreationFailed` mang theo nguyên nhân.

### Các loại exception [#exception-types]

| Exception                                          | Mô tả                                                                                                                |
| :------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------- |
| **Vòng đời**                                       |                                                                                                                      |
| `NotInitialized`                                   | Lời gọi cần `Accept.initialize()` được gọi trước.                                                                    |
| `NotAuthenticated`                                 | Lời gọi cần `Accept.auth.authenticate()` được gọi trước.                                                             |
| `SessionExpired`                                   | Phiên đăng nhập đã hết hạn; hãy xác thực lại.                                                                        |
| `UnknownEnvironment(key)`                          | Không nhận diện được khóa môi trường (overload initialize nội bộ).                                                   |
| **Thanh toán**                                     |                                                                                                                      |
| `PaymentInProgress`                                | Một lời gọi `pay()` trước đó vẫn đang chờ kết quả.                                                                   |
| `CurrencyNotAvailableForMerchant(code)`            | Loại tiền tệ chưa được cấu hình cho merchant.                                                                        |
| `UnsupportedCurrency(code)`                        | SDK không nhận diện được loại tiền tệ.                                                                               |
| `AmountBelowMinimum(minimum, currency)`            | Số tiền thấp hơn mức tối thiểu đã cấu hình.                                                                          |
| `TransactionTimeoutOutOfRange(transactionTimeout)` | `transactionTimeout` nằm ngoài khoảng từ 0 đến 120 giây.                                                             |
| `PaymentCreationFailed(cause)`                     | Mọi lỗi khác khi tạo khoản thanh toán.                                                                               |
| `PaymentNotFound`                                  | Backend không nhận ra token truyền cho `status()`/`cancel()`/`refund()`.                                             |
| **Vị trí**                                         |                                                                                                                      |
| `LocationPermissionRequired`                       | Ứng dụng chủ không có quyền truy cập vị trí.                                                                         |
| `LocationUnavailable`                              | Không có provider nào xác định được vị trí.                                                                          |
| `LocationTimeout`                                  | Không xác định được vị trí kịp thời.                                                                                 |
| **Plugin / Máy thanh toán**                        |                                                                                                                      |
| `PluginUnavailable`                                | Ứng dụng plugin chưa được cài đặt, hoặc không thể bind tới service của nó.                                           |
| `PluginTimeout`                                    | Plugin không phản hồi kịp thời.                                                                                      |
| `ActivationInProgress`                             | Một lời gọi `activateTerminal()` trước đó vẫn đang chờ kết quả.                                                      |
| `NoTidsAvailableForMerchant`                       | Merchant không còn máy thanh toán (TID) nào chưa được gán để kích hoạt.                                              |
| `PluginAlreadyAuthenticated`                       | Plugin đã đăng nhập cho merchant này.                                                                                |
| `PluginMerchantMismatch`                           | Plugin đã đăng nhập dưới một merchant khác; hãy gọi `plugin.logout()` trước.                                         |
| `PluginNotReadyForPayments(status)`                | `pay()` chạy trong khi plugin không ở trạng thái `PluginStatus.ReadyForPayments`; `status` chứa trạng thái hiện tại. |
| `MerchantOnboardingIncomplete(state)`              | Merchant chưa hoàn tất onboarding.                                                                                   |
| **Hệ thống**                                       |                                                                                                                      |
| `NoInternetConnection`                             | Thiết bị không có kết nối mạng.                                                                                      |
| `Unknown(cause)`                                   | Mọi lỗi mà SDK không nhận diện là lỗi của chính nó.                                                                  |

<Callout title="Kết quả và exception" type="info">
  Một khoản thanh toán đã đến được máy thanh toán nhưng không thành công **không** phải là exception; nó được trả về dưới dạng
  `PaymentEvent.Result` mang `PayResult.Declined`, `Canceled` hoặc `Failed(reason)`. Tương tự, việc kích hoạt máy thanh toán
  báo `ActivateTerminalResult.Failed(reason)`.
</Callout>

### Xử lý exception [#handling-exceptions]

Bọc lời gọi trong `try`/`catch` và rẽ nhánh theo các trường hợp cụ thể mà bạn xử lý, với một nhánh catch tổng quát làm phương án dự phòng.
Kotlin rẽ nhánh theo lớp con exception; React Native kiểm tra bằng `AcceptError.is()` và dùng switch trên `code`.

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    try {
        val status = Accept.payments.status(paymentToken)
    } catch (e: SessionExpired) {
        // Re-authenticate
    } catch (e: NoInternetConnection) {
        // Ask the user to check connectivity
    } catch (e: AcceptException) {
        // Handle any other SDK error
        Log.e("Payment", "Error: ${e.message}")
    }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    import Accept, { AcceptError } from '@tapayadot/accept-react-native';

    try {
        const status = await Accept.payments.status(paymentToken);
    } catch (error) {
        if (!AcceptError.is(error)) throw error;

        switch (error.code) {
            case 'SessionExpired':
                // Re-authenticate
                break;
            case 'NoInternetConnection':
                // Ask the user to check connectivity
                break;
            case 'AmountBelowMinimum':
                // Payload fields sit on the error itself
                alert(`Minimum is ${error.minimum} ${error.currency}`);
                break;
            default:
                throw error;
        }
    }
    ```
  </CodeBlockTab>
</CodeBlockTabs>

### Ghi log [#logging]

Bật log gỡ lỗi trong quá trình phát triển để khắc phục sự cố.

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    Accept.setDebugLoggingEnabled(true)
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    Accept.setDebugLoggingEnabled(true);
    ```
  </CodeBlockTab>
</CodeBlockTabs>

### Đăng xuất [#logging-out]

Xóa toàn bộ trạng thái SDK được lưu cục bộ (auth token, device id, cấu hình đã lưu đệm) và đặt lại trạng thái SDK về
*idle*. Gọi hàm này khi người dùng đăng xuất. Để đồng thời đăng xuất merchant khỏi ứng dụng Tapaya Terminal, hãy gọi
`Accept.plugin.logout()` trước.

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    Accept.plugin.logout() // suspend; signs out of the Tapaya Terminal app
    Accept.clear()         // suspend; clears local SDK state
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    await Accept.plugin.logout(); // signs out of the Tapaya Terminal app
    await Accept.clear();         // clears local SDK state
    ```
  </CodeBlockTab>
</CodeBlockTabs>
