SDK Flutter
Lihat di GitHubPaket tracive_sdk, versi 0.1.0, mode OAuth saja di Tahap 1, untuk Android dan iOS. Permukaannya sengaja sepadan dengan SDK Kotlin, Swift, dan React Native, sehingga integrasi lintas platform membaca sama.
Pasang
dependencies:
tracive_sdk: ^0.1.0 # Dart ^3.5.0, Flutter >=3.24.0Setup Android & iOS
Paket ini Dart murni, bukan plugin, jadi ia tidak bisa menyisipkan apa pun ke manifest atau Info.plist Anda. Tanpa deklarasi di bawah, SDK tidak bisa melihat apakah aplikasi Tracive terpasang, dan setiap pengguna dikirim ke halaman install tanpa ada error apa pun.
<!-- AndroidManifest.xml, anak langsung dari <manifest> -->
<queries>
<intent> <!-- aplikasi Tracive production -->
<action android:name="android.intent.action.VIEW" />
<data android:scheme="tracive" />
</intent>
<intent> <!-- build sandbox -->
<action android:name="android.intent.action.VIEW" />
<data android:scheme="tracivesandbox" />
</intent>
<intent> <!-- Custom Tab dan halaman install -->
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
</queries>
<!-- Di dalam <activity> Anda, yang ber-launchMode="singleTask" -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="app-anda.example" android:pathPrefix="/oauth/callback" />
</intent-filter>- · Daftarkan
tracivesandboxjuga. Pemeriksaan kehadiran aplikasi mengikutienvironment, jadi yang terlupa hanya merusak sandbox, dan itu yang paling sulit ketahuan. - · Tidak perlu entri
<package>. Pemeriksaannya memakai skema, dan satu nama package tidak bisa mengikuti environment. - · Redirect Anda sebaiknya App Link dan Universal Link di domain milik Anda, lengkap dengan
assetlinks.jsondanapple-app-site-association. Skema kustom bisa diklaim aplikasi lain di perangkat yang sama.
Membuat client
Satu instance per aplikasi. Pakai konstanta TraciveScopes, bukan string: server menolak seluruh permintaan consent kalau satu nama scope tidak dikenalnya.
final client = TraciveClient(
mode: TraciveMode.oauth(
clientId: 'trcvc_…', // dari portal
redirectUri: 'https://app-anda.example/oauth/callback', // sama persis dengan yang terdaftar
partnerUserId: customerId, // id Anda sendiri, buram dan stabil
scopes: const [TraciveScopes.scammerCheck, TraciveScopes.profileRead],
),
environment: TraciveEnvironment.sandbox, // production adalah bawaannya
cache: await openTraciveCache(), // SQLCipher, kunci di Keystore/Keychain
);Otorisasi
Dua panggilan. authorize() membuka layar consent lalu selesai; handleCallback() yang menyelesaikan otorisasinya saat redirect tiba. Aman mengalirkan setiap deep link ke sana: null berarti “bukan milik saya”, dan tidak ada yang disentuh.
final start = await client.authorize();
switch (start) {
case null: break; // halaman terbuka, tunggu redirect
case AuthAppNotInstalled(): showInstallHint();
case AuthFailed(:final error): report(error);
default: break;
}Mengecek
TraciveQuery.phone hanya menerima E.164; normalizePhoneNumber menambahkan kode negaranya, dari kode alpha-3 (IDN) atau kode telepon (+62). Tersedia juga .email, .bankAccount, .iban, .crypto, dan .username. Untuk menyebut bank dengan kode nasional atau BIC, pakai .bankAccountByShortCode atau .bankAccountByBic. Kode yang tidak ada di daftar kode bank ditolak di perangkat dengan unknownBankCode, jadi tidak pernah ditagih.
final TraciveQuery query;
try {
query = TraciveQuery.phone(normalizePhoneNumber('IDN', input)); // "0857-1860-9079" → +6285718609079
} on TraciveQueryException catch (e) {
return showInputError(e.reason); // tidak ada yang terkirim, tidak ditagih
}
final result = await client.checkScammer(query);
render(result.riskTier, result.reportTotal, result.disclaimer); // disclaimer wajib tampilSemuanya melipat identifier di klien dengan aturan yang sama dengan server, dan menolak input yang mustahil cocok sebelum ada yang terkirim, karena miss tetap ditagih. Rinciannya di grammar input.
Cache
Tanpa argumen cache, client memakai cache di memori yang hilang setiap aplikasi dimulai ulang, dan setiap pengecekan ulang setelahnya ditagih lagi. openTraciveCache() di contoh di atas membuka cache persisten, dan tidak pernah melempar error: kalau gagal dibuka, cache-nya selalu miss dan errornya ada di cache.lastStoreError. Pantau itu, karena cache yang mati berarti setiap pengecekan memakan kredit.
Cache yang sama juga mencatat hasil yang ditunjukkan pada satu transaksi, supaya layar detail transaksi tetap bisa menampilkannya kemudian tanpa pengecekan kedua. Setiap catatan adalah snapshot dengan retensi 90 hari. Jangan kirim hasil ini ke server Anda: ketentuan partner melarangnya.
// Setelah transfer tercipta dan punya nomor referensi.
await client.cache.writeTransactionCheck(transfer.reference, result);
// Di detail transaksi, kapan pun kemudian. null = tidak ada pengecekan; jangan tampilkan apa-apa.
final shown = await client.cache.readTransactionCheck(transfer.reference);
if (shown != null) renderSecurityCheckCard(shown);Error & biaya
Setiap error.code dari server menjadi subclass TraciveException. Token kedaluwarsa tidak perlu Anda tangani: SDK me-refresh lalu mengulang sekali.
try {
await client.checkScammer(query, idempotencyKey: attemptId); // satu kunci per pengecekan
} on TraciveInsufficientCreditsException {
promptTopUp(); // 402: jangan cek credits() sebelum setiap pengecekan
} on TraciveGrantRevokedException {
reauthorize(); // token sudah dihapus; jangan diulang
} on TraciveInvalidTokenException {
reauthorize(); // belum ada grant yang bisa dipakai; tidak ada yang terkirim
} on TraciveRateLimitedException catch (e) {
backOff(e.retryAfter); // tidak pernah diulang otomatis
}- · Setiap request yang sampai ke server ditagih, termasuk miss.
- · SDK hanya mengulang pengecekan yang gagal kalau Anda memberi
idempotencyKey: 5xx atau timeout tidak memberi tahu apakah pengecekan pertama sudah ditagih. - · Jangan panggil
credits()sebelum setiap pengecekan. Tangani 402-nya.
Keluar
await client.signOut(); // revoke best-effort, lalu token dan cache dihapus apa pun hasilnyaKenapa mode-nya bukan const
TraciveOAuthMode sengaja bukan konstruktor const, dan alasannya layak diketahui karena ia menjelaskan satu kelas bug yang sulit dilacak.
Pemeriksaan partnerUserId yang kosong dulu berupa assert, dan compiler AOT membuang assert. Akibatnya build release mengirim partner_user_id= kosong ke jaringan sementara build debug menolaknya, dan selisih itu baru muncul di production. Konstruktor const juga tidak bisa memanggil trim(), jadi ia tidak akan pernah menangkap id berisi spasi.
Kehilangan const memakan satu kata kunci; mempertahankannya memakan pemeriksaannya.