Xử lý sự kiện flow & paywall - Unity

Hướng dẫn này đề cập đến cách 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ý các hành động flow 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 hoặc 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ể xử lý. Các sự kiện đó bao gồm 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 xử lý những sự kiện này bên dưới.

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

Xử lý sự kiện

Để kiểm soát hoặc theo dõi các tiến trình xảy ra trên màn hình flow trong ứng dụng di động của bạn, hãy implement interface IAdaptyFlowsEventsListener và đăng ký nó với Adapty.SetFlowsEventsListener():

using UnityEngine;
using AdaptySDK;

public class FlowEventsHandler : MonoBehaviour, IAdaptyFlowsEventsListener
{
    void Start()
    {
        Adapty.SetFlowsEventsListener(this);
    }

    // Implement all interface methods below
}

Đây là nơi bạn thêm logic tùy chỉnh để xử lý các sự kiện flow. SDK không áp dụng hành vi mặc định nào cho chúng: một lần mua thành công hay lỗi đều không tự động đóng view — hãy tự gọi view.Dismiss(...) khi thích hợp.

Sự kiện do người dùng tạo ra

Flow xuất hiện

Được gọi khi giao diện flow hiển thị trên màn hình.

Trên iOS, cũng được gọi khi người dùng nhấn vào nút web paywall bên trong một flow và web paywall mở ra trong trình duyệt trong ứng dụng.

public void FlowViewDidAppear(AdaptyUIFlowView view) { }

Flow biến mất

Được gọi khi giao diện flow bị đóng khỏi màn hình.

Trên iOS, cũng được gọi khi một web paywall mở từ một flow trong trình duyệt trong ứng dụng biến mất khỏi màn hình.

public void FlowViewDidDisappear(AdaptyUIFlowView view) { }

Lựa chọn sản phẩm

Được gọi khi một sản phẩm được chọn để mua (bởi người dùng hoặc hệ thống).

public void FlowViewDidSelectProduct(
    AdaptyUIFlowView view,
    string productId
) { }
Ví dụ sự kiện (Nhấn để mở rộng)
{
  "productId": "premium_monthly"
}

Bắt đầu mua hàng

Được gọi khi người dùng bắt đầu quá trình mua hàng.

public void FlowViewDidStartPurchase(
    AdaptyUIFlowView view,
    AdaptyPaywallProduct product
) { }

Trong Observer mode, các giao dịch mua hàng được khởi tạo từ một flow sẽ được chuyển đến IAdaptyUIObserverModeResolver của bạn.

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

Mua thành công, đã hủy hoặc đang chờ xử lý

Phương thức này sẽ được gọi khi giao dịch mua thành công, người dùng hủy giao dịch, hoặc giao dịch đang ở trạng thái chờ xử lý. Các trường hợp người dùng hủy và thanh toán đang chờ (ví dụ: cần phê duyệt của phụ huynh) sẽ kích hoạt phương thức này, không phải FlowViewDidFailPurchase.

Flow vẫn mở sau khi mua hàng cho đến khi bạn đóng nó, vì vậy hãy tự gọi view.Dismiss(...) sau khi người dùng có quyền truy cập:

public void FlowViewDidFinishPurchase(
    AdaptyUIFlowView view,
    AdaptyPaywallProduct product,
    AdaptyPurchaseResult purchasedResult
) {
    switch (purchasedResult.Type) {
        case AdaptyPurchaseResultType.Success:
            // Check if user has access to premium features
            if (purchasedResult.Profile != null
                && purchasedResult.Profile.AccessLevels.TryGetValue("premium", out var premium)
                && premium.IsActive) {
                view.Dismiss(null);
            }
            break;
        case AdaptyPurchaseResultType.Pending:
            // Handle pending purchase (e.g., user will pay offline with cash)
            break;
        case AdaptyPurchaseResultType.UserCancelled:
            // Handle user cancellation
            break;
        default:
            break;
    }
}
Ví dụ về sự kiện (Nhấp để mở rộng)
// 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": "Success",
    "profile": {
      "accessLevels": {
        "premium": {
          "id": "premium",
          "isActive": true,
          "expiresAt": "2024-02-15T10:30:00Z"
        }
      }
    }
  }
}

