Cách tự động hóa theo dõi giới thiệu ứng dụng di động cho lượt chia sẻ trên web?

opoinstall
2026-10-28
5 min read

Cách thiết lập theo dõi giới thiệu tự động cho di động? Thiết lập theo dõi giới thiệu tự động yêu cầu tích hợp SDK phân bổ, đính kèm mã giới thiệu ẩn (shareCode) vào liên kết chia sẻ trên web, thu hút người dùng trên trang đích và tận dụng tính năng liên kết sâu trì hoãn (deferred deep linking) được nền tảng hỗ trợ để khôi phục tham số trong lần mở ứng dụng đầu tiên.

Theo dõi giới thiệu tự động là một khung kỹ thuật phân bổ cài đặt ứng dụng di động theo chương trình cho các hành động chia sẻ của người dùng hiện có. Bằng cách truyền định danh người mời ẩn (shareCode) qua các trang đích trên web và cố gắng giải quyết ngữ cảnh này khi khởi chạy ứng dụng ban đầu, việc theo dõi giới thiệu tự động giúp giảm bớt việc nhập mã khuyến mại thủ công và hỗ trợ phân bổ trên các vùng chứa trình duyệt trong ứng dụng.

Thuật ngữ Định nghĩa Thực thể liên quan Vai trò ý định tìm kiếm
Theo dõi giới thiệu Việc phân bổ theo chương trình cho việc thu hút người dùng hạ nguồn đối với các phiên chia sẻ của người dùng hiện có cụ thể. Cài đặt tham số ứng dụng Thông tin / Thương mại
phần mềm theo dõi giới thiệu Một SDK và cơ sở hạ tầng máy chủ được thiết kế để tự động hóa việc liên kết giữa người mời và người được mời. Liên kết sâu trì hoãn Thương mại
chương trình giới thiệu ứng dụng Sáng kiến tăng trưởng khuyến khích người dùng hiện có mời bạn bè mới cài đặt ứng dụng. Thống kê kênh & Theo dõi Thương mại
hệ thống giới thiệu Kiến trúc kỹ thuật đầu cuối quản lý việc tạo liên kết, phân bổ và phát hành phần thưởng. Web JS SDK Kỹ thuật

Tổng quan kiến trúc: Cách theo dõi giới thiệu tự động kết nối lượt chia sẻ mạng xã hội với việc cài đặt ứng dụng

Phân tích chi tiết về hệ thống giới thiệu cũ: Lý do mã khuyến mại thủ công làm giảm tốc độ phễu

Các chương trình giới thiệu ứng dụng di động truyền thống dựa trên mã khuyến mại gồm chữ và số thủ công. Theo quy trình làm việc cũ này, người dùng hiện có tạo mã mời (như INVITE2026), sao chép văn bản thủ công và gửi cho bạn bè qua SMS hoặc tin nhắn xã hội. Người được mời sau đó phải tải ứng dụng từ cửa hàng ứng dụng, hoàn tất đăng ký, tìm trường nhập mã khuyến mại chuyên dụng và sao chép chính xác chuỗi ký tự.

Quy trình thủ công này tạo ra sự ma sát đáng kể trong phễu chuyển đổi:

  • Sự sụt giảm về nhận thức và thao tác: Người dùng thường quên sao chép mã voucher, không tìm thấy ô nhập mã đổi thưởng trong các menu đăng ký sâu hoặc bỏ dở quy trình khi mã không hoạt động do lỗi viết hoa hoặc lỗi sao chép.
  • Rò rỉ kênh và thu thập mã: Mã chữ-số rất dễ bị trích xuất và công bố trên các trang tổng hợp mã giảm giá công khai hoặc bảng diễn đàn. Điều này cắt đứt mối quan hệ ngang hàng đích thực, cho phép những người dùng hữu cơ không được giới thiệu nhận phần thưởng trong khi làm lu mờ hiệu quả kinh tế của đơn vị chiến dịch.
  • Điểm mù đo lường: Chỉ dựa vào mã khuyến mại thủ công khiến các mốc quan trọng ở phễu trung gian (như số lần hiển thị liên kết, nhấp vào webview xã hội và tỷ lệ chuyển đổi cửa hàng) không được đo lường trừ khi triển khai công cụ phân tích riêng biệt.

Đường ống chia sẻ theo chương trình: Hành động của người mời, dàn dựng web, bàn giao qua cửa hàng và giải pháp Ngày 0

Ngữ cảnh giới thiệu truyền từ liên kết chia sẻ trên thiết bị di động sang trang đích trên web.

Theo dõi giới thiệu tự động thay thế việc sao chép thủ công bằng một đường ống truyền tham số liên tục, theo chương trình. Thay vì phân phối liên kết cửa hàng chưa có tham số cùng với mã voucher văn bản ngắt kết nối, ứng dụng sẽ gom nhóm danh tính của người mời trực tiếp vào URL chia sẻ web tương tác.

Vòng đời kỹ thuật hoạt động qua bốn giai đoạn riêng biệt:

[Người dùng hiện có mời bạn bè] ──> Gọi shareTelemetry.reportIntent(shareCode, Platform)
              │
              ▼
[Tạo liên kết web động] ───> Thêm ?shareCode=usr_9f82a1b4 vào URL đích
              │
              ▼
[Chia sẻ trong WeChat/Line] ────> Bạn bè nhấn vào liên kết bên trong Webview được bảo vệ
              │
              ▼
[Dàn dựng & Bàn giao Web JS SDK] ─> Lưu đệm ngữ cảnh & Thực thi Khởi chạy Ứng dụng / Tải xuống Cửa hàng
              │
              ▼
[Khởi chạy ứng dụng đầu tiên trên thiết bị] ───> SDK gốc truy vấn Khôi phục tham số
              │
              ▼
[Liên kết phụ trợ ứng dụng] ──> Phụ trợ xác thực mã thông báo & Tạo liên kết chờ xử lý
              │
              ▼
[Mốc kích hoạt đã xác minh] ─> Phụ trợ giải ngân phần thưởng khi hoàn thành mốc quan trọng

Khi người được mời mở liên kết, trang đích trên web được lưu trữ sẽ thực thi Web JS SDK trích xuất mã thông báo của người mời (shareCode) và dàn dựng ngữ cảnh giới thiệu. Khi người dùng tiến hành cài đặt và mở ứng dụng gốc, SDK khách sẽ truy xuất tải trọng đã dàn dựng trong lần khởi chạy đầu tiên, cho phép phụ trợ ứng dụng tự động liên kết mối quan hệ giới thiệu mà không yêu cầu nhập dữ liệu thủ công từ bất kỳ bên tham gia nào.

Khôi phục khởi chạy đầu tiên sau cửa hàng: Hạn chế dành riêng cho nền tảng

Sau khi người dùng cài đặt ứng dụng từ cửa hàng ứng dụng, việc khôi phục tham số giới thiệu phụ thuộc vào nền tảng và cơ chế được triển khai bởi ứng dụng hoặc SDK phân bổ. Việc truyền shareCode qua URL web tự bản thân nó không đảm bảo rằng tham số sẽ có sẵn cho ứng dụng mới cài đặt.

    • Android / Google Play: Google Play Install Referrer có thể cung cấp siêu dữ liệu giới thiệu được hỗ trợ liên quan đến việc cài đặt Cửa hàng Play. Ứng dụng phải định cấu hình và kiểm tra tải trọng giới thiệu thực tế, đồng thời xác minh rằng các tham số dự kiến tồn tại qua quá trình chuyển hướng và cài đặt. Các SDK phân bổ của bên thứ ba có thể sử dụng các cơ chế bổ sung, phải được lập tài liệu riêng.
    • iOS / App Store: App Store không tự động chuyển các tham số truy vấn trang đích tùy ý vào ứng dụng đã cài đặt. Universal Links hỗ trợ định tuyến trực tiếp khi ứng dụng đã được cài đặt và cấu hình miền liên quan hợp lệ; chúng không tự đảm bảo khôi phục tham số trì hoãn sau khi cài đặt mới.
    • SDK Phân bổ: Chỉ sử dụng các cơ chế khôi phục được tài liệu hóa cho chính xác phiên bản SDK và nền tảng đang được triển khai. Xác minh việc triển khai, thu thập dữ liệu và các hàm ý về quyền riêng tư thay vì giả định rằng SDK có thể khôi phục mọi tham số giới thiệu trên mọi trình duyệt và đường dẫn cài đặt.

Nếu không thể khôi phục ngữ cảnh giới thiệu thông qua cơ chế được hỗ trợ, hãy cho phép người dùng hoàn tất quá trình giới thiệu và ghi nhận việc cài đặt là chưa được phân bổ. Không suy diễn hoặc tạo ra mối quan hệ giới thiệu từ một kết quả khớp chưa được xác minh.

Các đường dẫn khôi phục giới thiệu trên Android và iOS hội tụ tại quá trình xác thực phụ trợ.

