API harga yang aman saat price change mendadak dari vendor harus memisahkan dengan jelas antara harga saat ini dan harga yang dipakai untuk transaksi. Jika dua konsep ini dicampur, reseller atau marketplace akan sering mengalami kasus klasik: pengguna melihat harga A, checkout dengan harga A, tetapi backend menagih harga B karena vendor baru saja mengubah harga.

Dalam konteks perubahan harga perangkat yang bisa terjadi tiba-tiba—misalnya tren penyesuaian harga Apple yang menjadi pemicu kebutuhan bisnis, bukan sekadar berita—masalah utamanya bukan hanya sinkronisasi data, tetapi kontrak API. Sistem perlu menjawab pertanyaan berikut secara eksplisit: harga mana yang sedang berlaku, kapan efektifnya, apakah harga masih bisa dipakai untuk order draft, dan bagaimana perilaku sistem jika harga berubah di tengah checkout.

Masalah inti: satu field price tidak cukup

Banyak integrasi gagal karena hanya mengembalikan satu field seperti price tanpa konteks. Field tunggal ini menimbulkan ambiguitas:

  • Apakah itu harga yang sedang aktif saat response dibuat?
  • Apakah itu harga yang dijamin untuk checkout selama beberapa menit?
  • Apakah harga tersebut sudah termasuk pajak, diskon, atau biaya channel?
  • Apakah harga bisa berubah sebelum order dikonfirmasi?

Untuk integrasi reseller, distributor, atau marketplace, harga sebaiknya dibagi minimal menjadi dua konsep:

  • Current price: harga vendor yang saat ini aktif untuk produk tertentu.
  • Price snapshot: salinan harga yang dibekukan untuk konteks tertentu, misalnya order draft atau sesi checkout.

Pemisahan ini penting karena sistem operasional biasanya butuh keduanya. Katalog dan product listing ingin melihat current price, sedangkan checkout butuh price snapshot yang stabil agar transaksi dapat diproses secara deterministik.

Kontrak API: pisahkan current price dan price snapshot

Endpoint untuk current price

Endpoint ini dipakai untuk menampilkan harga terbaru. Response harus menjelaskan identitas harga, waktu efektif, dan versi datanya.

GET /v1/products/{sku}/price
{
  "sku": "IPHONE-128-BLK",
  "currency": "IDR",
  "current_price": {
    "amount": 18999000,
    "price_list_id": "vendor-retail-id",
    "price_version": "2026-06-22T10:15:00Z#42",
    "effective_at": "2026-06-22T10:15:00Z",
    "expires_at": null
  },
  "fetched_at": "2026-06-22T10:16:03Z"
}

Beberapa catatan penting:

  • price_version adalah identitas logis perubahan harga. Ini bisa berupa timestamp yang diperkaya sequence, UUID event, atau nomor versi monotonik.
  • effective_at menjelaskan kapan harga mulai berlaku, bukan kapan API dipanggil.
  • fetched_at berguna untuk debugging cache, race condition, dan observabilitas.

Endpoint untuk membuat price snapshot

Saat pengguna menambahkan barang ke keranjang atau membuat draft order, backend sebaiknya meminta snapshot harga yang dapat dipakai ulang selama periode tertentu.

POST /v1/price-snapshots
{
  "channel": "marketplace-a",
  "customer_id": "cust-8821",
  "items": [
    {
      "sku": "IPHONE-128-BLK",
      "quantity": 1
    }
  ]
}
{
  "snapshot_id": "ps_01JXYZ9M8K",
  "currency": "IDR",
  "items": [
    {
      "sku": "IPHONE-128-BLK",
      "quantity": 1,
      "unit_price": 18999000,
      "line_total": 18999000,
      "price_version": "2026-06-22T10:15:00Z#42",
      "effective_at": "2026-06-22T10:15:00Z"
    }
  ],
  "created_at": "2026-06-22T10:16:10Z",
  "valid_until": "2026-06-22T10:31:10Z"
}

Snapshot ini bukan sekadar cache. Ia adalah objek bisnis yang bisa direferensikan saat membuat draft order atau checkout. Dengan begitu, backend tidak perlu menebak harga mana yang dipakai pengguna.

Endpoint checkout dengan referensi snapshot

