Cara Menghitung Angsuran Pinjaman Flat dan Anuitas di PHP + Denda Keterlambatan
Hampir semua aplikasi koperasi, leasing, atau pinjaman karyawan membutuhkan satu fungsi yang sama: membuat jadwal angsuran. Masalahnya, ada dua metode bunga yang paling sering dipakai, yaitu Flat dan Anuitas, dan hasilnya cukup berbeda. Belum lagi urusan pembulatan rupiah dan denda keterlambatan.
Di tutorial ini kita buat fungsi jadwal_angsuran() dan denda_angsuran() dengan PHP murni, lalu menyimpannya ke MySQL. Contohnya diambil dari Aplikasi Koperasi Simpan Pinjam yang bisa kamu download gratis di Papicode (link di akhir artikel).
1. Flat vs Anuitas, apa bedanya?
Misalkan pinjaman Rp 10.000.000, bunga 1,5% per bulan, tenor 12 bulan.
Flat: bunga selalu dihitung dari pokok awal.
- Pokok per bulan = 10.000.000 / 12 = 833.334
- Bunga per bulan = 10.000.000 × 1,5% = 150.000
- Angsuran = 983.400 (setelah dibulatkan), sama setiap bulan
Anuitas: angsurannya juga tetap setiap bulan, tetapi bunga dihitung dari sisa pokok. Di awal porsi bunga besar, makin lama makin kecil, sementara porsi pokok makin besar.
Rumus angsuran anuitas:
A = P × r / (1 − (1 + r)^−n)
dengan P pokok pinjaman, r bunga per bulan (0,015), dan n tenor. Hasilnya 916.800 per bulan.
| Flat | Anuitas | |
|---|---|---|
| Angsuran per bulan | 983.400 | 916.800 |
| Total bunga | 1.800.000 | 1.001.599 |
Dengan suku bunga nominal yang sama, anuitas jauh lebih ringan bagi peminjam. Itulah sebabnya aplikasi yang baik menampilkan perbandingan keduanya sebelum anggota mengajukan.
2. Fungsi jadwal angsuran
Dua hal penting dalam praktiknya:
- Bulatkan ke atas kelipatan Rp100 agar mudah dibayar tunai.
- Koreksi selisih pembulatan di angsuran terakhir supaya total pokok tetap persis sama dengan jumlah pinjaman.
<?php
function jadwal_angsuran(int $pokok, float $bungaPersen, int $tenor, string $metode): array
{
$r = $bungaPersen / 100;
$baris = [];
$sisa = $pokok;
if ($metode === 'anuitas' && $r > 0) {
$cicilan = (int) (ceil($pokok * $r / (1 - pow(1 + $r, -$tenor)) / 100) * 100);
for ($i = 1; $i <= $tenor; $i++) {
$bunga = (int) round($sisa * $r); // bunga dari SISA pokok
$porsiPokok = $i === $tenor ? $sisa : min($sisa, - );
[] = [ => , => , => ,
=> + , => - ];
-= ;
}
} {
= () (( / / ) * );
= () ( * );
( = ; <= ; ++) {
= === ? : (, );
[] = [ => , => , => ,
=> + , => - ];
-= ;
}
}
;
}
Angsuran terakhir selalu memakai $sisa, sehingga sisa di baris terakhir pasti 0. Coba cek:
$j = jadwal_angsuran(10000000, 1.5, 12, 'anuitas');
echo array_sum(array_column($j, 'pokok')); // 10000000
echo end($j)['sisa']; // 0
3. Simpan jadwal saat pinjaman dicairkan
Jadwal disimpan ke tabel sendiri, satu baris per angsuran. Constraint UNIQUE (pinjaman_id, ke) mencegah angsuran yang sama tercatat dua kali.
CREATE TABLE angsuran (
id INT AUTO_INCREMENT PRIMARY KEY,
pinjaman_id INT NOT NULL,
ke TINYINT NOT NULL,
jatuh_tempo DATE NOT NULL,
pokok INT NOT NULL,
bunga INT NOT NULL,
total INT NOT NULL,
denda INT NOT NULL DEFAULT 0,
dibayar_at DATE NULL,
UNIQUE KEY uk_ke (pinjaman_id, ke)
) ENGINE=InnoDB;
Hati-hati menghitung tanggal jatuh tempo. strtotime('+1 month') dari 31 Januari menghasilkan 3 Maret, bukan akhir Februari. Gunakan fungsi yang menjepit tanggal ke akhir bulan:
<?php
function tambah_bulan(string $tanggal, int $n): string
{
$d = new DateTime($tanggal);
$hari = (int) $d->format('j');
$d->modify('first day of this month')->modify("+$n month");
$d->setDate((int) $d->format('Y'), (int) $d->format('n'), min($hari, (int) $d->format('t')));
return $d->format('Y-m-d');
}
foreach (jadwal_angsuran($jumlah, $bunga, $tenor, $metode) as $a) {
$pdo->prepare()
->([, [], (, []), [], [], []]);
}
4. Denda keterlambatan
Aturan yang umum di koperasi: denda 0,2% dari angsuran per hari, tetapi maksimal 10% agar tidak memberatkan.
<?php
function denda_angsuran(array $a, float $persenHari = 0.2, float $maksPersen = 10): int
{
if ($a['dibayar_at']) return (int) $a['denda']; // sudah dibayar: pakai denda yang tercatat
$telat = (int) floor((strtotime(date('Y-m-d')) - strtotime($a['jatuh_tempo'])) / 86400);
if ($telat <= 0) return 0;
return (int) round(min($telat * $persenHari, $maksPersen) / 100 * $a['total']);
}
Contoh: angsuran 425.000 telat 12 hari, dendanya 12 × 0,2% = 2,4% = 10.200. Kalau telat 100 hari, dendanya mentok di 10% = 42.500.
Denda dihitung saat pembayaran, lalu nilainya disimpan ke kolom denda supaya laporan tidak berubah meskipun aturan denda diganti di kemudian hari.
5. Bayar angsuran dengan aman
Dua teller bisa saja menerima pembayaran untuk pinjaman yang sama pada saat bersamaan. Kunci baris pinjaman di dalam transaksi, lalu ambil angsuran tertua yang belum dibayar:
<?php
$pdo->beginTransaction();
$pdo->prepare('SELECT id FROM pinjaman WHERE id = ? FOR UPDATE')->execute([$id]);
$st = $pdo->prepare('SELECT * FROM angsuran WHERE pinjaman_id = ? AND dibayar_at IS NULL ORDER BY ke LIMIT 1');
$st->execute([$id]);
$a = $st->fetch(PDO::FETCH_ASSOC);
$denda = denda_angsuran($a);
$pdo->prepare('UPDATE angsuran SET denda = ?, dibayar_at = CURDATE() WHERE id = ?')->execute([$denda, $a['id']]);
$pdo->commit();
Request kedua akan menunggu sampai request pertama selesai, lalu otomatis membayar angsuran berikutnya, bukan angsuran yang sama.
6. Bonus: pelunasan dipercepat
Anggota yang ingin melunasi lebih cepat sebaiknya tidak membayar bunga untuk bulan-bulan yang belum dijalani. Aturan yang adil:
- Angsuran yang sudah jatuh tempo dibayar penuh beserta dendanya.
- Angsuran bulan berjalan dibayar penuh.
- Angsuran setelahnya hanya pokok, sehingga bunganya tidak ditagih.
UPDATE angsuran SET bunga = 0, total = pokok, dibayar_at = CURDATE() WHERE id = ?
Dengan begitu kartu angsuran tetap mencatat dengan jujur berapa yang benar-benar dibayar.
Ringkasan
- Flat: bunga dari pokok awal. Anuitas: bunga dari sisa pokok, total bunganya lebih kecil.
- Bulatkan angsuran ke kelipatan Rp100 dan koreksi selisihnya di angsuran terakhir.
- Hitung jatuh tempo dengan fungsi yang menjepit akhir bulan.
- Denda = persen per hari × angsuran, dibatasi maksimal, dan disimpan saat dibayar.
- Kunci pinjaman dengan FOR UPDATE agar tidak ada pembayaran ganda.
Download source code lengkapnya
Semua logika di atas sudah dipakai di Aplikasi Koperasi Simpan Pinjam PHP Native & MySQL: simpanan pokok, wajib, dan sukarela, simulasi Flat vs Anuitas, persetujuan ketua dan pencairan bendahara, kartu angsuran dengan denda otomatis, pelunasan dipercepat, serta laporan kolektibilitas dan NPL. Gratis dan langsung jalan di XAMPP.
Source code lengkapnya gratis
Lihat semua yang dibahas di artikel ini langsung berjalan.