Phân bổ iOS và ranh giới quyền riêng tư

Quá trình triển khai giới thiệu trên iOS không được dựa vào việc lấy dấu vân tay thiết bị bị cấm để nhận dạng người dùng hoặc thiết bị trên các ứng dụng hoặc trang web. Các yêu cầu về quyền riêng tư và sử dụng dữ liệu của Apple cấm việc lấy ra mã định danh duy nhất từ tín hiệu thiết bị, trình duyệt, cấu hình, vị trí hoặc mạng cho mục đích này. Ủy quyền Minh bạch theo dõi ứng dụng không làm cho việc lấy dấu vân tay vốn bị cấm trở nên được phép.

Sử dụng Liên kết phổ quát (Universal Links) để định tuyến ứng dụng trực tiếp đã được xác minh khi ứng dụng được cài đặt. Đối với việc khôi phục giới thiệu sau khi cài đặt, hãy sử dụng cơ chế có tài liệu có hành vi đã được xem xét cho phiên bản SDK được triển khai và các yêu cầu hiện hành của Apple. Xem xét các thông báo về quyền riêng tư của SDK và các khai báo API lý do bắt buộc nếu có, nhưng không coi sự hiện diện của Tuyên bố Quyền riêng tư là bằng chứng cho thấy việc triển khai phân bổ tuân thủ.

Nếu SDK đã chọn dựa vào phương pháp khớp không thể được xác minh là được phép, không đề xuất phương pháp đó cho việc triển khai iOS. Nhận sự làm rõ từ nhà cung cấp SDK và chọn một giải pháp thay thế tuân thủ. Trường hợp không có cơ chế được phép nào có thể thiết lập mối quan hệ giới thiệu một cách đáng tin cậy, hãy giữ nguyên quy trình giới thiệu thông thường và đánh dấu việc cài đặt là chưa được phân bổ.

Tham khảo: Quyền riêng tư của người dùng Apple và sử dụng dữ liệu.

Xem thêm: Theo dõi giới thiệu ──> Cài đặt tham số ứng dụng

Bước 1: Tích hợp API báo cáo chia sẻ phía máy khách

Khởi tạo SDK phân bổ gốc trên các điểm đầu vào Android và iOS

Trước khi ứng dụng có thể báo cáo hoạt động chia sẻ hoặc truy xuất ngữ cảnh giới thiệu đến, SDK di động gốc phải khởi tạo sớm trong vòng đời ứng dụng.

Trên Android, quá trình khởi tạo thuộc về phương thức onCreate() của lớp con Application tùy chỉnh, đảm bảo rằng các trình lắng nghe phân bổ và cấu trúc bộ nhớ cốt lõi được thiết lập trước khi các thành phần giao diện người dùng tiền cảnh được gắn kết. Trên iOS, quá trình khởi tạo diễn ra trong ủy quyền thực thi application(_:didFinishLaunchingWithOptions:).

Các nhà phát triển có thể nhận các gói SDK di động chính thức trực tiếp từ Trung tâm tải xuống SDK OpoInstall.

Gọi phương thức báo cáo chia sẻ trước khi kích hoạt bảng chia sẻ hệ điều hành gốc

Ứng dụng nên ghi lại ý định chia sẻ trước khi mở giao diện chia sẻ nền tảng. Thực hiện việc này thông qua dịch vụ đo đạc thuộc sở hữu của ứng dụng hoặc phương thức SDK phân bổ được xác minh so với chính xác phiên bản SDK đang sử dụng. Ví dụ dưới đây sử dụng trừu tượng hóa shareTelemetry.reportIntent(...) thuộc sở hữu của ứng dụng; đây không phải là API OpoInstall chính thức. Bộ điều hợp phải thực hiện yêu cầu mạng thực tế, xử lý lỗi và hành vi gọi lại cho ứng dụng đã được triển khai.

Để duy trì dữ liệu đo đạc sạch sẽ, các nhóm kỹ thuật phải phân biệt giữa năm sự kiện riêng biệt trong vòng đời chia sẻ:

  • Ý định chia sẻ đã được khởi tạo (share_intent_initiated): Giao diện người dùng của ứng dụng khách ghi lại rằng người dùng đã nhấp vào hành động mời.
  • Chia sẻ đã được điều phối (share_dispatched): Ứng dụng khách chuyển tải trọng đến nền tảng đích được xác định (ví dụ: khởi chạy ý định xã hội rõ ràng) hoặc mở trình chọn chia sẻ hệ thống.
  • Chia sẻ đã hoàn tất (share_completed): SDK mạng xã hội của bên thứ ba trả về lệnh gọi lại hoàn thành được xác thực xác nhận thông báo đã được đăng hoặc điều phối (chỉ áp dụng khi sử dụng SDK nền tảng của bên thứ ba chuyên dụng có hỗ trợ gọi lại).
  • Giới thiệu đã được nhấp (referral_click): Người nhận mở trang đích trên web, kích hoạt ghi nhật ký đầu vào phía web.
  • Giới thiệu đã được liên kết (referral_bound): Người nhận hoàn tất đăng ký tài khoản và phụ trợ xác minh ứng viên shareCode đồng thời ghi lại mối quan hệ giới thiệu vào cơ sở dữ liệu.

Việc ghi lại dữ liệu đo đạc chia sẻ nắm bắt ý định chia sẻ của người dùng. Điều này cho phép các đường ống phân tích lập chỉ mục shareCode của người mời so với kênh nền tảng được chỉ định, chuẩn bị cho hệ thống tương quan với các lượt truy cập trang đích trên web tiếp theo.

Chỉ định mã định danh nền tảng: Ánh xạ các kênh xã hội

Các mã định danh sau đây chỉ là ví dụ minh họa. Sử dụng liệt kê kênh chính xác được hỗ trợ bởi phiên bản SDK đã triển khai và xác minh từng mã định danh dựa trên tham chiếu API chính thức của nó:

  • Mạng nhắn tin khép kín: WechatSession (Trò chuyện trực tiếp WeChat), WechatTimeline (Khoảnh khắc WeChat), Line, WhatsApp, FacebookMessenger.
  • Nguồn cấp dữ liệu xã hội và Microblog công khai: Twitter, Facebook, Instagram, Weibo, Douyin, TikTok.
  • Truyền tải hệ điều hành trực tiếp: Sms, Email, Copy (Sao chép liên kết khay nhớ tạm) và Other (Bảng chia sẻ hệ thống).

Khi ứng dụng khởi launches một bảng chia sẻ hệ thống chung nơi lựa chọn đích cuối cùng của người dùng không thể được xác định trước, sự kiện nên được phân loại là Other thay vì mã hóa cứng một mạng xã hội cụ thể.

Cấu trúc mã thông báo danh tính người mời: Thực tiễn tốt nhất để định dạng shareCode

shareCode hoạt động như một mã thông báo kinh doanh cấp ứng dụng kết nối cài đặt mới trở lại tài khoản giới thiệu. Mặc dù nhà phát triển có thể sử dụng các chuỗi chữ và số tùy ý, việc tuân thủ các thực tiễn tốt nhất về cấu trúc sẽ ngăn chặn tình trạng hỏng tham số:

  • Mã thông báo do máy chủ cấp mờ: shareCode phải là mã thông báo giới thiệu mờ do máy chủ cấp (ví dụ: mã thông báo an toàn cho URL hoặc UUID như usr_9f82a1b4) thay vì ID tự động tăng cơ sở dữ liệu thô có thể liệt kê (1024). Phụ trợ ứng dụng giải quyết mã thông báo này cho tài khoản người mời, thực thi thời hạn và giới hạn tốc độ.
  • Ràng buộc độ dài: Giữ cho mã thông báo nhỏ gọn (được khuyến nghị dưới 64 ký tự; nghiêm ngặt \le 128 ký tự) để tránh làm phồng độ dài URL hoặc kích hoạt việc cắt ngắn chuỗi trong các ứng dụng nhắn tin.
  • Bộ ký tự an toàn: Giới hạn các ký tự cho các giá trị chữ và số an toàn cho URL, dấu gạch ngang và dấu gạch dưới ([A-Za-z0-9_-]). Tránh các dấu phân cách thô như &, =, ? hoặc khoảng trắng làm thay đổi phân tích cú pháp URI.

Trên Android 11 (Cấp độ API 30) trở lên, việc truy vấn xem các gói xã hội của bên thứ ba đã được cài đặt hay chưa yêu cầu khai báo các phần tử <queries> tương ứng trong AndroidManifest.xml theo Hướng dẫn dành cho nhà phát triển Android của Google về khả năng hiển thị gói trong Android 11. Mẫu triển khai dưới đây minh họa cách khai báo khả năng hiển thị gói, xử lý thời gian chờ báo cáo chia sẻ không chặn, quản lý việc đặt lại trạng thái an toàn vòng đời và điều phối các ý định xã hội rõ ràng với các tính năng dự phòng khay nhớ tạm:

