// —————————————————————————-
// 1. KONFIGURASI PELAYARAN
// —————————————————————————-
$CARRIERS = [
‘meratus’ => [
‘name’ => ‘Meratus Line’,
‘tracking_url’ => ‘https://clcbisa.meratusline.com/page/SearchTaxTracking’,
‘requires_login’ => false,
‘js_rendered’ => true,
],
‘spil’ => [
‘name’ => ‘SPIL (Salam Pacific Indonesia Lines)’,
‘tracking_url’ => ‘https://www.myspil.com/myspilcom/Front/tracktrace’,
‘requires_login’ => true,
‘js_rendered’ => true,
],
‘temas’ => [
‘name’ => ‘Temas Line’,
‘tracking_url’ => ‘https://www.kliktemas.com/’,
‘requires_login’ => true,
‘js_rendered’ => true,
],
‘tanto’ => [
‘name’ => ‘Tanto Intim Line’,
‘tracking_url’ => ‘https://www.tantonet.com/’,
‘requires_login’ => false,
‘js_rendered’ => true,
],
‘icon’ => [
‘name’ => ‘ICON (PT Indo Container Lines)’,
‘tracking_url’ => ‘https://www.iconlinebooking.co.id/’,
‘requires_login’ => true,
‘js_rendered’ => true,
],
‘samudera’ => [
‘name’ => ‘Samudera Indonesia’,
‘tracking_url’ => ‘https://www.samudera.com/tracing-tracking’,
‘requires_login’ => false,
‘js_rendered’ => true,
],
];
// —————————————————————————-
// 2. HTTP CLIENT (cURL wrapper ringan)
// —————————————————————————-
function http_request(string $method, string $url, array $postFields = []): ?string
{
$ch = curl_init();
$cookieJar = sys_get_temp_dir() . ‘/tracker_’ . md5($url) . ‘.cookie’;
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_TIMEOUT => 20,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_COOKIEJAR => $cookieJar,
CURLOPT_COOKIEFILE => $cookieJar,
CURLOPT_USERAGENT => ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) ‘
. ‘AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36’,
CURLOPT_HTTPHEADER => [
‘Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8’,
‘Accept-Language: id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7’,
],
]);
if ($method === ‘POST’) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postFields));
}
$response = curl_exec($ch);
$errno = curl_errno($ch);
curl_close($ch);
@unlink($cookieJar);
return $errno === 0 && $response !== false ? $response : null;
}
// —————————————————————————-
// 3. LOGIC TRACKING (generic untuk semua pelayaran)
// —————————————————————————-
function track_resi(array $carrier, string $resi): array
{
$resi = trim($resi);
$base = [
‘success’ => false,
‘carrier’ => $carrier[‘name’],
‘resi’ => $resi,
‘status’ => null,
‘history’ => [],
‘raw_url’ => $carrier[‘tracking_url’],
‘message’ => ”,
];
if ($resi === ”) {
$base[‘message’] = ‘Nomor resi tidak boleh kosong.’;
return $base;
}
// Kasus yang memang tidak bisa diotomatisasi murni: butuh login akun pelanggan.
if ($carrier[‘requires_login’]) {
$base[‘message’] =
“Tracking penuh di {$carrier[‘name’]} memerlukan login akun pelanggan terdaftar. ” .
“Ini bukan keterbatasan sistem ini, tapi memang situs resminya mengharuskan login. ” .
“Silakan login dan cek resi Anda langsung di portal resmi.”;
return $base;
}
$html = http_request(‘POST’, $carrier[‘tracking_url’], [‘resi’ => $resi]);
if ($html === null) {
$base[‘message’] =
“Gagal menghubungi situs {$carrier[‘name’]}. Server mungkin sedang down atau memblokir ” .
“request otomatis. Silakan cek manual di link resmi.”;
return $base;
}
libxml_use_internal_errors(true);
$dom = new DOMDocument();
$dom->loadHTML($html);
libxml_clear_errors();
$xpath = new DOMXPath($dom);
// Selector generic — sesuaikan sendiri kalau kamu sudah menemukan
// struktur HTML/endpoint asli lewat DevTools (lihat catatan di bawah).
$statusNodes = $xpath->query(‘//*[contains(@class,”status”)]’);
$status = ($statusNodes && $statusNodes->length > 0)
? trim($statusNodes->item(0)->textContent)
: null;
$history = [];
$rows = $xpath->query(‘//table[contains(@class,”history”)]//tr’);
if ($rows) {
foreach ($rows as $row) {
$text = trim(preg_replace(‘/\s+/’, ‘ ‘, $row->textContent));
if ($text !== ”) $history[] = $text;
}
}
if ($status === null && empty($history)) {
if ($carrier[‘js_rendered’]) {
$base[‘message’] =
“Halaman tracking {$carrier[‘name’]} memuat datanya lewat JavaScript, sehingga HTML ” .
“mentah yang diambil PHP tidak berisi data status. Untuk kasus ini otomatisasi butuh ” .
“headless browser (Puppeteer/Playwright), bukan cURL biasa. Cek manual di link resmi ya.”;
} else {
$base[‘message’] = “Data tracking tidak ditemukan di respons {$carrier[‘name’]}.”;
}
return $base;
}
$base[‘success’] = true;
$base[‘status’] = $status;
$base[‘history’] = $history;
$base[‘message’] = ‘Berhasil mengambil data tracking.’;
return $base;
}
// —————————————————————————-
// 4. ENDPOINT AJAX (dipanggil oleh JS lewat fetch, tanpa reload halaman)
// —————————————————————————-
if ($_SERVER[‘REQUEST_METHOD’] === ‘POST’ && isset($_POST[‘ajax’])) {
header(‘Content-Type: application/json’);
$key = $_POST[‘carrier’] ?? ”;
$resi = $_POST[‘resi’] ?? ”;
if (!isset($CARRIERS[$key])) {
echo json_encode([‘success’ => false, ‘message’ => ‘Pelayaran tidak dikenali.’]);
exit;
}
echo json_encode(track_resi($CARRIERS[$key], $resi));
exit;
}
?>
Lacak kiriman kontainer Anda dari 6 pelayaran domestik dalam satu tempat.
Masukkan nomor resi, kontainer, atau B/L — pilih pelayarannya, biar sistem yang cari statusnya.
Track & Trace
Sea Freight
Air Freight
Land Freight
Hasil pelacakan akan muncul di sini.

