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

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ị flow. Bạn cũng có thể thiết lập xử lý nút (đóng flow, mở liên kết, hành động tùy chỉnh, v.v.). Xem hướng dẫn xử lý hành động nút để biết thêm chi tiết.

Flow và paywall được xây dựng bằng Flow 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 lần 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 flow. Tìm hiểu cách phản hồi các sự kiện này bên dưới.

Để 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 triển khai phương thức view.setEventHandlers:

Bạn chỉ có thể đặt một handler cho mỗi sự kiện: gọi setEventHandlers nhiều lần sẽ ghi đè các handler bạn đã cung cấp, thay thế cả handler mặc định lẫn các handler đã đặt trước đó cho những sự kiện cụ thể đó. Các handler bạn không đặt vẫn giữ nguyên hành vi mặc định. setEventHandlers trả về một hàm hủy đăng ký, và view.dismiss() sẽ xóa tất cả các handler.


const view = await createFlowView(flow);

const unsubscribe = await view.setEventHandlers({
  onCloseButtonPress() {
    return true; // close the flow (default behavior)
  },
  onAndroidSystemBack() {
    return true; // close the flow; by default, it stays open
  },
  onPurchaseCompleted(purchaseResult, product) {
    return purchaseResult.type === 'success'; // close the flow on a successful purchase, keep it open for cancelled or pending purchases
  },
  onPurchaseStarted(product) { /***/ },
  onPurchaseFailed(error, product) { /***/ },
  onRestoreCompleted(profile) { /***/ },
  onRestoreFailed(error) { /***/ },
  onProductSelected(productId) { /***/ },
  onError(error) { /***/ },
  onLoadingProductsFailed(error) { /***/ },
  onUrlPress(url, openIn) {
    adapty.openWebUrl({ url, openIn }).catch(console.warn); // same as the SDK default
    return false; // keep the flow open
  },
  onAppeared() { /***/ },
  onDisappeared() { /***/ },
  onWebPaymentNavigationFinished() { /***/ },
});
Ví dụ về sự kiện (Nhấp để mở rộng)

Các ví dụ dưới đây hiển thị các thuộc tính có sẵn trong mỗi handler, với các giá trị minh họa trong phần chú thích.

// onUrlPress
url;    // 'https://example.com/terms'
openIn; // 'browser_in_app' or 'browser_out_app'

// onCustomAction
actionId; // 'login'

// onProductSelected
productId; // 'premium_monthly'

// onPurchaseStarted, onPurchaseCompleted, onPurchaseFailed
product.vendorProductId;        // 'premium_monthly'
product.localizedTitle;         // 'Premium Monthly'
product.localizedDescription;   // 'Premium subscription for 1 month'
product.price?.amount;          // 9.99
product.price?.currencyCode;    // 'USD'
product.price?.localizedString; // '$9.99'

// onPurchaseCompleted
purchaseResult.type; // 'success', 'pending', or 'user_cancelled'
if (purchaseResult.type === 'success') {
  purchaseResult.profile.accessLevels['premium']?.isActive; // true
}

// onRestoreCompleted
profile.accessLevels['premium']?.isActive; // true

// onPurchaseFailed, onRestoreFailed, onError, onLoadingProductsFailed
error.message; // 'Purchase failed due to insufficient funds'

Bạn có thể đăng ký các event handler mà bạn cần, và bỏ qua những cái không cần. Như vậy, các event listener không dùng đến sẽ không được tạo ra. Không có event handler nào là bắt buộc.

Các event handler trả về một giá trị boolean. Nếu trả về true, quá trình hiển thị được coi là hoàn tất, flow screen sẽ đóng lại và các event listener cho view này sẽ bị xóa. Một số event handler có hành vi mặc định mà bạn có thể ghi đè nếu cần:

  • onCloseButtonPress: đóng flow khi người dùng nhấn nút đóng.
  • onUrlPress: mở URL được nhấn trong trình duyệt gốc thông qua adapty.openWebUrl, tuân theo tùy chọn Open in đã thiết lập trong builder, và giữ flow mở.
  • onAndroidSystemBack: giữ flow mở khi người dùng nhấn nút Back. Trả về true để đóng flow.
  • onPurchaseCompleted: giữ flow mở sau khi hoàn tất giao dịch mua. Trả về true để đóng flow.
  • onRestoreCompleted: giữ flow mở sau khi khôi phục thành công. Trả về true để đóng flow.
  • onError: đóng flow nếu quá trình render thất bại.

