Use Cases

Otomatisasi Checkout Multi-Langkah dengan Solve CAPTCHA

Skrip regression test checkout yang berhenti begitu CAPTCHA muncul adalah keluhan klasik tim QA e-commerce. Solusinya: satu automator Python yang menyambungkan keranjang → pengiriman → pembayaran → konfirmasi, dan otomatis menyelesaikan reCAPTCHA v2/v3 maupun Cloudflare Turnstile lewat CaptchaAI di tiap langkah.

Tanpa penanganan otomatis, skrip Selenium biasa berhenti total begitu CAPTCHA muncul di tengah alur — padahal pada checkout modern, tantangan itu bisa muncul di lima titik berbeda dalam satu proses, bukan cuma sekali di awal. Panduan ini membahas arsitektur automator lengkap, class Python yang siap dipakai, dan skenario kegagalan yang paling sering ditemui tim QA di lapangan.


Tahap Checkout yang Paling Sering Memicu CAPTCHA

Tahap Jenis CAPTCHA Alasan
Tambah ke keranjang reCAPTCHA v3 (invisible) Deteksi bot
Login / checkout tamu reCAPTCHA v2 Verifikasi akun
Alamat pengiriman Cloudflare Turnstile Pembatasan rate
Halaman pembayaran reCAPTCHA v2/v3 Pencegahan fraud
Konfirmasi pesanan reCAPTCHA v2 Validasi akhir

Tabel di atas mencerminkan pola umum platform e-commerce; konfigurasi tiap situs bisa sedikit berbeda, tapi kelima titik ini konsisten muncul di hampir semua alur checkout modern — sehingga automator harus siap menangani CAPTCHA di mana pun, bukan cuma di satu langkah.


Kenapa Ini Penting bagi Tim E-commerce di Indonesia

Toko online lokal menambah lapisan CAPTCHA menjelang Harbolnas (12.12) atau flash sale — justru saat tim QA paling butuh regression test berulang kali. Model harga thread CaptchaAI cocok buat kontraktor QA lepas dengan beberapa klien: BASIC ($15/bulan, 5 thread) sudah cukup, tanpa biaya per-solve yang membengkak menjelang big sale.

Volume test case biasanya melonjak drastis seminggu sebelum tanggal kembar, karena tim harus memvalidasi ulang seluruh alur checkout — mulai dari banner promo sampai halaman pembayaran — di berbagai kombinasi device dan browser. Karena harga CaptchaAI berbasis thread, bukan per-solve, biaya tidak ikut membengkak walau jumlah run regression test naik berkali-kali lipat menjelang big sale. Satu catatan penting: selalu pakai akun dan kartu uji di lingkungan sandbox untuk automator ini, jangan pernah data pelanggan asli — sejalan dengan semangat UU Pelindungan Data Pribadi (UU 27/2022).


Arsitektur Alur Checkout Lima Tahap

Pemeriksaan CAPTCHA berjalan di tiap transisi, bukan cuma sekali di awal:

┌────────┐    ┌───────────┐    ┌──────────┐    ┌─────────┐    ┌──────────┐
│  Cart  │───▶│  Shipping │───▶│ Payment  │───▶│ Review  │───▶│ Confirm  │
│        │    │  Address  │    │  Info    │    │  Order  │    │          │
└────────┘    └───────────┘    └──────────┘    └─────────┘    └──────────┘
     │              │               │               │              │
     ▼              ▼               ▼               ▼              ▼
  [CAPTCHA?]    [CAPTCHA?]     [CAPTCHA?]      [CAPTCHA?]     [CAPTCHA?]

Setiap panah pada diagram mewakili satu transisi halaman, dan _solve_if_captcha() dipanggil persis di titik itu — supaya automator tidak pernah berasumsi halaman berikutnya bebas CAPTCHA hanya karena halaman sebelumnya bebas.


Membangun Automator Checkout di Python

CaptchaSolver membungkus in.php/res.php; CheckoutAutomator menjalankan Selenium lewat lima langkah dan otomatis solve saat widget terdeteksi.

