---
title: "Xử lý sự kiện flow & paywall - Android"
description: "Xử lý các sự kiện flow và paywall trong ứng dụng Android của bạn."
---

:::important
Hướng dẫn này đề cập đến việc xử lý sự kiện cho các lần mua hàng, khôi phục, chọn sản phẩm và hiển thị flow. Bạn cũng cần triển khai xử lý nút bấm (đóng flow, mở liên kết, v.v.). Xem [hướng dẫn xử lý hành động nút bấm](android-handle-paywall-actions) của chúng tôi để biết thêm chi tiết.
:::
Flow và paywall được cấu hình bằng [Flow Builder](adapty-flow-builder) hoặc [Paywall Builder](adapty-paywall-builder) không cần thêm code để thực hiện và khôi phục giao dịch mua. Tuy nhiên, chúng tạo ra một số sự kiện mà ứng dụng của bạn có thể phản hồi. Các sự kiện đó bao gồm thao tác nhấn nút (nút đóng, URL, chọn sản phẩm, v.v.) cũng như thông báo về các hành động liên quan đến giao dịch mua. Tìm hiểu cách phản hồi các sự kiện này bên dưới.

:::tip

Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác.

:::

Nếu bạn cần kiểm soát hoặc theo dõi các quy trình diễn ra trên màn hình thanh toán, hãy triển khai các phương thức `AdaptyFlowEventListener`.
Nếu bạn muốn giữ nguyên hành vi mặc định trong một số trường hợp, bạn có thể extend `AdaptyFlowDefaultEventListener` và chỉ override những method bạn muốn thay đổi.

Dưới đây là các giá trị mặc định từ `AdaptyFlowDefaultEventListener`.
### Sự kiện do người dùng tạo ra \{#user-generated-events\}

#### Chọn sản phẩm \{#product-selection\}

Nếu một sản phẩm được chọn để mua (bởi người dùng hoặc hệ thống), phương thức này sẽ được gọi:

```kotlin showLineNumbers title="Kotlin"
public override fun onProductSelected(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
```

<Details>
<summary>Ví dụ sự kiện (Nhấn để mở rộng)</summary>
```javascript
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}
```
</Details>

#### Bắt đầu mua hàng \{#started-purchase\}

Nếu người dùng bắt đầu quá trình mua hàng, phương thức này sẽ được gọi:

```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseStarted(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
```

<Details>
<summary>Ví dụ sự kiện (Nhấn để mở rộng)</summary>
```javascript
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}
```
</Details>

Phương thức này sẽ không được gọi trong chế độ Observer. Tham khảo chủ đề [Android - Hiển thị paywall Paywall Builder trong chế độ Observer](android-present-paywall-builder-paywalls-in-observer-mode) để biết thêm chi tiết.

#### Mua thành công, đã hủy hoặc đang chờ xử lý \{#successful-canceled-or-pending-purchase\}

Nếu giao dịch mua thành công, phương thức này sẽ được gọi:
```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseFinished(
    purchaseResult: AdaptyPurchaseResult,
    product: AdaptyPaywallProduct,
    context: Context,
) {
    if (purchaseResult !is AdaptyPurchaseResult.UserCanceled)
        context.getActivityOrNull()?.onBackPressed()
}
```

<Details>
<summary>Ví dụ sự kiện (Nhấn để mở rộng)</summary>
```javascript
// Successful purchase
{
  "purchaseResult": {
    "type": "Success",
    "profile": {
      "accessLevels": {
        "premium": {
          "id": "premium",
          "isActive": true,
          "expiresAt": "2024-02-15T10:30:00Z"
        }
      }
    }
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

// Cancelled purchase
{
  "purchaseResult": {
    "type": "UserCanceled"
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

// Pending purchase
{
  "purchaseResult": {
    "type": "Pending"
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}
```
</Details>
Chúng tôi khuyến nghị đóng màn hình trong trường hợp đó.

