---
title: "Flutter - Xử lý sự kiện paywall"
description: "Khám phá cách xử lý các sự kiện liên quan đến gói đăng ký trong Flutter bằng Adapty để theo dõi tương tác người dùng hiệu quả."
---

:::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, 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](flutter-handle-paywall-actions) để biết thêm chi tiết.
:::

Các 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 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 trên paywall. 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.

:::

Để kiểm soát hoặc theo dõi các tiến trình diễn ra trên màn hình paywall trong ứng dụng di động của bạn, hãy triển khai các phương thức `AdaptyUIPaywallsEventsObserver` và thiết lập observer trước khi hiển thị bất kỳ màn hình nào:

```javascript showLineNumbers title="Flutter"
AdaptyUI().setPaywallsEventsObserver(this);
```

:::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.

:::

### Sự kiện do người dùng tạo ra \{#user-generated-events\}

#### Paywall xuất hiện \{#paywall-appeared\}

Phương thức này được gọi khi màn hình paywall được hiển thị trên màn hình.

:::note
Trên iOS, cũng được gọi khi người dùng nhấn vào [nút web paywall](web-paywall#step-2a-add-a-web-purchase-button) bên trong paywall và một web paywall mở ra trong trình duyệt trong ứng dụng.
:::

```javascript showLineNumbers title="Flutter"
void paywallViewDidAppear(AdaptyUIPaywallView view) {
}
```

#### Paywall biến mất \{#paywall-disappeared\}

Phương thức này được gọi khi màn hình paywall bị đóng khỏi màn hình.

:::note
Trên iOS, cũng được gọi khi một [web paywall](web-paywall#step-2a-add-a-web-purchase-button) được mở từ paywall trong trình duyệt trong ứng dụng biến mất khỏi màn hình.
:::

```javascript showLineNumbers title="Flutter"
void paywallViewDidDisappear(AdaptyUIPaywallView view) {
}
```

#### 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:

```javascript showLineNumbers title="Flutter"
void paywallViewDidSelectProduct(AdaptyUIPaywallView view, String productId) {
}
```

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

```javascript
{
  "productId": "premium_monthly"
}
```
</Details>

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

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

```javascript showLineNumbers title="Flutter"
void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) {
}
```

<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>

#### Hoàn thành mua \{#finished-purchase\}

Phương thức này được gọi khi giao dịch mua thành công, người dùng hủy giao dịch mua, hoặc giao dịch mua đang ở trạng thái chờ xử lý:

```javascript showLineNumbers title="Flutter"
void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, 
                                  AdaptyPaywallProduct product, 
                                  AdaptyPurchaseResult purchaseResult) {
    switch (purchaseResult) {
      case AdaptyPurchaseResultSuccess(profile: final profile):
        // successful purchase
        break;
      case AdaptyPurchaseResultPending():
        // purchase is pending
        break;
      case AdaptyPurchaseResultUserCancelled():
        // user cancelled the purchase
        break;
      default:
        break;
    }
}
```

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

```javascript
// Successful purchase
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "purchaseResult": {
    "type": "AdaptyPurchaseResultSuccess",
    "profile": {
      "accessLevels": {
        "premium": {
          "id": "premium",
          "isActive": true,
          "expiresAt": "2024-02-15T10:30:00Z"
        }
      }
    }
  }
}

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

// User cancelled purchase
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "purchaseResult": {
    "type": "AdaptyPurchaseResultUserCancelled"
  }
}
```
</Details>

Chúng tôi khuyến nghị đóng màn hình trong trường hợp đó. Tham khảo [Phản hồi hành động nút bấm](flutter-handle-paywall-actions) để biết chi tiết về cách đóng màn hình paywall.

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

Phương thức này được gọi sau khi thử mở một [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 và thất bại:

```javascript showLineNumbers title="Flutter"
void paywallViewDidFinishWebPaymentNavigation(AdaptyUIPaywallView view, 
                                               AdaptyPaywallProduct? product, 
                                               AdaptyError? error) {
}
```

**Tham số:**

| Tham số     | Mô tả                                                                                                          |
|:------------|:---------------------------------------------------------------------------------------------------------------|
| **product** | Một `AdaptyPaywallProduct` mà web paywall được mở cho. 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>

#### Mua thất bại \{#failed-purchase\}

Phương thức này được gọi khi giao dịch mua thất bại (ví dụ: do lỗi thanh toán hoặc lỗi mạng). Phương thức này **không** kích hoạt khi người dùng chủ động hủy hoặc giao dịch đang chờ xử lý — những trường hợp đó được xử lý bởi `paywallViewDidFinishPurchase`:

```javascript showLineNumbers title="Flutter"
void paywallViewDidFailPurchase(AdaptyUIPaywallView view, 
                                AdaptyPaywallProduct product, 
                                AdaptyError error) {
}
```

<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"
  },
  "error": {
    "code": "purchase_failed",
    "message": "Purchase failed due to insufficient funds",
    "details": {
      "underlyingError": "Insufficient funds in account"
    }
  }
}
```
</Details>

#### Bắt đầu khôi phục \{#started-restore\}

Nếu người dùng khởi tạo quá trình khôi phục, phương thức này sẽ được gọi:

```javascript showLineNumbers title="Flutter"
void paywallViewDidStartRestore(AdaptyUIPaywallView view) {
}
```

#### Khôi phục thành công \{#successful-restore\}

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

```javascript showLineNumbers title="Flutter"
void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) {
}
```

<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ý](flutter-listen-subscription-changes) để tìm hiểu cách kiểm tra và chủ đề [Phản hồi hành động nút bấm](flutter-handle-paywall-actions) để tìm hiểu cách đóng màn hình paywall.

#### Khôi phục thất bại \{#failed-restore\}

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

```javascript showLineNumbers title="Flutter"
void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) {
}
```

<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>

### 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 mảng 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ừ máy chủ. 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 này:

```javascript showLineNumbers title="Flutter"
void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyError error) {
}
```

<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>

#### Lỗi hiển thị \{#rendering-errors\}

Nếu xảy ra lỗi trong quá trình hiển thị giao diện, lỗi đó sẽ được báo cáo bằng cách gọi phương thức này. Theo mặc định (kể từ v3.15.2), paywall sẽ tự động bị đóng khi xảy ra lỗi hiển thị, nhưng bạn có thể ghi đè hành vi này nếu cần.

```javascript showLineNumbers title="Flutter"
void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) {
  // Default behavior: view.dismiss()
  // Override with custom logic if needed, for example:
  // - Log the error
  // - Show an error message to the user
}
```

<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 điều kiện bình thường, các lỗi như vậ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.