// 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": "UserCancelled"
  }
}

// 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": "Pending"
  }
}

Chúng tôi khuyến nghị đóng màn hình flow khi mua hàng thành công.

Mua hàng thất bại

Nếu mua hàng thất bại do lỗi, phương thức này sẽ được gọi. Điều này bao gồm lỗi StoreKit/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 huỷ, FlowViewDidFinishPurchase sẽ được gọi với kết quả đã huỷ thay thế, và các thanh toán đang chờ xử lý không kích hoạt phương thức này.

public void FlowViewDidFailPurchase(
    AdaptyUIFlowView view,
    AdaptyPaywallProduct product,
    AdaptyError error
) { }
Ví dụ sự kiện (Nhấn để mở rộng)
{
  "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"
    }
  }
}

Bắt đầu khôi phục

Được gọi khi người dùng khởi tạo quá trình khôi phục:

public void FlowViewDidStartRestore(AdaptyUIFlowView view) { }

Khôi phục thành công

Được gọi khi khôi phục giao dịch mua thành công. Flow vẫn mở sau khi khôi phục cho đến khi bạn đóng nó lại:

public void FlowViewDidFinishRestore(
    AdaptyUIFlowView view,
    AdaptyProfile profile
) {
    // Check if user has access to premium features
    if (profile.AccessLevels.TryGetValue("premium", out var premium) && premium.IsActive) {
        view.Dismiss(null);
    }
}
Ví dụ sự kiện (Nhấn để mở rộng)
{
  "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"
      }
    ]
  }
}

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ý để tìm hiểu cách kiểm tra.

Khôi phục thất bại

Được gọi khi quá trình khôi phục giao dịch thất bại:

public void FlowViewDidFailRestore(
    AdaptyUIFlowView view,
    AdaptyError error
) { }
Ví dụ sự kiện (Nhấn để mở rộng)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

Hoàn thành điều hướng thanh toán web

Sau khi cố gắng mở web paywall để thực hiện mua hàng (dù thành công hay thất bại), phương thức này sẽ được gọi:

public void FlowViewDidFinishWebPaymentNavigation(
    AdaptyUIFlowView view,
    AdaptyPaywallProduct product,
    AdaptyError error
) { }

Tham số:

  • product: Sản phẩm mà web paywall được mở (hoặc thử mở), hoặc null
  • error: null nếu web paywall mở thành công, hoặc một AdaptyError nếu thất bại
Ví dụ về sự kiện (Nhấn để mở rộng)
// 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": null,
  "error": {
    "code": "wrong_param",
    "message": "Current method is not available for this product",
    "details": {
      "underlyingError": "Product not configured for web purchases"
    }
  }
}

Tải dữ liệu và hiển thị

Lỗi tải sản phẩm

Được gọi khi tải sản phẩm thất bại và cung cấp AdaptyError. 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ự lấy các đối tượng cần thiết từ máy chủ. Thao tác này có thể thất bại và AdaptyUI sẽ báo lỗi bằng cách gọi phương thức này:

public void FlowViewDidFailLoadingProducts(
    AdaptyUIFlowView view,
    AdaptyError error
) { }
Ví dụ sự kiện (Nhấn để mở rộng)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

Lỗi render và runtime

Nếu xảy ra lỗi trong quá trình render giao diện, hoặc một lỗi runtime không liên quan đến giao dịch mua xảy ra, lỗi đó sẽ được báo cáo qua phương thức này. View không tự động bị đóng — hãy tự gọi view.Dismiss(...) nếu cần:

public void FlowViewDidReceiveError(
    AdaptyUIFlowView view,
    AdaptyError error
) { }
Event example (Click to expand)
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render flow interface",
    "details": {
      "underlyingError": "Invalid flow configuration"
    }
  }
}

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

Sự kiện analytics

FlowViewDidReceiveAnalyticEvent được 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 gửi những sự kiện này đến code của bạn, vì vậy hãy để phần thân phương thức trống — IAdaptyFlowsEventsListener là một interface C#, nên phương thức vẫn phải có mặt:

public void FlowViewDidReceiveAnalyticEvent(
    AdaptyUIFlowView view,
    string name,
    IDictionary<string, object> @params
) { }

Xử lý các yêu cầu hệ thống

IAdaptyUISystemRequestsHandler (đăng ký thông qua Adapty.SetSystemRequestsHandler(...)) được dành riêng cho các yêu cầu hệ thống từ một flow: các hộp thoại cấp quyền của hệ điều hành (như thông báo đẩy hoặc quyền truy cập camera) và yêu cầu đánh giá ứng dụng. Hiện tại các flow chưa kích hoạt những yêu cầu này, vì vậy bạn chưa cần đăng ký handler.

Nút back hệ thống Android

Nút back hệ thống Android (hoặc thao tác vuốt back) được chuyển đến FlowViewDidPerformAction dưới dạng action SystemBack và không tự động đóng flow — người dùng thoát khỏi flow thông qua một đường dẫn bạn định nghĩa, chẳng hạn như nút Close hoặc action on_device_back trong builder. Nếu bạn muốn nút back hệ thống đóng flow, hãy tự xử lý action đó:

public void FlowViewDidPerformAction(
    AdaptyUIFlowView view,
    AdaptyUIUserAction action
) {
    switch (action.Type) {
        case AdaptyUIUserActionType.Close:
        case AdaptyUIUserActionType.SystemBack:
            view.Dismiss(null);
            break;
        default:
            // handle other events
            break;
    }
}

Xem hướng dẫn xử lý các hành động flow để biết danh sách đầy đủ các hành động.

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ị paywall. Bạn cũng phải 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 của chúng tôi để biết thêm chi tiết.

Các paywall được cấu hình bằng 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ể xử lý. Các sự kiện đó bao gồm thao tác nhấn nút (nút đóng, URL, lựa 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. Tìm hiểu cách xử lý các sự kiện này bên dưới.

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

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

Xử lý sự kiện

Để kiểm soát hoặc theo dõi các tiến trình xảy ra trên màn hình paywall trong ứng dụng di động của bạn, hãy triển khai interface AdaptyPaywallsEventsListener:

using UnityEngine;
using AdaptySDK;

public class PaywallEventsHandler : MonoBehaviour, AdaptyPaywallsEventsListener
{
    void Start()
    {
        Adapty.SetPaywallsEventsListener(this);
    }

    // Implement all required interface methods below
}

Sự kiện do người dùng tạo ra

Paywall xuất hiện

Được gọi khi màn hình paywall hiển thị trên màn hình.

Trên iOS, cũng được gọi khi người dùng nhấn vào nút web paywall bên trong một paywall và web paywall mở trong trình duyệt trong ứng dụng.

public void PaywallViewDidAppear(AdaptyUIPaywallView view) { }

Paywall biến mất

Được gọi khi màn hình paywall bị đóng khỏi màn hình.

Trên iOS, cũng được gọi khi web paywall được mở từ paywall trong trình duyệt trong ứng dụng biến mất khỏi màn hình.

public void PaywallViewDidDisappear(AdaptyUIPaywallView view) { }

Chọn sản phẩm

Được gọi khi một sản phẩm được chọn để mua (bởi người dùng hoặc hệ thống).

public void PaywallViewDidSelectProduct(
    AdaptyUIPaywallView view, 
    string productId
) { }
Ví dụ sự kiện (Nhấn để mở rộng)
{
  "productId": "premium_monthly"
}

Bắt đầu mua

Được gọi khi người dùng khởi tạo quá trình mua hàng.

public void PaywallViewDidStartPurchase(
    AdaptyUIPaywallView view, 
    AdaptyPaywallProduct product
) { }
Ví dụ sự kiện (Nhấn để mở rộng)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

Mua thành công, đã hủy hoặc đang chờ xử lý

Nếu giao dịch mua thành công, người dùng hủy giao dịch, hoặc giao dịch mua đang ở trạng thái chờ xử lý, phương thức này sẽ được gọi. Các trường hợp người dùng hủy và thanh toán đang chờ xử lý (ví dụ: cần phê duyệt của phụ huynh) sẽ kích hoạt phương thức này, không phải PaywallViewDidFailPurchase.

public void PaywallViewDidFinishPurchase(
    AdaptyUIPaywallView view, 
    AdaptyPaywallProduct product, 
    AdaptyPurchaseResult purchasedResult
) { }
Ví dụ sự kiện (Nhấn để mở rộng)
// 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": "Success",
    "profile": {
      "accessLevels": {
        "premium": {
          "id": "premium",
          "isActive": true,
          "expiresAt": "2024-02-15T10:30:00Z"
        }
      }
    }
  }
}