Phương thức này sẽ không được gọi trong Observer mode. Tham khảo chủ đề [Android - Hiển thị paywall Paywall Builder trong Observer mode](android-present-paywall-builder-paywalls-in-observer-mode) để biết thêm chi tiết.

#### Giao dịch mua thất bại \{#failed-purchase\}
Nếu một giao dịch thất bại do lỗi, phương thức này sẽ được gọi. Điều này bao gồm các lỗi Google Play Billing (hạn chế thanh toán, sản phẩm không hợp lệ, lỗi mạng), lỗi xác minh giao dịch và lỗi hệ thống. Lưu ý rằng khi người dùng hủy, `onPurchaseFinished` sẽ được gọi với kết quả đã hủy thay thế, và các giao dịch đang chờ xử lý không kích hoạt phương thức này.

```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseFailure(
    error: AdaptyError,
    product: AdaptyPaywallProduct,
    context: Context,
) {}
```

<Details>
<summary>Ví dụ sự kiện (Nhấn để mở rộng)</summary>
```javascript
{
  "error": {
    "code": "purchase_failed",
    "message": "Purchase failed due to insufficient funds",
    "details": {
      "underlyingError": "Insufficient funds in account"
    }
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}
```
</Details>
Phương thức này sẽ không được gọi trong chế độ Observer. Tham khảo chủ đề [Android - Hiển thị paywall Paywall Builder trong chế độ Observer](android-present-paywall-builder-paywalls-in-observer-mode) để biết thêm chi tiết.

#### Hoàn tất điều hướng thanh toán web \{#finished-web-payment-navigation\}

Phương thức này được gọi sau khi có một lần thử mở [web paywall](web-paywall) cho một sản phẩm cụ thể. Điều này bao gồm cả các lần điều hướng thành công lẫn thất bại:

```kotlin showLineNumbers title="Kotlin"
public override fun onFinishWebPaymentNavigation(
    product: AdaptyPaywallProduct?,
    error: AdaptyError?,
    context: Context,
) {}
```
**Tham số:**

| Tham số     | Mô tả                                                                                                          |
|:------------|:---------------------------------------------------------------------------------------------------------------|
| **product** | Một `AdaptyPaywallProduct` mà web paywall đã được mở. Có thể là `null`.                                        |
| **error**   | Một đối tượng `AdaptyError` nếu điều hướng web paywall thất bại; `null` nếu điều hướng thành công. |

<Details>
<summary>Ví dụ về sự kiện (Nhấn để mở rộng)</summary>
```javascript
// Successful navigation
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": null
}

// Failed navigation
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": {
    "code": "web_navigation_failed",
    "message": "Failed to open web paywall",
    "details": {
      "underlyingError": "Browser unavailable"
    }
  }
}
```
</Details>
#### Khôi phục thành công \{#successful-restore\}

Nếu việc khôi phục giao dịch mua thành công, phương thức này sẽ được gọi:

```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreSuccess(
    profile: AdaptyProfile,
    context: Context,
) {}
```

<Details>
<summary>Ví dụ sự kiện (Nhấn để mở rộng)</summary>
```javascript
{
  "profile": {
    "accessLevels": {
      "premium": {
        "id": "premium",
        "isActive": true,
        "expiresAt": "2024-02-15T10:30:00Z"
      }
    },
    "subscriptions": [
      {
        "vendorProductId": "premium_monthly",
        "isActive": true,
        "expiresAt": "2024-02-15T10:30:00Z"
      }
    ]
  }
}
```
</Details>

Chúng tôi khuyến nghị đóng màn hình nếu người dùng có `accessLevel` yêu cầu. Tham khảo chủ đề [Trạng thái gói đăng ký](android-listen-subscription-changes) để tìm hiểu cách kiểm tra.

#### Khôi phục thất bại \{#failed-restore\}
Nếu `Adapty.restorePurchases()` thất bại, phương thức này sẽ được gọi:

```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreFailure(
    error: AdaptyError,
    context: Context,
) {}
```