<!-- Cấu hình Android và Triển khai Chia sẻ phía Khách -->
<!-- Phần 1: Khả năng hiển thị gói AndroidManifest.xml & Khai báo ứng dụng -->
<!-- Tệp: app/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.app">

    <!-- Khai báo khả năng hiển thị gói rõ ràng bắt buộc cho Android 11+ (API 30+) -->
    <queries>
        <package android:name="com.tencent.mm" />
        <package android:name="jp.naver.line.android" />
    </queries>

    <application
        android:name=".CustomApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light.NoActionBar">
        
        <activity
            android:name=".InviteActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
// Điểm vào vòng đời ứng dụng Android
// Phần 2: CustomApplication.kt (Khởi tạo vòng đời SDK)
// Tệp: app/src/main/java/com/example/app/CustomApplication.kt

package com.example.app

import android.app.Application
import com.opoinstall.api.OpoInstall

class CustomApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // Khởi tạo máy khách cốt lõi OpoInstall trên luồng chính trước khi gắn kết hoạt động
        OpoInstall.initialize(this)
    }
}
// Triển khai Hoạt động Chia sẻ trên Android
// Phần 3: InviteActivity.kt (Đo đạc Ứng dụng & Điều phối Mục tiêu kèm Dự phòng Khay nhớ tạm)
// Tệp: app/src/main/java/com/example/app/InviteActivity.kt

package com.example.app

import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import java.util.concurrent.atomic.AtomicBoolean

class InviteActivity : AppCompatActivity() {

    companion object {
        private const val TAG = "ReferralShareTracking"
        // Ví dụ về điểm cuối chuyển hướng thuộc sở hữu của ứng dụng; hãy thay thế bằng tuyến HTTPS đã triển khai
        private const val BASE_INVITE_HOST = "promo.example.com"
        private const val BASE_INVITE_PATH = "invite.html"
        
        // Các gói nền tảng đích để định tuyến ý định rõ ràng
        private const val PACKAGE_WECHAT = "com.tencent.mm"
        private const val PACKAGE_LINE = "jp.naver.line.android"
        
        // Thời gian chờ ví dụ do ứng dụng xác định; điều chỉnh và kiểm tra cho dịch vụ đo đạc thực tế
        private const val SHARE_TIMEOUT_MS = 800L
    }

    // Khóa chống dội để ngăn chặn các lệnh gọi ý định chia sẻ đồng thời nhanh chóng
    private val isSharingInProgress = AtomicBoolean(false)

    // Trừu tượng hóa bộ điều hợp đo đạc thuộc sở hữu của ứng dụng
    private val shareTelemetry = ShareTelemetryService()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_invite)

        val btnShareWeChat = findViewById<Button>(R.id.btn_share_wechat)
        val btnShareLine = findViewById<Button>(R.id.btn_share_line)
        val btnShareGeneric = findViewById<Button>(R.id.btn_share_generic)

        // Mã thông báo giả lập minh họa: Trong môi trường sản xuất, lấy mã thông báo do máy chủ cấp từ phiên được xác thực
        val currentInviterToken = "usr_9f82a1b4"

        btnShareWeChat.setOnClickListener {
            initiateShareFlow(currentInviterToken, "WechatSession", targetPackage = PACKAGE_WECHAT)
        }

        btnShareLine.setOnClickListener {
            initiateShareFlow(currentInviterToken, "Line", targetPackage = PACKAGE_LINE)
        }

        btnShareGeneric.setOnClickListener {
            // Bảng chia sẻ hệ thống chưa được phân đoạn; nền tảng được phân loại là Khác
            initiateShareFlow(currentInviterToken, "Other", targetPackage = null)
        }
    }

    /**
     * Trợ giúp xác minh xem ứng dụng xã hội đích đã được cài đặt chưa trước khi báo cáo dữ liệu đo đạc kênh cụ thể.
     * Yêu cầu khai báo <queries> trong AndroidManifest.xml trên Android 11+ (API 30+).
     */
    private fun isAppInstalled(packageName: String?): Boolean {
        if (packageName.isNullOrEmpty()) return false
        return try {
            packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
            true
        } catch (e: PackageManager.NameNotFoundException) {
            false
        } catch (e: Exception) {
            Log.w(TAG, "Ngoại lệ kiểm tra gói cho $packageName: ${e.message}")
            false
        }
    }

    /**
     * Sao chép toàn bộ URL lời mời vào khay nhớ tạm của hệ thống như một phương án dự phòng cuối cùng.
     */
    private fun copyInviteLinkToClipboard(shareUri: String): Boolean {
        return try {
            val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
            clipboard.setPrimaryClip(ClipData.newPlainText("Liên kết lời mời giới thiệu", shareUri))
            true
        } catch (e: Exception) {
            Log.e(TAG, "Không thể sao chép liên kết lời mời: ${e.message}", e)
            false
        }
    }

    private fun showShareFailureFallback(shareUri: String) {
        val copied = copyInviteLinkToClipboard(shareUri)
        val message = if (copied) {
            "Không thể mở ứng dụng chia sẻ. Đã sao chép liên kết lời mời vào khay nhớ tạm."
        } else {
            "Không thể mở ứng dụng chia sẻ hoặc sao chép liên kết lời mời."
        }
        Toast.makeText(this, message, Toast.LENGTH_LONG).show()
    }

    /**
     * Ghi lại ý định chia sẻ thông qua dữ liệu đo đạc thuộc sở hữu của ứng dụng với tính năng dự phòng thời gian chờ không chặn.
     *
     * @param shareCode Mã thông báo người mời mờ (nghiêm ngặt <= 128 ký tự)
     * @param platformIdentifier Kênh mạng xã hội đích khớp với phân loại báo cáo
     * @param targetPackage Tên gói ứng dụng cụ thể hoặc null cho trình chọn hệ thống chung
     */
    private fun initiateShareFlow(shareCode: String, platformIdentifier: String, targetPackage: String?) {
        // Thực thi kiểm tra chống dội
        if (!isSharingInProgress.compareAndSet(false, true)) {
            Log.d(TAG, "Đang tiến hành gọi chia sẻ. Bỏ qua nhấp chuột trùng lặp.")
            return
        }

        val isTargetInstalled = isAppInstalled(targetPackage)
        val effectivePlatform = if (isTargetInstalled) platformIdentifier else "Other"
        val effectivePackage = if (isTargetInstalled) targetPackage else null

        val isDispatched = AtomicBoolean(false)
        val mainHandler = Handler(Looper.getMainLooper())

        // Đối tượng chạy thời gian chờ không chặn: Đảm bảo bảng chia sẻ UI mở ngay cả khi bắt tay mạng bị chậm trễ
        val timeoutRunnable = Runnable {
            if (isDispatched.compareAndSet(false, true)) {
                Log.w(TAG, "Báo cáo trước khi chia sẻ đã hết thời gian chờ sau ${SHARE_TIMEOUT_MS}ms. Tiếp tục điều phối.")
                dispatchShareSafely(shareCode, effectivePlatform, effectivePackage)
            }
        }
        mainHandler.postDelayed(timeoutRunnable, SHARE_TIMEOUT_MS)

        // Báo cáo bất đồng bộ ý định chia sẻ thông qua bộ điều hợp đo đạc thuộc sở hữu của ứng dụng
        shareTelemetry.reportIntent(shareCode, effectivePlatform) { result ->
            mainHandler.removeCallbacks(timeoutRunnable)
            if (isDispatched.compareAndSet(false, true)) {
                if (result.isFailure) {
                    Log.w(TAG, "Dữ liệu đo đạc chia sẻ không thành công: ${result.exceptionOrNull()?.message}")
                } else {
                    Log.d(TAG, "Đã ghi lại ý định chia sẻ cho $effectivePlatform")
                }
                mainHandler.post {
                    dispatchShareSafely(shareCode, effectivePlatform, effectivePackage)
                }
            }
        }
    }

    /**
     * Thực thi điều phối ý định với việc kiểm tra vòng đời và đặt lại trạng thái được đảm bảo ở khối cuối cùng.
     */
    private fun dispatchShareSafely(shareCode: String, platform: String, targetPackage: String?) {
        try {
            if (isFinishing || isDestroyed) {
                Log.d(TAG, "Hoạt động đã kết thúc hoặc bị hủy; bỏ qua điều phối chia sẻ.")
                return
            }
            dispatchShareAction(shareCode, platform, targetPackage)
        } catch (e: Exception) {
            Log.e(TAG, "Điều phối chia sẻ gặp ngoại lệ chưa được xử lý: ${e.message}", e)
        } finally {
            isSharingInProgress.set(false)
        }
    }

    private fun dispatchShareAction(shareCode: String, platform: String, targetPackage: String?) {
        // Xây dựng URL đích một cách an toàn bằng Uri.Builder để thực thi mã hóa RFC 3986
        val shareUri = Uri.Builder()
            .scheme("https")
            .authority(BASE_INVITE_HOST)
            .appendEncodedPath(BASE_INVITE_PATH)
            .appendQueryParameter("shareCode", shareCode)
            .appendQueryParameter("platform", platform)
            .build()
            .toString()

        val shareMessage = "Tham gia cùng tôi trên ứng dụng và nhận voucher chào mừng! Tải xuống tại đây: $shareUri"

        val sendIntent = Intent(Intent.ACTION_SEND).apply {
            putExtra(Intent.EXTRA_TEXT, shareMessage)
            type = "text/plain"
        }

        if (targetPackage != null) {
            sendIntent.setPackage(targetPackage)
            try {
                startActivity(sendIntent)
                return
            } catch (e: Exception) {
                Log.w(TAG, "Khởi chạy ứng dụng đích không thành công: ${e.message}", e)
            }
        }

        // Sử dụng trình chọn chia sẻ hệ thống khi ứng dụng đích không khả dụng hoặc khởi chạy chia sẻ nhắm mục tiêu không thành công
        try {
            sendIntent.setPackage(null)
            val fallbackChooser = Intent.createChooser(sendIntent, "Mời bạn bè qua:")
            startActivity(fallbackChooser)
        } catch (e: Exception) {
            Log.e(TAG, "Trình chọn chia sẻ hệ thống không thành công: ${e.message}", e)
            showShareFailureFallback(shareUri)
        }
    }
}

