Sampul buku
Seri Membangun & Menjual · Volume Monetisasi

License Manager & Billing

Cara Menjual Aplikasimu — Lisensi, Langganan, Midtrans & Stripe. Dari kode yang jalan menjadi kode yang dibayar.
LICENSE · Pro Annualx1
SubtotalRp 1.200.000
PPN 11%Rp 132.000
TOTAL DIBAYARRp 1.332.000
KEY7F3K-9QAM-2XTR-H8VP
oleh Galih Prasetyo · Edisi 2026 · Bahasa Indonesia
Panduan Teknis & Bisnis · Semua Level · Dapat Dibaca Manusia & AI

Kata Pengantar

rd · monetisasi · 2026

Ada momen yang sunyi dalam hidup setiap pembuat perangkat lunak: kode selesai, aplikasi berjalan mulus di layar, dan tidak ada satu rupiah pun yang masuk. Aplikasi yang hebat bukanlah aplikasi yang jalan; aplikasi yang hebat adalah aplikasi yang dibayar. Perbedaan di antara keduanya bukanlah kualitas kode — melainkan sebuah lapisan tipis yang jarang diajarkan di kelas pemrograman: lisensi, penagihan, dan pembayaran.

Buku ini menutup celah itu. Ia ditulis untuk developer yang sudah bisa membangun aplikasi tetapi belum pernah membangun mesin uang di sekelilingnya — kunci lisensi yang tak bisa dipalsukan, langganan yang menagih otomatis tiap bulan, webhook Midtrans yang mengubah status pembayaran menjadi akses, dan invoice yang sah secara pajak. Semuanya, dari nol.

"Harga adalah apa yang Anda bayar. Nilai adalah apa yang Anda dapatkan."

— Warren Buffett

Kita akan memperlakukan monetisasi sebagai disiplin rekayasa, bukan renungan pemasaran. Setiap bab menyertakan kode siap salin, alur yang bisa langsung diterapkan, dan — di Bab 14 — dua alat interaktif yang hidup di dalam halaman ini: sebuah generator & validator kunci lisensi dan sebuah kalkulator MRR/ARR. Anda bisa mencobanya sekarang, tanpa memasang apa pun.

Untuk siapa buku ini

Solo developer yang menjual aplikasi desktop; tim SaaS yang menyusun tier langganan; konsultan yang menagih lisensi on-premise ke korporat; dan siapa pun yang pernah bertanya "bagaimana caranya orang membayar aplikasiku dengan aman?"

Cara Membaca Buku Ini

peta · konvensi · jalur cepat

Buku ini bisa dibaca lurus dari depan ke belakang, atau dilompati sesuai kebutuhan. Berikut konvensi yang dipakai di seluruh halaman.

Konvensi visual

  • § Judul emas menandai konsep inti sebuah bab.
  • Blok kode berwarna gelap dengan font monospace — semuanya siap salin, tanpa placeholder tersembunyi.
  • Kotak Tip (emerald) berisi praktik yang disarankan; Kotak Peringatan (emas) menandai jebakan umum; Kotak Bahaya (merah) menandai kesalahan yang membuat sistem uang Anda bocor.
  • Kotak AI (abu) memberi petunjuk bagaimana asisten AI sebaiknya memakai isi bab tersebut — dijelaskan penuh di Bab 15.

Tiga jalur pembaca

Kalau Anda...Mulai dariLompati dulu
Jual app desktop sekali bayarBab 2 → 3 → 11 → 12Bab 5, 10
Bangun SaaS langgananBab 2 → 5 → 6/7 → 8 → 10Bab 11
Jual lisensi ke korporat IndonesiaBab 3 → 4 → 6 → 9 → 13Bab 7

Apa pun jalurnya, Bab 14 (alat interaktif) dan Bab 16 (checklist rilis) layak dikunjungi di akhir.

01
Fondasi

Dari Bikin App ke Jual App

// mindset · anatomi · uang sebagai fitur

Sebagian besar developer memperlakukan pembayaran sebagai renungan terakhir — sesuatu yang "nanti dipasang kalau sudah ada yang mau beli". Ini terbalik. Sistem penjualan bukan aksesori; ia adalah antarmuka antara nilai yang Anda ciptakan dan uang yang Anda terima. Bab ini menata ulang cara pandang: memperlakukan lisensi, penagihan, dan pembayaran sebagai fitur produk kelas satu, dengan bug-nya sendiri, keamanannya sendiri, dan kegembiraannya sendiri saat pertama kali sebuah webhook berbunyi "settlement".

Kenapa Monetisasi Terasa Sulit

Membangun aplikasi itu deterministik: Anda menulis kode, ia jalan atau tidak. Menjualnya melibatkan pihak-pihak yang tak Anda kendalikan — bank, gateway pembayaran, pembeli yang lupa membayar, penipu yang mencoba membajak. Kompleksitasnya bukan pada satu bagian, melainkan pada rantai-nya: sebuah pembelian harus menghasilkan sebuah kunci, kunci itu harus divalidasi, validasi harus tahan pemalsuan, langganan harus menagih ulang, penagihan gagal harus ditangani dengan anggun, dan seluruh jejaknya harus sah untuk pajak.

"Jika Anda tidak menemukan cara untuk menghasilkan uang saat tidur, Anda akan bekerja sampai mati."

— Warren Buffett

Kabar baiknya: setiap mata rantai itu sudah punya pola baku. Anda tidak sedang menemukan sistem perbankan; Anda sedang merangkai balok-balok yang teruji. Buku ini adalah katalog balok tersebut.

Anatomi Sebuah Sistem Penjualan

Apa pun modelnya, sebuah sistem penjualan perangkat lunak punya lima komponen yang selalu muncul. Kenali kelimanya sekarang; sisa buku ini hanyalah pendalaman masing-masing.

KomponenTugasDibahas di
Katalog & HargaMendefinisikan apa yang dijual dan berapa (tier, paket, kuota).Bab 2, 14
EntitlementMenyatakan siapa berhak atas apa — kunci lisensi atau baris langganan.Bab 3, 4
BillingMenagih uang: sekali, berulang, atau berdasarkan pemakaian.Bab 5, 10
Payment GatewayMemindahkan uang sungguhan (Midtrans, Stripe) dan mengabari via webhook.Bab 6, 7, 8
Dokumen & LegalInvoice, kwitansi, pajak — bukti sah bahwa transaksi terjadi.Bab 9, 13
Pembeliklik beli GatewayMidtrans/Stripe Webhooksettlement Serverbuat kunci Aksesaktif
Gambar 1.1 — Rantai monetisasi: setiap kotak adalah satu bab dalam buku ini.
Catatan untuk AI

Ketika membantu merancang monetisasi, mulailah dengan menanyakan model bisnis (Bab 2) sebelum menyarankan gateway. Salah memilih model membuat seluruh implementasi teknis sia-sia.

02
Strategi Harga

Model Lisensi

// perpetual · subscription · usage · freemium

Sebelum satu baris kode ditulis, sebuah keputusan menentukan segalanya: bagaimana bentuk uang mengalir? Sekali bayar untuk selamanya, atau sedikit-sedikit tiap bulan? Model lisensi bukan hanya soal harga — ia menentukan arsitektur teknis, hubungan dengan pelanggan, dan bahkan kesehatan arus kas Anda selama bertahun-tahun.

Perpetual (Sekali Bayar, Selamanya)

Pelanggan membayar sekali dan memiliki versi itu selamanya. Model klasik perangkat lunak desktop. Sederhana secara moral ("saya beli, saya punya") tetapi berat secara arus kas: Anda harus terus mencari pembeli baru untuk tumbuh, karena pembeli lama tak membayar lagi.

"Adalah jauh lebih baik membeli perusahaan hebat dengan harga wajar ketimbang perusahaan biasa dengan harga hebat."

— Warren Buffett

Subscription (Langganan)

Pelanggan membayar berulang — bulanan atau tahunan — dan berhenti mengakses saat berhenti membayar. Mesin di balik hampir semua SaaS modern. Menghasilkan pendapatan yang bisa diprediksi (MRR/ARR), tetapi menuntut nilai yang berkelanjutan: pelanggan mengevaluasi ulang setiap siklus.

AspekPerpetualSubscription
Arus kasLonjakan di mukaRata & dapat diprediksi
Metrik utamaUnit terjualMRR, churn, LTV
Beban serverRendah (offline OK)Perlu server lisensi aktif
RisikoHarus terus cari pembeli baruChurn menggerus pertumbuhan