POST /v1/orders/drafts
{
  "idempotency_key": "draft-9d7c8f6e-1c9d-4d8b-a4eb-1b624d9f5f72",
  "snapshot_id": "ps_01JXYZ9M8K",
  "customer_id": "cust-8821",
  "items": [
    {
      "sku": "IPHONE-128-BLK",
      "quantity": 1
    }
  ]
}
{
  "order_draft_id": "od_01JXYZM4KR",
  "status": "priced",
  "currency": "IDR",
  "pricing": {
    "snapshot_id": "ps_01JXYZ9M8K",
    "price_status": "locked",
    "valid_until": "2026-06-22T10:31:10Z"
  },
  "total_amount": 18999000,
  "created_at": "2026-06-22T10:16:13Z"
}

Jika snapshot sudah kedaluwarsa atau vendor mewajibkan reprice, backend harus menolak dengan status yang jelas, bukan diam-diam mengganti total.

Versioning field harga: jangan hanya ubah nilai, beri identitas perubahan

Ketika vendor mengubah harga mendadak, masalah terbesar biasanya muncul saat berbagai sistem melihat data pada waktu berbeda. Untuk itu, setiap nilai harga yang material sebaiknya membawa versi. Tujuannya:

  • mendeteksi apakah frontend menampilkan harga lama,
  • membandingkan dua state harga tanpa membandingkan nominal saja,
  • membangun audit trail yang bisa ditelusuri,
  • mencegah race condition saat beberapa worker memproses order yang sama.

Field yang umum dan berguna:

  • price_version: identitas perubahan harga.
  • effective_at: kapan harga mulai berlaku.
  • captured_at: kapan snapshot dibuat.
  • source: asal harga, misalnya vendor API, manual override, atau pricing engine internal.
  • reason_code: opsional, untuk keperluan audit seperti vendor_update, tax_change, atau campaign_end.

Jangan memakai nominal harga saja sebagai penanda perubahan. Dua harga yang nominalnya sama bisa berasal dari versi berbeda dengan aturan diskon, batas channel, atau kewajiban pajak yang tidak sama.

Idempotency untuk order draft dan retry yang aman

Saat vendor atau jaringan lambat, klien cenderung melakukan retry. Tanpa idempotency, request yang sama bisa membuat lebih dari satu draft order, lebih dari satu reservasi stok, atau snapshot ganda.

Kapan idempotency wajib

  • Pembuatan order draft
  • Konfirmasi checkout
  • Pembuatan payment intent
  • Pembuatan snapshot yang terkait transaksi

Desain sederhana

Klien mengirim Idempotency-Key sebagai header atau field request. Backend menyimpan kombinasi berikut:

  • idempotency key,
  • hash payload penting,
  • response akhir,
  • status pemrosesan.

Jika request identik datang lagi dengan key yang sama, backend mengembalikan response yang sama. Jika key sama tetapi payload berbeda, kembalikan error konflik.

POST /v1/orders/drafts
Idempotency-Key: draft-9d7c8f6e-1c9d-4d8b-a4eb-1b624d9f5f72

Contoh respons jika key sama dipakai dengan payload berbeda:

{
  "error": {
    "code": "IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_PAYLOAD",
    "message": "Idempotency key sudah pernah dipakai untuk payload yang berbeda"
  }
}

Status code yang masuk akal untuk kasus ini adalah 409 Conflict.

Retry yang aman

Retry hanya aman jika operasi benar-benar idempotent atau read-only. Rekomendasi umum:

  • GET current price: boleh retry dengan backoff.
  • POST create snapshot: gunakan idempotency jika snapshot terkait sesi transaksi.
  • POST order draft: wajib idempotent.
  • POST final order/commit: wajib idempotent dan idealnya memakai state machine eksplisit.

Hindari retry buta pada error 4xx. Untuk 5xx atau timeout, retry boleh dilakukan, tetapi tetap periksa apakah request sebelumnya mungkin sudah sukses dan response hilang di jaringan.

Cache invalidation: harga bukan data statis

Harga sering dicache karena dibaca jauh lebih sering daripada diubah. Namun cache harga berbahaya jika invalidation tidak dirancang dengan benar. Masalah yang umum:

  • Frontend menampilkan harga lama karena TTL terlalu panjang.
  • Backend checkout membaca harga baru, sementara halaman produk masih menampilkan harga lama.
  • Beberapa node aplikasi memiliki cache lokal yang tidak konsisten.