// Giao diện dịch vụ đo đạc thuộc sở hữu của ứng dụng minh họa
class ShareTelemetryService {
    fun reportIntent(shareCode: String, platform: String, callback: (Result<Unit>) -> Unit) {
        // Triển khai: điều phối yêu cầu mạng đến phụ trợ đo đạc ứng dụng
        callback(Result.success(Unit))
    }
}

Để đăng ký hồ sơ ứng dụng và truy xuất các khóa cấu hình cần thiết để theo dõi chia sẻ, các nhóm có thể truy cập Bảng điều khiển dành cho nhà phát triển OpoInstall.

Bước 2: Định cấu hình Web JS SDK để chụp các tham số chia sẻ động

Tích hợp SDK Web thông qua bộ điều hợp đã xác minh phiên bản

Việc triển khai trang đích dưới đây là một mẫu tích hợp minh họa, không phải là tích hợp SDK OpoInstall thả-vào đã được xác minh. Trước khi triển khai, hãy xác nhận chính xác URL gói JavaScript, chữ ký khởi tạo, lệnh gọi lại sẵn sàng, phương thức đánh thức hoặc cài đặt và lược đồ tham số dựa trên tài liệu chính thức cho phiên bản SDK đang được triển khai.

Giữ hành vi dành riêng cho SDK đằng sau một bộ điều hợp ứng dụng nhỏ. Bộ điều hợp nên hiển thị kết quả khởi tạo được ghi lại trong tài liệu và một thao tác đánh thức hoặc cài đặt duy nhất cho trang đích. Ánh xạ các thao tác này với API nhà cung cấp thực tế chỉ sau khi xác minh tên phương thức tương ứng, hành vi gọi lại và các yêu cầu cấu hình.

Trang đích nên tiếp tục xác thực shareCode đến, bảo tồn các tham số giới thiệu trên các tuyến dự phòng được phê duyệt và ngăn chặn điều hướng trùng lặp. Nếu SDK không khởi tạo được hoặc điểm vào được tài liệu hóa của nó không khả dụng, hãy sử dụng tuyến dự phòng được định cấu hình. Không khẳng định rằng tính năng dự phòng có thể khôi phục phân bổ trì hoãn trừ khi phụ trợ và tích hợp nền tảng đã được kiểm tra để hỗ trợ tính năng đó.

Web JS SDK dàn dựng các tham số giới thiệu trước khi người dùng khởi xướng ứng dụng hoặc điều hướng cửa hàng.

Tự động hóa việc trỗi chuỗi truy vấn shareCode thông qua Trình duyệt URLSearchParams

Khi các URL chia sẻ được xây dựng, ứng dụng sẽ nối mã thông báo của người mời dưới dạng tham số truy vấn (ví dụ: https://promo.example.com/invite.html?shareCode=usr_9f82a1b4&platform=WechatSession).

Trên trang đích, các tập lệnh phía máy khách trích xuất giá trị này bằng các giao diện trình duyệt tiêu chuẩn như URLSearchParams. Để chống lại đầu vào độc hại hoặc tập lệnh chéo trang (XSS), tập lệnh nên xác thực mã thông báo dựa trên danh sách cho phép và mẫu định dạng trước khi khởi tạo SDK:

const query = new URLSearchParams(window.location.search);
const rawShareCode = query.get("shareCode");
const safePattern = /^[A-Za-z0-9_-]{1,128}$/;
const sanitizedShareCode = (rawShareCode && safePattern.test(rawShareCode)) ? rawShareCode : "";

Dàn dựng ngữ cảnh giới thiệu vào bộ nhớ tạm thời và bộ đệm hệ thống

Khi Web JS SDK khởi tạo, nó sẽ tiếp thu shareCode đã được làm sạch cùng với bất kỳ mã định danh chiến dịch bổ sung nào (channelCode). Khi người dùng nhấn nút kêu gọi hành động chính, SDK sẽ phối hợp dàn dựng ngữ cảnh:

  • Đăng ký máy chủ phân bổ: Tập lệnh truyền một yêu cầu mạng không đồng bộ liên kết phiên web hiện tại và ngữ cảnh máy khách với shareCode.
  • Dàn dựng khay nhớ tạm hệ thống: Truy cập khay nhớ tạm tuân theo quyền của trình duyệt, yêu cầu kích hoạt của người dùng và các chính sách bảo mật cụ thể của việc triển khai. Việc triển khai trên web nên coi việc dàn dựng khay nhớ tạm là một cải tiến tùy chọn và cung cấp phương án dự phòng khi quyền truy cập bị từ chối hoặc không khả dụng.
  • Phối hợp dự phòng: Nếu quyền truy cập khay nhớ tạm bị hạn chế, bị từ chối bởi lời nhắc của người dùng hoặc bị xóa bởi các hành động tiếp theo của người dùng, kiến trúc phân bổ sẽ quay trở lại khớp phiên phía máy chủ hoặc các kênh giới thiệu cài đặt được nền tảng hỗ trợ.

Liên kết nút CTA Tải xuống để thực thi đăng ký tham số trì hoãn

Trang đích liên kết nút tải xuống chính của nó trực tiếp với phương thức thực thi của SDK (wakeupOrInstall()).

Web JS SDK quản lý nội bộ vòng đời chuyển hướng: nó bắt đầu một nỗ lực đánh thức ứng dụng thông qua các lược đồ được định cấu hình hoặc Liên kết Phổ quát, và nếu ứng dụng chưa được cài đặt, nó thực thi việc chuyển hướng chậm trễ đến cửa hàng ứng dụng được định cấu hình. Mã người dùng nên dựa vào việc quản lý điều hướng nội bộ của SDK trong quá trình thực thi bình thường, dự trữ điều hướng dự phòng trực tiếp cho các tình huống tập lệnh SDK không tải được, ném ra ngoại lệ đồng bộ hoặc bị chặn bởi các điều khiển bảo mật của trình duyệt.

Mẫu triển khai dưới đây minh họa cách tích hợp Web JS SDK để nắm bắt các tham số truy vấn shareCode đến, phát hiện các vùng chứa xã hội trong ứng dụng, quản lý trạng thái hàng đợi nhấp chuột và bảo lưu các tham số trên các tuyến dự phòng mà không có các phím trùng lặp hoặc các đoạn URI bị lỗi:

<!-- Nhập tham số chia sẻ động Web JS SDK và Định tuyến -->
<!-- Tệp: /var/www/html/invite.html -->
<!--
LƯU Ý TÍCH HỢP:
Ví dụ này yêu cầu bộ điều hợp được xác minh so với phiên bản SDK đã triển khai.
Không giả định rằng hàm tạo, đường dẫn CDN, lệnh gọi lại hoặc tên phương thức được hiển thị
trong một đoạn mã minh họa khớp với SDK nhà cung cấp hiện tại.
-->
<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chấp nhận Lời mời Giới thiệu của Bạn</title>
    <!-- Bao gồm Web JS SDK OpoInstall đã xác minh từ CDN chính thức -->
    <script src="https://web.cdn.opoinstallcloud.com/opoinstall.js"></script>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            margin: 0;
            padding: 20px;
            text-align: center;
            background-color: #f8fafc;
        }
        .card {
            background: #ffffff;
            border-radius: 12px;
            padding: 32px 24px;
            max-width: 420px;
            margin: 40px auto;
            box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
        }
        button {
            background-color: #2563eb;
            color: #ffffff;
            border: none;
            padding: 14px 28px;
            font-size: 16px;
            font-weight: 600;
            border-radius: 8px;
            cursor: pointer;
            width: 100%;
            margin-top: 20px;
        }
        button:disabled {
            background-color: #94a3b8;
            cursor: not-allowed;
        }
        /* Lớp phủ hướng dẫn trực quan cho các vùng chứa webview trong ứng dụng bị hạn chế */
        .webview-guide-mask {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.85);
            color: #ffffff;
            z-index: 9999;
            padding-top: 40px;
            box-sizing: border-box;
        }
        .guide-arrow {
            font-size: 32px;
            text-align: right;
            padding-right: 30px;
        }
        .guide-text {
            font-size: 18px;
            line-height: 1.6;
            margin-top: 20px;
            padding: 0 30px;
        }
    </style>