Trình xử lý sự kiện

Trình xử lý sự kiệnMô tả
onCustomActionĐược gọi khi người dùng thực hiện một hành động tùy chỉnh, ví dụ: nhấp vào nút tùy chỉnh.
onUrlPressĐược gọi khi người dùng nhấp vào một URL trong flow của bạn.
onAndroidSystemBackĐược gọi khi người dùng nhấn nút Back hệ thống của Android. Theo mặc định, flow vẫn mở; trả về true để đóng flow.
onCloseButtonPressĐược gọi khi nút đóng hiển thị và người dùng nhấn vào nó. Nên đóng màn hình flow trong trình xử lý này.
onPurchaseCompletedĐược gọi khi giao dịch mua hoàn tất, dù thành công, bị người dùng hủy, hay đang chờ phê duyệt. Nếu mua thành công, sự kiện cung cấp AdaptyProfile đã được cập nhật. 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 sự kiện này, không phải onPurchaseFailed.
onPurchaseStartedĐược gọi khi người dùng nhấn nút hành động “Mua” để bắt đầu quá trình thanh toán.
onPurchaseFailedĐược gọi khi giao dịch mua thất bại do lỗi (ví dụ: 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). Không được gọi khi người dùng hủy hoặc thanh toán đang chờ xử lý — những trường hợp đó sẽ kích hoạt onPurchaseCompleted.
onRestoreStartedĐược gọi khi người dùng bắt đầu quá trình khôi phục giao dịch mua.
onRestoreCompletedĐược gọi khi khôi phục giao dịch mua thành công và cung cấp AdaptyProfile đã được cập nhật. Nên đóng màn hình nếu người dùng có accessLevel yêu cầu. Xem chủ đề Trạng thái gói đăng ký để biết cách kiểm tra.
onRestoreFailedĐược gọi khi quá trình khôi phục thất bại và cung cấp AdaptyError.
onProductSelectedĐược gọi khi bất kỳ sản phẩm nào trong giao diện flow được chọn, cho phép bạn theo dõi lựa chọn của người dùng trước khi mua.
onErrorĐược gọi khi xảy ra lỗi trong quá trình render giao diện và cung cấp AdaptyError. 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.
onLoadingProductsFailedĐược gọi khi tải sản phẩm thất bại và cung cấp AdaptyError. Nếu bạn chưa đặt prefetchProducts: true khi tạo giao diện, AdaptyUI sẽ tự động lấy các đối tượng cần thiết từ máy chủ.
onAppearedĐược gọi khi flow được hiển thị cho người dùng. Trên iOS, cũng được gọi khi người dùng nhấn nút web paywall bên trong một flow và một web paywall mở trong trình duyệt trong ứng dụng.
onDisappearedĐược gọi khi người dùng đóng flow. Trên iOS, cũng được gọi khi một web paywall được mở từ flow trong trình duyệt trong ứng dụng biến mất khỏi màn hình.
onWebPaymentNavigationFinishedĐược gọi sau khi cố gắng mở một web paywall để mua, dù thành công hay thất bại.
onRequestAppReviewDành riêng cho yêu cầu đánh giá ứng dụng từ một flow. Các flow chưa kích hoạt yêu cầu đánh giá ứng dụng, nên bạn không cần triển khai nó.
onAnalyticsDành riêng cho các sự kiện phân tích tùy chỉnh từ một flow. Các flow chưa phát ra những sự kiện này vào mã của bạn, nên bạn không cần triển khai nó.
onRequestPermissionDành riêng cho các yêu cầu quyền hệ thống (như thông báo đẩy hoặc quyền truy cập camera) từ một flow. Các flow chưa kích hoạt yêu cầu quyền, nên bạn không cần triển khai nó.
onObserverPurchaseInitiatedChỉ dành cho chế độ observer: Được gọi khi người dùng nhấn nút mua trong một flow. Adapty không thực hiện giao dịch mua — hãy thực hiện bằng mã mua hàng của riêng bạn, sau đó báo cáo giao dịch cho Adapty. Xem Xử lý giao dịch mua trong chế độ observer bên dưới.
onObserverRestoreInitiatedChỉ dành cho chế độ observer: Được gọi khi người dùng nhấn nút khôi phục trong một flow. Adapty không thực hiện khôi phục — hãy tự thực hiện, sau đó báo cáo bất kỳ giao dịch nào đã khôi phục. Xem Xử lý giao dịch mua trong chế độ observer bên dưới.