Class CaptchaSolver menangani seluruh siklus hidup task: submit ke in.php, polling res.php sampai status berubah jadi 1, lalu melempar exception kalau task gagal atau timeout setelah 60 kali percobaan. Class CheckoutAutomator memecah tiap langkah checkout jadi method sendiri (add_to_cart, fill_shipping, fill_payment, confirm_order), dan memanggil _solve_if_captcha() di awal maupun akhir tiap method — supaya widget yang muncul kapan saja tetap tertangani sebelum automator lanjut ke langkah berikutnya. Method _solve_if_captcha() sendiri mendeteksi tipe CAPTCHA lewat selector CSS: elemen .cf-turnstile menandakan Turnstile, sementara atribut data-sitekey dikombinasikan dengan grecaptcha.execute di HTML menandakan reCAPTCHA v3 invisible — kalau tidak, automator jatuh ke reCAPTCHA v2 biasa.

Struktur Kelas CheckoutAutomator

import time
import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class CaptchaSolver:
    BASE = "https://ocr.captchaai.com"

    def __init__(self, api_key):
        self.api_key = api_key

    def solve(self, params, initial_wait=10):
        params["key"] = self.api_key
        params["json"] = 1
        resp = requests.post(f"{self.BASE}/in.php", data=params).json()
        if resp["status"] != 1:
            raise Exception(resp["request"])
        task_id = resp["request"]
        time.sleep(initial_wait)
        for _ in range(60):
            result = requests.get(
                f"{self.BASE}/res.php",
                params={"key": self.api_key, "action": "get", "id": task_id, "json": 1},
            ).json()
            if result["request"] == "CAPCHA_NOT_READY":
                time.sleep(5)
                continue
            if result["status"] == 1:
                return result["request"]
            raise Exception(result["request"])
        raise TimeoutError("Timed out")

