# ฟอร์ม HTML

หนึ่งในวิธีเพิ่มการชำระเงินคริปโตให้กับเว็บไซต์ ร้านค้าออนไลน์ หรือบอท ลูกค้ากดปุ่ม แล้วหน้าชำระเงินพร้อมใบแจ้งหนี้จะเปิดขึ้น

ฟอร์มส่งได้เฉพาะแบบ POST ไปยัง `https://dash.bitsby.app/invoices/form` และประกอบด้วยสองฟิลด์:

| ฟิลด์ | เก็บอะไร |
| --- | --- |
| `data` | base64url ของ JSON ที่มีฟิลด์ของใบแจ้งหนี้ |
| `signature` | HMAC-SHA256 ของสตริง `data` ความยาว 64 ตัวอักษร hex |

ในแต่ละขณะ โปรเจกต์หนึ่งมีใบแจ้งหนี้ที่ยังไม่ชำระซึ่งสร้างด้วยวิธีนี้ได้ไม่เกิน 50 ใบ

## ตัวอย่างฟอร์ม

```html
<form method="post" action="https://dash.bitsby.app/invoices/form" target="_blank">
    <input type="hidden" name="data" value="eyJwcm9qZWN0SWQiOiJhMWIyYzNkNC01ZTZm...">
    <input type="hidden" name="signature" value="f77d6da1be35bd2900e0bfed9f202b04...">
    <button type="submit">Pay</button>
</form>
```

เซิร์ฟเวอร์ของร้านค้าเป็นผู้กรอกค่าทั้งสองตอนเรนเดอร์หน้า ส่วนคีย์ลับไม่ปรากฏในมาร์กอัปเลย

แอตทริบิวต์ `target="_blank"` ไม่บังคับ: เมื่อใช้ ตะกร้าสินค้าจะยังเปิดอยู่ในแท็บเดิม

## วิธีสร้างคอนเทนเนอร์

1. สร้างออบเจ็กต์ JSON ที่มีฟิลด์ของใบแจ้งหนี้
2. เข้ารหัสเป็น base64url ผลลัพธ์คือสตริง `data`
3. คำนวณ `signature = HMAC-SHA256(data, formSecret)`

สตริง `data` ถูกเซ็นทั้งก้อน ดังนั้นลำดับคีย์ใน JSON การเยื้อง และรูปแบบการ escape อักขระ Unicode จึงไม่มีผล ฝั่งผู้รับยังยอมรับ base64 มาตรฐานด้วย ทั้งแบบมีและไม่มี padding

อย่าสร้าง `data` ใหม่หลังจากเซ็นแล้ว: หากมีแม้แต่ไบต์เดียวเปลี่ยนไป ต้องคำนวณลายเซ็นใหม่

วิธีคำนวณลายเซ็น พร้อมตัวอย่างสำเร็จรูปในสี่ภาษา อธิบายไว้ใน[ลายเซ็นฟอร์ม HTML](./form-signature.md)

## คีย์ภายใน data

| คีย์ | จำเป็น | เก็บอะไร |
| --- | --- | --- |
| `projectId` | ใช่ | ID ของโปรเจกต์จากการตั้งค่าในแดชบอร์ด เป็น uuid |
| `amountFiat` | ใช่ | จำนวนเงินตั้งแต่ 1 ถึง 100,000 ทศนิยมไม่เกินสองตำแหน่ง ส่งเป็นสตริง `"10.50"` หรือตัวเลข `10.5` ก็ได้ |
| `currencyFiat` | ใช่ | สกุลเงินเฟียต: USD, EUR หรือ RUB |
| `timeToPay` | ใช่ | กำหนดเวลาชำระเงินเป็นชั่วโมง: 0.5, 1, 3, 6, 12 ส่งเป็นสตริงหรือตัวเลขก็ได้ |
| `description` | ไม่ | คำอธิบายสำหรับลูกค้า ยาวได้ไม่เกิน 1,000 ตัวอักษร |
| `serviceData` | ไม่ | ตัวระบุคำสั่งซื้อฝั่งร้านค้า ยาวได้ไม่เกิน 1,000 ตัวอักษร ไม่แสดงให้ลูกค้าเห็นแต่มองเห็นได้ในซอร์สโค้ดของฟอร์ม — อย่าใส่ข้อมูลอ่อนไหวไว้ที่นี่ |
| `output` | ไม่ | `errors` — แสดงรายละเอียดของข้อผิดพลาดการตรวจสอบ |