<Details>
<summary>Ví dụ sự kiện (Nhấn để mở rộng)</summary>

```javascript
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}
```
</Details>

#### Nâng cấp gói đăng ký \{#upgrade-subscription\}
Khi người dùng cố mua một gói đăng ký mới trong khi đã có gói đăng ký đang hoạt động, bạn có thể kiểm soát cách xử lý giao dịch mới bằng cách ghi đè phương thức này. Bạn có hai lựa chọn:
1. **Thay thế gói đăng ký hiện tại** bằng gói mới:
```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingPurchaseParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback,
): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked {
    onPurchaseParamsReceived(
        AdaptyPurchaseParameters.Builder()
            .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...))
            .build()
    )
    return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked
}
```
2. **Giữ cả hai gói đăng ký** (thêm gói mới riêng biệt):
```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingPurchaseParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback,
): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked {
    onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty)
    return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked
}
```
:::note
Nếu bạn không override phương thức này, hành vi mặc định là giữ nguyên cả hai gói đăng ký (tương đương với việc sử dụng `AdaptyPurchaseParameters.Empty`).
:::

Bạn cũng có thể đặt thêm các tham số mua hàng nếu cần:
```kotlin
AdaptyPurchaseParameters.Builder()
    .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription
    .withOfferPersonalized(true) // optional - if using personalized pricing
    .build()
```

<Details>
<summary>Ví dụ sự kiện (Nhấn để mở rộng)</summary>
```javascript
{
  "product": {
    "vendorProductId": "premium_yearly",
    "localizedTitle": "Premium Yearly",
    "localizedDescription": "Premium subscription for 1 year",
    "localizedPrice": "$99.99",
    "price": 99.99,
    "currencyCode": "USD"
  },
  "subscriptionUpdateParams": {
    "replacementMode": "with_time_proration"
  }
}
```
</Details>
### Tải dữ liệu và hiển thị \{#data-fetching-and-rendering\}

#### Lỗi tải sản phẩm \{#product-loading-errors\}

Nếu bạn không truyền sản phẩm trong quá trình khởi tạo, AdaptyUI sẽ tự động lấy các đối tượng cần thiết từ server. Nếu thao tác này thất bại, AdaptyUI sẽ báo lỗi bằng cách gọi phương thức sau:

```kotlin showLineNumbers title="Kotlin"
public override fun onLoadingProductsFailure(
    error: AdaptyError,
    context: Context,
): Boolean = false
```

<Details>
<summary>Ví dụ sự kiện (Nhấn để mở rộng)</summary>
```javascript
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}
```
</Details>

Nếu bạn trả về `true`, AdaptyUI sẽ lặp lại yêu cầu sau 2 giây.

#### Lỗi kết xuất giao diện \{#rendering-errors\}

Nếu xảy ra lỗi trong quá trình kết xuất giao diện, lỗi đó sẽ được báo cáo thông qua phương thức này:

```kotlin showLineNumbers title="Kotlin"
public override fun onError(
    error: AdaptyError,
    context: Context,
) {}
```

<Details>
<summary>Ví dụ sự kiện (Nhấn để mở rộng)</summary>
```javascript
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render flow interface",
    "details": {
      "underlyingError": "Invalid flow configuration"
    }
  }
}
```
</Details>

Trong điều kiện bình thường, các lỗi này sẽ không xảy ra, vì vậy nếu bạn gặp phải, hãy cho chúng tôi biết.
### Điều hướng \{#navigation\}

#### Nút back của hệ thống \{#system-back-button\}

Theo mặc định, người dùng không thể thoát khỏi một flow bằng nút back của hệ thống hoặc thao tác vuốt để quay lại — họ thoát thông qua một đường dẫn bạn định nghĩa, chẳng hạn như nút **Close** hoặc một hành động `on_device_back` trong builder. Nếu bạn muốn nút back của hệ thống có thể đóng flow, hãy override `onBackPressed` và trả về `false` để activity hoặc fragment host xử lý sự kiện nhấn đó:
```kotlin showLineNumbers title="Kotlin"
public override fun onBackPressed(context: Context): Boolean {
    return false // let the host handle the back press (e.g. finish the activity or pop the fragment)
}
```