Xử lý mua hàng trong Observer mode

Nếu bạn khởi tạo SDK ở chế độ Observer mode (observerMode: true) và hiển thị flow được Adapty render, SDK sẽ không tự thực hiện mua hàng cho bạn. Khi người dùng nhấn nút mua hoặc khôi phục, SDK sẽ gọi onObserverPurchaseInitiated hoặc onObserverRestoreInitiated để bạn có thể tự xử lý việc mua hoặc khôi phục bằng code của riêng mình. Xem Hiển thị flow trong Observer mode để biết cách thiết lập đầy đủ.

Hướng dẫn này đề cập đến việc xử lý sự kiện cho các lần 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 của chúng tôi để biết thêm chi tiết.

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ể phản hồi. Những sự kiện này bao gồm các lần 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 xem cách phản hồi các sự kiện này bên dưới.

Để 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 phương thức view.setEventHandlers:


const view = await createPaywallView(paywall);

const unsubscribe = view.setEventHandlers({
  onCloseButtonPress() {
    console.log('User closed paywall');
    return true; // Allow the paywall to close
  },
  onAndroidSystemBack() {
    console.log('User pressed back button');
    return true; // Allow the paywall to close
  }, 
  onAppeared() {
    console.log('Paywall appeared');
    return false; // Don't close the paywall
  }, 
  onDisappeared() {
    console.log('Paywall disappeared');
  },
  onPurchaseCompleted(purchaseResult, product) {
    console.log('Purchase completed:', purchaseResult);
    return purchaseResult.type !== 'user_cancelled'; // Close if not cancelled
  },
  onPurchaseStarted(product) {
    console.log('Purchase started:', product);
    return false; // Don't close the paywall
  },
  onPurchaseFailed(error, product) {
    console.error('Purchase failed:', error);
    return false; // Don't close the paywall
  },
  onRestoreCompleted(profile) {
    console.log('Restore completed:', profile);
    return true; // Close the paywall after successful restore
  },
  onRestoreFailed(error) {
    console.error('Restore failed:', error);
    return false; // Don't close the paywall
  },
  onProductSelected(productId) {
    console.log('Product selected:', productId);
    return false; // Don't close the paywall
  },
  onRenderingFailed(error) {
    console.error('Rendering failed:', error);
    return false; // Don't close the paywall
  },
  onLoadingProductsFailed(error) {
    console.error('Loading products failed:', error);
    return false; // Don't close the paywall
  },
  onUrlPress(url) {
    window.open(url, '_blank');
    return false; // Don't close the paywall
  },
});
Ví dụ sự kiện (Nhấp để mở rộng)
// onCloseButtonPress
{
  "event": "close_button_press"
}

// onAndroidSystemBack
{
  "event": "android_system_back"
}

// onAppeared
{
  "event": "paywall_shown"
}

// onDisappeared
{
  "event": "paywall_closed"
}

// onUrlPress
{
  "event": "url_press",
  "url": "https://example.com/terms"
}

// onCustomAction
{
  "event": "custom_action",
  "actionId": "login"
}

// onProductSelected
{
  "event": "product_selected",
  "productId": "premium_monthly"
}

// onPurchaseStarted
{
  "event": "purchase_started",
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

// onPurchaseCompleted - Success
{
  "event": "purchase_completed",
  "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"
  }
}

// onPurchaseCompleted - Cancelled
{
  "event": "purchase_completed",
  "purchaseResult": {
    "type": "user_cancelled"
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

// onPurchaseFailed
{
  "event": "purchase_failed",
  "error": {
    "code": "purchase_failed",
    "message": "Purchase failed due to insufficient funds",
    "details": {
      "underlyingError": "Insufficient funds in account"
    }
  }
}

// onRestoreCompleted
{
  "event": "restore_completed",
  "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"
      }
    ]
  }
}