คีย์ที่ไม่บังคับจะละไว้จาก JSON ก็ได้ — มีผลเหมือนสตริงว่าง คีย์เรียงลำดับใดก็ได้ ส่วนคีย์ที่ไม่รู้จักผู้รับจะไม่สนใจ

ค่าเป็นสตริงหรือตัวเลขของ JSON ส่วน Boolean อาร์เรย์ และออบเจ็กต์ซ้อนไม่ถือเป็นค่าฟิลด์ และจะมาถึงเป็นสตริงว่าง

ตัวอย่างเนื้อหาของ `data`:

```json
{
    "projectId": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "amountFiat": 10.5,
    "currencyFiat": "USD",
    "timeToPay": 1,
    "description": "Order #7, delivery",
    "serviceData": "order-7"
}
```

ในตัวอย่างนี้ จำนวนเงินและกำหนดเวลาชำระเงินส่งเป็นตัวเลข จะส่งเป็นสตริงก็ยอมรับเช่นกัน ส่วนคีย์ `output` ที่ไม่บังคับไม่ได้ตั้งค่าไว้เลย

## รูปแบบจำนวนเงิน

ใช้ได้เฉพาะตัวเลขและจุดทศนิยม ทศนิยมไม่เกินสองตำแหน่ง: `10`, `10.00`, `1000.50` ช่องว่างหัวท้ายจะถูกตัดออก

รูปแบบอื่นถูกปฏิเสธทั้งหมด — ตัวคั่นหลักพัน จุลภาคแทนจุด สัญกรณ์เลขชี้กำลัง เครื่องหมายหน้าเลข สัญลักษณ์สกุลเงิน ไม่มีการปัดเศษ: ทศนิยมตำแหน่งที่สามไม่ถูกตัดทิ้ง แต่ทำให้ถูกปฏิเสธ

## คีย์ลับของฟอร์ม

คีย์ลับนี้อยู่ในการตั้งค่าโปรเจกต์ในแดชบอร์ด ในฟิลด์คีย์ลับของฟอร์ม (Form secret) เซิร์ฟเวอร์เป็นผู้ออกให้ตอนสร้างโปรเจกต์ คุณตั้งค่าเองไม่ได้ ทำได้เพียงออกคีย์ลับใหม่ — เช่น ในกรณีที่คีย์รั่วไหล

คีย์ลับต้องอยู่บนเซิร์ฟเวอร์ของร้านค้าเท่านั้น หากไปปรากฏใน HTML หน้าร้าน ลายเซ็นก็หมดความหมาย

คีย์ลับของฟอร์มกับคีย์ลับของ Webhook เป็นคนละคีย์กัน อย่าสับสน

## ตัวระบุคำสั่งซื้อ

กรอก `serviceData` เสมอ: ใส่ตัวระบุคำสั่งซื้อของคุณเองไว้ที่นั่น ค่านี้จะถูกส่งกลับมาใน[การแจ้งเตือนการชำระเงิน](../../webhook-url/index.md) — ร้านค้าใช้ค่านี้ค้นหาคำสั่งซื้อ

`serviceData` ที่กรอกไว้ช่วยป้องกันใบซ้ำ การส่งฟอร์มซ้ำหรือดับเบิลคลิกปุ่มจะไม่สร้างใบแจ้งหนี้ใบที่สอง: ลูกค้าจะถูกพาไปยังใบเดิมที่ยังไม่ชำระ การจับคู่พิจารณาจากฟิลด์ของคำสั่งซื้อ — โปรเจกต์ จำนวนเงิน สกุลเงิน คำอธิบาย และ `serviceData` — ไม่ใช่จากไบต์ของ `data`

ค่านี้ต้องไม่ซ้ำกันในแต่ละคำสั่งซื้อ หากใช้ค่าเดียวกัน ลูกค้าคนที่สองจะไปเจอใบแจ้งหนี้ที่ยังไม่ชำระของลูกค้าคนแรก

