ระบบรับชำระเงินที่ทดสอบ กระทบยอด และทำงานอัตโนมัติได้ โดยไม่สับสนระหว่างเงินที่ลูกค้าจ่ายกับเงินที่จ่ายให้ผู้ขาย
1. แบ่งหน้าที่ให้ชัดเจน
ร้านของคุณดูแลแค็ตตาล็อก บัญชีผู้ขาย ตะกร้า การจัดส่ง และการดำเนินการตามคำสั่งซื้อ ส่วน Wholly Crypto ดูแลหน้าชำระเงิน การตรวจสอบการชำระเงิน การแบ่งยอดให้ผู้ขาย และการจ่ายเงินที่อนุมัติแล้ว รายการผู้ขายไม่ใช่บัญชีเข้าสู่ระบบผู้ขาย และปลั๊กอินร้านค้าที่มีอยู่ไม่ได้แบ่งตะกร้าหลายผู้ขายให้อัตโนมัติ
- ตะกร้ารวมเดียว
- ใบแจ้งหนี้ลูกค้าใบเดียว
- ส่วนแบ่งผู้ขายที่ตรวจสอบแล้ว
- การจ่ายเงินที่อนุมัติแล้ว
ลูกค้าจ่ายเข้ากระเป๋าของโปรเจกต์ก่อน คุณควบคุมคีย์และถือเงินที่ต้องจ่ายให้ผู้ขาย นี่ไม่ใช่การแบ่งเงินตรงจากลูกค้าไปยังผู้ขาย และไม่ใช่บริการแบบไม่รับฝากเงินสำหรับผู้ขายของคุณ
Marketplace รองรับ Bitcoin mainnet และเหรียญ EVM กับโทเคน ERC-20 มาตรฐานที่รองรับ ผู้ขายจะได้รับสินทรัพย์บนเครือข่ายที่ลูกค้าใช้จ่าย ไม่ได้แปลงเป็นเงินทั่วไปให้อัตโนมัติ การจ่ายเงิน Marketplace ไม่ได้รองรับครบทั้ง 30 เครือข่ายที่ใช้รับเงิน
2. คำนวณยอดเงิน
ผู้ขายสามรายขายสินค้ารายละ $100 ในตัวอย่างนี้ ตั้งค่าคอมมิชชันของโปรเจกต์เป็น 4% โดยไม่มีการตั้งค่าทับระดับร้านหรือผู้ขาย
| ส่วนแบ่ง | ยอดรวมก่อนหัก | คอมมิชชันของคุณ | ผู้ขายได้รับ |
|---|---|---|---|
| ผู้ขายแต่ละราย | $100 | $4 | $96 |
| รวมทั้งสามราย | $300 | $12 | $288 |
ยอดเหล่านี้เป็นมูลค่าเทียบเท่าตามราคาที่ล็อกไว้ในใบแจ้งหนี้ โดยจ่ายเป็นคริปโต ไม่ใช่การรับประกันมูลค่าดอลลาร์ในอนาคต ค่าประมวลผลปกติ 1% ใช้เครดิตเติมล่วงหน้า $3 สำหรับใบแจ้งหนี้ $300 นี้เพียงครั้งเดียว ไม่ใช่ครั้งละหนึ่งผู้ขาย เมื่อมีค่าธรรมเนียมนี้ คอมมิชชันรวม $12 ของคุณจะเหลือ $9 ก่อนหักค่าเครือข่าย
เก็บเหรียญหลักที่ยังไม่ติดภาระไว้ต่างหากสำหรับค่าธรรมเนียม Bitcoin หรือ gas ของ EVM ค่าธรรมเนียมต้องไม่กินเงินต้นของผู้ขายที่กันไว้ การ sweep ปกติใช้เงินจากที่อยู่รับเงิน Marketplace ไม่ได้
3. เตรียมกระเป๋าและผู้ขาย
- ในโปรเจกต์ → Marketplace → การตั้งค่า ให้เปิด Marketplace เลือกร้าน และตั้งค่าคอมมิชชัน ระหว่างตั้งค่าให้พักการจ่ายเงินและปิดนโยบายอัตโนมัติไว้ก่อน
- สำรองกระเป๋าของโปรเจกต์และฐานข้อมูล เปิดวิธีรับเงิน BTC/EVM ที่ต้องการในร้าน ตรวจสอบความพร้อมของสแกนเนอร์ และเติมเครดิตประมวลผลพร้อมเหรียญหลักสำหรับค่าธรรมเนียมแยกต่างหาก
- เพิ่มผู้ขายแต่ละราย เก็บ UUID คู่กับ ID ผู้ขายในร้านของคุณ โดยใช้ external_id เก็บรหัสอ้างอิงนั้นได้ ตรวจสอบและอนุมัติที่อยู่รับเงินแต่ละแห่งโดยอิสระให้ตรงทั้งเชนและเครือข่าย
แต่ละวิธีชำระเงินต้องมีปลายทางที่ตรงกันและอนุมัติแล้วสำหรับผู้ขายทุกรายที่ร่วมรายการ การเปลี่ยนที่อยู่ผู้ขายภายหลังจะไม่เปลี่ยนปลายทางของยอดค้างจ่ายเดิมแบบเงียบ ๆ
การตรวจสอบการจ่ายผู้ขายต้องมีการยืนยันมากกว่าศูนย์และผู้ให้บริการอิสระที่เข้ากันได้สองราย แม้หน้าชำระเงินจะอนุญาตให้ใช้ศูนย์การยืนยันหรือสแกนเนอร์เพียงรายเดียวก็ตาม
4. เชื่อมต่อตะกร้ารวม
สร้างข้อมูลรับรอง Marketplace ที่จำกัดเฉพาะโปรเจกต์ในตั้งค่า → การเข้าถึง API ให้ backend ชำระเงินมีสิทธิ์ marketplace.read และ invoices.write พร้อมจำกัดร้านตามความเหมาะสม อย่าใส่สิทธิ์อนุมัติที่อยู่และอนุมัติจ่ายเงินในคีย์นี้
คำนวณราคา ส่วนลด ภาษี และค่าจัดส่งบนเซิร์ฟเวอร์ของคุณ แล้วจัดสรรเป็นส่วนแบ่งผู้ขาย ส่งผู้ขายไม่ซ้ำกัน 1–100 ราย พร้อมยอดบวกในรูปสตริงทศนิยม โดยยอดรวมก่อนหักของทุกรายต้องเท่ากับใบแจ้งหนี้พอดี อย่าเชื่อส่วนแบ่งจากเบราว์เซอร์หรือคำนวณเงินด้วยเลขทศนิยมแบบ floating point
แทนชื่อโฮสต์ API และ UUID ตัวอย่างด้านล่าง โหลด WHOLLY_TOKEN จาก environment ของเซิร์ฟเวอร์ คำขอนี้ใช้คอมมิชชัน 4% ที่ตั้งไว้ จึงไม่ต้องมีสิทธิ์เปลี่ยนคอมมิชชัน
เปิดตัวอย่างคำขอใน cURL, JavaScript, PHP หรือ Python
cURL
: "${WHOLLY_TOKEN:?Set WHOLLY_TOKEN to your server-side API token}"
# Keep this key and the exact body for retries; use a new key for each new operation.
curl --fail-with-body --max-time 30 \
--request POST \
--url "https://api.example.com/v1/marketplace/invoices" \
--header "Authorization: Bearer $WHOLLY_TOKEN" \
--header 'Idempotency-Key: cart-1042-marketplace-v1' \
--header 'Content-Type: application/json' \
--data-raw '{
"project_id": "YOUR_PROJECT_ID",
"store_id": "YOUR_STORE_ID",
"amount": "300.00",
"currency": "USD",
"order_id": "cart-1042",
"description": "One order from three vendors",
"ipn_url": "https://shop.example.com/payments/wholly-ipn",
"metadata": {
"cart_id": "cart-1042"
},
"allocations": [
{
"vendor_id": "VENDOR_A_UUID",
"gross_amount": "100.00"
},
{
"vendor_id": "VENDOR_B_UUID",
"gross_amount": "100.00"
},
{
"vendor_id": "VENDOR_C_UUID",
"gross_amount": "100.00"
}
]
}'JavaScript
// Node.js 18+ · run on your server, never in browser code.
const token = process.env.WHOLLY_TOKEN;
if (!token) throw new Error("Set WHOLLY_TOKEN");
// Keep this key and the exact body for retries; use a new key for each new operation.
const body = `{
"project_id": "YOUR_PROJECT_ID",
"store_id": "YOUR_STORE_ID",
"amount": "300.00",
"currency": "USD",
"order_id": "cart-1042",
"description": "One order from three vendors",
"ipn_url": "https://shop.example.com/payments/wholly-ipn",
"metadata": {
"cart_id": "cart-1042"
},
"allocations": [
{
"vendor_id": "VENDOR_A_UUID",
"gross_amount": "100.00"
},
{
"vendor_id": "VENDOR_B_UUID",
"gross_amount": "100.00"
},
{
"vendor_id": "VENDOR_C_UUID",
"gross_amount": "100.00"
}
]
}`;
const response = await fetch("https://api.example.com/v1/marketplace/invoices", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Idempotency-Key": "cart-1042-marketplace-v1",
"Content-Type": "application/json"
},
body,
redirect: "error",
signal: AbortSignal.timeout(30000)
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
console.log(await response.json());PHP
<?php
// PHP 8+ with the cURL extension; run on your server.
$token = getenv('WHOLLY_TOKEN');
if (!$token) { throw new RuntimeException('Set WHOLLY_TOKEN'); }
// Keep this key and the exact body for retries; use a new key for each new operation.
$body = <<<'JSON'
{
"project_id": "YOUR_PROJECT_ID",
"store_id": "YOUR_STORE_ID",
"amount": "300.00",
"currency": "USD",
"order_id": "cart-1042",
"description": "One order from three vendors",
"ipn_url": "https://shop.example.com/payments/wholly-ipn",
"metadata": {
"cart_id": "cart-1042"
},
"allocations": [
{
"vendor_id": "VENDOR_A_UUID",
"gross_amount": "100.00"
},
{
"vendor_id": "VENDOR_B_UUID",
"gross_amount": "100.00"
},
{
"vendor_id": "VENDOR_C_UUID",
"gross_amount": "100.00"
}
]
}
JSON;
$ch = curl_init("https://api.example.com/v1/marketplace/invoices");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $token, "Idempotency-Key: cart-1042-marketplace-v1", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response === false) { throw new RuntimeException(curl_error($ch)); }
curl_close($ch);
if ($status < 200 || $status >= 300) { throw new RuntimeException("HTTP $status: $response"); }
print_r(json_decode($response, true, 512, JSON_THROW_ON_ERROR));Python
# Python 3 · standard library; run on your server.
import json
import os
from urllib.request import Request, build_opener, HTTPRedirectHandler
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Keep this key and the exact body for retries; use a new key for each new operation.
headers = {
"Idempotency-Key": "cart-1042-marketplace-v1",
"Content-Type": "application/json"
}
headers["Authorization"] = "Bearer " + os.environ["WHOLLY_TOKEN"]
body = """{
"project_id": "YOUR_PROJECT_ID",
"store_id": "YOUR_STORE_ID",
"amount": "300.00",
"currency": "USD",
"order_id": "cart-1042",
"description": "One order from three vendors",
"ipn_url": "https://shop.example.com/payments/wholly-ipn",
"metadata": {
"cart_id": "cart-1042"
},
"allocations": [
{
"vendor_id": "VENDOR_A_UUID",
"gross_amount": "100.00"
},
{
"vendor_id": "VENDOR_B_UUID",
"gross_amount": "100.00"
},
{
"vendor_id": "VENDOR_C_UUID",
"gross_amount": "100.00"
}
]
}""".encode("utf-8")
request = Request("https://api.example.com/v1/marketplace/invoices",
method="POST", headers=headers, data=body)
# Non-2xx responses raise HTTPError. Do not retry writes with a new key.
with build_opener(NoRedirect()).open(request, timeout=30) as response:
print(json.load(response))บันทึก body ของคำขอและ Idempotency-Key ก่อนส่ง หากหมดเวลา ให้ลอง body เดิมด้วยคีย์เดิม เก็บ data.invoice_id คู่กับคำสั่งซื้อ และส่งลูกค้าไปที่ links.checkout ระดับบนสุด อย่าใส่คีย์ API ในเบราว์เซอร์ของลูกค้า
หา UUID ของโปรเจกต์และร้านได้ที่ไหน →
อยากใช้ SDK? เริ่มจากตัวอย่าง Marketplace:
5. ติดตามการรับเงินและการจ่ายผู้ขายแยกกัน
URL กลับจากหน้าชำระเงินไม่ใช่หลักฐานการชำระ ตรวจสอบลายเซ็น callback จาก raw body ตรวจเวลาและโปรเจกต์ที่คาดไว้ แล้วเก็บ event_id แบบไม่ซ้ำก่อนตอบรับ ใช้ตัวป้องกันระดับคำสั่งซื้อแยกต่างหากเพื่อไม่ให้การลองซ้ำทำคำสั่งซื้อซ้ำสองครั้ง
| เหตุการณ์ | บอกอะไรคุณ |
|---|---|
invoice.settled | ใบแจ้งหนี้ของลูกค้าชำระเสร็จแล้ว ตรวจธงตรวจสอบและยอดที่ Marketplace พักไว้ก่อนดำเนินการตามคำสั่งซื้อ |
marketplace.allocations.available | ส่วนแบ่งที่ตรวจสอบแล้วพร้อมจ่าย แต่ไม่ได้หมายความว่ามีผู้ขายได้รับเงินแล้ว |
marketplace.payout.confirmed | การจ่ายเงินผ่านการตรวจยืนยันครบแล้ว |
IPN ของใบแจ้งหนี้ใช้ IPN secret ของร้าน ส่วน webhook วงจร Marketplace ใช้ signing secret ของ endpoint เอง แยกตัวรับทั้งสองให้ชัดเจน เมื่อต้องกระทบยอดเหตุการณ์ที่ขาดหายหรือมาผิดลำดับ ให้อ่านใบแจ้งหนี้หรือการจ่ายเงินปัจจุบันผ่าน API
Callback ใบแจ้งหนี้และการตรวจสอบลายเซ็น · เหตุการณ์ Marketplace และคู่มือ API
6. ตรวจสอบก่อน แล้วค่อยเปิดอัตโนมัติ
เมื่อส่วนแบ่งที่ตรวจสอบแล้วพร้อมจ่าย ให้เลิกพักการจ่ายเงินแต่ยังปิดนโยบายอัตโนมัติไว้ ใน Marketplace → การจ่ายเงิน เตรียมส่วนแบ่ง ตรวจผู้รับและเพดานค่าธรรมเนียม/gas ของเหรียญหลัก แล้วอนุมัติแผนที่แน่นอนนั้นครั้งเดียว ติดตามจนถึงจ่ายแล้ว ไม่ใช่แค่ส่งเข้าบล็อกเชนแล้ว
Bitcoin รวมทุกส่วนแบ่งที่ยังไม่จ่ายของใบแจ้งหนี้ที่เลือกเป็นชุดเดียว การจ่ายโทเคน EVM อาจต้องเติม gas ก่อนโอนโทเคน จึงเป็นหลายธุรกรรม ไม่ใช่การแบ่งแบบสำเร็จทั้งหมดหรือไม่ทำอะไรเลยในธุรกรรมเดียว
หลังทดสอบยอดเล็กสำเร็จ ให้ตั้งนโยบายอัตโนมัติหนึ่งชุดต่อสินทรัพย์: ยอดจ่ายขั้นต่ำ เพดานเงินต้นต่อครั้งและต่อวัน งบค่าธรรมเนียม/gas รอบเวลา และจำนวนการยืนยัน การเปิดใช้งานเป็นการอนุญาตให้ส่งโดยไม่ต้องกดเพิ่ม แต่เครดิตต่ำหรือการปิดสิทธิ์โอนของเซิร์ฟเวอร์ยังพักการจ่ายได้
7. จัดการกรณีที่ต้องระวัง
จ่ายขาด จ่ายช้า จ่ายหลายแบบ หรือบล็อกเชนจัดลำดับใหม่
หน้าชำระเงินที่เสร็จแล้วไม่ได้รับประกันว่ามีเงินครบสำหรับส่วนแบ่งผู้ขาย ค่าคลาดเคลื่อนสร้างเงินที่ขาดไม่ได้ ตรวจส่วนแบ่งที่ถูกพัก รอรับเงิน คืนเงิน หรืออนุมัติส่วนแบ่งที่น้อยลงแต่มีเงินจริงรองรับอย่างชัดเจน เงินที่จ่ายเกินไม่กลายเป็นรายได้เพิ่มของ Marketplace โดยอัตโนมัติ
การจ่ายเกินไม่บล็อกจ่ายผู้ขายตามราคาเสนอบน Bitcoin เหรียญ EVM หรือ ERC-20 ที่รองรับ ส่วนผู้ขายคงเดิมและส่วนเกินแยกไว้กระทบยอด
การจ่ายค้างหรือคำขอหมดเวลา
ตรวจแฮชธุรกรรมที่บันทึกไว้ ยอดต้นทาง gas และเหตุผลที่ต้องตรวจสอบ ทำต่อหรือกระทบยอดการจ่ายเดิม อย่าสร้างการโอนครั้งที่สองเพียงเพราะคำตอบหาย หลังคืนค่าจาก backup ให้พักการจ่ายไว้จนผลบนเชนตรงกับบัญชีรายการ
การทำต่อแทนธุรกรรม EVM ที่ล้มเหลวได้ก็ต่อเมื่อผู้ให้บริการอิสระสองรายยืนยันว่าธุรกรรมบนเชน revert แล้ว ผลที่ยังไม่ทราบจะคงการจองเงินไว้ และค่าธรรมเนียมจากครั้งที่ล้มเหลวยังนับในงบเดิม
ลูกค้าต้องการคืนเงิน
ตรวจว่าที่อยู่คืนเงินเป็นของลูกค้า การคืนเงินที่รองรับเป็นยอดเต็มในสินทรัพย์เดิม หลังจ่ายผู้ขายครบแล้วต้องเติมเงินที่กระเป๋าหลักต่างหาก Wholly Crypto ดึงเงินกลับจากผู้ขายไม่ได้ ชุดที่จ่ายผู้ขายไปบางรายต้องกระทบยอดด้วยตนเอง ไม่ใช่ถือว่ามีปุ่มคืนบางส่วนให้ใช้
8. ตรวจสอบก่อนเปิดใช้งานจริง
- ทดสอบการจ่าย BTC และ/หรือ EVM ยอดเล็กจนการจ่ายผู้ขายได้รับการยืนยัน ตรวจเชน สัญญาโทเคน ปลายทาง คอมมิชชัน และค่าธรรมเนียมแยกต่างหาก
- ทดสอบ callback ซ้ำ คำตอบ API หมดเวลา การจ่ายขาด และการจ่ายที่ถูกพัก ตรวจว่าไม่มีกรณีใดทำให้ดำเนินคำสั่งซื้อหรือส่งเงินซ้ำ
- เก็บ backup ส่วนตัวไว้นอกเซิร์ฟเวอร์ เฝ้าดูการจ่ายที่ล้มเหลว และกระทบยอดหนี้ต่อผู้ขายกับเงินจริงบนเชนเป็นประจำ
เริ่มจากอนุมัติการจ่ายด้วยตนเองและผู้ขายจำนวนน้อย เปิดอัตโนมัติเฉพาะสินทรัพย์ งบ และปลายทางที่ทดสอบแล้ว