Untuk subscription, dua metrik menjadi napas: MRR (Monthly Recurring Revenue) dan churn (persentase pelanggan yang pergi tiap bulan). Kalkulator di Bab 14 membiarkan Anda memainkan angka-angka ini secara langsung.

Usage-based (Bayar Sesuai Pemakaian)

Pelanggan membayar sebanding dengan yang mereka pakai — jumlah panggilan API, GB data, menit komputasi, token diproses. Adil dan selaras dengan nilai, tetapi menuntut metering yang akurat (Bab 10) dan membuat tagihan sulit diprediksi pelanggan.

Freemium

Sebuah tingkat gratis selamanya menarik banyak pengguna; sebagian kecil naik ke tier berbayar. Bukan model penagihan melainkan strategi akuisisi yang ditumpangkan pada subscription/usage. Kuncinya adalah garis pembatas yang jujur: fitur gratis harus benar-benar berguna, fitur berbayar harus benar-benar bernilai.

Peringatan: garis freemium

Jika tingkat gratis terlalu murah hati, tak ada yang upgrade. Jika terlalu pelit, tak ada yang datang. Ukur tingkat konversi free→paid (patokan sehat SaaS: 2–5%) dan sesuaikan garisnya, bukan harganya.

Memilih model: pohon keputusan singkat

# pseudo-decision
if nilai_produk == "terus bertambah tiap bulan":
    return "subscription"
elif biaya_variabel_tinggi (API, komputasi):
    return "usage-based" # atau hibrida
elif produk == "tool desktop, jarang update":
    return "perpetual (+ update 1 thn)"
else:
    return "freemium di atas subscription"
Praktik baik

Anda boleh mengubah model, tetapi mahal. Mulailah dengan model yang paling cocok dengan bentuk nilai produk Anda, bukan yang paling populer di Twitter.

03
Entitlement

License Key

// format · HMAC · checksum · aktivasi · device binding

Kunci lisensi adalah kalimat pertama dalam percakapan antara aplikasi Anda dan uang. Ia harus mudah dibaca manusia, tahan salah ketik, mustahil dipalsukan, dan — idealnya — bisa memberi tahu aplikasi apa yang boleh dilakukan pemiliknya bahkan tanpa internet. Bab ini membangun kunci semacam itu dari nol, lengkap dengan kode yang bisa Anda salin.

Format Key yang Manusiawi

Format klasik XXXX-XXXX-XXXX-XXXX bukan sekadar estetika. Pengelompokan empat-empat memudahkan mata, tanda hubung mencegah salah baca, dan pilihan alfabet menentukan keramahannya. Aturan emas: buang huruf ambigu — I, L, O, U — karena tertukar dengan 1, 0, dan V saat diketik ulang dari email.

KeputusanPilihan umumAlasan
AlfabetCrockford Base32 (32 simbol tanpa I,L,O,U)Ramah manusia, padat, tahan salah baca
Panjang16–25 simbolCukup entropi tanpa menyiksa pengetik
PemisahTanda hubung tiap 4–5 simbolMengelompokkan agar mudah dibaca
Grup terakhirChecksumMenolak salah ketik sebelum menyentuh server

Alat interaktif di Bab 14 memakai persis format ini: tiga grup payload ditambah satu grup checksum. Anda bisa membongkar cara kerjanya di sana.

Signing & HMAC — Kunci yang Membawa Bukti

Ada dua filosofi menyimpan hak pada sebuah kunci:

1. Kunci sebagai penanda (opaque token)

Kunci hanyalah nomor acak; seluruh arti ("Pro, kadaluarsa 2027, 3 perangkat") disimpan di database server. Sederhana, mudah dicabut, tetapi butuh server online untuk memverifikasi apa pun.

2. Kunci sebagai pembawa hak (signed license)

Kunci — atau berkas lisensi yang menyertainya — memuat data hak sekaligus tanda tangan kriptografis. Aplikasi bisa memverifikasi sendiri secara offline memakai kunci publik. Inilah jantung lisensi desktop & on-premise.

Untuk menandatangani, dua keluarga algoritma dipakai:

Node.js — menandatangani berkas lisensi dengan HMAC (server-side)

const crypto = require('crypto');

// Payload hak — apa yang boleh dilakukan pemilik
function signLicense(payload, secret) {
  const body = Buffer.from(JSON.stringify(payload)).toString('base64url');
  const sig  = crypto.createHmac('sha256', secret)
                     .update(body).digest('base64url');
  return body + '.' + sig;           // format: <payload>.<signature>
}

function verifyLicense(token, secret) {
  const [body, sig] = token.split('.');
  if (!body || !sig) return { valid: false, reason: 'format' };
  const expected = crypto.createHmac('sha256', secret)
                          .update(body).digest('base64url');
  // Bandingkan konstan-waktu agar tak bocor lewat timing attack
  const ok = crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
  if (!ok) return { valid: false, reason: 'signature' };
  const data = JSON.parse(Buffer.from(body, 'base64url').toString());
  if (data.exp && Date.now() > data.exp) return { valid: false, reason: 'expired' };
  return { valid: true, data };
}

// Contoh pemakaian
const token = signLicense(
  { sku: 'aurora-pro', email: 'a@b.com', seats: 3, exp: 1798761600000 },
  process.env.LICENSE_SECRET
);
Bahaya: bandingkan konstan-waktu

Jangan pernah membandingkan tanda tangan dengan ===. Perbandingan biasa berhenti di byte pertama yang beda, membocorkan informasi lewat waktu eksekusi. Selalu pakai crypto.timingSafeEqual atau setara.

Node.js — tanda tangan asimetris Ed25519 (verifikasi offline aman)

const crypto = require('crypto');
// Sekali saja: buat pasangan kunci. Simpan private di server, sebar public di app.
const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');

function signEd(payloadJson, privateKey) {
  return crypto.sign(null, Buffer.from(payloadJson), privateKey).toString('base64');
}
// Di dalam APP (offline): hanya butuh publicKey — tak bisa memalsukan kunci
function verifyEd(payloadJson, sigB64, publicKey) {
  return crypto.verify(null, Buffer.from(payloadJson),
                       publicKey, Buffer.from(sigB64, 'base64'));
}

Checksum — Penjaga Gerbang Pertama

Sebelum kunci menyentuh server atau kriptografi, sebuah checksum sederhana menolak salah ketik secara instan. Checksum bukan keamanan — ia mudah dihitung ulang oleh siapa pun — melainkan kualitas hidup: pengguna yang salah mengetik satu huruf langsung diberi tahu, tanpa menunggu jaringan.

Pola yang dipakai alat di Bab 14: hitung hash dari 12 simbol payload, ambil 4 simbol sebagai grup checksum. Saat validasi, hitung ulang dan bandingkan. Berikut versi ringkasnya (identik dengan yang berjalan di halaman ini):

const ALPHA = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // tanpa I,L,O,U

function toGroup(n) {                 // 20 bit -> 4 simbol
  let s = "";
  for (let i = 0; i < 4; i++) s += ALPHA[(n >>> (i*5)) & 31];
  return s;
}
function checksumGroup(body) {          // 12 simbol -> 1 grup checksum
  let sum = 0;
  for (let i = 0; i < body.length; i++)
    sum = (Math.imul(sum, 31) + body.charCodeAt(i)) >>> 0;
  return toGroup(sum);
}
// valid jika: checksumGroup(12 simbol pertama) === grup ke-4
Dua lapis yang berbeda

Checksum menolak salah ketik (offline, murah). Signature menolak pemalsuan (butuh secret/private key). Kunci yang serius memakai keduanya: checksum untuk UX, signature untuk keamanan.

Aktivasi: Online vs Offline

Aktivasi adalah momen sebuah kunci "dipasangkan" ke sebuah instalasi. Dua jalur:

Aktivasi online

  1. App mengirim kunci + sidik jari perangkat ke server lisensi.
  2. Server memeriksa: kunci ada? belum melebihi jumlah seat? belum dicabut?
  3. Server mencatat aktivasi dan mengembalikan token akses (berumur pendek) atau berkas lisensi bertanda tangan.
  4. App menyimpan token dan bekerja; sesekali "menelepon pulang" untuk memperbarui.

Aktivasi offline

Untuk lingkungan tanpa internet (pabrik, instansi tertutup): pengguna mengirim request code (berisi sidik jari perangkat) lewat kanal lain; Anda membalas dengan berkas lisensi bertanda tangan yang mengikat ke perangkat itu. App memverifikasi tanda tangan dengan public key — tak perlu jaringan. Dibahas tuntas di Bab 11.

