Lewati ke konten utama

Rule Basics

Rule adalah JSON config yang dievaluasi oleh WASM engine. Bayangkan seperti checklist satpam: setiap rule adalah satu pertanyaan yang dijawab sebelum pembayaran diizinkan.

PAY.ID mendukung 3 format rule yang bisa digabungkan secara bebas.


Format A: SimpleRule — Satu Kondisi

Format paling dasar. Tanyakan satu hal tentang pembayaran.

interface SimpleRule {
id: string;
if: { field: string; op: string; value: any; };
message?: string; // ditampilkan saat rule memblokir pembayaran
}

Contoh — hanya terima USDC:

{
"id": "usdc_saja",
"if": { "field": "tx.asset", "op": "==", "value": "USDC" },
"message": "Hanya USDC yang diterima"
}

Contoh — minimum 10 USDC:

{
"id": "min_jumlah",
"if": { "field": "tx.amount", "op": ">=", "value": "10000000" },
"message": "Pembayaran minimum 10 USDC"
}
Catatan Desimal

Jumlah menggunakan presisi desimal token. USDC punya 6 desimal, jadi 10 USDC = "10000000" (10 × 10⁶).


Format B: MultiConditionRule — AND/OR dari Beberapa Kondisi

Cek beberapa kondisi sekaligus dengan "logic": "AND" atau "logic": "OR".

Contoh — jumlah harus antara 10 dan 500 USDC (keduanya harus lolos → AND):

{
"id": "range_jumlah",
"logic": "AND",
"conditions": [
{ "field": "tx.amount", "op": ">=", "value": "10000000" },
{ "field": "tx.amount", "op": "<=", "value": "500000000" }
],
"message": "Jumlah harus antara 10 dan 500 USDC"
}

Contoh — terima USDC atau USDT (salah satu lolos → OR):

{
"id": "stablecoin",
"logic": "OR",
"conditions": [
{ "field": "tx.asset", "op": "==", "value": "USDC" },
{ "field": "tx.asset", "op": "==", "value": "USDT" }
],
"message": "Hanya stablecoin yang diterima"
}

Format C: NestedRule — Rules di Dalam Rules

Untuk logika yang lebih kompleks, kamu bisa nest rules di dalam rules lain.

Contoh — pelanggan VIP bisa kirim berapa saja, yang lain dibatasi 50 USDC:

{
"id": "vip_atau_kecil",
"logic": "OR",
"rules": [
{
"id": "adalah_vip",
"if": { "field": "tx.sender", "op": "in", "value": ["0xVIP1...", "0xVIP2..."] }
},
{
"id": "jumlah_kecil",
"if": { "field": "tx.amount", "op": "<=", "value": "50000000" }
}
]
}

Root RuleConfig

Rules kamu dibungkus dalam objek root RuleConfig yang mendefinisikan logika keseluruhan:

interface RuleConfig {
version?: string; // Tag versi opsional
logic: "AND" | "OR"; // Cara rules top-level digabungkan
rules: AnyRule[]; // Array dari SimpleRule, MultiConditionRule, atau NestedRule
requires?: string[]; // Modul context yang dibutuhkan: ["oracle", "risk", "state"]
message?: string; // Pesan fallback
}

Contoh policy merchant lengkap:

{
"version": "1",
"logic": "AND",
"rules": [
{
"id": "usdc_saja",
"if": { "field": "tx.asset", "op": "==", "value": "USDC" }
},
{
"id": "min_jumlah",
"if": { "field": "tx.amount", "op": ">=", "value": "10000000" }
},
{
"id": "range_jumlah",
"logic": "AND",
"conditions": [
{ "field": "tx.amount", "op": ">=", "value": "10000000" },
{ "field": "tx.amount", "op": "<=", "value": "500000000" }
]
}
]
}

Semua 3 rules harus lolos (logika AND) agar pembayaran di-ALLOW.


Referensi Operator

OperatorBerlaku UntukContoh
==Nilai apapunasset == "USDC"
!=Nilai apapunasset != "ETH"
>=Angka atau stringamount >= "100000000"
<=Angka atau stringamount <= "5000000000"
inArray nilaiasset in ["USDC","USDT"]
not_inArray nilaichainId not_in [56, 97]
between[min, max]timestamp between [8, 22]
not_between[min, max]timestamp not_between [23, 6]

Referensi Field Paths

Field-field yang bisa kamu gunakan dalam rules:

FieldDeskripsiContoh Nilai
tx.senderAlamat wallet payer"0xAbCd..."
tx.receiverAlamat wallet receiver"0x1234..."
tx.assetSimbol token"USDC"
tx.amountJumlah dalam unit token (string)"150000000"
tx.chainIdID jaringan blockchain4202
payId.idString payment identity"pay.id/merchant"
payId.ownerPemilik PAY.ID"0xOwner..."
env.timestampUnix timestamp saat ini1700000000
state.spentTodayJumlah yang dihabiskan hari ini (dari server)"50000000"
state.dailyLimitBatas pengeluaran harian"500000000"
oracle.countryNegara pengguna (dari server)"ID"
oracle.kycLevelLevel verifikasi KYC"2"
risk.scoreSkor risiko 0–100 (dari server)15
risk.categoryKategori risiko (dari server)"low"

Field di bawah state.*, oracle.*, dan risk.* membutuhkan Server Mode dan harus dideklarasikan di requires:

{ "version": "1", "logic": "AND", "requires": ["oracle", "state"], "rules": [...] }

Referensi Cross-field

Nilai rule bisa merujuk ke field lain dengan prefix $:

{
"id": "dalam_batas_harian",
"if": {
"field": "state.spentTodayPlusTx",
"op": "<=",
"value": "$state.dailyLimit"
},
"message": "Batas pengeluaran harian terlampaui"
}

Rule ini lolos kalau total pengeluaran hari ini (termasuk transaksi ini) tidak melebihi batas harian.


Rule Hashing

Rules disimpan di IPFS dan hash-nya dicatat on-chain. Untuk memastikan hash deterministik, selalu canonicalize (urutkan keys secara alfabetis) sebelum hashing:

function canonicalize(obj: any): string {
if (Array.isArray(obj))
return `[${obj.map(canonicalize).join(",")}]`;
if (obj !== null && typeof obj === "object") {
return `{${Object.keys(obj).sort()
.map(k => `"${k}":${canonicalize(obj[k])}`)
.join(",")}}`;
}
return JSON.stringify(obj);
}

// Hash rule untuk disimpan on-chain
const ruleHash = keccak256(toUtf8Bytes(canonicalize(ruleObject)));
Urutan Kunci Penting!

{"id":"a","if":{...}} dan {"if":{...},"id":"a"} menghasilkan hash yang berbeda. Selalu canonicalize dulu sebelum hashing.