D
P
0

WordPress & PHP

Checkout Selalu Balik ke Homepage: `empty( $property['id'] )` Selalu `true` karena `get_property_data()` Tak Punya Key `id`

22 Juli 2026·4 menit baca
Checkout Selalu Balik ke Homepage: `empty( $property['id'] )` Selalu `true` karena `get_property_data()` Tak Punya Key `id`

Sebuah situs booking properti yang saya pegang punya masalah yang bikin panik. Setiap kali pengunjung membuka halaman checkout, mereka langsung dilempar balik ke homepage. Bukan error, bukan halaman putih, cuma redirect mulus. URL-nya jelas benar, /checkout/?property=123&checkin=...&checkout=..., tapi begitu halaman coba dimuat, browser langsung meloncat ke beranda. Seluruh alur booking praktis mati, dan yang bikin merinding: ini sudah begitu sejak situs live, hampir dua minggu, dan tidak ada satu pun booking yang berhasil.

Yang menyesatkan, di mata pemilik situs semuanya kelihatan jalan. Tombol book ada, halaman properti tampil bagus, klik book mengarah ke URL checkout yang benar. Tidak ada error di layar, tidak ada yang merah. Justru redirect yang mulus itu yang menyamarkan bug-nya jadi seolah perilaku normal.

Menelusuri jejaknya

Redirect tanpa error artinya ada wp_redirect() yang dipanggil sengaja di suatu tempat, bukan crash. Jadi saya buka template-checkout.php dan cari kata redirect. Ketemu cepat di bagian paling atas template, sebuah guard:

$property = Property::get_property_data( $id );
if ( empty( $property['id'] ) ) {
    wp_redirect( home_url() );
    exit;
}

Logikanya kelihatan wajar: kalau propertinya tidak valid, tendang ke homepage. Tapi kalau guard ini selalu true, setiap checkout pasti redirect. Jadi pertanyaannya menyempit: apakah $property['id'] benar-benar kosong?

Saya dump isi $property tepat setelah pemanggilan:

$property = Property::get_property_data( $id );
error_log( '[checkout] ' . print_r( $property, true ) );

Hasilnya menjelaskan semuanya. Array-nya penuh: price_per_night, capacity, gallery, amenities, semua field meta ada. Tapi tidak ada key id. Sama sekali. $property['id'] menunjuk ke key yang tidak pernah ada, jadi nilainya null, dan empty( null ) selalu true. Guard itu tidak pernah punya kesempatan lolos.

Akar masalahnya

Saya buka method get_property_data(). Ternyata dia memang dirancang untuk mengembalikan hanya field meta properti: harga, kapasitas, galeri, fasilitas. Dia tidak pernah menaruh ID post ke dalam array-nya, karena ID itu justru argumen yang kamu oper masuk. Dari sisi method itu, ini masuk akal: kamu sudah tahu ID-nya, ngapain dikembalikan lagi.

Masalahnya, penulis template checkout berasumsi sebaliknya. Dia pikir array yang balik punya key id, lalu memakainya sebagai penanda properti valid atau tidak. Dua asumsi yang bertabrakan diam-diam.

Kenapa ini lolos sampai produksi? Karena alur lama berbeda. Versi sebelumnya memakai parameter ?booking=X yang objek booking-nya dibuat lebih dulu di server, jadi guard-nya mengecek hal lain dan kebetulan lolos. Saat alur dipindah ke ?property=X langsung, guard baru ini masuk tapi tidak pernah benar-benar diuji di browser sungguhan. Scaffolding Stripe di sekitarnya dites lewat unit, bukan lewat klik manusia. Bug yang cuma muncul saat halaman dibuka betulan itu lolos dari semua tes yang ada.

Dan ini bukan satu-satunya. Saat menyisir template yang sama, saya menemukan pola serupa: kode di template-checkout.php dan template-owner-add-property.php memanggil Property::get( $id ), method yang bahkan tidak ada, lalu mengakses hasilnya dengan sintaks objek: $property->price_per_night. Padahal data properti di sistem ini selalu berupa associative array, diakses dengan $property['price_per_night']. Sekelas bug yang sama: template menebak bentuk data, dan tebakannya salah.

Perbaikannya

Kuncinya: jangan validasi keberadaan properti lewat key yang belum tentu ada di array meta. Validasi lewat sumber kebenaran yang sebenarnya, yaitu post itu sendiri.

// SALAH: get_property_data() tak punya key 'id', empty() selalu true
$property = Property::get_property_data( $id );
if ( empty( $property['id'] ) ) {
    wp_redirect( home_url() );
    exit;
}

Ganti dengan cek get_post(), lalu suntikkan id dan title ke array supaya template ringkasan tetap punya data yang dibutuhkannya:

$id   = absint( $_GET['property'] ?? 0 );
$post = get_post( $id );
 
if ( ! $post || $post->post_type !== 'property' ) {
    wp_redirect( home_url() );
    exit;
}
 
$property          = Property::get_property_data( $id );
$property['id']    = $post->ID;
$property['title'] = $post->post_title;

Sekarang guard mengecek hal yang benar: apakah post-nya betul-betul ada dan bertipe properti. Kalau ada, alur lanjut; kalau tidak, baru redirect. Dan karena saya suntikkan id plus title, template-part ringkasan booking bisa menampilkan judul properti tanpa query tambahan.

Untuk pola sintaks objek, perbaikannya sekadar menyamakan dengan bentuk data yang sebenarnya: buang pemanggilan Property::get() yang tidak ada, pakai get_property_data(), dan akses semuanya dengan sintaks array.

// SALAH: method tak ada, lalu sintaks objek di atas associative array
$property = Property::get( $id );
$price    = $property->price_per_night;
 
// BENAR
$property = Property::get_property_data( $id );
$price    = $property['price_per_night'];

Setelah kedua perbaikan itu, checkout berhenti melempar orang ke homepage dan booking pertama akhirnya masuk.

Checklist

  • Redirect tanpa error bukan crash: cari wp_redirect() yang dipanggil sengaja, biasanya di guard paling atas template.
  • Kalau guard selalu jalan, dump variabel yang dia cek. empty() pada key yang tidak ada selalu true, dan itu senyap.
  • Jangan pernah anggap sebuah method mengembalikan key yang tidak dia janjikan. Baca isi method-nya, jangan tebak dari namanya.
  • Validasi keberadaan entitas lewat sumber kebenaran, get_post(), bukan lewat key opsional di array meta.
  • Cek bentuk data sebelum mengaksesnya: array pakai ['key'], objek pakai ->prop. Sintaks yang salah di atas bentuk yang salah gagal senyap.
  • Bug yang cuma muncul saat halaman dibuka sungguhan tidak akan ketangkap unit test. Klik alur kritis di browser sebelum bilang selesai.