English · ภาษาไทย
A tiny self-contained payment terminal: an ESP32 with a 2.8" touch screen that displays a Thai PromptPay QR generated through Stripe, then flips to a green success screen (with a beep!) the moment the customer's payment lands.
boot ─▶ WiFi ─▶ enter amount on touch keypad ─▶ create QR via Stripe ─▶ show QR
▲ │ poll every 3 s
│ tap screen ▼
◀──────────────────────────────────── PAYMENT RECEIVED ✓
- Enter the amount on the on-screen keypad (minimum 10 THB — Stripe's floor for PromptPay)
- The board creates a PaymentIntent (
amount,currency=thb,payment_method_types[]=promptpay,confirm=true) — one call to the fully documented Stripe API, and the response already contains the EMVCo QR payload - The QR renders on the white card; the board polls the PaymentIntent every 3 seconds
succeeded→ full-screen green check + beep. Tap to return to the keypad.
No Payment Link needed — a single secret key is enough to take payments.
The library also keeps a legacy
createQr()that derives the QR from a Payment Link via Stripe's undocumented checkout endpoints. It breaks withpayment_method_types_mismatchwhen the link doesn't offer PromptPay — prefercreateQrDirect().
All three values come from the Stripe Dashboard:
| Value | Where |
|---|---|
pk_live_... publishable key |
Developers → API keys (only needed for the legacy Payment Link path) |
sk_live_... secret key |
Developers → API keys (click Reveal) |
plink_... payment link id |
(optional, legacy path only) Payment Links → create one — the id is in its details |
PromptPay must be enabled in Settings → Payment methods (Thai Stripe accounts).
-
Keys live in
include/secrets.h, which is gitignored. Copy the template and fill it in:cp include/secrets.h.example include/secrets.h
-
The secret key can move money-related state on your account. If it ever leaks — a screenshot, a pastebin, a committed file — roll it immediately: Dashboard → Developers → API keys → ⋯ → Roll key.
-
Better: instead of
sk_live_..., create a restricted key (Developers → API keys → Create restricted key) with only PaymentIntents read/write — that's all this project needs. -
TLS note: the firmware uses
setInsecure()(no certificate pinning), standard practice on ESP32 hobby projects but worth knowing.
Runs on a 2.8" ESP32 display board of the CYD family (pinout-compatible with the well-documented ESP32-2432S028R). Everything is on the board — screen, touch, speaker for the payment beep. No wiring.
👉 The exact board used: Shopee listing
If colors look inverted remove
-D TFT_INVERSION_ON=1fromplatformio.ini; if touch is offset adjusttouch.setCal(...)insrc/main.cpp.
git clone https://github.com/moomdate/CYD-PromptPay-Terminal.git
cd CYD-PromptPay-Terminal
cp include/secrets.h.example include/secrets.h # fill in WiFi + Stripe values
pio run -t uploadThe screen walks through: connecting WiFi → amount keypad → QR screen with the amount → success screen. Errors show Stripe's message with a tap-to-retry.
Configuration in include/secrets.h:
| Setting | Meaning |
|---|---|
UI_LANGUAGE |
on-screen language: LANG_TH or LANG_EN (Thai Sarabun font is embedded in the firmware) |
AMOUNT_SATANG |
default amount when nothing is typed, in satang (1000 = 10.00 THB) |
The Stripe logic is a standalone library published on the PlatformIO Registry (source) — use it in any project:
lib_deps = moomdate/StripePromptPay @ ^1.0.0#include <StripePromptPay.h>
StripePromptPay stripe;
stripe.begin(STRIPE_PK, STRIPE_SK);
if (stripe.createQrDirect(1000, "you@example.com")) { // 10.00 THB
render(stripe.qrData()); // EMVCo payload for any QR lib
String st = stripe.paymentStatus(); // "requires_action" → "succeeded"
}
Serial.println(stripe.lastError()); // populated on failureIt has no display dependencies — just WiFi + ArduinoJson — so it works on any ESP32.
include/
├── secrets.h.example # template — copy to secrets.h (gitignored)
src/
├── main.cpp # UI: amount keypad, QR card, polling, success screens
├── lang.h # EN/TH string tables (UI_LANGUAGE)
├── fonts/ # Sarabun (OFL) smooth fonts with Thai glyphs
tools/gen_vlw.py # regenerate the fonts from any OFL TTF
Stripe logic comes from the StripePromptPay registry package.