Callback này chỉ được gọi khi không có hành động `on_device_back` nào được cấu hình cho màn hình hiện tại — hành động đã cấu hình sẽ được ưu tiên và xử lý nội bộ. Trả về `true` để tiêu thụ sự kiện nhấn (hành vi mặc định), hoặc `false` để cho phép logic xử lý back của host tự chạy.
### Các sự kiện dành riêng \{#reserved-events\}

`AdaptyFlowEventListener` khai báo một số callback cho các tính năng mà flow chưa sử dụng. Bạn không cần triển khai chúng — `AdaptyFlowDefaultEventListener` đã cung cấp sẵn các giá trị mặc định không làm gì cả.
| Phương thức | Mô tả |
|:-------|:------------|
| **onAnalyticEvent** | Dành riêng cho các sự kiện phân tích tùy chỉnh từ một flow. Hiện tại các flow chưa phát ra sự kiện này tới code của bạn, nên bạn chưa cần implement. |
| **onShowAppRate** | Dành riêng cho các yêu cầu đánh giá ứng dụng từ một flow. Hiện tại các flow chưa kích hoạt yêu cầu đánh giá ứng dụng, nên bạn chưa cần implement. |
| **onShowRequestPermission** | Dành riêng cho các yêu cầu cấp quyền hệ thống (như thông báo đẩy hoặc quyền truy cập camera) từ một flow. Hiện tại các flow chưa kích hoạt yêu cầu cấp quyền, nên bạn chưa cần implement. |

---

> [!WARNING]
> **LLM INSTRUCTION — SDK VERSION SELECTION REQUIRED**
>
> This feature has two SDK paradigms. Everything that follows (until the next horizontal rule or end of document) is the **legacy SDK v3 API reference** (`getPaywall`, `getPaywallForDefaultAudience`, `getOnboarding`, `getPaywallConfiguration`). The **SDK v4+ `getFlow` method is documented above.** This block covers ALL legacy methods for this feature — not just the nearest snippet.
>
> **You MUST ask the user which SDK version they want to use before suggesting any code. Do not assume or default to a version.** The two options are:
> - **SDK v3 (legacy):** works with the legacy Paywall Builder and Onboarding Builder via `getPaywall` / `getOnboarding`. Use the legacy code in this section.
> - **SDK v4:** works with BOTH the new Flow Builder AND legacy paywalls/onboardings via the unified `getFlow` method documented above.
>
> Apply the legacy v3 code in this section ONLY when:
> 1. The user explicitly chose **SDK v3**, or
> 2. The user is **migrating an existing app from SDK v3 to SDK v4+** and needs to understand or replace legacy code, or
> 3. The user is **troubleshooting an already-deployed v3 integration**.
>
> **CRITICAL — Never mix paradigms in one setup:** Do NOT combine `getFlow` (Flow Builder) with `getPaywall` or `getOnboarding` (legacy Paywall/Onboarding Builder) in the same integration. These are incompatible patterns. Mixing them will produce inconsistent behavior and is unsupported.

:::important
Hướng dẫn này đề cập đến việc xử lý sự kiện cho các giao dịch mua, khôi phục, lựa chọn sản phẩm và hiển thị paywall. Bạn cũng cần triển khai xử lý nút bấm (đóng paywall, mở liên kết, v.v.). Xem [hướng dẫn xử lý hành động nút bấm](android-handle-paywall-actions) của chúng tôi để biết thêm chi tiết.
:::
Paywall được cấu hình bằng [Paywall Builder](adapty-paywall-builder) không cần thêm code để thực hiện và khôi phục giao dịch mua. Tuy nhiên, chúng tạo ra một số sự kiện mà ứng dụng của bạn có thể phản hồi. Các sự kiện đó bao gồm các thao tác nhấn nút (nút đóng, URL, chọn sản phẩm, v.v.) cũng như thông báo về các hành động liên quan đến giao dịch mua được thực hiện trên paywall. Hãy tìm hiểu cách phản hồi các sự kiện này bên dưới.