class CheckoutAutomator:
    def __init__(self, api_key):
        self.solver = CaptchaSolver(api_key)
        self.driver = webdriver.Chrome()
        self.wait = WebDriverWait(self.driver, 15)

    def _find(self, selector):
        return self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))

    def _click(self, selector):
        self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, selector))).click()

    def _fill(self, selector, value):
        el = self._find(selector)
        el.clear()
        el.send_keys(value)

    def _solve_if_captcha(self):
        """Check for and solve any CAPTCHA on the current page."""
        html = self.driver.page_source
        page_url = self.driver.current_url

        # Turnstile
        turnstile = self.driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile")
        if turnstile:
            sitekey = turnstile[0].get_attribute("data-sitekey")
            token = self.solver.solve({
                "method": "turnstile",
                "sitekey": sitekey,
                "pageurl": page_url,
            })
            self.driver.execute_script(
                f'document.querySelector("[name=cf-turnstile-response]").value = "{token}";'
            )
            return "turnstile"

        # reCAPTCHA
        recaptcha = self.driver.find_elements(By.CSS_SELECTOR, "[data-sitekey]")
        if recaptcha and "recaptcha" in html.lower():
            sitekey = recaptcha[0].get_attribute("data-sitekey")

            # Check if v3 (invisible)
            if "grecaptcha.execute" in html:
                token = self.solver.solve({
                    "method": "userrecaptcha",
                    "googlekey": sitekey,
                    "pageurl": page_url,
                    "version": "v3",
                    "action": "checkout",
                }, initial_wait=20)
            else:
                token = self.solver.solve({
                    "method": "userrecaptcha",
                    "googlekey": sitekey,
                    "pageurl": page_url,
                })

            self.driver.execute_script(
                f'document.querySelector("[name=g-recaptcha-response]").value = "{token}";'
            )
            return "recaptcha"

        return None

    # --- Checkout Steps ---

    def add_to_cart(self, product_url, quantity=1):
        """Step 1: Navigate to product and add to cart."""
        self.driver.get(product_url)
        time.sleep(2)

        # Set quantity if field exists
        qty_fields = self.driver.find_elements(By.CSS_SELECTOR, "input[name='quantity']")
        if qty_fields:
            qty_fields[0].clear()
            qty_fields[0].send_keys(str(quantity))

        self._solve_if_captcha()
        self._click("[data-action='add-to-cart'], .add-to-cart, #add-to-cart")
        time.sleep(2)
        return True

    def fill_shipping(self, address):
        """Step 2: Fill shipping address."""
        self._solve_if_captcha()

        field_map = {
            "first_name": "#shipping-first-name, [name='firstName']",
            "last_name": "#shipping-last-name, [name='lastName']",
            "address": "#shipping-address, [name='address1']",
            "city": "#shipping-city, [name='city']",
            "state": "#shipping-state, [name='state']",
            "zip": "#shipping-zip, [name='postalCode']",
            "phone": "#shipping-phone, [name='phone']",
        }

        for field, selectors in field_map.items():
            if field in address:
                for selector in selectors.split(", "):
                    elements = self.driver.find_elements(By.CSS_SELECTOR, selector)
                    if elements:
                        elements[0].clear()
                        elements[0].send_keys(address[field])
                        break

        self._solve_if_captcha()
        self._click("[data-step='shipping-submit'], .continue-to-payment")
        time.sleep(2)
        return True

    def fill_payment(self, payment):
        """Step 3: Fill payment information."""
        self._solve_if_captcha()

        # Handle iframe for card fields (common pattern)
        iframes = self.driver.find_elements(By.CSS_SELECTOR, "iframe[name*='card']")
        if iframes:
            self.driver.switch_to.frame(iframes[0])
            self._fill("input[name='cardnumber']", payment["card_number"])
            self.driver.switch_to.default_content()

            if len(iframes) > 1:
                self.driver.switch_to.frame(iframes[1])
                self._fill("input[name='exp-date']", payment["expiry"])
                self.driver.switch_to.default_content()

            if len(iframes) > 2:
                self.driver.switch_to.frame(iframes[2])
                self._fill("input[name='cvc']", payment["cvv"])
                self.driver.switch_to.default_content()
        else:
            # Direct fields
            self._fill("[name='cardNumber'], #card-number", payment["card_number"])
            self._fill("[name='expiry'], #card-expiry", payment["expiry"])
            self._fill("[name='cvv'], #card-cvv", payment["cvv"])

        self._solve_if_captcha()
        self._click("[data-step='payment-submit'], .continue-to-review")
        time.sleep(2)
        return True

    def confirm_order(self):
        """Step 4: Review and confirm order."""
        self._solve_if_captcha()
        self._click("[data-step='confirm'], .place-order, #place-order")
        time.sleep(3)

        # Check for confirmation
        html = self.driver.page_source.lower()
        if "order confirmed" in html or "thank you" in html or "confirmation" in html:
            return True
        return False

    def run_checkout(self, product_url, address, payment, quantity=1):
        """Run selesaikan QA checkout flow."""
        steps = [
            ("Add to cart", lambda: self.add_to_cart(product_url, quantity)),
            ("Proceed to checkout", lambda: self._click(".checkout-btn, a[href*='checkout']")),
            ("Fill shipping", lambda: self.fill_shipping(address)),
            ("Fill payment", lambda: self.fill_payment(payment)),
            ("Confirm order", lambda: self.confirm_order()),
        ]

        for step_name, step_fn in steps:
            print(f"Step: {step_name}...")
            try:
                result = step_fn()
                print(f"  ✓ {step_name} complete")
            except Exception as e:
                print(f"  ✗ {step_name} failed: {e}")
                return False
        return True

    def close(self):
        self.driver.quit()

Menjalankan Automator

Panggil run_checkout() dengan URL produk, alamat, dan data kartu:

automator = CheckoutAutomator("YOUR_API_KEY")

try:
    success = automator.run_checkout(
        product_url="https://store.example.com/product/widget-pro",
        address={
            "first_name": "Jane",
            "last_name": "Smith",
            "address": "123 Test Street",
            "city": "San Francisco",
            "state": "CA",
            "zip": "94102",
            "phone": "415-555-0100",
        },
        payment={
            "card_number": "4111111111111111",
            "expiry": "12/26",
            "cvv": "123",
        },
        quantity=1,
    )
    print(f"Checkout {'succeeded' if success else 'failed'}")
finally:
    automator.close()

Method run_checkout() menjalankan kelima langkah secara berurutan dan berhenti begitu satu langkah gagal — sengaja tidak dibuat auto-retry di level ini supaya log kegagalan tetap jelas menunjukkan langkah mana yang bermasalah. Untuk retry otomatis, bungkus tiap langkah dengan retry_step_with_captcha() di bagian berikutnya.


