Build a Thai E-Commerce Checkout with Address Autocomplete API
If you've ever built an e-commerce checkout for Thai customers, you know the pain: 77 provinces, 930 districts, 7,452 subdistricts, and zip codes that don't map cleanly to anything. Customers abandon carts because filling in Thai addresses is a nightmare.
In this tutorial, we'll fix that in under 100 lines of JavaScript using the Thai Utility API — a free-tier API on RapidAPI that handles Thai addresses, phone validation, and even PromptPay QR code generation.
What We're Building
A checkout form that:
- Autocompletes Thai addresses as the user types
- Validates Thai phone numbers and detects the carrier (AIS, DTAC, TRUE, NT)
- Generates a PromptPay QR code so customers can pay instantly via mobile banking
API Base URL:
https://hubaiasia.com/thai-api
Get your free key: Thai Utility API on RapidAPI — 100 requests/day free
Setup
Grab your API key from RapidAPI. We'll use it in every request header.
const RAPID_API_KEY = "YOUR_RAPIDAPI_KEY_HERE";
const BASE_URL = "https://hubaiasia.com/thai-api";
const headers = {
"x-rapidapi-key": RAPID_API_KEY,
"x-rapidapi-host": "hubaiasia.com"
};
Part 1: Address Autocomplete
Thai addresses have a strict hierarchy: Province → District → Subdistrict → Zip Code. The API's /search endpoint lets you skip loading all 7,452 subdistricts upfront and instead query on demand.
Full-Text Search Across All Levels
async function searchAddress(keyword) {
const res = await fetch(
`${BASE_URL}/search?q=${encodeURIComponent(keyword)}`,
{ headers }
);
const data = await res.json();
return data.results; // Array of matching locations
}
// Example: user types "ลาดพร้าว"
const results = await searchAddress("ลาดพร้าว");
console.log(results);
// [
// { province: "กรุงเทพมหานคร", district: "ลาดพร้าว",
// subdistrict: "ลาดพร้าว", zip_code: "10230" },
// { province: "กรุงเทพมหานคร", district: "ลาดพร้าว",
// subdistrict: "จรเข้บัว", zip_code: "10230" },
// ...
// ]
Cascading Dropdowns (Province → District → Subdistrict)
For customers who prefer dropdowns, use the cascading endpoints:
// Step 1: Load all 77 provinces
async function getProvinces() {
const res = await fetch(`${BASE_URL}/provinces`, { headers });
return (await res.json()).provinces;
}
// Step 2: Get districts for the selected province
async function getDistricts(provinceId) {
const res = await fetch(
`${BASE_URL}/districts?province_id=${provinceId}`,
{ headers }
);
return (await res.json()).districts;
}
// Step 3: Get subdistricts (with zip codes!) for the selected district
async function getSubdistricts(districtId) {
const res = await fetch(
`${BASE_URL}/subdistricts?district_id=${districtId}`,
{ headers }
);
return (await res.json()).subdistricts;
// Each item includes zip_code — auto-fill the zip field!
}
Auto-fill Zip Code by Lookup
If a customer already knows their zip code:
async function lookupByZip(zipCode) {
const res = await fetch(`${BASE_URL}/zipcode/${zipCode}`, { headers });
const data = await res.json();
// Returns all matching province/district/subdistrict combos for that zip
return data.locations;
}
// Auto-populate the address form
const locations = await lookupByZip("10230");
// Fill province, district, subdistrict dropdowns automatically
Result: Customers type 5 digits and the rest fills itself. Cart abandonment drops.
Part 2: Phone Validation + Carrier Detection
Shipping carriers and SMS notifications need valid Thai phone numbers. The API validates the format and tells you which mobile carrier it belongs to.
async function validatePhone(phoneNumber) {
// Strip spaces/dashes before sending
const clean = phoneNumber.replace(/[\s\-]/g, "");
const res = await fetch(`${BASE_URL}/validate/phone/${clean}`, { headers });
return await res.json();
}
// Usage in your form
const result = await validatePhone("081-234-5678");
console.log(result);
// {
// valid: true,
// number: "0812345678",
// carrier: "AIS",
// type: "mobile"
// }
// Inline validation feedback
function handlePhoneInput(value) {
validatePhone(value).then(result => {
if (!result.valid) {
showError("กรุณากรอกเบอร์โทรศัพท์ที่ถูกต้อง");
} else {
showSuccess(`✓ ${result.carrier} — เบอร์ถูกต้อง`);
}
});
}
Supported carriers: AIS, DTAC, TRUE, NT (NTTD/CAT). This is useful for routing SMS OTPs through the cheapest carrier gateway.
Part 3: PromptPay QR Code Generation
PromptPay is Thailand's national instant payment system — virtually every Thai bank app can scan a QR code and transfer money in seconds. The API generates EMVCo-standard QR codes that work with all Thai banking apps.
async function generatePromptPayQR(phoneOrTaxId, amount) {
const res = await fetch(`${BASE_URL}/promptpay/qr`, {
method: "POST",
headers: {
...headers,
"Content-Type": "application/json"
},
body: JSON.stringify({
target: phoneOrTaxId, // Phone number or tax ID (13 digits)
amount: amount // Amount in THB (e.g., 299.00)
})
});
const data = await res.json();
return data.qr_image; // Base64 PNG — drop straight into <img src>
}
// On checkout: generate QR for order total
const qrBase64 = await generatePromptPayQR("0812345678", 599.00);
document.getElementById("qr-container").innerHTML =
`<img src="data:image/png;base64,${qrBase64}" alt="PromptPay QR" />
<p>สแกนจ่าย ฿599.00</p>`;
No payment gateway fees. No redirect. No card required. The customer opens their banking app, scans, and you get the money. For small Thai businesses, this alone is worth integrating.
Bonus: Thai National ID Validation
If your checkout collects ID for age-restricted products or tax invoices:
async function validateThaiId(idNumber) {
const res = await fetch(
`${BASE_URL}/validate/thai-id/${idNumber}`,
{ headers }
);
return await res.json();
// { valid: true/false, id: "1234567890123" }
}
The API validates using the official Thai ID checksum algorithm — no need to implement it yourself.
Putting It All Together
Here's the complete checkout flow in ~50 lines:
const api = {
headers: {
"x-rapidapi-key": "YOUR_KEY",
"x-rapidapi-host": "hubaiasia.com"
},
base: "https://hubaiasia.com/thai-api",
async get(path) {
const res = await fetch(`${this.base}${path}`, { headers: this.headers });
return res.json();
},
async post(path, body) {
const res = await fetch(`${this.base}${path}`, {
method: "POST",
headers: { ...this.headers, "Content-Type": "application/json" },
body: JSON.stringify(body)
});
return res.json();
}
};
// Address search with debounce
let debounceTimer;
document.getElementById("address-input").addEventListener("input", e => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(async () => {
if (e.target.value.length < 2) return;
const { results } = await api.get(`/search?q=${encodeURIComponent(e.target.value)}`);
renderAddressSuggestions(results);
}, 300);
});
// Phone validation on blur
document.getElementById("phone-input").addEventListener("blur", async e => {
const { valid, carrier } = await api.get(
`/validate/phone/${e.target.value.replace(/\D/g, "")}`
);
updatePhoneField(valid, carrier);
});
// Generate PromptPay QR on order confirm
async function onOrderConfirm(recipientPhone, totalAmount) {
const { qr_image } = await api.post("/promptpay/qr", {
target: recipientPhone,
amount: totalAmount
});
showQRModal(qr_image);
}
What's Covered by the Free Tier
The Thai Utility API free tier gives you 100 requests/day — enough for development and low-traffic stores. Paid plans on RapidAPI scale from there.
| Endpoint | Use Case |
|---|---|
GET /provinces |
Load province dropdown once, cache client-side |
GET /districts |
On province select |
GET /subdistricts |
On district select (includes zip codes) |
GET /zipcode/:code |
Auto-fill from zip input |
GET /search |
Real-time address autocomplete |
GET /validate/thai-id/:id |
Tax invoice / age verification |
GET /validate/phone/:number |
Phone validation + carrier detection |
POST /promptpay/qr |
Payment QR on checkout |
Final Thoughts
Building for Thai customers used to mean maintaining a local database of 7,452 subdistricts or paying for expensive localization libraries. With the Thai Utility API, you get clean REST endpoints, free tier access, and PromptPay QR generation that most payment solutions don't offer out of the box.
Get started: Thai Utility API on RapidAPI
Have questions or built something cool with it? Drop a comment below!