</head>
<body>
    <main class="card">
        <h1>Chào mừng đến với Ứng dụng</h1>
        <p id="invite-desc">Bạn bè của bạn đã mời bạn tham gia. Nhấn vào bên dưới để cài đặt và nhận tín dụng chào mừng của bạn.</p>
        
        <!-- Nút chuyển đổi chính -->
        <button id="btn-install" type="button">Tải xuống và Nhận Thưởng</button>
        
        <!-- Ví dụ về điểm cuối chuyển hướng thuộc sở hữu của ứng dụng; thay thế bằng tuyến HTTPS đã triển khai -->
        <p style="margin-top: 16px; font-size: 14px;">
            <a id="fallback-store-link" href="https://promo.example.com/app-redirect">Liên kết Cửa hàng Trực tiếp</a>
        </p>
    </main>

    <!-- Lời nhắc lớp phủ được hiển thị bên trong webview nhắn tin bị hạn chế (WeChat, Line) -->
    <div id="webview-guide" class="webview-guide-mask">
        <div class="guide-arrow">↗</div>
        <div class="guide-text">
            Nhấn vào menu ở góc trên cùng bên phải (•••) và chọn <strong>"Mở trong Trình duyệt"</strong> (Safari / Chrome) để tải ứng dụng xuống.
        </div>
    </div>

    <script>
    /**
     * Trích xuất và làm sạch tham số truy vấn shareCode từ window.location.
     * Thực thi các giới hạn chữ và số an toàn cho URL lên đến 128 ký tự theo thông số kỹ thuật.
     */
    function getSanitizedShareCode() {
        try {
            const query = new URLSearchParams(window.location.search);
            const rawCode = query.get("shareCode");
            const pattern = /^[A-Za-z0-9_-]{1,128}$/;
            return (rawCode && pattern.test(rawCode)) ? rawCode : "";
        } catch (e) {
            console.error("Không thể phân tích các tham số tìm kiếm:", e);
            return "";
        }
    }

    /**
     * Trích xuất và làm sạch tham số truy vấn nền tảng.
     */
    function getSanitizedPlatform() {
        try {
            const query = new URLSearchParams(window.location.search);
            const rawPlatform = query.get("platform");
            const pattern = /^[A-Za-z0-9_]{1,32}$/;
            return (rawPlatform && pattern.test(rawPlatform)) ? rawPlatform : "";
        } catch (e) {
            return "";
        }
    }

    /**
     * Phát hiện các vùng chứa trình duyệt trong ứng dụng bị hạn chế (WeChat, Line, WhatsApp, Facebook).
     */
    function detectInAppBrowser() {
        const ua = navigator.userAgent.toLowerCase();
        return {
            isWeChat: ua.indexOf("micromessenger") !== -1,
            isLine: ua.indexOf("line/") !== -1,
            isWhatsApp: ua.indexOf("whatsapp") !== -1,
            isFacebook: ua.indexOf("fbav") !== -1 || ua.indexOf("instagram") !== -1
        };
    }

    /**
     * Giải mã an toàn một thành phần URI, quay trở lại chuỗi thô khi mã hóa phần trăm bị lỗi.
     */
    function safeDecode(str) {
        if (!str) return "";
        try {
            return decodeURIComponent(str.replace(/\+/g, " "));
        } catch (e) {
            return str;
        }
    }

    /**
     * Thực thi điều hướng dự phòng, thay thế hoặc cập nhật tham số shareCode
     * trên các đường dẫn phân tích cú pháp URL và dự phòng chuỗi mà không làm hỏng các phân đoạn URL.
     */
    function executeFallbackNavigation(fallbackLink, shareCode, platform) {
        if (!fallbackLink || !fallbackLink.href) {
            console.error("Liên kết đích dự phòng không xác định.");
            return;
        }

        let destinationUrl = fallbackLink.href;
        try {
            const url = new URL(destinationUrl, window.location.origin);
            if (shareCode) {
                url.searchParams.set("shareCode", shareCode);
            }
            if (platform) {
                url.searchParams.set("platform", platform);
            }
            destinationUrl = url.toString();
        } catch (err) {
            console.warn("Phân tích cú pháp API URL không thành công, áp dụng dự phòng chuỗi mạnh mẽ:", err);
            const hashIndex = destinationUrl.indexOf("#");
            const fragment = hashIndex !== -1 ? destinationUrl.substring(hashIndex) : "";
            const rawBase = hashIndex !== -1 ? destinationUrl.substring(0, hashIndex) : destinationUrl;

            const qIndex = rawBase.indexOf("?");
            const pathname = qIndex !== -1 ? rawBase.substring(0, qIndex) : rawBase;
            const queryString = qIndex !== -1 ? rawBase.substring(qIndex + 1) : "";

            const params = {};
            if (queryString) {
                const pairs = queryString.split("&");
                for (let i = 0; i < pairs.length; i++) {
                    if (!pairs[i]) continue;
                    const parts = pairs[i].split("=");
                    const key = safeDecode(parts[0]);
                    const value = parts.length > 1 ? safeDecode(parts.slice(1).join("=")) : "";
                    if (key) {
                        params[key] = value;
                    }
                }
            }

            if (shareCode) params["shareCode"] = shareCode;
            if (platform) params["platform"] = platform;

            const serializedPairs = [];
            for (const k in params) {
                if (Object.prototype.hasOwnProperty.call(params, k)) {
                    serializedPairs.push(encodeURIComponent(k) + "=" + encodeURIComponent(params[k]));
                }
            }

            const newQuery = serializedPairs.length > 0 ? "?" + serializedPairs.join("&") : "";
            destinationUrl = pathname + newQuery + fragment;
        }

        window.location.href = destinationUrl;
    }

    document.addEventListener("DOMContentLoaded", function() {
        const shareCode = getSanitizedShareCode();
        const platform = getSanitizedPlatform();
        const inApp = detectInAppBrowser();
        const installBtn = document.getElementById("btn-install");
        const guideOverlay = document.getElementById("webview-guide");
        const fallbackLink = document.getElementById("fallback-store-link");

        // Hiển thị mặt nạ hướng dẫn trực quan nếu được mở trong các vùng chứa bị hạn chế chặn tải xuống
        if (inApp.isWeChat || inApp.isLine) {
            if (guideOverlay) guideOverlay.style.display = "block";
        }

        // Cập nhật ngay lập tức href của liên kết dự phòng tĩnh trên tải DOM để các lần nhấp trực tiếp bảo tồn các tham số
        if (fallbackLink && fallbackLink.href) {
            try {
                const initialUrl = new URL(fallbackLink.href, window.location.origin);
                if (shareCode) initialUrl.searchParams.set("shareCode", shareCode);
                if (platform) initialUrl.searchParams.set("platform", platform);
                fallbackLink.href = initialUrl.toString();
            } catch (e) {
                // Phân tích cú pháp dự phòng được xử lý tại thời điểm nhấp chuột
            }
        }

        // Đóng gói các tham số động để tiếp thu OpoInstall
        const dynamicData = {
            shareCode: shareCode,
            platform: platform
        };

        // Trạng thái sẵn sàng của SDK và hành động một lần
        let isSdkReady = false;
        let isSdkBlocked = false;
        let isClickQueued = false;
        let actionStarted = false;
        let installerInstance = null;
        let clickTimeoutId = null;

        // Ngưỡng dự phòng khởi tạo do ứng dụng xác định, không phải mặc định của nhà cung cấp đã được xác minh
        const SDK_CLICK_TIMEOUT_MS = 1500;

        function clearClickTimeout() {
            if (clickTimeoutId !== null) {
                clearTimeout(clickTimeoutId);
                clickTimeoutId = null;
            }
        }

        function restoreInstallButton() {
            if (!installBtn) return;
            installBtn.disabled = false;
            installBtn.textContent = "Tải xuống và Nhận Thưởng";
        }

        function triggerSdkWakeup() {
            try {
                if (installerInstance && typeof installerInstance.wakeupOrInstall === "function") {
                    installerInstance.wakeupOrInstall({
                        data: dynamicData,
                        channelCode: "viral_social_share"
                    });
                } else {
                    executeFallbackNavigation(fallbackLink, shareCode, platform);
                }
            } catch (err) {
                console.error("Đánh thức SDK không thành công:", err);
                executeFallbackNavigation(fallbackLink, shareCode, platform);
            }
        }

        // Tất cả các đường dẫn điều hướng phải đi qua bộ bảo vệ một lần này
        function runOnce(action) {
            if (actionStarted) return;
            actionStarted = true;
            isClickQueued = false;
            clearClickTimeout();

            if (installBtn) {
                installBtn.disabled = true;
                installBtn.textContent = "Đang mở...";
            }

            action();
        }

        function handleInstallClick() {
            if (actionStarted) return;

            if (isSdkReady) {
                runOnce(triggerSdkWakeup);
                return;
            }

            if (isSdkBlocked) {
                runOnce(function() {
                    executeFallbackNavigation(fallbackLink, shareCode, platform);
                });
                return;
            }

            // Xếp hàng đợi nhấp chuột trong khi SDK khởi tạo
            isClickQueued = true;
            if (installBtn) {
                installBtn.disabled = true;
                installBtn.textContent = "Đang kết nối với Cửa hàng Ứng dụng...";
            }

            clearClickTimeout();

            clickTimeoutId = setTimeout(function() {
                if (actionStarted || isSdkReady || !isClickQueued) {
                    return;
                }
                isSdkBlocked = true;
                runOnce(function() {
                    executeFallbackNavigation(fallbackLink, shareCode, platform);
                });
            }, SDK_CLICK_TIMEOUT_MS);
        }

        if (installBtn) {
            installBtn.addEventListener("click", handleInstallClick);
        }

        // Khởi tạo Web JS SDK
        if (typeof OpoInstall === "function") {
            try {
                // Thay thế bằng khóa ứng dụng lấy được từ bảng điều khiển nhà cung cấp được định cấu hình;
                // không bao giờ cam kết các bí mật sản xuất cho kiểm soát nguồn công khai.
                installerInstance = new OpoInstall({
                    appKey: "OPOINSTALL_APPKEY_PLACEHOLDER",
                    onready: function() {
                        if (this && typeof this.wakeupOrInstall === "function") {
                            installerInstance = this;
                        }

                        isSdkReady = true;
                        clearClickTimeout();

                        // Lệnh gọi lại trễ không bao giờ được lặp lại một hành động đã hoàn thành
                        if (actionStarted) return;

                        if (isClickQueued) {
                            runOnce(triggerSdkWakeup);
                        } else {
                            restoreInstallButton();
                        }
                    }
                });
            } catch (err) {
                console.error("Khởi tạo OpoInstall không thành công:", err);
                isSdkBlocked = true;
                clearClickTimeout();

                if (isClickQueued) {
                    runOnce(function() {
                        executeFallbackNavigation(fallbackLink, shareCode, platform);
                    });
                } else {
                    restoreInstallButton();
                }
            }
        } else {
            console.warn("SDK OpoInstall không khả dụng; đã bật điều hướng dự phòng.");
            isSdkBlocked = true;
            restoreInstallButton();
        }
    });
    </script>