Strategi yang lebih aman

  1. Cache current price dengan TTL pendek untuk listing dan detail produk.
  2. Jangan gunakan current price cache sebagai sumber kebenaran checkout. Checkout harus mengacu ke snapshot atau revalidasi backend.
  3. Gunakan event invalidation saat vendor mengirim perubahan harga.
  4. Simpan versi harga di cache key jika memungkinkan, misalnya price:SKU:version, untuk mengurangi risiko stale overwrite.

Jika Anda memakai cache terdistribusi seperti Redis, pastikan ada kebijakan invalidation yang eksplisit. Jika memakai cache in-memory per instance, webhook harga harus memicu sinkronisasi lintas instance atau cache tersebut akan divergen.

Trade-off TTL pendek vs invalidation event

  • TTL pendek lebih sederhana, tetapi tetap punya jendela stale data.
  • Webhook/event invalidation lebih cepat dan akurat, tetapi menambah kompleksitas delivery, retry, dan observabilitas.

Dalam praktik, kombinasi keduanya biasanya paling aman: webhook untuk invalidation cepat, TTL sebagai pagar jika event terlambat atau hilang.

Webhook price update dan timestamp efektif

Jika vendor mendukung notifikasi perubahan harga, webhook jauh lebih baik daripada polling murni. Tetapi webhook hanya berguna jika payload-nya cukup kaya untuk diproses secara deterministik.

Contoh payload webhook

POST /webhooks/vendor/price-updates
{
  "event_id": "evt_01JXYZT1RM",
  "event_type": "price.updated",
  "occurred_at": "2026-06-22T10:14:58Z",
  "effective_at": "2026-06-22T10:15:00Z",
  "items": [
    {
      "sku": "IPHONE-128-BLK",
      "old_price": 18499000,
      "new_price": 18999000,
      "currency": "IDR",
      "price_version": "2026-06-22T10:15:00Z#42"
    }
  ]
}

Field yang paling penting adalah effective_at. Tanpa field ini, sistem Anda hanya tahu kapan event diterima, bukan kapan harga seharusnya mulai berlaku. Perbedaan ini penting untuk:

  • menentukan apakah order draft yang dibuat beberapa detik sebelumnya masih valid,
  • menjelaskan sengketa harga ke reseller,
  • mereproduksi kejadian saat audit atau incident review.

Praktik aman untuk webhook

  • Verifikasi signature agar endpoint tidak bisa dipalsukan.
  • Simpan event_id untuk deduplikasi.
  • Proses event secara idempotent.
  • Jangan anggap urutan kedatangan selalu sama dengan urutan kejadian.
  • Gunakan effective_at dan price_version untuk memutuskan event mana yang lebih baru.

Kesalahan umum adalah langsung menimpa harga hanya berdasarkan waktu terima event. Pada sistem terdistribusi, event bisa datang terlambat, terduplikasi, atau out-of-order.

Validasi backend saat checkout: harga lama boleh dipakai atau harus reprice?

Ini adalah keputusan bisnis yang harus diterjemahkan menjadi aturan teknis yang eksplisit. Setidaknya ada tiga model umum:

1. Hard reprice

Saat checkout, backend selalu memeriksa current price terbaru. Jika berbeda dari snapshot, transaksi ditolak dan klien harus menampilkan harga baru.

Kelebihan: risiko margin negatif paling kecil.

Kekurangan: pengalaman pengguna lebih buruk saat harga sering berubah.

2. Soft lock dengan valid_until

Snapshot harga dianggap sah sampai valid_until. Jika checkout terjadi sebelum waktu itu, harga lama tetap diterima meskipun current price sudah berubah.

Kelebihan: UX lebih stabil.

Kekurangan: vendor atau platform menanggung risiko selisih harga selama jendela lock.

3. Conditional lock

Harga lama tetap sah hanya jika perubahan berada dalam aturan tertentu, misalnya:

  • produk belum masuk kategori volatile,
  • selisih tidak melewati threshold internal,
  • channel reseller tertentu memiliki SLA harga berbeda.

Model ini lebih fleksibel, tetapi logikanya lebih kompleks dan perlu audit trail yang rapi.