// onRestoreFailed
{
  "event": "restore_failed",
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

// onRenderingFailed
{
  "event": "rendering_failed",
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render paywall interface",
    "details": {
      "underlyingError": "Invalid paywall configuration"
    }
  }
}

// onLoadingProductsFailed
{
  "event": "loading_products_failed",
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

Bạn có thể đăng ký những event handler cần thiết và bỏ qua những cái không cần. Như vậy, các event listener không dùng đến sẽ không được tạo ra. Không có event handler nào là bắt buộc.

Event handler trả về giá trị boolean. Nếu trả về true, quá trình hiển thị được coi là hoàn tất, paywall sẽ đóng lại và các event listener của view đó sẽ bị xóa. Một số event handler có hành vi mặc định mà bạn có thể ghi đè nếu cần:

  • onCloseButtonPress: đóng paywall khi nhấn nút đóng.
  • onAndroidSystemBack: đóng paywall khi nhấn nút Back.
  • onRestoreCompleted: đóng paywall sau khi khôi phục thành công.
  • onPurchaseCompleted: đóng paywall trừ khi người dùng hủy.
  • onRenderingFailed: đóng paywall nếu việc hiển thị thất bại.
  • onUrlPress: mở URL trong trình duyệt hệ thống và giữ paywall mở.

Trình xử lý sự kiện

Event handlerMô tả
onCustomActionĐược gọi khi người dùng thực hiện một hành động tùy chỉnh, ví dụ: nhấp vào nút tùy chỉnh.
onUrlPressĐược gọi khi người dùng nhấp vào một URL trong paywall.
onAndroidSystemBackĐược gọi khi người dùng nhấn nút Back hệ thống của Android.
onCloseButtonPressĐược gọi khi nút đóng hiển thị và người dùng nhấn vào đó. Nên đóng màn hình paywall trong handler này.
onPurchaseCompletedĐược gọi khi quá trình mua hoàn tất, bất kể thành công, người dùng hủy hay đang chờ phê duyệt. Trong trường hợp mua thành công, sự kiện này cung cấp một AdaptyProfile đã được cập nhật. Việc người dùng hủy và các khoản thanh toán đang chờ (ví dụ: cần phê duyệt của phụ huynh) sẽ kích hoạt sự kiện này, không phải onPurchaseFailed.
onPurchaseStartedĐược gọi khi người dùng nhấn nút hành động “Mua” để bắt đầu quá trình mua.
onPurchaseCancelledĐược gọi khi người dùng bắt đầu quá trình mua nhưng tự ý ngắt nó (hủy hộp thoại thanh toán).
onPurchaseFailedĐược gọi khi giao dịch mua thất bại do lỗi (ví dụ: 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). Không được gọi khi người dùng hủy hoặc khi thanh toán đang chờ — những trường hợp đó sẽ kích hoạt onPurchaseCompleted.
onRestoreStartedĐược gọi khi người dùng bắt đầu quá trình khôi phục giao dịch mua.
onRestoreCompletedĐược gọi khi khôi phục giao dịch mua thành công và cung cấp một AdaptyProfile đã được cập nhật. Nên đóng màn hình nếu người dùng có accessLevel cần thiết. Xem thêm tại Trạng thái gói đăng ký để biết cách kiểm tra.
onRestoreFailedĐược gọi khi quá trình khôi phục thất bại và cung cấp AdaptyError.
onProductSelectedĐược gọi khi người dùng chọn bất kỳ sản phẩm nào trong paywall, giúp bạn theo dõi lựa chọn của người dùng trước khi mua.
onAppearedĐược gọi khi màn hình paywall xuất hiện trên màn hình. Trên iOS, cũng được gọi khi người dùng nhấn nút web paywall bên trong paywall và một web paywall mở ra trong trình duyệt trong ứng dụng.
onDisappearedĐược gọi khi màn hình paywall biến mất khỏi màn hình. Trên iOS, cũng được gọi khi một 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.
onRenderingFailedĐược gọi khi xảy ra lỗi trong quá trình render giao diện và cung cấp AdaptyError. 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, vui lòng thông báo cho chúng tôi.
onLoadingProductsFailedĐược gọi khi tải sản phẩm thất bại và cung cấp AdaptyError. Nếu bạn chưa đặt prefetchProducts: true khi tạo view, AdaptyUI sẽ tự động lấy các đối tượng cần thiết từ máy chủ.