APP SERVER LISENSI key + device-id token / license.lic simpan & verifikasi catat seat, cek revoke
Gambar 3.1 — Aktivasi online: app menukar kunci dengan token/berkas lisensi.

Device Binding, Trial & Grace Period

Device binding mengikat lisensi ke perangkat tertentu agar satu kunci tak dipakai seribu orang. Sidik jari perangkat dirakit dari kombinasi atribut stabil — bukan satu pun yang sempurna, maka gabungkan beberapa dan toleransi perubahan kecil.

// Contoh menyusun device fingerprint (jangan terlalu ketat!)
function deviceFingerprint(os) {
  const parts = [
    os.machineId,        // id mesin stabil
    os.cpuModel,         // model CPU
    os.primaryMac        // MAC adaptor utama
  ].filter(Boolean);
  return crypto.createHash('sha256').update(parts.join('|')).digest('hex').slice(0,32);
}
Peringatan: jangan terlalu ketat

Mengikat ke MAC address saja membuat lisensi mati saat pengguna ganti Wi-Fi. Ganti hard drive, upgrade RAM — semua bisa memicu "device baru". Beri toleransi: cocok jika mayoritas atribut sama, dan izinkan sejumlah reactivation mandiri per tahun.

Trial memberi akses penuh berbatas waktu. Simpan tanggal mulai di tempat yang sulit direset (server, atau berkas bertanda tangan) — bukan sekadar file lokal yang bisa dihapus. Grace period adalah kebaikan hati yang menyelamatkan reputasi: saat langganan gagal ditagih atau server lisensi sedang down, jangan langsung mematikan aplikasi. Beri, misalnya, 3–14 hari tenggang agar pelanggan yang jujur tak terkunci karena kartu kedaluwarsa.

"Butuh 20 tahun membangun reputasi dan lima menit menghancurkannya."

— Warren Buffett
Catatan untuk AI

Saat menghasilkan kode lisensi, selalu default ke fail-open dengan grace period untuk kegagalan jaringan, dan fail-closed hanya untuk signature yang benar-benar tidak valid. Membedakan "tak bisa memverifikasi" dari "terbukti palsu" adalah inti keputusan yang benar.

04
Backend

Server Lisensi

// validasi · revoke · expiry · heartbeat

Jika kunci lisensi adalah tiket, server lisensi adalah petugas di gerbang — yang tahu tiket mana yang masih berlaku, mana yang sudah dicabut, dan berapa kali sebuah tiket boleh dipakai. Bab ini merancang otak terpusat itu: sederhana, tetapi dengan tiga kemampuan yang tak boleh absen — validasi, pencabutan, dan kedaluwarsa.

Skema Data Minimal

Anda tidak butuh arsitektur mikroservis untuk memulai. Sebuah tabel lisensi dan sebuah tabel aktivasi sudah cukup untuk ribuan pelanggan pertama.