Contoh validasi backend

  1. Ambil order draft dan snapshot.
  2. Periksa apakah snapshot masih dalam status valid.
  3. Bandingkan price_version snapshot dengan aturan bisnis saat ini.
  4. Jika snapshot kedaluwarsa atau tidak boleh dipakai, kembalikan error terstruktur.
  5. Jika masih sah, teruskan ke finalisasi order dengan menyimpan semua metadata harga yang dipakai.
{
  "error": {
    "code": "PRICE_CHANGED",
    "message": "Harga telah berubah sejak draft dibuat",
    "details": {
      "snapshot_id": "ps_01JXYZ9M8K",
      "previous_price_version": "2026-06-22T10:15:00Z#42",
      "current_price_version": "2026-06-22T10:20:00Z#43",
      "current_amount": 19199000,
      "currency": "IDR"
    }
  }
}

Untuk kasus ini, 409 Conflict biasanya cocok karena state resource berubah dan klien perlu merefresh konteks transaksi. Jika snapshot kadaluwarsa karena aturan waktu, 422 Unprocessable Entity atau 409 Conflict sama-sama bisa dipakai selama konsisten dan terdokumentasi.

Status code yang tepat untuk skenario harga

  • 200 OK: harga saat ini berhasil diambil, atau order draft berhasil dibuat.
  • 201 Created: snapshot atau draft order baru berhasil dibuat.
  • 202 Accepted: update harga diterima tetapi diproses async.
  • 400 Bad Request: payload tidak valid secara sintaks atau field wajib hilang.
  • 401 Unauthorized / 403 Forbidden: autentikasi atau otorisasi gagal.
  • 404 Not Found: SKU, snapshot, atau draft order tidak ditemukan.
  • 409 Conflict: harga berubah, idempotency key bentrok, atau versi state tidak cocok.
  • 410 Gone: snapshot pernah ada tetapi sudah tidak berlaku dan memang tidak dapat dipakai lagi.
  • 422 Unprocessable Entity: payload valid secara sintaks, tetapi tidak lolos aturan bisnis, misalnya kuantitas atau channel tidak berhak memakai price list tertentu.
  • 429 Too Many Requests: throttling untuk polling harga yang terlalu agresif.
  • 500/502/503/504: error internal atau ketergantungan vendor bermasalah.

Yang paling penting bukan sekadar status code, tetapi error body yang dapat diotomasi. Sertakan code yang stabil, message yang manusiawi, dan details seperlunya.

Audit trail: simpan alasan, versi, dan sumber harga

Saat terjadi sengketa harga, log biasa tidak cukup. Anda memerlukan audit trail yang bisa menjawab:

  • harga berapa yang ditampilkan saat itu,
  • versi harga mana yang dipakai untuk checkout,
  • apakah ada webhook update yang masuk sebelum order dikonfirmasi,
  • siapa atau sistem apa yang mengubah harga,
  • aturan bisnis mana yang memutuskan menerima atau menolak harga lama.

Minimal simpan pada level order line:

  • sku, quantity, unit_price, line_total, currency,
  • price_version, effective_at, captured_at,
  • snapshot_id, source, reason_code,
  • decision_result, misalnya accepted_under_lock_window atau rejected_due_to_price_change.

Hindari desain yang hanya menyimpan total akhir order tanpa metadata harga per item. Saat satu order memiliki beberapa item dengan perubahan harga berbeda, total saja tidak cukup untuk forensik.

Edge case penting saat checkout memakai harga lama

Harga berubah setelah draft dibuat tetapi sebelum pembayaran

Tentukan sejak awal apakah pembayaran masih boleh lanjut berdasarkan valid_until. Jangan menunggu sampai callback payment untuk memutuskan, karena itu membuat status order sulit dijelaskan.

Vendor mengirim update terlambat

Jika webhook terlambat tetapi effective_at menunjukkan harga sebenarnya sudah aktif lebih awal, sistem perlu aturan rekonsiliasi. Di sinilah audit trail dan keputusan bisnis menjadi penting.

Beberapa item berubah, sebagian tidak

Jangan paksa seluruh order selalu gagal jika hanya satu item yang bermasalah, kecuali memang itu kebijakan bisnis. Kembalikan detail item-level agar frontend dapat menawarkan reprice parsial.