:::warning
Hướng dẫn này chỉ dành cho **paywall Paywall Builder mới** yêu cầu Adapty SDK v3.0 trở lên.
:::

:::tip

Muốn xem ví dụ thực tế về cách tích hợp Adapty SDK vào ứng dụng di động? Hãy xem [ứng dụng mẫu](sample-apps) của chúng tôi, nơi minh họa toàn bộ quá trình thiết lập, bao gồm hiển thị paywall, thực hiện mua hàng và các chức năng cơ bản khác.

:::
Nếu bạn cần kiểm soát hoặc theo dõi các tiến trình diễn ra trên màn hình mua hàng, hãy implement các phương thức của `AdaptyUiEventListener`.

Nếu bạn muốn giữ nguyên hành vi mặc định trong một số trường hợp, bạn có thể extend `AdaptyUiDefaultEventListener` và chỉ override những phương thức bạn muốn thay đổi.

Dưới đây là các giá trị mặc định từ `AdaptyUiDefaultEventListener`.
### Sự kiện do người dùng tạo ra \{#user-generated-events\}

#### Chọn sản phẩm \{#product-selection\}

Nếu một sản phẩm được chọn để mua (bởi người dùng hoặc hệ thống), phương thức này sẽ được gọi:

```kotlin showLineNumbers title="Kotlin"
public override fun onProductSelected(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
```

<Details>
<summary>Ví dụ sự kiện (Nhấp để mở rộng)</summary>
```javascript
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}
```
</Details>

#### Bắt đầu mua hàng \{#started-purchase\}

Nếu người dùng bắt đầu quá trình mua hàng, phương thức này sẽ được gọi:

```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseStarted(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
```

<Details>
<summary>Ví dụ sự kiện (Nhấn để mở rộng)</summary>
```javascript
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}
```
</Details>

Phương thức này sẽ không được gọi trong chế độ Observer. Tham khảo chủ đề [Android - Present Paywall Builder paywalls in Observer mode](android-present-paywall-builder-paywalls-in-observer-mode) để biết thêm chi tiết.

#### Mua hàng thành công, đã hủy hoặc đang chờ xử lý \{#successful-canceled-or-pending-purchase\}

Nếu mua hàng thành công, phương thức này sẽ được gọi:
```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseFinished(
    purchaseResult: AdaptyPurchaseResult,
    product: AdaptyPaywallProduct,
    context: Context,
) {
    if (purchaseResult !is AdaptyPurchaseResult.UserCanceled)
        context.getActivityOrNull()?.onBackPressed()
}
```

<Details>
<summary>Ví dụ về sự kiện (Nhấn để mở rộng)</summary>
```javascript
// Successful purchase
{
  "purchaseResult": {
    "type": "Success",
    "profile": {
      "accessLevels": {
        "premium": {
          "id": "premium",
          "isActive": true,
          "expiresAt": "2024-02-15T10:30:00Z"
        }
      }
    }
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

// Cancelled purchase
{
  "purchaseResult": {
    "type": "UserCanceled"
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

// Pending purchase
{
  "purchaseResult": {
    "type": "Pending"
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}
```
</Details>
Chúng tôi khuyến nghị bạn đóng màn hình trong trường hợp đó.

Phương thức này sẽ không được gọi trong Observer mode. Tham khảo chủ đề [Android - Hiển thị paywall Paywall Builder trong Observer mode](android-present-paywall-builder-paywalls-in-observer-mode) để biết thêm chi tiết.

