STUDI KASUS · DEMO DATA SINTETIS
Pesanan dan pembayaran terpisah.
Selisihnya terlihat jelas.
Contoh alur rekonsiliasi untuk bisnis yang menerima banyak transaksi: dua file diproses menjadi daftar pesanan cocok, belum dibayar, dibayar ganda, dan nominal berbeda.
Ini demonstrasi, bukan hasil proyek klien. Semua identitas dan transaksi dibuat secara sintetis. Angka di bawah berasal dari skrip yang dapat dijalankan ulang; tidak mewakili performa atau hasil bisnis nyata.
01 / MASALAH
File ada, gambaran transaksi belum utuh.
Tim operasional memegang daftar pesanan dan laporan pembayaran yang terpisah. Membandingkan baris satu per satu menyulitkan saat jumlah transaksi bertambah, terutama ketika ada pesanan tanpa pembayaran, lebih dari satu pembayaran berhasil, atau jumlah uang yang tidak sesuai.
Demo ini menjawab pertanyaan sederhana: pesanan mana yang sudah beres, dan mana yang perlu diperiksa manusia?
02 / SUMBER & ALUR
Dari CSV menuju daftar tindak lanjut.
Tidak ditemukan pembayaran berstatus berhasil untuk order_id tersebut.
Ada lebih dari satu pembayaran berhasil yang berbeda untuk satu pesanan.
Satu pembayaran berhasil ditemukan, tetapi jumlahnya tidak sama dengan tagihan.
Tepat satu pembayaran berhasil dengan jumlah yang sama.
03 / HASIL DEMO
Contoh tampilan dashboard.
10.000 pesanan sintetis · 9.950 baris pembayaran · periode simulasi September 2026
| ID pesanan | Tagihan | Terbayar | Pembayaran | Status |
|---|---|---|---|---|
| ORD-00001 | Rp57.919 | Rp57.919 | 1 | Cocok |
| ORD-09301 | Rp554.619 | Rp0 | 0 | Belum dibayar |
| ORD-09601 | Rp80.319 | Rp160.638 | 2 | Dibayar ganda |
| ORD-09801 | Rp714.119 | Rp724.119 | 1 | Nominal berbeda |
Selain kategori pesanan di atas, terdeteksi 50 pembayaran berhasil tanpa pesanan yang cocok. Ini dilaporkan terpisah agar tidak mengubah total 10.000 pesanan.
COBA SENDIRI · 8 PESANAN SINTETIS
Ubah pembayaran, lihat statusnya.
Atur jumlah pembayaran berhasil dan total terbayar. Hasil dihitung di perangkat Anda; contoh ini tidak mengirim data ke server.
| ID pesanan | Tagihan | Pembayaran berhasil | Total terbayar (Rp) | Status |
|---|---|---|---|---|
| ORD-DEMO-01 | Rp 125.000 | Cocok | ||
| ORD-DEMO-02 | Rp 82.000 | Cocok | ||
| ORD-DEMO-03 | Rp 250.000 | Belum dibayar | ||
| ORD-DEMO-04 | Rp 95.000 | Dibayar ganda | ||
| ORD-DEMO-05 | Rp 175.000 | Nominal berbeda | ||
| ORD-DEMO-06 | Rp 45.000 | Cocok | ||
| ORD-DEMO-07 | Rp 320.000 | Cocok | ||
| ORD-DEMO-08 | Rp 69.000 | Cocok |
Urutan aturan: 0 pembayaran → belum dibayar; lebih dari 1 → dibayar ganda; 1 pembayaran → nominal dibandingkan. Ini demo sederhana, belum mencakup refund, cicilan, settlement, dan biaya penyedia pembayaran. Skrip Python di bawah dapat digunakan untuk menghasilkan file CSV lengkap.
DEMO LANJUTAN
Ingin mencoba dari dua file CSV?
Ubah data pesanan dan pembayaran contoh, jalankan validasi, lalu telusuri alasan setiap status dan pembayaran tanpa pesanan.
Angka dan contoh ID di halaman ini dihasilkan oleh skrip Python di bawah. Salin sebagai rekonsiliasi.py, lalu jalankan python3 rekonsiliasi.py --output demo/output untuk membuat CSV pesanan, pembayaran, hasil rekonsiliasi, dan ringkasannya. Untuk implementasi klien, aturan refund, cicilan, biaya penyedia pembayaran, batas waktu, dan data settlement harus disepakati lebih dahulu.
Lihat dan salin kode demo lengkap
"""Reproducible synthetic order/payment reconciliation demo for NAFAF.
Run: python3 rekonsiliasi.py --output demo/output
No external packages or customer data are required.
"""
import argparse
import csv
import json
from collections import Counter, defaultdict
from datetime import datetime, timedelta
from pathlib import Path
def write_csv(path, rows, fields):
with path.open("w", newline="", encoding="utf-8") as handle:
writer = csv.DictWriter(handle, fieldnames=fields)
writer.writeheader()
writer.writerows(rows)
def read_csv(path):
with path.open(newline="", encoding="utf-8") as handle:
return list(csv.DictReader(handle))
def generate(output):
orders = []
payments = []
day = datetime(2026, 9, 1, 9, 0)
def add_payment(order_id, amount):
index = len(payments) + 1
payments.append({
"payment_id": f"PAY-{index:05d}",
"order_id": order_id,
"paid_at": (day + timedelta(minutes=index)).isoformat(timespec="minutes"),
"amount_idr": amount,
"status": "SUCCESS",
"provider_reference": f"REF-{index:06d}",
})
for index in range(1, 10001):
order_id = f"ORD-{index:05d}"
amount = 50000 + (index * 7919) % 950000
orders.append({
"order_id": order_id,
"order_date": (day + timedelta(minutes=index)).isoformat(timespec="minutes"),
"customer_code": f"CUS-{1 + index % 750:04d}",
"amount_idr": amount,
})
if index <= 9300:
add_payment(order_id, amount)
elif index <= 9600:
continue # No successful payment.
elif index <= 9800:
add_payment(order_id, amount)
add_payment(order_id, amount) # Two distinct successful captures.
else:
add_payment(order_id, amount + 10000)
for index in range(1, 51):
add_payment(f"ORD-X{index:04d}", 75000 + index * 1000)
write_csv(output / "orders.csv", orders, ["order_id", "order_date", "customer_code", "amount_idr"])
write_csv(output / "payments.csv", payments, ["payment_id", "order_id", "paid_at", "amount_idr", "status", "provider_reference"])
def reconcile(output):
orders = read_csv(output / "orders.csv")
payments = read_csv(output / "payments.csv")
by_order = defaultdict(list)
for payment in payments:
if payment["status"] == "SUCCESS":
by_order[payment["order_id"]].append(payment)
results = []
for order in orders:
matched = by_order.pop(order["order_id"], [])
expected = int(order["amount_idr"])
paid = sum(int(payment["amount_idr"]) for payment in matched)
if len(matched) == 0:
status = "BELUM_DIBAYAR"
elif len(matched) > 1:
status = "DIBAYAR_GANDA"
elif paid != expected:
status = "NOMINAL_BERBEDA"
else:
status = "COCOK"
results.append({
"order_id": order["order_id"],
"expected_idr": expected,
"successful_payments": len(matched),
"paid_idr": paid,
"difference_idr": paid - expected,
"status": status,
"payment_ids": "|".join(payment["payment_id"] for payment in matched),
})
unmatched = [payment for matches in by_order.values() for payment in matches]
write_csv(output / "reconciliation.csv", results,
["order_id", "expected_idr", "successful_payments", "paid_idr", "difference_idr", "status", "payment_ids"])
write_csv(output / "unmatched_payments.csv", unmatched,
["payment_id", "order_id", "paid_at", "amount_idr", "status", "provider_reference"])
summary = {
"dataset": "SYNTHETIC_DEMO",
"orders": len(orders),
"payment_rows": len(payments),
"status_counts": dict(sorted(Counter(row["status"] for row in results).items())),
"payments_without_order": len(unmatched),
}
(output / "summary.json").write_text(json.dumps(summary, indent=2) + "\n", encoding="utf-8")
return summary
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, default=Path("demo/output"))
args = parser.parse_args()
args.output.mkdir(parents=True, exist_ok=True)
generate(args.output)
print(json.dumps(reconcile(args.output), indent=2))
if __name__ == "__main__":
main()
04 / PENERAPAN
Mulai dari datanya, lalu operasionalnya.
Pada proyek nyata, contoh alur ini dapat diterapkan dengan Apache Hop untuk mengambil dan menjadwalkan data, PostgreSQL untuk penyimpanan dan pemeriksaan, dbt Core atau SQL untuk aturan rekonsiliasi, serta Metabase untuk dashboard.
- Pemetaan kolom dan aturan status yang disetujui
- Pipeline yang dapat dijalankan ulang tanpa menggandakan data
- Dashboard ringkasan dan daftar pengecualian
- Dokumentasi, hak akses, dan prosedur tindak lanjut
Demo ini mencakup pesanan dan pembayaran. Rekonsiliasi settlement bank atau penyedia pembayaran merupakan tahap berikutnya dengan data dan aturan tambahan.
BAHAS KEBUTUHAN ANDA
File transaksi Anda masih perlu diperiksa manual?
Ceritakan sumber data, jumlah transaksi, dan masalah yang sering muncul. Kami bantu susun ruang lingkup yang sesuai.
Diskusikan kebutuhan