Mata uang atau komponen pajak ikut berubah

Perubahan nominal bukan satu-satunya risiko. Jika price list berubah bersama pajak, biaya channel, atau kurs, pastikan snapshot menyimpan komponen yang dibutuhkan untuk rekonstruksi.

Stok dan harga berubah bersamaan

Jangan gabungkan validasi stok dan harga secara implisit dalam satu field. Keduanya bisa berubah independen. Response error juga sebaiknya membedakan mana yang gagal: stok, harga, atau keduanya.

Contoh desain state machine order draft

State machine membantu menghindari transisi liar saat retry atau event datang tidak berurutan.

draft_created
  -> priced
  -> awaiting_confirmation
  -> confirmed
  -> expired
  -> repricing_required
  -> cancelled

Contoh aturan:

  • priced -> confirmed hanya boleh jika snapshot masih valid.
  • priced -> repricing_required terjadi jika ada perubahan harga yang melanggar aturan lock.
  • repricing_required -> priced terjadi setelah klien menerima snapshot baru.

Dengan state machine, retry pada endpoint konfirmasi menjadi lebih aman karena backend bisa mengecek state terakhir sebelum mengeksekusi aksi lanjutan.

Checklist integrasi reseller/marketplace

  1. Apakah API membedakan current price dan price snapshot?
  2. Apakah setiap harga memiliki price_version dan effective_at?
  3. Apakah order draft memakai idempotency key?
  4. Apakah retry policy hanya diterapkan pada operasi yang aman?
  5. Apakah checkout memvalidasi snapshot terhadap aturan harga terbaru secara eksplisit?
  6. Apakah ada valid_until untuk lock window harga?
  7. Apakah webhook update harga diverifikasi, dideduplikasi, dan diproses idempotent?
  8. Apakah cache harga diinvalidasi melalui event, bukan hanya TTL?
  9. Apakah error response memiliki code yang stabil seperti PRICE_CHANGED?
  10. Apakah audit trail menyimpan source, version, timestamp, dan hasil keputusan?
  11. Apakah edge case seperti event terlambat, item parsial, dan harga lama saat payment sudah diuji?
  12. Apakah frontend mampu menampilkan pesan reprice yang jelas tanpa membuat total berubah diam-diam?

Kesalahan implementasi yang sering terjadi

  • Menggunakan satu field price untuk semua kebutuhan.
  • Checkout langsung memakai harga dari cache listing produk.
  • Mengubah total order di backend tanpa memberi sinyal konflik ke klien.
  • Retry endpoint POST tanpa idempotency.
  • Mengandalkan timestamp penerimaan webhook, bukan effective_at.
  • Tidak menyimpan versi harga pada order line.
  • Menghapus snapshot lama sehingga investigasi sengketa menjadi mustahil.

Debugging saat insiden price mismatch

Jika terjadi mismatch antara harga yang dilihat pengguna dan yang diproses backend, telusuri urutannya:

  1. Kapan current price diubah oleh vendor?
  2. Kapan webhook diterima dan diproses?
  3. Versi harga apa yang diterima frontend?
  4. Snapshot mana yang dipakai saat draft dibuat?
  5. Apakah snapshot masih valid saat konfirmasi?
  6. Apakah ada retry yang membuat draft ganda atau response tertukar?
  7. Apakah cache lama masih hidup di salah satu node?

Tambahkan korelasi ID pada request, event, dan order agar timeline bisa dirangkai dengan cepat. Tanpa korelasi ID, insiden seperti ini sering terlihat acak padahal sebenarnya hanya race condition yang tidak teramati.

Penutup

Merancang API harga yang aman saat price change mendadak dari vendor bukan sekadar soal sinkronisasi nominal, tetapi soal kontrak data dan aturan transaksi. Pisahkan current price dari price snapshot, beri versi pada harga, gunakan idempotency untuk draft order, proses webhook secara aman, dan simpan audit trail yang lengkap.

Jika prinsip-prinsip ini diterapkan, reseller atau marketplace tetap bisa beroperasi stabil meskipun vendor mengubah harga perangkat secara tiba-tiba. Sistem tidak perlu menebak-nebak harga mana yang benar, karena setiap keputusan sudah memiliki timestamp, versi, dan konteks bisnis yang jelas.