</body>
</html>

Bước 3: Khôi phục các tham số giới thiệu và liên kết tài khoản

Truy xuất ngữ cảnh giới thiệu trong lần khởi chạy ứng dụng ban đầu

Sau khi cài đặt, ứng dụng nên cố gắng truy xuất ngữ cảnh giới thiệu bằng cơ chế được hỗ trợ bởi nền tảng và SDK phân bổ được triển khai.

Không giả định rằng một tham số có trong URL trang đích web sẽ tự động tồn tại sau khi cài đặt qua cửa hàng ứng dụng.

Trên Android, ứng dụng có thể sử dụng Google Play Install Referrer khi đường dẫn cài đặt và tải trọng người giới thiệu được định cấu hình hỗ trợ. Nếu SDK phân bổ của bên thứ ba cung cấp API truy xuất bổ sung, hãy xác minh chữ ký phương thức, hành vi gọi lại và lược đồ tải trọng của nó so với chính xác phiên bản SDK đang sử dụng.

Trên iOS, việc xử lý Liên kết phổ quát trực tiếp và khôi phục giới thiệu sau khi cài đặt phải được coi là các luồng riêng biệt. Ứng dụng phải sử dụng cơ chế được tài liệu hóa, được nền tảng cho phép để khôi phục trì hoãn. Nếu cơ chế không thể được xác minh hoặc ngữ cảnh giới thiệu không thể được khôi phục, hãy tiếp tục quá trình giới thiệu mà không quy kết việc cài đặt cho người mời.

Truy xuất không chặn và hành vi thử lại

Việc truy xuất giới thiệu không được chặn luồng giao diện người dùng chính hoặc ngăn người dùng hoàn tất đăng ký. Coi thao tác truy xuất là không đồng bộ, áp dụng thời gian chờ phù hợp với SDK hoặc dịch vụ ứng dụng thực tế và ghi lại kết quả rõ ràng cho việc khôi phục thành công, kết quả trống, thời gian chờ và thất bại.

Định cấu hình thời gian chờ theo hành vi SDK được tài liệu hóa và các yêu cầu giới thiệu của ứng dụng. Bất kỳ giá trị thời gian chờ nào hiển thị trong ví dụ này đều là cấu hình cấp ứng dụng, không phải mặc định của nhà cung cấp trừ khi tài liệu SDK liên quan xác nhận rõ ràng.

Việc thử lại có thể được thực hiện khi API được tài liệu hóa hỗ trợ và ứng dụng có chính sách thử lại đã xác định. Ngăn chặn các yêu cầu liên kết tài khoản trùng lặp và tránh liên tục phát hành phần thưởng khi lệnh gọi lại bị chậm đến sau khi đăng ký đã hoàn tất.

Xác thực giới thiệu phía máy chủ

Coi tất cả các giá trị giới thiệu do máy khách báo cáo là đầu vào không đáng tin cậy. Phụ trợ ứng dụng phải xácvalidate mã thông báo người mời ứng viên, xác minh tính đủ điều kiện của người được mời và thực thi các quy tắc kinh doanh của chương trình giới thiệu trước khi tạo liên kết.

Sử dụng ràng buộc tính duy nhất ở cấp cơ sở dữ liệu hoặc điều khiển giao dịch tương đương để ngăn chặn nhiều liên kết người mời cho cùng một người được mời. Giữ liên kết giới thiệu tách biệt với việc phát hành phần thưởng và chỉ phát hành phần thưởng sau khi mốc kích hoạt bắt buộc đã được xác minh trên máy chủ.

Xác thực phía máy chủ tách biệt các mã thông báo giới thiệu không đáng tin cậy khỏi các liên kết và phần thưởng đã được xác minh.

Quản lý các hạn chế của trình duyệt trong ứng dụng trên WeChat, Line và WhatsApp

Rào cản hộp cát vùng chứa: Cách các ứng dụng nhắn tin chặn các lược đồ tùy chỉnh và tải xuống APK trực tiếp

Đại đa số các lượt chia sẻ giới thiệu lan truyền xảy ra bên trong các ứng dụng nhắn tin trên thiết bị di động. Tuy nhiên, các nền tảng như WeChat, Line và WhatsApp hiển thị các liên kết web bên ngoài bên trong các vùng chứa webview trong ứng dụng được cách ly (sandbox) thường chặn, hạn chế hoặc sửa đổi việc tải xuống tệp nhị phân trực tiếp và các lược đồ liên kết sâu tùy thuộc vào phiên bản hệ điều hành và chính sách bảo mật của vùng chứa.