`serviceData` ที่ว่างเปล่าทำให้การป้องกันนี้หายไป: ไม่มีอะไรให้ใช้จดจำการส่งซ้ำ และการส่งทุกครั้งจะสร้างใบแจ้งหนี้ใหม่ ใบซ้ำกินโควตา 50 ใบที่ยังไม่ชำระ และเมื่อมีการชำระใบใดใบหนึ่ง ร้านค้าก็ไม่มีอะไรใช้ผูกการชำระเงินเข้ากับคำสั่งซื้อ

## ข้อผิดพลาดและโหมดดีบัก

ฟอร์มผ่านการตรวจสอบสองขั้น: ขั้นแรกคือลายเซ็นและคอนเทนเนอร์ จากนั้นคือค่าของฟิลด์

| เกิดอะไรขึ้น | บริการแสดงอะไร |
| --- | --- |
| ลายเซ็นไม่ตรง คอนเทนเนอร์อ่านไม่ได้ โปรเจกต์เป็นของคนอื่นหรือไม่มีอยู่ | ข้อผิดพลาดทั่วไป ไม่ระบุสาเหตุ |
| ลายเซ็นตรง แต่ฟิลด์กรอกไม่ถูกต้อง | ข้อผิดพลาดทั่วไป หากใช้ `"output":"errors"` — ข้อผิดพลาดแบบละเอียด |
| ผ่านทั้งสองขั้น | หน้าชำระเงินพร้อมใบแจ้งหนี้ |

การตรวจสอบขั้นแรกไม่เปิดเผยสาเหตุเลย ไม่ว่าตั้งค่าอย่างไร

คู่ `"output":"errors"` ใน JSON เปิดรายละเอียดของการตรวจสอบขั้นที่สอง: บริการจะระบุชื่อฟิลด์ แสดงรายการค่าที่อนุญาต และแจ้งเมื่อถึงเพดาน 50 ใบที่ยังไม่ชำระ คีย์นี้อยู่ภายในคอนเทนเนอร์ที่เซ็นแล้ว จึงไม่มีใครแทรกจากภายนอกได้

ระหว่างตั้งค่าฟอร์ม ให้ใส่ `"output":"errors"` ไว้ใน JSON เมื่อตั้งค่าเสร็จแล้ว ให้ลบคีย์นี้ออกและสร้างคอนเทนเนอร์ใหม่

หากลายเซ็นเองไม่ตรง ให้เทียบ `data` และลายเซ็นของคุณกับชุดอ้างอิงใน[ลายเซ็นฟอร์ม HTML](./form-signature.md)

## ขั้นตอนถัดไป

ใบแจ้งหนี้ที่สร้างด้วยฟอร์มได้รับการประมวลผลแบบเดียวกับใบที่สร้างผ่าน API หรือแดชบอร์ด:

* **การยืนยันการชำระเงิน**จะมาถึงที่ [Webhook URL](../../webhook-url/index.md) ตรวจสอบ[ลายเซ็นของการแจ้งเตือน](../../webhook-url/signature-verification.md)ด้วยคีย์ลับของ Webhook และส่งมอบคำสั่งซื้อโดยอิงจากการแจ้งเตือนนี้เท่านั้น
* **การพาลูกค้ากลับไปยังเว็บไซต์ของคุณ**ตั้งค่าผ่าน URL เมื่อสำเร็จ (Successful URL) และ URL เมื่อไม่สำเร็จ (Unsuccessful URL) — ดูส่วน “การพาลูกค้ากลับไปยังเว็บไซต์ร้านค้า” บนหน้า[วงจรชีวิตของใบแจ้งหนี้](../../invoice-lifecycle.md) การมาถึง URL เหล่านี้ไม่ใช่การยืนยันการชำระเงิน
* **สถานะใบแจ้งหนี้และหน้าต่างค้นหาการชำระเงิน**อธิบายไว้ใน[วงจรชีวิตของใบแจ้งหนี้](../../invoice-lifecycle.md)
* **ลูกค้าโอนเงินผิดจำนวน** — ดู[การจับคู่การชำระเงินและยอดเงินไม่ตรงกัน](../../payment-matching.md)