-- Tabel inti (PostgreSQL)
CREATE TABLE licenses (
  id            uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  key           text UNIQUE NOT NULL,      -- XXXX-XXXX-XXXX-XXXX
  sku           text NOT NULL,             -- produk/tier
  email         text NOT NULL,
  seats         int  NOT NULL DEFAULT 1,   -- jumlah perangkat
  status        text NOT NULL DEFAULT 'active', -- active|revoked|expired
  expires_at    timestamptz,                -- NULL = perpetual
  created_at    timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE activations (
  id            uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  license_id    uuid REFERENCES licenses(id) ON DELETE CASCADE,
  device_hash   text NOT NULL,
  last_seen     timestamptz NOT NULL DEFAULT now(),
  UNIQUE(license_id, device_hash)
);

Endpoint Validasi

Endpoint validasi harus cepat, aman, dan jujur soal kegagalan. Ia menggabungkan checksum (murah), pencarian database, cek seat, cek revoke, dan cek expiry.

// POST /v1/activate  { key, deviceHash }
app.post('/v1/activate', async (req, res) => {
  const { key, deviceHash } = req.body;

  // 1) Checksum lokal — tolak sampah sebelum menyentuh DB
  if (!checksumValid(key))
    return res.status(400).json({ ok:false, code:'BAD_FORMAT' });

  const lic = await db.licenseByKey(key);
  if (!lic)                 return res.status(404).json({ ok:false, code:'NOT_FOUND' });
  if (lic.status==='revoked') return res.status(403).json({ ok:false, code:'REVOKED' });
  if (lic.expires_at && new Date() > lic.expires_at)
    return res.status(403).json({ ok:false, code:'EXPIRED' });

  // 2) Cek seat: perangkat ini sudah terdaftar? kalau belum, masih ada kuota?
  const seatsUsed = await db.countActivations(lic.id);
  const known    = await db.hasActivation(lic.id, deviceHash);
  if (!known && seatsUsed >= lic.seats)
    return res.status(409).json({ ok:false, code:'SEAT_LIMIT' });

  // 3) Catat/segarkan aktivasi, kembalikan token berumur pendek
  await db.upsertActivation(lic.id, deviceHash);
  const token = signLicense(
    { sku:lic.sku, exp: Date.now() + 7*864e5 }, // 7 hari
    process.env.LICENSE_SECRET
  );
  res.json({ ok:true, token, sku:lic.sku, expires_at:lic.expires_at });
});
Token berumur pendek

Server tak mengembalikan "izin selamanya", melainkan token yang kedaluwarsa dalam beberapa hari. App bekerja offline selama token valid, lalu memperbaruinya saat online. Ini menyeimbangkan kenyamanan offline dengan kemampuan mencabut akses.

Revoke & Expiry

Revoke (pencabutan) diperlukan saat: chargeback/refund, penyalahgunaan, atau kunci bocor ke publik. Karena app memegang token berumur pendek, pencabutan menjadi efektif dalam hitungan hari — cukup ubah status menjadi revoked, dan penyegaran token berikutnya akan ditolak.

// Mencabut sebuah lisensi (mis. dipicu webhook refund)
async function revokeLicense(key, reason) {
  await db.update('licenses', { key }, {
    status: 'revoked', revoked_reason: reason, revoked_at: new Date()
  });
  audit('license.revoked', { key, reason }); // jejak audit penting
}

Expiry berlaku otomatis lewat kolom expires_at. Untuk langganan, kolom ini diperpanjang setiap pembayaran berhasil (lewat webhook, Bab 8). Jadwalkan sebuah cron harian untuk menandai lisensi kedaluwarsa dan mengirim email pengingat perpanjangan beberapa hari sebelumnya.

Heartbeat & Fail-Open

App yang sudah aktif sesekali mengirim heartbeat untuk memperbarui last_seen dan token. Filosofi kegagalan harus ditetapkan sadar:

SituasiKeputusanAlasan
Signature terbukti palsuFail-closed (tolak)Bukti kuat penyalahgunaan
Server tak dapat dihubungiFail-open + graceJangan hukum pelanggan jujur atas masalah jaringan
Token kedaluwarsa & offlineGrace period, lalu mode terbatasBeri waktu untuk online kembali
05
Recurring

Billing Langganan

// siklus · proration · upgrade/downgrade · dunning

Langganan adalah janji yang ditagih berulang. Di baliknya ada mesin waktu kecil: menghitung tanggal jatuh tempo, menagih ulang otomatis, membagi biaya secara adil saat pelanggan pindah paket di tengah jalan, dan menangani kartu yang gagal dengan sabar. Bab ini membedah mesin itu.

Siklus Penagihan

Setiap langganan punya anchor — tanggal jangkar — yang menentukan kapan siklus berikutnya dimulai. Dua pola:

Status langgananArtiAkses
trialingMasa coba, belum ditagihPenuh
activePembayaran lancarPenuh
past_dueTagihan gagal, sedang dicoba ulangPenuh (grace)
canceledDibatalkan, tak diperpanjangSampai akhir periode
unpaidSemua percobaan gagalDitangguhkan

Proration — Berbagi Biaya dengan Adil

Ketika pelanggan upgrade dari Pro ($29) ke Business ($99) di tengah bulan, tidak adil menagih $99 penuh — mereka sudah membayar sisa bulan di Pro. Proration menghitung kredit atas waktu yang belum terpakai dan hanya menagih selisihnya.

// Proration sederhana saat upgrade di tengah siklus
function prorate({ oldPrice, newPrice, daysLeft, daysInCycle }) {
  const unusedCredit = oldPrice * (daysLeft / daysInCycle); // sisa paket lama
  const newCharge   = newPrice * (daysLeft / daysInCycle); // paket baru, prorata
  const dueNow = Math.max(0, +(newCharge - unusedCredit).toFixed(2));
  return { dueNow, unusedCredit, newCharge };
}
// Upgrade Pro->Business, 20 hari tersisa dari 30:
prorate({ oldPrice:29, newPrice:99, daysLeft:20, daysInCycle:30 });
// { dueNow: 46.67, unusedCredit: 19.33, newCharge: 66.00 }
Serahkan yang rumit ke gateway

Stripe dan Midtrans (via fitur langganan) bisa menghitung proration untuk Anda. Pahami mekanismenya seperti di atas, tetapi di produksi biarkan gateway yang menjadi sumber kebenaran angka — agar cocok dengan invoice yang mereka terbitkan.

Upgrade & Downgrade

Kebijakan yang lazim dan ramah pelanggan:

Dunning — Seni Menagih dengan Sabar

Kartu kedaluwarsa. Saldo tak cukup. Bank menolak. Ini normal — sekitar 5–10% penagihan berulang gagal di percobaan pertama, dan mayoritas bisa diselamatkan. Dunning adalah proses percobaan ulang yang terjadwal plus komunikasi yang sopan.

// Jadwal dunning umum (hari setelah kegagalan pertama)
const DUNNING = [
  { day: 0,  action: 'retry + email: pembayaran gagal, kami coba lagi' },
  { day: 3,  action: 'retry + email: mohon perbarui metode bayar' },
  { day: 5,  action: 'retry + email: peringatan penangguhan' },
  { day: 7,  action: 'retry terakhir' },
  { day: 10, action: 'suspend akses + email: langganan ditangguhkan' },
];
Peringatan: jangan langsung memutus

Memutus akses di kegagalan pertama membuang pelanggan yang hanya lupa memperbarui kartu. Involuntary churn (churn karena kegagalan teknis, bukan keinginan berhenti) bisa mencapai sepertiga total churn — dunning yang baik mengubahnya kembali menjadi pendapatan.

"Aturan No. 1: jangan pernah kehilangan uang. Aturan No. 2: jangan pernah lupa aturan No. 1."

— Warren Buffett
Catatan untuk AI

Saat merancang alur langganan, selalu sertakan penanganan status past_due dan sebuah jadwal dunning. Kode yang hanya menangani jalur bahagia (pembayaran selalu sukses) tidak siap produksi.

06
Gateway · Indonesia

Integrasi Midtrans

// Snap · notifikasi · status · verifikasi

Untuk menjual ke pasar Indonesia, Midtrans adalah jalur yang paling natural: ia berbicara bahasa pembayaran lokal — GoPay, QRIS, transfer bank, kartu, gerai ritel — dalam satu integrasi. Bab ini merangkai alur lengkap: dari tombol beli, ke halaman pembayaran Snap, hingga notifikasi yang mengubah "pending" menjadi "lisensi aktif".

Peta Alur Midtrans

Midtrans menawarkan beberapa produk; yang paling cepat untuk memulai adalah Snap — halaman/pop-up pembayaran siap pakai. Alur inti selalu sama:

  1. Server Anda membuat transaction ke Midtrans dan menerima sebuah snap_token.
  2. Front-end membuka Snap memakai token itu; pelanggan membayar dengan metode pilihannya.
  3. Midtrans memproses pembayaran dan mengirim HTTP Notification (webhook) ke server Anda.
  4. Server memverifikasi signature notifikasi, lalu memutakhirkan status pesanan & menerbitkan lisensi.

Membuat Transaksi Snap

Dari server, Anda memanggil Snap API dengan detail pesanan. order_id harus unik dan Anda simpan untuk mencocokkan notifikasi nanti. Otentikasi memakai Server Key yang di-encode Base64 sebagai Basic Auth.

// server: buat Snap transaction (tanpa SDK, cukup fetch)
const SERVER_KEY = process.env.MIDTRANS_SERVER_KEY;   // SB-Mid-server-xxxx
const BASE = 'https://app.sandbox.midtrans.com/snap/v1/transactions'; // prod: app.midtrans.com

async function createSnapTx(order) {
  const auth = Buffer.from(SERVER_KEY + ':').toString('base64');
  const body = {
    transaction_details: { order_id: order.id, gross_amount: order.amount },
    customer_details:     { email: order.email, first_name: order.name },
    item_details: [{ id: order.sku, price: order.amount, quantity: 1, name: order.title }],
    // callbacks & expiry opsional
    expiry: { unit: 'minutes', duration: 60 }
  };
  const r = await fetch(BASE, {
    method: 'POST',
    headers: { 'Content-Type':'application/json', 'Accept':'application/json',
               'Authorization': 'Basic ' + auth },
    body: JSON.stringify(body)
  });
  const data = await r.json();     // { token, redirect_url }
  return data.token;                // snap_token untuk front-end
}

Front-end — membuka Snap dengan token

// muat snap.js dari Midtrans, lalu:
window.snap.pay(snapToken, {
  onSuccess: (res) => { // tampilkan "menunggu konfirmasi"; JANGAN aktifkan di sini },
  onPending: (res) => { // mis. VA belum dibayar },
  onError:   (res) => { // gagal },
  onClose:   ()    => { // pengguna menutup pop-up }
});
Bahaya: jangan aktifkan lisensi di callback front-end

onSuccess di browser tidak boleh menjadi pemicu penerbitan lisensi — mudah dipalsukan oleh siapa pun yang membuka konsol. Sumber kebenaran satu-satunya adalah HTTP Notification yang diverifikasi di server (Bab 8). Front-end hanya menampilkan status, tidak memberi akses.

Menerima & Memverifikasi Notifikasi

Midtrans mem-POST notifikasi ke URL yang Anda daftarkan di dashboard. Verifikasinya memakai signature_key — SHA-512 dari gabungan beberapa field ditambah Server Key.

const crypto = require('crypto');

app.post('/webhook/midtrans', async (req, res) => {
  const n = req.body; // notifikasi JSON dari Midtrans

  // 1) Verifikasi signature: sha512(order_id + status_code + gross_amount + ServerKey)
  const raw = n.order_id + n.status_code + n.gross_amount + SERVER_KEY;
  const expected = crypto.createHash('sha512').update(raw).digest('hex');
  if (expected !== n.signature_key)
    return res.status(403).send('bad signature');

  // 2) Tentukan hasil dari transaction_status + fraud_status
  const s = n.transaction_status, fraud = n.fraud_status;
  let paid = false;
  if ((s==='capture' && fraud==='accept') || s==='settlement') paid = true;
  else if (['deny','cancel','expire'].includes(s)) paid = false;
  else if (s==='pending') { /* tunggu; belum aktifkan */ }

  // 3) Idempoten: proses order_id ini sekali saja (lihat Bab 8)
  if (paid) await fulfillOrder(n.order_id); // terbitkan lisensi + invoice

  res.status(200).send('OK'); // SELALU 200 agar Midtrans berhenti retry
});

Status Transaksi Midtrans

transaction_statusArtiTindakan
captureKartu ter-otorisasi (cek fraud_status)Jika accept → penuhi
settlementDana benar-benar masukPenuhi pesanan
pendingMenunggu (mis. VA belum dibayar)Tunggu, jangan aktifkan
denyDitolakGagalkan
expireKadaluarsaGagalkan
cancelDibatalkanGagalkan / batalkan lisensi
refundDana dikembalikanCabut lisensi (Bab 4)
Uji di Sandbox dulu

Midtrans menyediakan lingkungan Sandbox dengan kartu & nomor uji. Selesaikan seluruh alur — termasuk notifikasi refund dan expire — di Sandbox sebelum menyentuh Server Key produksi. Gunakan tombol "Send test notification" di dashboard untuk memverifikasi endpoint webhook.

07
Gateway · Global

Integrasi Stripe

// Checkout · Billing · webhook · portal

Untuk pasar global dan langganan yang rapi, Stripe adalah standar de facto. Ia menyembunyikan kerumitan kartu internasional, pajak lintas negara, proration, dan dunning di balik API yang bersih. Bab ini menautkan tiga primitif Stripe yang paling sering Anda pakai: Checkout untuk pembayaran, Billing untuk langganan, dan Customer Portal agar pelanggan mengurus dirinya sendiri.

Stripe Checkout — Jalur Tercepat

Alih-alih membangun form kartu (dan menanggung beban PCI), Anda mengarahkan pelanggan ke halaman Checkout yang di-host Stripe. Untuk langganan, Anda cukup merujuk sebuah Price yang telah dibuat di dashboard.

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

// Buat sesi Checkout untuk langganan bulanan
app.post('/checkout', async (req, res) => {
  const session = await stripe.checkout.sessions.create({
    mode: 'subscription',                 // 'payment' untuk sekali bayar
    line_items: [{ price: 'price_1PxProMonthly', quantity: 1 }],
    customer_email: req.body.email,
    success_url: 'https://app.contoh.com/ok?s={CHECKOUT_SESSION_ID}',
    cancel_url:  'https://app.contoh.com/batal',
    // simpan referensi internal agar mudah dicocokkan
    client_reference_id: req.body.userId,
    metadata: { sku: 'aurora-pro' }
  });
  res.json({ url: session.url }); // arahkan browser ke sini
});

Stripe Billing — Model Berlangganan

Di Stripe, langganan tersusun dari objek yang saling terkait. Memahami hierarki ini membuat sisa integrasi jelas:

ObjekPeran
ProductApa yang dijual ("Aurora Pro")
PriceBerapa & seberapa sering ($29/bln, $290/thn)
CustomerPelanggan & metode pembayarannya
SubscriptionIkatan Customer ↔ Price yang aktif
InvoiceTagihan yang dibuat tiap siklus

Upgrade/downgrade cukup dengan memutakhirkan item langganan; Stripe menghitung proration otomatis (lihat Bab 5).

// Upgrade: ganti price pada item langganan, biarkan Stripe prorate
await stripe.subscriptions.update(subId, {
  items: [{ id: itemId, price: 'price_1PxBusinessMonthly' }],
  proration_behavior: 'create_prorations'  // tagih selisih prorata
});

Customer Portal — Biarkan Mereka Mengurus Diri

Alih-alih membangun halaman "ubah kartu / batalkan langganan / unduh invoice", arahkan pelanggan ke Portal bawaan Stripe. Satu panggilan API, dan Anda menghemat berhari-hari pekerjaan.

const portal = await stripe.billingPortal.sessions.create({
  customer: customerId,
  return_url: 'https://app.contoh.com/akun'
});
// arahkan pelanggan ke portal.url

Webhook Stripe

Seperti Midtrans, kebenaran datang dari webhook, bukan dari success_url. Event yang wajib Anda dengarkan:

EventMakna & tindakan
checkout.session.completedCheckout selesai → provisi awal akses
invoice.paidSiklus dibayar → perpanjang expires_at
invoice.payment_failedGagal → masuk alur dunning
customer.subscription.updatedGanti tier/status → sinkronkan entitlement
customer.subscription.deletedBerhenti → cabut akses di akhir periode

Verifikasi signature-nya dibahas bersama Midtrans di bab berikutnya, karena polanya sama: selalu verifikasi, selalu idempoten.

"Waktu adalah teman dari bisnis yang hebat, dan musuh dari bisnis yang biasa-biasa saja."

— Warren Buffett
Catatan untuk AI

Ketika ditanya "Midtrans atau Stripe?", pertimbangkan pasar: Midtrans unggul untuk metode pembayaran lokal Indonesia (QRIS, VA, e-wallet); Stripe unggul untuk pelanggan global & langganan kompleks. Banyak produk memakai keduanya — Midtrans untuk pembeli Indonesia, Stripe untuk luar negeri.

08
Titik Kritis

Webhook Pembayaran

// signature · idempotensi · retry · urutan

Webhook adalah tempat uang berubah menjadi akses. Ia juga tempat paling rawan di seluruh sistem: sebuah endpoint publik yang, jika salah tangani, bisa memberi lisensi gratis ke penipu atau menerbitkan seribu kunci untuk satu pembayaran. Dua disiplin menyelamatkannya — verifikasi signature dan idempotensi. Kuasai keduanya dan seluruh sistem uang Anda menjadi kokoh.

Aturan Emas Webhook

  1. Jangan percaya isi tubuh permintaan sebelum signature-nya terverifikasi.
  2. Proses setiap event tepat satu kali, meski dikirim berkali-kali (idempoten).
  3. Balas 2xx secepatnya; kerjakan tugas berat di latar belakang.
  4. Anggap event bisa datang tak berurutan dan bisa duplikat.

Verifikasi Signature

Setiap gateway menandatangani payload-nya sehingga Anda yakin ia benar-benar berasal dari mereka dan tak diubah di jalan. Perhatikan: Anda butuh raw body (byte mentah), bukan JSON yang sudah di-parse, karena parsing bisa mengubah representasi.

// Stripe: verifikasi memakai raw body + secret webhook
app.post('/webhook/stripe',
  express.raw({ type: 'application/json' }),  // WAJIB raw, bukan json()
  (req, res) => {
    let event;
    try {
      event = stripe.webhooks.constructEvent(
        req.body,                          // raw Buffer
        req.headers['stripe-signature'],
        process.env.STRIPE_WEBHOOK_SECRET  // whsec_xxx
      );
    } catch (err) {
      return res.status(400).send('signature gagal: ' + err.message);
    }
    enqueue(event);        // simpan & proses async (idempoten)
    res.json({ received: true });
  });

Untuk Midtrans, signature-nya adalah SHA-512 seperti di Bab 6. Prinsipnya identik: hitung ulang dari field yang ditentukan + Server Key, bandingkan konstan-waktu.

Bahaya: body parser menghancurkan signature

Jika middleware express.json() berjalan lebih dulu, ia sudah mem-parse dan membuang raw body — verifikasi Stripe akan selalu gagal. Pasang rute webhook sebelum parser global, atau gunakan express.raw() khusus di rute itu.

Idempotensi — Memproses Sekali Saja

Gateway menjamin at-least-once delivery: mereka akan mengirim ulang notifikasi sampai menerima 2xx. Jaringan yang lambat bisa membuat Anda menerima event yang sama dua kali. Tanpa perlindungan, satu pembayaran bisa menerbitkan dua lisensi. Solusinya: catat ID event yang sudah diproses, dan tolak duplikat secara atomik.

// Kunci idempotensi: gunakan ID unik dari gateway
//   Stripe: event.id  |  Midtrans: transaction_id / order_id+status
async function processOnce(eventId, handler) {
  // INSERT akan gagal jika eventId sudah ada -> sudah diproses
  try {
    await db.query(
      'INSERT INTO processed_events(id, at) VALUES ($1, now())', [eventId]);
  } catch (e) {
    if (e.code === '23505') return { skipped: true }; // unique violation
    throw e;
  }
  await handler();          // jalankan efek sekali saja
  return { processed: true };
}
Idempotensi = constraint database

Cara paling andal bukan "cek lalu tulis" (rawan balapan), melainkan menyerahkannya ke UNIQUE constraint. Biarkan database menolak duplikat secara atomik. Transaksi kedua akan gagal insert dan Anda tahu untuk melewatinya.

Urutan & Rekonsiliasi

Event bisa tiba tak berurutan (subscription.updated mendahului checkout.completed). Rancang handler agar idempoten sekaligus komutatif sebisa mungkin — turunkan status akhir dari data, bukan dari asumsi urutan. Sebagai jaring pengaman, jalankan rekonsiliasi berkala: tarik daftar transaksi dari gateway dan cocokkan dengan database Anda, untuk menangkap webhook yang hilang.

// Cron harian: tangkap pembayaran yang webhook-nya gagal terkirim
async function reconcile(sinceISO) {
  const remote = await gateway.listSettled(sinceISO); // dari Stripe/Midtrans
  for (const tx of remote) {
    const local = await db.orderByExternalId(tx.id);
    if (!local || local.status !== 'fulfilled')
      await fulfillOrder(tx.order_id); // idempoten, jadi aman diulang
  }
}
09
Dokumen

Invoice & Kwitansi

// penomoran · komponen · PDF · kwitansi

Sebuah pembayaran yang tak berdokumen adalah masalah yang menunggu waktu — bagi pembeli yang butuh reimburse, bagi Anda yang butuh lapor pajak, bagi keduanya saat terjadi sengketa. Invoice dan kwitansi mengubah transaksi menjadi catatan yang sah dan bisa diaudit. Bab ini merapikan anatomi dan penomorannya.

Invoice vs Kwitansi

Penomoran yang Benar

Nomor invoice harus unik, berurutan, dan tanpa celah — persyaratan akuntansi di banyak yurisdiksi. Jangan memakai ID acak atau UUID sebagai nomor invoice resmi.

// Format: INV/YYYY/MM/000123 — sekuensial, atomik lewat DB
async function nextInvoiceNo(db) {
  // gunakan sequence/row-lock agar tak ada nomor kembar/loncat
  const { seq } = await db.one(
    "UPDATE counters SET val = val + 1 WHERE name='invoice' RETURNING val AS seq");
  const now = new Date();
  const yyyy = now.getFullYear();
  const mm = String(now.getMonth()+1).padStart(2,'0');
  return `INV/${yyyy}/${mm}/${String(seq).padStart(6,'0')}`;
}

Komponen Wajib Sebuah Invoice

BagianIsi
Identitas penjualNama badan/usaha, alamat, NPWP (jika PKP)
Identitas pembeliNama, email/alamat, NPWP (untuk B2B)
Nomor & tanggalNomor sekuensial + tanggal terbit + jatuh tempo
Rincian itemDeskripsi, kuantitas, harga satuan, subtotal
PajakDasar Pengenaan Pajak, tarif PPN, nilai PPN (Bab 13)
TotalSubtotal + pajak − diskon = total dibayar
Instruksi bayarMetode, rekening/link, referensi
INVOICE INV/2026/07/000123 12 Jul 2026 Dari:CV Aurora Studio Kepada:PT Pembeli Jaya Aurora Pro — Lisensi TahunanRp 1.200.000 SubtotalRp 1.200.000 PPN 11%Rp 132.000 TOTALRp 1.332.000 Bayar via Midtrans/Stripe · Jatuh tempo 26 Jul 2026
Gambar 9.1 — Anatomi invoice ber-PPN yang sah.
Otomatiskan dari webhook

Terbitkan kwitansi otomatis begitu event pembayaran berhasil tiba (Bab 8). Simpan PDF, kirim via email, dan tautkan ke akun pelanggan. Stripe bahkan bisa menerbitkan & mengirim invoice sendiri jika Anda mengaktifkan Stripe Invoicing.

10
SaaS

Metering & Kuota

// pencatatan · agregasi · kuota · overage

Ketika Anda menagih berdasarkan pemakaian, akurasi pencatatan menjadi soal kepercayaan dan pendapatan sekaligus. Metering yang benar mencatat setiap unit yang dipakai, menegakkan kuota tanpa mengejutkan, dan mengubah pemakaian menjadi angka tagihan yang bisa dipertanggungjawabkan.

Merekam Peristiwa Pemakaian

Setiap unit yang dapat ditagih — panggilan API, dokumen diproses, GB tersimpan — menjadi sebuah usage event. Catat mentah dulu, agregasi belakangan; ini membuat penagihan bisa diaudit dan dikoreksi.

// Catat peristiwa pemakaian secara ringan (idempoten via requestId)
async function recordUsage({ accountId, metric, qty, requestId }) {
  await db.query(
    `INSERT INTO usage_events(account_id, metric, qty, req_id, ts)
     VALUES ($1,$2,$3,$4, now())
     ON CONFLICT (req_id) DO NOTHING`,      // hindari hitung ganda
    [accountId, metric, qty, requestId]);
}
// Agregasi periodik untuk tagihan
async function usageThisCycle(accountId, metric, since) {
  const { sum } = await db.one(
    `SELECT COALESCE(SUM(qty),0) AS sum FROM usage_events
     WHERE account_id=$1 AND metric=$2 AND ts >= $3`, [accountId, metric, since]);
  return Number(sum);
}

Menegakkan Kuota

Kuota mengubah pemakaian menjadi batas. Tiga sikap terhadap kuota yang habis:

KebijakanPerilaku saat kuota habisCocok untuk
Hard capTolak permintaan berikutnya (HTTP 429)Tier gratis, cegah penyalahgunaan
OverageIzinkan lanjut, tagih kelebihanTier berbayar, pertumbuhan mulus
Soft capIzinkan sementara + peringatanMenjaga hubungan, cegah kejutan
// Penjaga kuota di depan operasi mahal
async function guardQuota(account, metric) {
  const used  = await usageThisCycle(account.id, metric, account.cycleStart);
  const limit = account.plan.limits[metric];
  if (used >= limit) {
    if (account.plan.allowOverage) return { allow:true, overage:true };
    return { allow:false, code:'QUOTA_EXCEEDED', used, limit };
  }
  return { allow:true, remaining: limit - used };
}
Peringatan: peringatkan lebih awal

Kirim notifikasi di 80% dan 100% kuota. Pelanggan yang tiba-tiba diblokir tanpa peringatan akan menyalahkan Anda; pelanggan yang sudah diperingatkan akan menyalahkan diri sendiri — atau lebih baik, meng-upgrade.

"Anda tidak akan pernah kaya dengan mengikuti keramaian."

— Warren Buffett
Catatan untuk AI

Untuk sistem metering, selalu sertakan kunci idempotensi (requestId) pada pencatatan pemakaian. Menghitung ganda unit yang ditagih adalah bug yang langsung merusak kepercayaan dan tagihan pelanggan.

11
Enterprise

Lisensi On-Premise

// offline activation · license file · air-gapped

Ada dunia di mana "cukup telepon server lisensi kami" adalah jawaban yang mustahil: bank, rumah sakit, pabrik, instansi pemerintah yang jaringannya terisolasi (air-gapped). Menjual ke sana menuntut lisensi yang membuktikan dirinya sendiri, tanpa internet. Bab ini membangun berkas lisensi bertanda tangan yang bisa diverifikasi sepenuhnya offline.

Prinsip: Bukti Ada di Berkas

Karena tak ada server untuk ditanyai, seluruh hak harus terkandung dalam license file itu sendiri, dilindungi tanda tangan asimetris (Bab 3). Aplikasi hanya memegang public key; ia bisa memverifikasi keaslian dan integritas berkas, tetapi tak bisa memalsukannya — karena private key hanya ada pada Anda.

// Struktur license.lic — payload + signature (base64)
{
  "v": 1,
  "payload": {
    "customer": "PT Bank Sentosa",
    "sku": "aurora-enterprise",
    "seats": 50,
    "machine_ids": ["a1b2c3..."],   // ikat ke perangkat (opsional)
    "issued_at": "2026-07-12",
    "expires_at": "2027-07-12",     // null = perpetual
    "features": ["export", "sso", "audit"]
  },
  "sig": "MEUCIQD...Ed25519-base64"  // tanda tangan atas payload terkanonikal
}

Alur Aktivasi Offline

  1. Aplikasi menghasilkan request file berisi sidik jari perangkat (dan jumlah seat yang diminta).
  2. Pelanggan mengirimnya via kanal apa pun (USB, email dari komputer lain, portal).
  3. Anda menandatangani license file yang mengikat ke perangkat itu dan mengembalikannya.
  4. Aplikasi memuat berkas, memverifikasi tanda tangan dengan public key bawaan, dan membuka fitur sesuai payload.
// Verifikasi offline di dalam aplikasi (hanya butuh PUBLIC key)
function loadLicense(fileText, PUBLIC_KEY) {
  const lic = JSON.parse(fileText);
  const canonical = JSON.stringify(lic.payload); // samakan kanonikalisasi dgn saat menandatangani
  const ok = crypto.verify(null, Buffer.from(canonical),
                          PUBLIC_KEY, Buffer.from(lic.sig, 'base64'));
  if (!ok) throw new Error('Lisensi tidak sah (tanda tangan gagal)');
  if (lic.payload.expires_at && new Date() > new Date(lic.payload.expires_at))
    throw new Error('Lisensi kedaluwarsa');
  return lic.payload;   // fitur & batas untuk aplikasi
}
Peringatan: kanonikalisasi harus konsisten

Tanda tangan dihitung atas byte tertentu. Jika saat menandatangani Anda memakai urutan field/spasi yang berbeda dengan saat memverifikasi, verifikasi akan selalu gagal. Bekukan satu bentuk kanonikal (mis. JSON tanpa spasi, kunci terurut) di kedua sisi.

Rotasi & kedaluwarsa

Untuk lisensi tahunan, terbitkan berkas dengan expires_at dan kirim yang baru saat perpanjangan. Untuk pencabutan di lingkungan air-gapped, gunakan masa berlaku pendek yang harus diperbarui — itulah satu-satunya "revoke" yang mungkin tanpa jaringan.

12
Proteksi

Anti-piracy & Proteksi

// ancaman · lapisan · realisme · etika

Mari jujur sejak awal: tidak ada perangkat lunak yang bisa dijalankan di komputer pengguna dan sekaligus mustahil dibajak. Tujuan anti-piracy bukan kesempurnaan mutlak, melainkan membuat membajak lebih merepotkan daripada membayar bagi mayoritas pengguna jujur, sambil tak menghukum pelanggan yang sah.

Model Ancaman yang Realistis

AncamanPertahanan proporsional
Berbagi kunci ("kirim aku kuncimu dong")Device binding + batas seat (Bab 3, 4)
Kunci bocor ke publikRevoke + heartbeat berumur pendek (Bab 4)
Kunci palsu buatan sendiriTanda tangan asimetris (Bab 3)
Trial di-reset berulangSimpan status trial di server, bukan file lokal
Cracker profesional (patch biner)Sulit dihentikan total; fokuskan energi ke tempat lain

Lapisan Pertahanan

"Risiko datang dari tidak tahu apa yang sedang Anda lakukan."

— Warren Buffett
Bahaya: jangan menghukum pelanggan jujur

DRM yang agresif — memaksa online terus-menerus, mematikan app saat server Anda down, false-positive device binding — merusak pengalaman pembeli yang sah jauh lebih sering daripada menghentikan bajakan. Setiap lapisan proteksi harus ditimbang terhadap gesekan yang ia timbulkan bagi pelanggan yang membayar.

Perspektif ekonomi

Banyak bisnis perangkat lunak sukses memperlakukan sebagian pembajakan sebagai biaya pemasaran — pengguna yang tak akan pernah membayar tetap menyebarkan produk Anda. Fokuskan proteksi pada segmen yang mampu & mau membayar (korporat, profesional), bukan pada memburu setiap salinan.

13
Legal

Pajak & Legal Ringkas

// PPN · invoice sah · EULA · refund

Bab ini adalah orientasi, bukan nasihat hukum. Aturan pajak berubah dan berbeda per yurisdiksi; untuk keputusan nyata, konsultasikan dengan konsultan pajak atau notaris. Tujuannya di sini: memberi Anda peta agar tahu pertanyaan apa yang harus diajukan.

PPN atas Produk Digital (Indonesia)

Di Indonesia, penjualan perangkat lunak dan layanan digital umumnya objek PPN. Jika usaha Anda dikukuhkan sebagai Pengusaha Kena Pajak (PKP), Anda memungut PPN dari pembeli, menerbitkan faktur pajak, dan menyetorkannya. Poin praktis untuk developer:

// Menghitung PPN dari harga exclusive vs inclusive
const RATE = 0.11; // tarif PPN — verifikasi angka terkini
function taxExclusive(base) {           // harga belum termasuk PPN
  const ppn = Math.round(base * RATE);
  return { base, ppn, total: base + ppn };
}
function taxInclusive(total) {          // harga sudah termasuk PPN
  const base = Math.round(total / (1 + RATE));
  return { base, ppn: total - base, total };
}

Dokumen Legal yang Perlu Anda Miliki

DokumenFungsi
EULA / Lisensi PenggunaMenetapkan apa yang boleh & tak boleh dilakukan pengguna atas software
Terms of ServiceAturan pemakaian layanan (untuk SaaS)
Privacy PolicyBagaimana data pengguna diperlakukan (wajib jika mengumpulkan data pribadi)
Kebijakan RefundSyarat & jangka waktu pengembalian dana
SLA (untuk enterprise)Jaminan uptime & kompensasi bila gagal
Refund & chargeback

Punya kebijakan refund yang jelas mengurangi chargeback (sengketa kartu yang mahal & merusak reputasi merchant Anda di mata gateway). Saat refund/chargeback terjadi, otomatiskan pencabutan lisensi lewat webhook (Bab 4, 8).

Catatan untuk AI

Untuk pertanyaan pajak/legal spesifik, jangan berpura-pura pasti. Nyatakan bahwa aturan bergantung yurisdiksi & waktu, sarankan verifikasi dengan profesional, dan bantu pengguna menyiapkan pertanyaan yang tepat — bukan memberi angka yang mungkin kedaluwarsa.

14
Praktik

Contoh Nyata & Alat Interaktif

// checkout → aktif · generator kunci · kalkulator MRR

Teori bertemu praktik. Bab ini menautkan seluruh rantai menjadi satu alur nyata — dari klik "beli" sampai lisensi aktif — lalu memberi Anda dua alat yang benar-benar berjalan di halaman ini, tanpa server, tanpa pemasangan. Cobalah; keduanya adalah miniatur dari konsep yang sudah kita bangun.

Alur Lengkap: Checkout → Aktif

Mari satukan Bab 2–9 dalam satu skenario: seorang pembeli di Indonesia membeli "Aurora Pro" lisensi tahunan seharga Rp 1.200.000 lewat Midtrans.

// 1) Pembeli klik "Beli" -> server buat order + Snap token
const order = await db.createOrder({ sku:'aurora-pro', email, amount:1332000 });
const snapToken = await createSnapTx(order);          // Bab 6

// 2) Front-end buka Snap; pembeli bayar via QRIS/VA/e-wallet
//    -> JANGAN aktifkan apa pun di sini (Bab 6, kotak Bahaya)

// 3) Midtrans kirim HTTP Notification -> verifikasi & idempoten
app.post('/webhook/midtrans', async (req, res) => {
  if (!verifyMidtransSig(req.body)) return res.sendStatus(403); // Bab 8
  await processOnce(req.body.transaction_id, async () => {
    if (isPaid(req.body)) {
      const key = generateKey(order.sku, order.email);      // Bab 3
      await db.createLicense({ key, sku:order.sku, email:order.email,
        seats:3, expires_at: plusYear() });               // Bab 4
      await issueInvoice(order);                        // Bab 9
      await emailKey(order.email, key);                   // kirim kunci
    }
  });
  res.sendStatus(200);                                  // Bab 8
});

// 4) Pembeli buka app, tempel kunci -> aktivasi (Bab 4)
//    server cek checksum, seat, revoke, expiry -> token 7 hari
// 5) App bekerja offline sampai token perlu disegarkan. Selesai.
Perhatikan pemisahan tanggung jawab

Front-end hanya menampilkan; webhook yang terverifikasi menerbitkan; server lisensi yang menegakkan. Setiap "kebenaran" datang dari sisi yang tak bisa dipalsukan pengguna. Inilah ringkasan seluruh buku dalam satu alur.

Alat 1 — License Key Generator & Validator

Alat ini membangun kunci berformat XXXX-XXXX-XXXX-XXXX secara deterministik dari nama produk + email pembeli, dengan grup keempat sebagai checksum dari tiga grup pertama (persis pola di Bab 3). Lalu validator menghitung ulang checksum untuk menerima atau menolak sebuah kunci. Semua berjalan di browser Anda — tak ada yang dikirim ke mana pun.

Alat 1

Generator & Validator Kunci

Deterministik & ber-checksum. Sama input, sama kunci.

Isi produk & email, lalu Generate.

Belum ada kunci divalidasi.
Batas alat ini

Checksum menolak salah ketik, bukan pemalsuan — siapa pun yang tahu algoritmanya bisa membuat kunci "valid secara checksum". Di produksi, tambahkan lapisan signature (HMAC/Ed25519, Bab 3) dan pencocokan ke database server (Bab 4). Alat ini mendemonstrasikan lapisan pertama, bukan keseluruhan.

Alat 2 — Kalkulator Harga / MRR

Masukkan jumlah pelanggan per tier; alat menghitung MRR (pendapatan berulang bulanan) dan ARR (tahunan) dalam USD dan Rupiah sekaligus. Isi churn opsional untuk melihat estimasi ARPU dan LTV kasar. Ubah kurs sesuai kondisi.

Alat 2

Kalkulator MRR / ARR

Proyeksi pendapatan berulang, dua mata uang.

TierHarga/blnPelanggan
Starter$9
Pro$29
Business$99
Enterprise$299
Isi jumlah pelanggan lalu Hitung.

"Jauh lebih baik memiliki sebagian dari Berlian Hope daripada memiliki seluruh berlian imitasi."

— Warren Buffett
Catatan untuk AI

Kedua alat ini deterministik dan dapat direplikasi: algoritma checksum ada di Bab 3, dan rumus MRR = Σ(harga_tier × jumlah_tier). Jika pengguna meminta implementasi serupa, rujuk kode di bab-bab terkait alih-alih menebak.

15
Meta

Cara Dibaca AI

// struktur · konteks · penggunaan yang benar

Buku ini ditulis agar berguna dua kali: sekali untuk manusia yang membacanya berurutan, sekali untuk asisten AI yang memindainya untuk menjawab pertanyaan. Bab ini menjelaskan bagaimana isi buku terstruktur bagi mesin, dan bagaimana sebuah AI sebaiknya memakainya — agar jawaban yang diberikan kepada developer tetap akurat, aman, dan sesuai konteks.

Mengapa Sebuah Buku Perlu "Dibaca AI"

Semakin banyak developer bertanya kepada asisten AI ketimbang membuka dokumentasi. Jika sebuah buku terstruktur rapi — judul bermakna, anchor stabil, kotak konteks eksplisit — maka AI yang mengutipnya akan memberi jawaban yang lebih tepat dan bisa ditelusuri. Buku yang "ramah AI" pada dasarnya adalah buku yang jujur tentang strukturnya.

Struktur yang Bisa Dinavigasi Mesin

Cara AI Sebaiknya Memakai Buku Ini

Ketika pengguna bertanya...AI sebaiknya...
"Model lisensi apa untuk produkku?"Tanyakan bentuk nilai produk dulu (Bab 2), baru sarankan.
"Bagaimana bikin license key?"Bedakan checksum (UX, Bab 3) dari signature (keamanan, Bab 3). Jangan sarankan checksum saja untuk anti-pemalsuan.
"Midtrans atau Stripe?"Tanyakan pasar target (Bab 6, 7). Bukan salah satu selalu benar.
"Webhook-ku dobel memproses"Arahkan ke idempotensi via UNIQUE constraint (Bab 8).
Pertanyaan pajak spesifikNyatakan ketergantungan yurisdiksi/waktu, sarankan profesional (Bab 13).
Prinsip utama untuk AI

Dua default yang menyelamatkan dalam sistem uang: (1) untuk kegagalan verifikasi karena jaringan, fail-open dengan grace period; untuk signature yang terbukti palsu, fail-closed. (2) Setiap efek yang dipicu pembayaran harus idempoten. Jika ragu, condongkan saran ke arah "tidak menghukum pelanggan jujur" dan "tidak memproses ganda".

"Butuh 20 tahun membangun reputasi dan lima menit menghancurkannya. Jika Anda memikirkan itu, Anda akan melakukan sesuatu secara berbeda."

— Warren Buffett
16
Penutup

Penutup & Rujukan

// rangkuman · FAQ · glosarium · checklist

Kita mulai dari kode yang jalan tetapi tak dibayar, dan berakhir dengan mesin lengkap yang mengubah nilai menjadi pendapatan: model yang tepat, kunci yang tak bisa dipalsukan, server yang menegakkan hak, langganan yang menagih dengan sabar, gateway yang memindahkan uang sungguhan, dan dokumen yang sah secara hukum. Bagian penutup ini merangkumnya menjadi rujukan cepat.

Tujuh Pelajaran Inti

  1. Model lisensi adalah keputusan arsitektural, bukan sekadar harga (Bab 2).
  2. Checksum untuk salah ketik; signature untuk pemalsuan — pakai keduanya (Bab 3).
  3. Token berumur pendek + heartbeat menyeimbangkan offline & kemampuan revoke (Bab 4).
  4. Dunning menyelamatkan pendapatan yang hilang karena kegagalan teknis (Bab 5).
  5. Kebenaran datang dari webhook terverifikasi, bukan callback front-end (Bab 6–8).
  6. Idempotensi = UNIQUE constraint, bukan "cek lalu tulis" (Bab 8).
  7. Proteksi harus menimbang gesekan bagi pelanggan jujur (Bab 12).

"Seseorang duduk di tempat teduh hari ini karena orang lain menanam pohon sejak lama."

— Warren Buffett

FAQ

Haruskah saya pakai layanan lisensi pihak ketiga atau bikin sendiri?

Untuk memulai cepat, layanan siap pakai menghemat waktu. Bikin sendiri memberi kendali penuh & tanpa biaya per-lisensi, tetapi Anda menanggung keamanan & pemeliharaan. Buku ini membekali Anda untuk keduanya: memahami cara kerja agar bisa memilih, atau membangun.

Kunci lisensi saya bisa dibongkar orang. Apa gunanya?

Tujuannya bukan mustahil dibobol, melainkan membuat membayar lebih mudah daripada membajak bagi mayoritas (Bab 12). Tambahkan signature server-side dan pindahkan nilai ke server untuk pertahanan yang lebih kuat.

Bagaimana menangani refund & chargeback?

Dengarkan event refund/dispute dari gateway (Bab 8), cabut lisensi otomatis (Bab 4), dan miliki kebijakan refund yang jelas (Bab 13) untuk menekan chargeback.

Perlukah server lisensi jika saya jual perpetual desktop?

Tidak wajib. Lisensi bertanda tangan yang diverifikasi offline (Bab 3, 11) sudah cukup. Server berguna bila Anda ingin batas seat aktif dan kemampuan revoke.

Midtrans dan Stripe sekaligus, mungkin?

Sangat umum: Midtrans untuk pembeli Indonesia (QRIS, VA, e-wallet), Stripe untuk pelanggan global & langganan. Abstraksikan keduanya di balik satu antarmuka "fulfillOrder" yang idempoten.

Bagaimana menghitung harga yang tepat?

Mulai dari nilai bagi pelanggan, bukan biaya Anda. Uji beberapa titik harga, dan pantau MRR/churn (pakai kalkulator di Bab 14). Ingat kata Buffett: harga adalah yang dibayar, nilai adalah yang didapat.

Glosarium

IstilahArti singkat
MRR / ARRMonthly / Annual Recurring Revenue — pendapatan berulang bulanan/tahunan.
ChurnPersentase pelanggan yang berhenti berlangganan per periode.
LTVLifetime Value — total pendapatan yang diharapkan dari satu pelanggan.
ChecksumAngka pemeriksa untuk mendeteksi salah ketik; bukan pengaman kriptografis.
HMACTanda tangan simetris berbasis secret bersama.
Ed25519 / RSATanda tangan asimetris (private menandatangani, public memverifikasi).
EntitlementHak yang dimiliki pelanggan (fitur, batas, masa berlaku).
Device bindingMengikat lisensi ke perangkat tertentu.
Grace periodMasa tenggang saat pembayaran/verifikasi gagal sebelum akses ditangguhkan.
ProrationPembagian biaya adil saat pindah paket di tengah siklus.
DunningProses percobaan ulang & komunikasi saat penagihan gagal.
WebhookNotifikasi HTTP dari gateway ke server Anda saat suatu peristiwa terjadi.
IdempotenOperasi yang aman diulang tanpa efek ganda.
MeteringPencatatan pemakaian untuk penagihan usage-based.
SnapHalaman/pop-up pembayaran siap pakai dari Midtrans.
PKP / PPNPengusaha Kena Pajak / Pajak Pertambahan Nilai.
EULAEnd User License Agreement — perjanjian lisensi pengguna.
Air-gappedLingkungan tanpa koneksi internet, terisolasi.

Checklist Rilis

Sebelum menjual ke pelanggan pertama, lewati daftar ini. Ia menyilang seluruh buku.

Entitlement & Lisensi

Server & Billing

Pembayaran & Webhook

Dokumen & Legal

Langkah berikutnya

Kembali ke Bab 14 dan jalankan kedua alat dengan angka produk Anda sendiri. Kunci pertama yang Anda generate dan MRR pertama yang Anda proyeksikan adalah dua langkah paling nyata dari "kode yang jalan" menuju "kode yang dibayar".

"Peraturan yang paling penting dalam berinvestasi — dan dalam membangun — adalah kesabaran."

— Warren Buffett
GP
Tentang Penulis
Galih Prasetyo

Insinyur perangkat lunak dan pembangun produk yang menghabiskan lebih dari satu dekade di persimpangan antara kode dan komersialisasi — merancang sistem lisensi, gateway pembayaran, dan pipeline penagihan untuk produk yang benar-benar dijual.

Ia menulis buku ini karena terlalu banyak aplikasi bagus yang mati bukan karena kodenya jelek, melainkan karena tak pernah punya cara yang benar untuk dibayar. Baginya, sebuah checksum yang valid dan sebuah webhook yang idempoten sama indahnya dengan algoritma yang elegan.

Tersalin ke clipboard