// 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": "UserCancelled"
  }
}

// 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": "Pending"
  }
}

Chúng tôi khuyến nghị đóng màn hình trong trường hợp này.

Mua thất bại

Nếu giao dịch mua 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 StoreKit/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 các trường hợp người dùng hủy sẽ kích hoạt PaywallViewDidFinishPurchase với kết quả đã hủy, và các thanh toán đang chờ xử lý không kích hoạt phương thức này.

public void PaywallViewDidFailPurchase(
    AdaptyUIPaywallView view, 
    AdaptyPaywallProduct product, 
    AdaptyError error
) { }
Ví dụ sự kiện (Nhấn để mở rộng)
{
  "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"
    }
  }
}

Bắt đầu khôi phục

Được gọi khi người dùng bắt đầu quá trình khôi phục:

public void PaywallViewDidStartRestore(AdaptyUIPaywallView view) { }

Khôi phục thành công

Được gọi khi khôi phục giao dịch mua thành công:

public void PaywallViewDidFinishRestore(
    AdaptyUIPaywallView view, 
    AdaptyProfile profile
) { }
Ví dụ sự kiện (Nhấn để mở rộng)
{
  "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"
      }
    ]
  }
}

Chúng tôi khuyến nghị đóng màn hình nếu người dùng có accessLevel cần thiết. Tham khảo chủ đề Trạng thái gói đăng ký để tìm hiểu cách kiểm tra.

Khôi phục thất bại

Được gọi khi khôi phục giao dịch mua thất bại:

public void PaywallViewDidFailRestore(
    AdaptyUIPaywallView view, 
    AdaptyError error
) { }
Ví dụ sự kiện (Nhấn để mở rộng)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

Hoàn tất điều hướng thanh toán web

Sau khi cố gắng mở web paywall để mua hàng (dù thành công hay thất bại), phương thức này sẽ được gọi:

public void PaywallViewDidFinishWebPaymentNavigation(
    AdaptyUIPaywallView view, 
    AdaptyPaywallProduct product, 
    AdaptyError error
) { }

Tham số:

  • product: Sản phẩm mà web paywall được mở (hoặc thử mở)
  • error: null nếu web paywall mở thành công, hoặc AdaptyError nếu thất bại
Ví dụ sự kiện (Nhấn để mở rộng)
// 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": "wrong_param",
    "message": "Current method is not available for this product",
    "details": {
      "underlyingError": "Product not configured for web purchases"
    }
  }
}

Tải dữ liệu và hiển thị

Lỗi tải sản phẩm

Được gọi khi tải sản phẩm thất bại và cung cấp AdaptyError. 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ự lấy các đối tượng cần thiết từ server. Quá trình này có thể thất bại, và AdaptyUI sẽ báo lỗi bằng cách gọi phương thức này:

public void PaywallViewDidFailLoadingProducts(
    AdaptyUIPaywallView view, 
    AdaptyError error
) { }
Ví dụ sự kiện (Nhấn để mở rộng)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

Lỗi hiển thị

Được gọi khi xảy ra lỗi trong quá trình hiển thị giao diện và cung cấp AdaptyError:

public void PaywallViewDidFailRendering(
    AdaptyUIPaywallView view, 
    AdaptyError error
) { }
Ví dụ sự kiện (Nhấn để mở rộng)
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render paywall interface",
    "details": {
      "underlyingError": "Invalid paywall configuration"
    }
  }
}

Trong trường hợp bình thường, những 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.