#### Mua hàng thất bại \{#failed-purchase\}
Nếu một giao dịch thất bại do lỗi, phương thức này sẽ được gọi. Điều này bao gồm các lỗi Google Play Billing (hạn chế thanh toán, sản phẩm không hợp lệ, lỗi mạng), lỗi xác minh giao dịch, và lỗi hệ thống. Lưu ý rằng khi người dùng hủy giao dịch, `onPurchaseFinished` sẽ được gọi với kết quả đã hủy thay thế, và các khoản thanh toán đang chờ xử lý sẽ không kích hoạt phương thức này.

```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseFailure(
    error: AdaptyError,
    product: AdaptyPaywallProduct,
    context: Context,
) {}
```

<Details>
<summary>Ví dụ sự kiện (Nhấn để mở rộng)</summary>
```javascript
{
  "error": {
    "code": "purchase_failed",
    "message": "Purchase failed due to insufficient funds",
    "details": {
      "underlyingError": "Insufficient funds in account"
    }
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}
```
</Details>
Phương thức này sẽ không được gọi trong chế độ Observer. Tham khảo chủ đề [Android - Hiển thị paywall Paywall Builder trong chế độ Observer](android-present-paywall-builder-paywalls-in-observer-mode) để biết thêm chi tiết.

#### Hoàn tất điều hướng thanh toán web \{#finished-web-payment-navigation\}

Phương thức này được gọi sau khi có một lần thử mở [web paywall](web-paywall) cho một sản phẩm cụ thể. Điều này bao gồm cả các lần điều hướng thành công lẫn thất bại:

```kotlin showLineNumbers title="Kotlin"
public override fun onFinishWebPaymentNavigation(
    product: AdaptyPaywallProduct?,
    error: AdaptyError?,
    context: Context,
) {}
```
**Tham số:**

| Tham số     | Mô tả                                                                                                          |
|:------------|:---------------------------------------------------------------------------------------------------------------|
| **product** | Một `AdaptyPaywallProduct` mà web paywall được mở cho sản phẩm đó. Có thể là `null`.                          |
| **error**   | Một đối tượng `AdaptyError` nếu điều hướng web paywall thất bại; `null` nếu điều hướng thành công. |

<Details>
<summary>Ví dụ sự kiện (Nhấn để mở rộng)</summary>
```javascript
// Successful navigation
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": null
}

// Failed navigation
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": {
    "code": "web_navigation_failed",
    "message": "Failed to open web paywall",
    "details": {
      "underlyingError": "Browser unavailable"
    }
  }
}
```
</Details>
#### Khôi phục thành công \{#successful-restore\}

Nếu việc khôi phục giao dịch thành công, phương thức này sẽ được gọi:

```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreSuccess(
    profile: AdaptyProfile,
    context: Context,
) {}
```

<Details>
<summary>Ví dụ sự kiện (Nhấn để mở rộng)</summary>
```javascript
{
  "profile": {
    "accessLevels": {
      "premium": {
        "id": "premium",
        "isActive": true,
        "expiresAt": "2024-02-15T10:30:00Z"
      }
    },
    "subscriptions": [
      {
        "vendorProductId": "premium_monthly",
        "isActive": true,
        "expiresAt": "2024-02-15T10:30:00Z"
      }
    ]
  }
}
```
</Details>

Chúng tôi khuyến nghị đóng màn hình nếu người dùng có `accessLevel` yêu cầu. Tham khảo tài liệu [Trạng thái gói đăng ký](android-listen-subscription-changes) để biết cách kiểm tra.

#### Khôi phục thất bại \{#failed-restore\}
Nếu `Adapty.restorePurchases()` thất bại, phương thức này sẽ được gọi:

```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreFailure(
    error: AdaptyError,
    context: Context,
) {}
```

<Details>
<summary>Ví dụ sự kiện (Nhấp để mở rộng)</summary>

```javascript
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}
```
</Details>