Khi người được mời nhấn vào liên kết giới thiệu bên trong trình duyệt trong ứng dụng:

  • Tải xuống APK trực tiếp bị chặn: Trên Android, các cố gắng tải xuống các tệp nhị phân .apk trực tiếp từ trang đích trên web thường bị chặn hoặc bị cấm bởi vùng chứa webview.
  • Các lược đồ URL tùy chỉnh bị chặn: Các lệnh gọi của lược đồ URL riêng tư (myapp://invite?code=123) thường không thực thi được, ngăn chặn việc khởi chạy ứng dụng gốc trực tiếp cho những người dùng đã cài đặt ứng dụng.
  • Liên kết phổ quát có thể bị ẩn: Một số ứng dụng nhắn tin chặn điều hướng gửi đi, ngăn các Liên kết Phổ quát iOS tiêu chuẩn phân giải hướng ra ngoài hệ điều hành.

Phát hiện tiêu đề User-Agent trong ứng dụng

Để xử lý các hạn chế của vùng chứa này một cách uyển chuyển, trang đích chia sẻ phải kiểm tra chuỗi User-Agent của trình duyệt đến trong quá trình tải DOM ban đầu:

  • Phát hiện WeChat: Kiểm tra navigator.userAgent cho MicroMessenger.
  • Phát hiện Line: Kiểm tra navigator.userAgent cho Line/.
  • Phát hiện WhatsApp: Kiểm tra navigator.userAgent cho WhatsApp.
  • Facebook / Messenger / Instagram: Kiểm tra navigator.userAgent cho FBAV hoặc Instagram.

Đánh giá chiến thuật chuyển hướng: Bàn giao ứng dụng trực tiếp so với Chuyển đổi trình duyệt hệ thống

Hệ điều hành và ứng dụng xã hội thể hiện các hành vi điều hướng riêng biệt:

  • Môi trường WeChat và Line: Các vùng chứa này hạn chế tải xuống tệp nhị phân trực tiếp và thực thi lược đồ bên ngoài. Việc cố gắng chuyển hướng tự động thường kích hoạt màn hình cảnh báo của nền tảng. Trang đích hiển thị hướng dẫn trực quan rõ ràng nhắc nhở người dùng mở liên kết trong trình duyệt mặc định của thiết bị (Safari trên iOS, Chrome trên Android).
    • Môi trường WhatsApp: Trên các bản cài đặt iOS và Android hiện đại, WhatsApp thường mở các liên kết web trong các khung nhìn trong ứng dụng do hệ thống cung cấp (như SFSafariViewController trên iOS hoặc Chrome Custom Tabs trên Android). Các môi trường này cho phép điều hướng web tiêu chuẩn và hỗ trợ chuyển đổi sang các cửa hàng ứng dụng nền tảng, mặc dù các lược đồ URL tùy chỉnh vẫn có thể yêu cầu người dùng nhấn rõ ràng.

Triển khai hướng dẫn người dùng uyển chuyển: Phủ các lời nhắc menu gốc khi chuyển hướng trực tiếp không thành công

Khi webview trong ứng dụng ngăn chặn việc bàn giao bên ngoài theo chương trình, các trang đích sẽ triển khai hướng dẫn người dùng trực quan:

  • Lớp phủ mặt nạ có điều kiện: Khi phát hiện thấy MicroMessenger hoặc Line/, hãy hiển thị lớp phủ trực quan có độ tương phản cao chỉ về phía góc trên cùng bên phải của màn hình.
  • Bản sao hành động rõ ràng: Lời nhắc hướng dẫn người dùng: “Nhấn vào menu ở góc trên cùng bên phải (•••) và chọn ‘Mở trong Trình duyệt’ (Safari / Chrome) để tải ứng dụng xuống.”
  • Bảo tồn trạng thái tham số: Nếu người dùng chuyển đổi sang trình duyệt bên ngoài bằng cách tải cùng một URL, các tham số truy vấn (?shareCode=...) vẫn giữ nguyên trên thanh địa chỉ, miễn là các chuyển hướng trung gian không tước bỏ chúng. Khi trang đích tải lại trong Safari hoặc Chrome, Web JS SDK thực thi sạch sẽ và dàn dựng ngữ cảnh giới thiệu.

Khắc phục sự cố giảm tham số giới thiệu qua các phễu xã hội

Chẩn đoán việc tước tham số qua các proxy làm sạch liên kết trong ứng dụng mạnh mẽ

Một số nền tảng xã hội định tuyến các liên kết bên ngoài được chia sẻ thông qua các miền bao bọc hoặc làm sạch liên kết trung gian (ví dụ: l.facebook.com của Facebook hoặc t.co của Twitter). Trong một số trường hợp, các bước nhảy chuyển hướng này tước bỏ các tham số truy vấn phi tiêu chuẩn hoặc nối thêm các mã định danh nhấp chuột nội bộ xung đột (fbclid).

Để ngăn ngừa mất tham số qua các chuyển hướng xã hội nhiều bước:

    • Đặt tên tham số tiêu chuẩn: Gắn bó với các khóa truy vấn chữ-số tiêu chuẩn như shareCode và channelCode theo IETF RFC 3986.
    • Xác thực liên kết trước khi bay: Kiểm tra độ phân giải liên kết trên các phiên trò chuyện trực tiếp trên các mạng nhắn tin đích, kiểm tra window.location.search trên trang đích cuối cùng để xác minh rằng tham số shareCode tồn tại qua tất cả các chuyển hướng trung gian.

Xử lý chia sẻ web trên máy tính để bàn: Khi người dùng mở liên kết giới thiệu di động trên trình duyệt PC

Mặc dù các chiến dịch giới thiệu được thiết kế cho thiết bị di động, người dùng thường mở các liên kết trò chuyện trên máy tính để bàn (ví dụ: WhatsApp Web, WeChat cho Mac/Windows hoặc Slack). Nếu người dùng máy tính để bàn truy cập trang đích tải xuống di động, các chuyển hướng cửa hàng di động sẽ thất bại.

Các trang đích doanh nghiệp xử lý lưu lượng truy cập máy tính để bàn thông qua sự xuống cấp đáp ứng:

  • Phát hiện môi trường máy tính để bàn: Web JS SDK phát hiện hệ điều hành máy tính để bàn (Windows, macOS, Linux).
  • Tạo mã QR động: Thay vì hiển thị nút cửa hàng di động không thể nhấp, các mẫu đáp ứng có thể hiển thị có điều kiện vùng chứa mã QR trên các khung nhìn máy tính để bàn.
  • Quét đa thiết bị: Người dùng quét mã QR trên màn hình máy tính để bàn bằng thiết bị di động của họ. Mã QR mã hóa URL đích với shareCode ban đầu, chuyển phiên giới thiệu từ máy tính để bàn sang thiết bị di động.

Giảm thiểu các hạn chế truy cập khay nhớ tạm của hệ điều hành

Các điều khiển quyền riêng tư của hệ điều hành chi phối quyền truy cập khay nhớ tạm:

  • Quy tắc tiêu điểm nền trước Android: Dưới Android 10 (Cấp độ API 29) trở lên, các ứng dụng nền không thể đọc dữ liệu khay nhớ tạm thông qua Android ClipboardManager. SDK gốc phải trì hoãn việc kiểm tra khay nhớ tạm cho đến khi Hoạt động chính của ứng dụng có được tiêu điểm cửa sổ hoạt động.
    • Lời nhắc khay nhớ tạm iOS: Trên iOS 16 trở lên, quyền truy cập theo chương trình vào Apple UIPasteboard có thể kích hoạt thông báo hệ thống hoặc lời nhắc yêu cầu người dùng cho phép dán từ trình duyệt nguồn.
    • Phối hợp dự phòng mạng: Kiến trúc phân bổ giải thích cho các hạn chế này bằng cách coi bộ đệm khay nhớ tạm cục bộ là một cải tiến cơ hội, quay trở lại khớp phiên phía máy chủ khi các lần đọc khay nhớ tạm bị từ chối hoặc không khả dụng.

Giao thức xác minh trước khi bay cho các phễu giới thiệu xã hội trên các thiết bị vật lý

Trước khi khởi động chiến dịch giới thiệu khách hàng, các nhóm kỹ thuật tăng trưởng nên thực hiện danh sách kiểm tra xác thực có hệ thống sau đây:

  • Dự phòng chia sẻ nhắm mục tiêu Android: Xác nhận rằng khi WeChat hoặc Line được gỡ cài đặt, ứng dụng sẽ chuyển về trình chọn chia sẻ hệ thống một cách uyển chuyển.
  • Dự phòng sao chép liên kết khay nhớ tạm Android: Xác nhận rằng nếu trình chọn chia sẻ hệ thống thất bại hoặc ném ngoại lệ, toàn bộ liên kết giới thiệu sẽ được sao chép vào khay nhớ tạm và thông báo toast sẽ được hiển thị.
  • Độ trễ H5 SDK và Điều hướng một lần: Mô phỏng độ trễ khởi tạo SDK vượt quá 1500ms để xác minh rằng điều hướng dự phòng thực thi sạch sẽ và các sự kiện onready trễ không kích hoạt điều hướng trùng lặp.
  • Giữ lại tham số và phân đoạn H5: Kiểm tra điều hướng dự phòng trang đích với các URL chứa các tham số truy vấn hiện có, khóa trùng lặp và #fragments để xác minh việc thay thế tham số sạch sẽ.
  • Liên kết phổ quát iOS và Bàn giao cửa hàng: Kiểm tra việc mở ứng dụng trực tiếp cho người dùng hiện có và đánh giá việc khôi phục tham số sau khi cài đặt dựa trên các quy tắc chống lấy dấu vân tay của Apple.
  • Điều phối nhắn tin xã hội trực tiếp: Điều phối các liên kết vào các phiên trò chuyện WeChat và Line đang hoạt động để xác minh rằng các lớp phủ phát hiện trình duyệt trong ứng dụng hướng dẫn người dùng đến Safari hoặc Chrome mà không làm mất shareCode.
  • Liên kết phụ trợ và tính bất biến của phần thưởng: Xác minh rằng phụ trợ ứng dụng chấp nhận mã thông báo ứng viên, từ chối liên kết trùng lặp và giữ phần thưởng ở trạng thái chờ xử lý cho đến khi các mốc quan trọng được xác minh hoàn tất.

Phân tích so sánh các kiến trúc theo dõi giới thiệu

So sánh các kiến trúc theo dõi giới thiệu

Chiều kiến trúc Mã khuyến mại thủ công Lấy dấu vân tay thiết bị Tham số giới thiệu rõ ràng với Khôi phục được hỗ trợ bởi nền tảng
Sự ma sát khi giới thiệu người dùng Yêu cầu nhập thủ công Có thể thấp khi việc khớp thành công Thấp khi ngữ cảnh giới thiệu được khôi phục thành công
Cơ sở phân bổ Mã do người dùng nhập được xác thực bởi phụ trợ Khớp xác suất dựa trên các tín hiệu dành riêng cho việc triển khai Ngữ cảnh giới thiệu rõ ràng kết hợp với cơ chế khôi phục được hỗ trợ
Độ tin cậy phân bổ Phụ thuộc vào việc nhập và xác thực mã chính xác Có thể bị ảnh hưởng bởi thay đổi tín hiệu, hạn chế nền tảng và yêu cầu quyền riêng tư Phụ thuộc vào tính liên tục của tham số, khả năng nền tảng và cơ chế khôi phục đã chọn
Quyền riêng tư và Tuân thủ Yêu cầu xử lý mã thông báo an toàn và kiểm soát chống lạm dụng Phải được đánh giá dựa trên các chính sách nền tảng hiện hành; không được sử dụng việc lấy dấu vân tay bị cấm Yêu cầu xem xét việc triển khai SDK và các quy tắc nền tảng hiện hành
Độ phức tạp vận hành Triển khai ban đầu tương đối đơn giản; việc đổi quà thủ công tạo ra sự ma sát bổ sung Yêu cầu logic khớp, giám sát và xem xét chính sách Yêu cầu định tuyến liên kết, tích hợp dành riêng cho nền tảng, xác thực phụ trợ và xử lý dự phòng

Đánh đổi về độ phức tạp khi triển khai, ma sát chuyển đổi và độ chính xác phân bổ

Mã khuyến mại thủ công tương đối đơn giản để triển khai, nhưng chúng yêu cầu người dùng nhập và đổi mã. Điều này có thể gây ra ma sát và tạo cơ hội cho việc chia sẻ mã bên ngoài mối quan hệ giới thiệu dự định.

Lấy dấu vân tay thiết bị có vẻ làm giảm sự tương tác của người dùng, nhưng độ tin cậy của nó phụ thuộc vào việc thực hiện khớp và các tín hiệu có sẵn. Nó không được sử dụng ở nơi nó mâu thuẫn với các yêu cầu quyền riêng tư hiện hành, bao gồm các hạn chế của Apple đối với việc lấy dấu vân tay bị cấm.

Các tham số giới thiệu rõ ràng cung cấp một biểu diễn cấp độ kinh doanh rõ ràng về người mời dự định. Tuy nhiên, việc mang mã thông báo trong URL web không tương đương với việc khôi phục mã thông báo đó sau khi cài đặt qua cửa hàng ứng dụng. Giải pháp tổng thể phải kết hợp tính liên tục của tham số, cơ chế khôi phục được nền tảng hỗ trợ và xác thực phía máy chủ. Khi việc khôi phục không thành công, quá trình cài đặt nên giữ nguyên trạng thái chưa được phân bổ thay vì được gán cho một người mời chưa được xác minh.

Các câu hỏi thường gặp (FAQ)

Theo dõi giới thiệu tự động truyền tham số qua App Store mà không cần mã khuyến mại như thế nào?
Theo dõi giới thiệu tự động có thể loại bỏ việc nhập mã khuyến mại thủ công, nhưng việc khôi phục tham số phụ thuộc vào nền tảng và việc triển khai. Trên Android, API Cài đặt Người giới thiệu của Google Play có thể cung cấp siêu dữ liệu giới thiệu được hỗ trợ sau khi cài đặt. Trên iOS, App Store không tự động chuyển các tham số truy vấn trang đích tùy ý sang ứng dụng mới được cài đặt. Do đó, cơ chế khôi phục trì hoãn phải được đánh giá riêng cho việc triển khai thực tế, hỗ trợ nền tảng và tuân thủ quyền riêng tư. Trong cả hai trường hợp, ứng dụng nên coi dữ liệu giới thiệu được khôi phục là ứng viên không đáng tin cậy và xác thực người mời, tính đủ điều kiện của người được mời và các điều kiện phần thưởng trên phụ trợ ứng dụng.
Tại sao các tham số giới thiệu bị rớt khi các liên kết được chia sẻ bên trong WeChat hoặc Line?
Các ứng dụng nhắn tin có thể sửa đổi điều hướng liên kết, hạn chế một số hành vi mở ứng dụng nhất định hoặc hiển thị các trang bên trong các trình duyệt được nhúng. Các hành vi này thay đổi theo nền tảng, phiên bản hệ điều hành và phiên bản ứng dụng. Nếu các tham số giới thiệu bị xóa hoặc cơ chế khôi phục đã chọn không thể khớp với một bản cài đặt, mối quan hệ giới thiệu ban đầu có thể vẫn chưa được phân bổ. Kiểm tra chuỗi chuyển hướng hoàn chỉnh trong các ứng dụng nhắn tin đích và xác minh URL trang đích cuối cùng, hành vi mở ứng dụng trực tiếp và khôi phục sau khi cài đặt riêng biệt. Không giả định rằng mọi trình duyệt trong ứng dụng đều hành xử giống nhau hoặc việc mở liên kết trong trình duyệt hệ thống đảm bảo phân bổ trì hoãn thành công.
Phụ trợ ứng dụng ngăn chặn các vòng lặp giới thiệu gian lận như thế nào?
Các phụ trợ ứng dụng bảo vệ các chương trình giới thiệu bằng cách coi các mã thông báo giới thiệu do máy khách báo cáo là đầu vào ứng viên chưa được xác minh. Máy chủ phải xác minh rằng tài khoản được liên kết với số điện thoại hoặc email đã được xác minh, thực thi giới hạn tốc độ đối với tài khoản người mời, yêu cầu các mốc kích hoạt sau khi cài đặt (như hoàn thành đăng ký tài khoản hoặc giao dịch ban đầu) và thực thi tính bất biến của cơ sở dữ liệu trước khi giải ngân các ưu đãi tài chính.

Tóm tắt và Khung quyết định

Theo dõi giới thiệu ứng dụng di động tự động có thể làm giảm việc nhập mã giới thiệu thủ công bằng cách mang mã thông báo người mời rõ ràng qua luồng chia sẻ trên web và xác thực mối quan hệ giới thiệu kết quả trên phụ trợ ứng dụng.

Việc triển khai đáng tin cậy đòi hỏi nhiều hơn việc thêm tham số truy vấn shareCode. Trang đích phải bảo tồn mã thông báo thông qua các chuyển hướng được hỗ trợ, ứng dụng phải sử dụng cơ chế khôi phục đã xác minh cho nền tảng có liên quan và phụ trợ phải xác thực tính đủ điều kiện của tài khoản và thực thi liên kết bất biến cũng như phát hành phần thưởng.

Android và iOS nên được đánh giá riêng biệt. Google Play Install Referrer có thể cung cấp siêu dữ liệu cài đặt được hỗ trợ trên Android. Trên iOS, Universal Links hỗ trợ định tuyến trực tiếp khi ứng dụng được cài đặt, nhưng chúng không tự động đảm bảo khôi phục tham số trì hoãn sau khi cài đặt App Store. Bất kỳ cơ chế khôi phục bổ sung nào cũng phải được lập tài liệu, kiểm tra và xem xét dựa trên các yêu cầu quyền riêng tư của nền tảng hiện hành.

Đối với việc triển khai trong môi trường sản xuất, hãy xác minh tất cả tên phương thức SDK, giá trị cấu hình, mã định danh kênh và lược đồ gọi lại dựa trên chính xác phiên bản đang được tích hợp. Kiểm tra toàn bộ hành trình trên các thiết bị sạch và trong các ứng dụng nhắn tin đích. Khi giới thiệu không thể được khôi phục thông qua cơ chế được hỗ trợ, hãy cho phép quá trình giới thiệu tiếp tục và ghi lại kết quả là chưa được phân bổ thay vì tạo ra mối quan hệ giới thiệu chưa được xác minh.

Các SDK và tài liệu dành cho nhà phát triển của OpoInstall có thể cung cấp các thành phần để triển khai kiến trúc này. Xác nhận các khả năng cụ thể và hành vi nền tảng được hỗ trợ bởi bản phát hành đã được triển khai trước khi dựa vào chúng trong một chiến dịch giới thiệu sản xuất.

Để biết chi tiết tích hợp, hãy tham khảo tài liệu dành cho nhà phát triển OpoInstall, trung tâm tải xuống SDK OpoInstall và bảng điều khiển dành cho nhà phát triển OpoInstall. Sử dụng tài liệu nền tảng Android và Apple chính thức tương ứng để xác validate hành vi cài đặt và quyền riêng tư có liên quan đến ứng dụng.

Tài liệu liên quan

Share this article