</>

Developer Hub & REST API

Sinkronisasi Otomatis Google Sheets via GAS

Overview Alur Data 1. Script GAS & Throttling 2. Langkah Deploy GAS 3. Live Test Webhook
Webhook Google Sheets Auto-Sync

Simpan Otomatis Data Pesanan, Booking & Custom Fields ke Google Sheets

Setiap kali terjadi transaksi, booking baru, atau reservasi tiket (termasuk seluruh Custom Fields yang diset oleh store owner), data langsung dikirim via Webhook HTTP POST dan tercatat rapi baris per baris di spreadsheet Google Sheets Anda secara gratis tanpa langganan Zapier/Make.

1. Kode Google Apps Script (GAS) Lengkap (Dilengkapi Pembatas Frekuensi / Throttling)

Salin kode JavaScript Google Apps Script di bawah ini ke editor Script spreadsheet Anda (Extensions > Apps Script). Script ini otomatis mendeteksi kolom header, mengekstrak Custom Fields dinamis, merinci item pesanan, dan mencakup Throttling & Anti-Duplicate Debounce agar kuota eksekusi Google Apps Script Anda tidak cepat habis.

Code.gs (Google Apps Script)
// ============================================================
// GOOGLE APPS SCRIPT (GAS) - TOKO PLATFORM WEBHOOK RECEIVER
// Fitur: Secret Key Toko + Anti-Race Lock + Throttling & Debounce
// ============================================================

// [PENTING] Masukkan Master API Key toko Anda di sini sebagai pengaman:
var STORE_SECRET_KEY = "MASUKKAN_MASTER_API_KEY_TOKO_ANDA"; // Contoh: "RUMREN-SECURE-KEY-2026"

// Konfigurasi Pembatasan Frekuensi (Throttling & Cooldown)
var DEBOUNCE_SECONDS = 30; // Abaikan duplicate trigger order yang sama dalam 30 detik
var MIN_INTERVAL_MS = 1500; // Jeda minimum antar-penulisan baris (1.5 detik)