#### Nâng cấp gói đăng ký \{#upgrade-subscription\}
<Tabs groupId="current-os" queryString>
<TabItem value="new" label="SDK version 3.10.0 or later" default>

Khi người dùng cố gắng mua một gói đăng ký mới trong khi đã có một gói đăng ký khác đang hoạt động, bạn có thể kiểm soát cách xử lý giao dịch mua mới đó bằng cách override phương thức này. Bạn có hai lựa chọn:
1. **Thay thế gói đăng ký hiện tại** bằng gói mới:
```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingPurchaseParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback,
): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked {
    onPurchaseParamsReceived(
        AdaptyPurchaseParameters.Builder()
            .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...))
            .build()
    )
    return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked
}
```
2. **Giữ cả hai gói đăng ký** (thêm gói mới riêng biệt):
```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingPurchaseParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback,
): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked {
    onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty)
    return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked
}
```
:::note
Nếu bạn không override phương thức này, hành vi mặc định là giữ cả hai gói đăng ký hoạt động (tương đương với việc sử dụng `AdaptyPurchaseParameters.Empty`).
:::

Bạn cũng có thể đặt các tham số mua hàng bổ sung nếu cần:
```kotlin
AdaptyPurchaseParameters.Builder()
    .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription
    .withOfferPersonalized(true) // optional - if using personalized pricing
    .build()
```

</TabItem>
<TabItem value="old" label="Phiên bản SDK cũ hơn 3.10.0" default>
Nếu một gói đăng ký mới được mua trong khi gói khác vẫn còn hoạt động, hãy override phương thức này để thay thế gói hiện tại bằng gói mới. Nếu gói đăng ký đang hoạt động vẫn phải được giữ nguyên và gói mới được thêm riêng, hãy gọi `onSubscriptionUpdateParamsReceived(null)`:

```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingSubscriptionUpdateParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback,
) {
    onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...))
}
```
</TabItem>
</Tabs>

<Details>
<summary>Ví dụ sự kiện (Nhấp để mở rộng)</summary>

```javascript
{
  "product": {
    "vendorProductId": "premium_yearly",
    "localizedTitle": "Premium Yearly",
    "localizedDescription": "Premium subscription for 1 year",
    "localizedPrice": "$99.99",
    "price": 99.99,
    "currencyCode": "USD"
  },
  "subscriptionUpdateParams": {
    "replacementMode": "with_time_proration"
  }
}
```
</Details>
### Tải dữ liệu và hiển thị \{#data-fetching-and-rendering\}

#### Lỗi tải sản phẩm \{#product-loading-errors\}

Nếu bạn không truyền sản phẩm trong quá trình khởi tạo, AdaptyUI sẽ tự động lấy các đối tượng cần thiết từ server. Nếu thao tác này thất bại, AdaptyUI sẽ báo lỗi bằng cách gọi phương thức sau:

```kotlin showLineNumbers title="Kotlin"
public override fun onLoadingProductsFailure(
    error: AdaptyError,
    context: Context,
): Boolean = false
```

<Details>
<summary>Ví dụ sự kiện (Nhấn để mở rộng)</summary>
```javascript
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}
```
</Details>

Nếu bạn trả về `true`, AdaptyUI sẽ thực hiện lại yêu cầu sau 2 giây.

#### Lỗi rendering \{#rendering-errors\}

Nếu xảy ra lỗi trong quá trình render giao diện, lỗi đó sẽ được báo cáo qua phương thức này:

```kotlin showLineNumbers title="Kotlin"
public override fun onRenderingError(
    error: AdaptyError,
    context: Context,
) {}
```

<Details>
<summary>Ví dụ sự kiện (Nhấn để mở rộng)</summary>
```javascript
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render paywall interface",
    "details": {
      "underlyingError": "Invalid paywall configuration"
    }
  }
}
```
</Details>

Trong trường hợp bình thường, những lỗi này không nên xảy ra, vì vậy nếu bạn gặp phải, hãy cho chúng tôi biết.

---