Ubah kebutuhan donasi menjadi data contract: entity, field, status, relasi, dan gap yang harus dicatat sebelum AI menyentuh database.
35m bacaanKuis WajibMicro Badge Lesson
Data yang Dibutuhkan Platform Donasi
Di Module 01, kamu sudah menulis PRD mini dan project rules. Sekarang kita turun satu lapis: data apa yang harus ada supaya alur donasi bisa berjalan?
Kalau kamu meminta AI dengan kalimat:
text
buatkan database donasi
hasilnya bisa macam-macam. Bisa jadi AI membuat tabel donations, transactions, users, programs, atau nama lain yang tidak nyambung dengan starter. Bisa juga ia lupa bahwa donasi guest harus tetap bisa dilakukan, atau status donasi tidak boleh langsung dari form publik.
verified
Supaya itu tidak terjadi, kamu butuh data contract.
Data contract adalah dokumen pendek yang menjelaskan:
entity apa saja yang dipakai;
field penting tiap entity;
status yang diizinkan;
relasi antar entity;
bagian mana yang wajib, opsional, atau perlu snapshot.
Ini bukan dokumen hiasan. Nanti, setiap prompt yang menyentuh database harus merujuk ke contract ini.
Mulai dari alur donatur
Baca ulang alur utama v1:
text
donatur membuka halaman utama ↓melihat campaign published ↓membuka form donasi ↓mengisi nominal dan identitas ↓submit donasi ↓mendapat nomor donasi dan konfirmasi
Dari alur ini saja, kita sudah bisa menemukan beberapa benda penting:
Benda di alur
Makna teknis
campaign
program donasi yang tampil ke publik
donatur
orang yang memberi donasi; bisa guest atau login
donasi
transaksi/permintaan donasi dari donatur untuk campaign
pembayaran
attempt pembayaran atau bukti pembayaran untuk donasi
Jangan langsung loncat ke nama kolom. Mulai dulu dari bahasa domain. Setelah itu, baru cocokkan dengan migration.
Migration adalah kenyataan schema
Buka file ini:
text
supabase/migrations/20260829_vertical_slice.sql
Di bagian awal kamu akan melihat tabel campaigns:
sql
create table if not exists public.campaigns ( id uuid primary key default gen_random_uuid(), title text not null, slug text not null unique, summary text, description text not null, target_amount numeric(14,2) not null check (target_amount > 0), status text not null default 'draft' check (status in ('draft','published','paused','completed','archived')), starts_at timestamptz, ends_at timestamptz, cover_path text, created_at timestamptz not null default now(), updated_at timestamptz not null default now());
Dari potongan ini, kamu dapat beberapa fakta:
slug unik, jadi aman dipakai untuk URL campaign;
target_amount harus lebih dari 0;
status campaign bukan teks bebas;
hanya ada lima status: draft, published, paused, completed, archived.
Kalimat PRD “campaign yang tampil publik hanya yang published” sekarang punya bentuk data yang jelas: campaigns.status = 'published'.
Donatur bisa guest, maka relasinya tidak wajib
Baca tabel donors dan awal tabel donations:
sql
create table if not exists public.donors ( id uuid primary key default gen_random_uuid(), user_id uuid references auth.users(id) on delete set null, display_name text not null, email text, phone text, created_at timestamptz not null default now(), updated_at timestamptz not null default now());
sql
donor_id uuid references public.donors(id) on delete set null,donor_name_snapshot text not null,donor_email_snapshot text,donor_phone_snapshot text,is_anonymous boolean not null default false,user_id uuid references auth.users(id) on delete set null,
** Perhatikan dua hal.**
Pertama, donor_id tidak memakai not null. Artinya donasi bisa ada walaupun tidak terhubung ke row donor tertentu. Ini cocok dengan keputusan v1: donasi guest boleh.
Kedua, ada field snapshot seperti donor_name_snapshot. Ini penting. Kalau nama donatur di profil berubah bulan depan, histori donasi lama tidak boleh ikut berubah. Kuitansi dan riwayat butuh salinan data saat transaksi terjadi.
Jadi di data contract, jangan tulis “donasi wajib punya donor”. Yang benar:
text
Donasi wajib punya campaign, tetapi donor_id opsional. Identitas saat transaksi disimpan di snapshot fields.
Donasi punya status yang terbatas
Bagian donations punya status:
sql
status text not null default 'pending' check (status in ('pending','verified','rejected','cancelled','refunded')),
Ini mengunci nilai status. AI tidak boleh asal membuat status baru seperti success, paid, atau done untuk tabel donations kalau schema tidak mendukungnya.
Untuk alur awal, status paling penting adalah pending. Donasi baru masuk sebagai pending, lalu admin atau proses verifikasi mengubahnya menjadi verified, rejected, atau cancelled.
Nanti, kalau ada refund, status verified bisa berubah ke refunded. Tapi user publik tidak boleh memilih status itu dari form.
Payment bukan donation status
Tabel payments juga punya status:
sql
status text not null default 'unpaid' check (status in ('unpaid','pending','paid','failed','expired','refunded')),
Ini sering bikin pemula bingung.
donations.status menjawab:
text
status donasi sebagai record utama apa?
payments.status menjawab:
text
status attempt pembayaran apa?
Jangan mencampur keduanya. Kalau AI memakai paid sebagai status donations, itu salah untuk schema ini. paid hanya ada di payments.status; donasi memakai verified setelah pembayaran diterima/diakui.
Minta Antigravity membantu, tapi jangan biarkan ia mengarang
Gunakan prompt ini:
text
Saya sedang menulis docs/data-contract.md untuk Platform Donasi Amanah.Jangan mengubah file apa pun.Baca:- docs/prd-mini.md- docs/project-rules.md- supabase/migrations/20260829_vertical_slice.sqlTugasmu:1. Petakan entity utama: campaigns, donors, donations, payments.2. Untuk tiap entity, sebutkan field penting, status yang diizinkan, dan relasi.3. Bedakan status donations dan status payments.4. Tandai field yang wajib vs opsional berdasarkan NOT NULL di migration.5. Jangan menambahkan kolom atau status yang tidak ada di migration.Jawab sebagai draft docs/data-contract.md.Untuk setiap klaim penting, sebutkan sumbernya dari migration.
Prompt ini meminta bantuan analisis, bukan perubahan kode. Kalau agent mulai menawarkan migration baru, stop dulu. Kita sedang membaca contract, bukan mengubah schema.
Tulis docs/data-contract.md
Gunakan struktur seperti ini:
md
# Data Contract — Platform Donasi Amanah v1## Sumber- PRD mini: docs/prd-mini.md- Rules: docs/project-rules.md- Migration: supabase/migrations/20260829_vertical_slice.sql## Relasi utama- campaigns 1—N donations: satu campaign bisa punya banyak donasi.- donors 0—N donations: satu donor bisa punya banyak donasi, tetapi donasi guest boleh tanpa donor_id.- donations 1—N payments: satu donasi bisa punya attempt pembayaran.- donors.user_id 0—1 auth.users: donor boleh terkait akun login, tetapi tidak wajib.## campaigns- Fungsi: program donasi yang tampil di homepage.- URL key: slug, unik.- Field penting: title, slug, summary, description, target_amount, status.- Status: draft, published, paused, completed, archived.- Publik hanya membaca status published.## donors- Fungsi: identitas donatur jika perlu disimpan sebagai profil.- user_id opsional, karena donatur bisa guest.- display_name wajib.- email dan phone opsional.## donations- Fungsi: record utama donasi.- Wajib punya campaign_id.- donor_id opsional.- Snapshot wajib/opsional menyimpan identitas saat transaksi.- donation_number unik.- amount harus > 0.- Status: pending, verified, rejected, cancelled, refunded.- Donasi baru dari publik harus pending.## payments- Fungsi: attempt atau record pembayaran untuk donasi.- Wajib punya donation_id.- Status: unpaid, pending, paid, failed, expired, refunded.- Status payment tidak sama dengan status donation.## Gap / catatan- Jika UI ingin menampilkan progress terkumpul per campaign, perlu query agregasi donation verified.- Jika payment gateway otomatis dipakai, detail provider dan webhook perlu contract tambahan.
Bagian Gap / catatan penting. Kalau ada kebutuhan yang belum terlihat di migration, catat sebagai gap. Jangan diam-diam menyisipkan kolom baru ke contract seolah sudah ada.
Verifikasi contract
Jalankan command ini dari root starter:
bash
grep -n "create table if not exists public.campaigns" supabase/migrations/20260829_vertical_slice.sqlgrep -n "create table if not exists public.donors" supabase/migrations/20260829_vertical_slice.sqlgrep -n "create table if not exists public.donations" supabase/migrations/20260829_vertical_slice.sqlgrep -n "create table if not exists public.payments" supabase/migrations/20260829_vertical_slice.sqlgrep -n "status in" supabase/migrations/20260829_vertical_slice.sqlgrep -n "references public" supabase/migrations/20260829_vertical_slice.sql
Yang kamu cari:
empat tabel memang ada;
status campaign, donation, dan payment sesuai contract;
donations.campaign_id wajib karena ada not null;
donations.donor_id opsional karena tidak ada not null;
payments.donation_id wajib dan menunjuk donations.
Kalau satu baris contract tidak bisa dibuktikan dari migration atau PRD, turunkan menjadi catatan gap.
Nullable bukan detail kecil.donor_id opsional berarti alur guest memang didukung.
Enum/status tidak boleh dikarang. Kalau status tidak ada di CHECK constraint, jangan pakai di prompt atau kode.
Checkpoint
docs/data-contract.md ada.
Contract memuat campaigns, donors, donations, dan payments.
Kamu bisa membedakan donations.status dan payments.status.
Kamu tahu kenapa donor_id opsional tapi snapshot tetap penting.
Semua status di contract cocok dengan migration.
Gap dicatat sebagai gap, bukan disamarkan jadi fakta schema.
Gimana, mulai kebayang kan kenapa “buat database donasi” itu terlalu bahaya kalau dilempar mentah ke AI? Begitu kamu punya data contract, prompt berikutnya bisa jauh lebih tegas: kolomnya apa, statusnya apa, relasinya apa, dan bagian mana yang tidak boleh disentuh.
Checkpoint KuisTarget: 70%
Kuis 02.1 — Data yang Dibutuhkan Platform Donasi
Kuis ini wajib untuk sertifikat. Kamu tetap bisa lanjut ke pelajaran berikutnya dan mengerjakannya nanti.