function doPost(e) {
  // 1. CEGAH TABRAKAN DATA DENGAN SCRIPT LOCK (Antrean s.d 30 detik)
  var lock = LockService.getScriptLock();
  var lockAcquired = lock.tryLock(30000);
  if (!lockAcquired) {
    return ContentService.createTextOutput(JSON.stringify({
      status: "busy",
      message: "Server Google Apps Script sedang sibuk memproses antrean lain. Silakan coba kembali sesaat lagi."
    })).setMimeType(ContentService.MimeType.JSON);
  }

  try {
    var rawData = e.postData.contents;
    var payload = JSON.parse(rawData);

    // 2. VALIDASI KEAMANAN SECRET KEY TOKO
    var incomingKey = (payload.secret_key) || (e.parameter && e.parameter.key) || "";
    if (STORE_SECRET_KEY && STORE_SECRET_KEY !== "MASUKKAN_MASTER_API_KEY_TOKO_ANDA") {
      if (incomingKey !== STORE_SECRET_KEY) {
        return ContentService.createTextOutput(JSON.stringify({
          status: "error",
          message: "Akses Ditolak: Secret API Key tidak valid atau tidak cocok dengan Toko ini."
        })).setMimeType(ContentService.MimeType.JSON);
      }
    }

    var data = payload.data || payload;
    var orderNo = data.order_no || data.booking_code || "";
    var cache = CacheService.getScriptCache();

    // 3. PEMBATASAN FREKUENSI (ANTI-SPAM & DEBOUNCE THROTTLING)
    // Jika event pembaruan untuk order ini sudah diproses dalam 30 detik, abaikan duplikatnya
    if (orderNo) {
      var dedupeKey = "dedupe_" + orderNo + "_" + (data.order_status || "") + "_" + (data.payment_status || "");
      if (cache.get(dedupeKey)) {
        return ContentService.createTextOutput(JSON.stringify({
          status: "ignored",
          message: "Pembaruan diabaikan: Pesanan " + orderNo + " sudah dicatat dalam " + DEBOUNCE_SECONDS + " detik terakhir (Debounce Throttling aktif)."
        })).setMimeType(ContentService.MimeType.JSON);
      }
      cache.put(dedupeKey, "1", DEBOUNCE_SECONDS);
    }

    // Cooldown interval antar penulisan sheet untuk melindungi kuota Google Apps Script
    var lastHitKey = "last_hit_" + (payload.store_username || "toko");
    var lastHit = cache.get(lastHitKey);
    var nowMs = new Date().getTime();
    if (lastHit && (nowMs - Number(lastHit)) < MIN_INTERVAL_MS) {
      Utilities.sleep(MIN_INTERVAL_MS - (nowMs - Number(lastHit)));
    }
    cache.put(lastHitKey, String(new Date().getTime()), 60);

    var doc = SpreadsheetApp.getActiveSpreadsheet();
    var sheet = doc.getSheetByName("Pesanan & Booking");

    // 4. JIKA SHEET BELUM ADA, BUAT OTOMATIS BESERTA HEADER LENGKAP
    if (!sheet) {
      sheet = doc.insertSheet("Pesanan & Booking");
      var headers = [
        "Waktu Transaksi",
        "No. Pesanan",
        "Kode Booking / E-Tiket",
        "Nama Pelanggan",
        "WhatsApp / Telepon",
        "Email",
        "No. KTP / Identitas",
        "Titik Jemput / Lokasi",
        "Tipe Order",
        "Tanggal Mulai",
        "Tanggal Selesai",
        "Sesi Waktu",
        "Meja / Kamar / Unit",
        "Daftar Item / Tiket",
        "Subtotal",
        "Total Bayar",
        "Metode Pembayaran",
        "Status Pembayaran",
        "Status Pesanan",
        "Custom Fields (JSON / Detail)",
        "Catatan Tambahan"
      ];
      sheet.appendRow(headers);
      sheet.getRange(1, 1, 1, headers.length).setFontWeight("bold").setBackground("#10B981").setFontColor("#FFFFFF");
      sheet.setFrozenRows(1);
    }

    // 5. FORMAT RINCIAN ITEM TIKET / PRODUK
    var itemsStr = "";
    if (data.items && Array.isArray(data.items)) {
      itemsStr = data.items.map(function(it) {
        return (it.product_name || it.name || "Item") + " (x" + (it.quantity || it.qty || 1) + " @ Rp" + Number(it.unit_price || it.price || 0).toLocaleString('id-ID') + ")";
      }).join("; ");
    }

    // 6. FORMAT CUSTOM FIELDS DINAMIS TOKO
    var customFieldsStr = "";
    if (data.custom_fields) {
      if (typeof data.custom_fields === 'object') {
        customFieldsStr = Object.keys(data.custom_fields).map(function(k) {
          return k + ": " + data.custom_fields[k];
        }).join(" | ");
      } else {
        customFieldsStr = String(data.custom_fields);
      }
    }

    // 7. SUSUN & TAMBAHKAN BARIS TRANSAKSI BARU KE SPREADSHEET
    var row = [
      new Date().toLocaleString("id-ID", { timeZone: "Asia/Jakarta" }),
      data.order_no || "-",
      data.booking_code || "-",
      data.customer_name || data.name || "Pelanggan",
      data.customer_email || data.email || "-",
      data.id_number || "-",
      data.pickup_location || "-",
      data.order_type || "booking",
      data.start_date || data.booking_start_date || "-",
      data.end_date || data.booking_end_date || "-",
      data.time_slot || data.booking_time_slot || "-",
      data.unit_number || data.table_number || "-",
      itemsStr,
      Number(data.subtotal || data.total_amount || 0),
      Number(data.total_amount || 0),
      data.payment_method || "transfer",
      data.payment_status || "paid",
      data.order_status || "confirmed",
      customFieldsStr,
      data.notes || "-"
    ];

    sheet.appendRow(row);

    return ContentService.createTextOutput(JSON.stringify({
      status: "success",
      message: "Data transaksi toko berhasil diamankan & dicatat ke Google Sheets"
    })).setMimeType(ContentService.MimeType.JSON);

  } catch (error) {
    return ContentService.createTextOutput(JSON.stringify({
      status: "error",
      message: error.toString()
    })).setMimeType(ContentService.MimeType.JSON);
  } finally {
    lock.releaseLock();
  }
}

2. Panduan 4 Langkah Deploy Web App Google Sheets

1
Buka Google Sheets & Apps Script
Buat Google Spreadsheet baru di Google Drive Anda. Klik menu Extensions (Ekstensi) > Apps Script.
2
Tempelkan Kode Script di Atas
Hapus seluruh kode bawaan pada Code.gs, lalu paste kode JavaScript di Langkah 1. Tekan Save (Ctrl+S).
3
Deploy sebagai Web App (Akses Publik)
Klik tombol biru Deploy > New deployment di pojok kanan atas:
• Select type: Web app
• Execute as: Me (email Anda)
• Who has access: Anyone (Siapa saja) (Wajib dipilih agar server toko bisa mengirim data tanpa login Google).
4
Salin Web App URL ke Pengaturan Toko
Salin URL Web App yang dihasilkan (contoh: https://script.google.com/macros/s/.../exec) dan masukkan ke menu Admin > Pengaturan Toko > Webhook Google Sheets atau gunakan playground di bawah untuk uji coba.

3. Live Webhook Tester (Kirim Data Uji Coba Langsung ke Spreadsheet Anda)