Skenario yang Sering Bikin Automator Checkout Gagal

CAPTCHA yang Muncul Lagi Setelah Validasi Form Gagal

Banyak toko menampilkan CAPTCHA baru tiap validasi gagal. Bungkus tiap langkah dengan retry:

def retry_step_with_captcha(self, step_fn, max_retries=2):
    for attempt in range(max_retries + 1):
        try:
            return step_fn()
        except Exception:
            if attempt < max_retries:
                self._solve_if_captcha()
                continue
            raise

Batasi max_retries ke angka kecil (dua atau tiga sudah cukup) — retry tanpa batas pada langkah yang gagal karena field wajib kosong cuma memboroskan quota thread tanpa pernah benar-benar berhasil.

CAPTCHA yang Hanya Tampil Sekali per Sesi

Sejumlah situs cuma menantang sekali per sesi, lalu mempercayai cookie untuk sisa alur. Jangan buka driver baru di tengah jalan. Praktik amannya: pakai satu instance webdriver.Chrome() untuk seluruh proses run_checkout() — jangan panggil driver.quit() lalu membuka sesi baru di tengah langkah, karena cookie sesi yang hilang memicu tantangan CAPTCHA ulang di langkah yang sebelumnya sudah terlewati.

Field Formulir Muncul Belakangan

Checkout satu halaman sering me-render field secara asinkron. Tunggu indikator yang tepat, bukan time.sleep():

def wait_for_step(self, indicator_selector, timeout=15):
    """Wait for a step to fully load before proceeding."""
    WebDriverWait(self.driver, timeout).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, indicator_selector))
    )

Pilih indicator_selector yang benar-benar menandakan langkah sudah siap diisi — misalnya tombol submit langkah berikutnya, bukan sekadar container yang muncul di DOM lebih dulu daripada field-field di dalamnya.


Masalah yang Paling Sering Muncul

Masalah Penyebab Solusi
Token CAPTCHA ditolak saat pembayaran Token kedaluwarsa Selesaikan CAPTCHA sesaat sebelum submit
Field kartu tidak terisi Field ada di iframe Stripe/Braintree Pindah ke iframe yang benar dulu
Checkout melempar balik ke login Sesi berakhir Tambahkan langkah login ulang
Pesanan gagal tanpa pesan error Ada field wajib kosong Cek pesan error tiap langkah

Sebagian besar masalah di atas bukan soal CaptchaAI, tapi soal timing — automator mengklik submit sebelum halaman, token, atau field benar-benar siap dipakai.


Pertanyaan Seputar Otomasi Checkout dengan CaptchaAI

Bisakah saya menguji checkout tanpa transaksi kartu asli?

Bisa. Platform e-commerce umumnya punya mode sandbox dengan kartu uji — CaptchaAI menyelesaikan CAPTCHA yang sama di sana maupun di produksi.

Berapa thread yang saya perlukan untuk skenario checkout paralel?

BASIC ($15/bulan, 5 thread) cukup untuk tim kecil; puluhan skenario di CI/CD sebaiknya naik ke ADVANCE ($90/bulan, 50 thread). Semua paket thread-based, solve tak terbatas.

Apakah action pada reCAPTCHA v3 harus persis sama dengan situs?

Ya — Google mencocokkan action saat solve dengan grecaptcha.execute() di halaman (di atas: "checkout"). Beda situs, sesuaikan stringnya, atau skor v3 turun.

Bagaimana kalau checkout toko saya satu halaman?

Isi semua field, lalu panggil _solve_if_captcha() sekali sebelum submit — gabungkan fill_shipping() dan fill_payment() jadi satu fungsi.

Bisa dijalankan dari server di Indonesia?

Bisa — automator ini cuma HTTP client dan Selenium biasa, jadi jalan normal dari region seperti AWS ap-southeast-3 (Jakarta) atau GCP asia-southeast2. Kalau latency ke situs target lebih tinggi dari biasanya, naikkan initial_wait dan timeout WebDriverWait secukupnya.


Panduan Terkait


Ingin automator checkout Anda jalan mulus tanpa terhenti di tengah CAPTCHA? Sambungkan API key CaptchaAI ke skrip di atas dan jalankan regression test kapan saja.

Komentar dinonaktifkan untuk artikel ini.