接受穩定幣付款。
資金始終由你掌控。
MainPay 創建精確金額的帳單,監測商家控制的錢包,匹配鏈上轉帳併發送簽名 Webhook。無需轉移真實資金即可測試完整流程。
快速接入收銀台
在服務端創建訂單,在客戶自己的錢包中付款。
原生 REST API 可直接使用內置的 fetch,無需 SDK。前端只接收公開收銀台 URL。對於額度充值,由你的應用在驗證 invoice.paid 後管理並更新餘額。
// Server only — MAINPAY_API_KEY never goes to the browser.
const response = await fetch("https://api.mainpay.com/v1/invoices", {
method: "POST",
headers: {
"X-API-Key": process.env.MAINPAY_API_KEY,
"Idempotency-Key": crypto.randomUUID(),
"Content-Type": "application/json"
},
body: JSON.stringify({
customer_name: "user_42",
description: "2,500 AI credits",
fiat_amount: "25.00",
asset: "USDC",
network: "base",
receiving_account_id: process.env.MAINPAY_WALLET_ID,
client_reference_id: "user_42",
metadata: { credits: 2500 },
purpose: "credit_topup"
})
});
if (!response.ok) throw new Error(`MainPay error: ${response.status}`);
const order = await response.json();
// Return only order.hosted_url to the browser.托管收銀台會提示付款方在自己的錢包中籤名並廣播精確的 ERC-20 轉帳。MainPay 從不接觸密鑰,也不持有付款資金或額度餘額。
快速入門
從測試密鑰到已付款帳單
開始前,請連接一個 Base USDC 錢包,創建 mp_test_… 密鑰,並在控制台中配置公開的 HTTPS Webhook。示例需要 curl 和 jq。
設置環境
接入時使用測試模式。測試對象在結構上與真實結算及帳目隔離。
export MAINPAY_API_BASE=https://api.mainpay.com/v1
export MAINPAY_API_KEY=mp_test_xxxxxxxxxxxxxxxxxxxxxxxx
export MAINPAY_WALLET_ID=1de70609-a510-4fb9-8691-61a8d2c21a34查找收款錢包
選擇有效錢包的 public_id,並確認其幣種和網路與計劃創建的帳單一致。
curl --fail-with-body -s "$MAINPAY_API_BASE/wallets" \
-H "X-API-Key: $MAINPAY_API_KEY" | jq創建帳單
每個獨立的創建請求都應使用唯一的冪等鍵。因傳輸問題重試時,復用相同的鍵和請求體。
export IDEMPOTENCY_KEY="quickstart-$(date +%s)-$RANDOM"
ORDER_JSON=$(
curl --fail-with-body -sX POST "$MAINPAY_API_BASE/invoices" \
-H "X-API-Key: $MAINPAY_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg wallet "$MAINPAY_WALLET_ID" '{
description: "API services - quickstart",
fiat_amount: "500.00",
asset: "USDC",
network: "base",
receiving_account_id: $wallet,
customer_name: "Acme Corp",
customer_email: "ap@acme.example",
client_reference_id: "builder-user-42",
metadata: { credits: 2500 },
purpose: "credit_topup"
}')"
)
export INVOICE_ID=$(printf '%s' "$ORDER_JSON" | jq -r '.public_id')
printf '%s\n' "$ORDER_JSON" | jq{ "public_id": "…", "livemode": false, "status": "pending", "crypto_amount": "500.00", "hosted_url": "https://mainpay.com/pay/…" }模擬檢測與結算
模擬器使用與真實付款相同的狀態轉換和事件服務,但不會寫入真實鏈上憑證。
curl --fail-with-body -sX POST \
"$MAINPAY_API_BASE/test/invoices/$INVOICE_ID/pay" \
-H "X-API-Key: $MAINPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"scenario":"payment_detected"}' | jq
curl --fail-with-body -sX POST \
"$MAINPAY_API_BASE/test/invoices/$INVOICE_ID/pay" \
-H "X-API-Key: $MAINPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"scenario":"exact"}' | jq獲取權威狀態
Webhook 僅用於通知。集成需要最新狀態時,請主動獲取帳單。
curl --fail-with-body -s \
"$MAINPAY_API_BASE/invoices/$INVOICE_ID" \
-H "X-API-Key: $MAINPAY_API_KEY" | jq可選的便捷工具
使用帶類型的 Node SDK。
使用 npm i @mainpay/node 安裝 @mainpay/node v0.1.0。SDK 封裝相同的 REST API,上方原生 fetch 示例同樣是完整支持的接入方式。
// Optional typed convenience: @mainpay/node v0.1.0
// npm i @mainpay/node
// Server only — MAINPAY_API_KEY never goes to the browser.
import MainPay from "@mainpay/node";
const mainpay = new MainPay(process.env.MAINPAY_API_KEY);
const order = await mainpay.orders.create({
customer_name: "user_42",
description: "2,500 AI credits",
fiat_amount: "25.00",
asset: "USDC",
network: "base",
receiving_account_id: process.env.MAINPAY_WALLET_ID,
client_reference_id: "user_42",
metadata: { credits: 2500 },
purpose: "credit_topup"
});
// Return only order.hosted_url to the browser.
// import { openCheckout } from "@mainpay/node/checkout";
// openCheckout(order.hosted_url);
// In your webhook, verify raw bytes before granting access:
const event = mainpay.webhooks.constructEvent(
rawBody, signatureHeader, process.env.MAINPAY_WEBHOOK_SECRET
);
if (event.type === "invoice.paid") {
await grantCreditsOnce(event.id, event.data.invoice);
}身份驗證
一個密鑰、一個工作空間、一種模式
mp_test_…測試密鑰
創建隔離的測試帳單,模擬各種付款結果,並接收 livemode:false 事件。
mp_live_…正式密鑰
監測資金向你自己錢包的真實結算。通過測試試點後才會開通正式訪問權限。
X-API-Key: mp_test_xxxxxxxxxxxxxxxxxxxxxxxx密鑰僅展示一次,以雜湊形式存儲,限定權限範圍,可立即撤銷,並按工作空間和模式隔離。切勿在瀏覽器代碼中暴露密鑰。
接口參考
精簡的 API 接口
/invoices創建帳單201/invoices通過游標分頁列出帳單200/invoices/{public_id}獲取權威帳單狀態200/invoices/{public_id}/cancel取消待付款帳單200/checkout/{public_id}獲取適合向付款方展示的收銀台資訊200/checkout/{public_id}/events記錄匿名收銀台操作202/wallets列出有效收款錢包200/test/invoices/{public_id}/pay模擬測試付款200API 行為
適合付款運營的安全預設設置
十進制字符串
金額始終序列化為字符串,不使用浮點數。
冪等創建
相同的重試請求返回原始帳單。請求體變化時返回 409。
游標分頁
列表返回 data、has_more 和 next_cursor。
按密鑰限流
每個滾動分鐘允許 100 次請求,瞬時上限為每秒 20 次請求。
請求標識
每個響應都包含 X-Request-Id,用於支持與追蹤。
保護付款方隱私的收銀台
公開收銀台不包含客戶郵箱、內部 ID 或付款歷史。
Webhook
先驗證,再處理一次。
MainPay 使用 t=<unix>,v1=<hmac> 對原始請求體的精確字節簽名。拒絕過期時間戳,使用恆定時間比較,並根據穩定的事件 id 去重。
import crypto from "node:crypto";
export function verifyMainPaySignature(rawBody, header, secret) {
try {
const parts = Object.fromEntries(
header.split(",").map((part) => part.split("=", 2))
);
const timestamp = Number(parts.t);
if (!Number.isFinite(timestamp) ||
Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected = crypto.createHmac("sha256", secret)
.update(`${timestamp}.`).update(rawBody).digest("hex");
const supplied = parts.v1 ?? "";
const a = Buffer.from(expected);
const b = Buffer.from(supplied);
return a.length === b.length && crypto.timingSafeEqual(a, b);
} catch {
return false;
}
}事件類型
invoice.created帳單已準備好接收付款invoice.payment_detected匹配的轉帳等待確認invoice.paid精確金額或可接受金額的付款已確認invoice.underpaid已確認金額低於接受標準invoice.overpaid已確認金額超過帳單金額invoice.expired待付款帳單已到期invoice.cancelled商家已停止收款事件至少投遞一次,順序不保證。重試採用持久化退避策略。嵌入的帳單僅是快照,主動獲取的狀態才是權威依據。
錯誤
統一可預期的響應結構
{
"error": {
"type": "invalid_request_error",
"code": "not_found",
"message": "Invoice not found",
"param": null
}
}受控試點
準備好測試接入了嗎?
連接錢包,配置 Webhook,並聯繫 MainPay 為你的工作空間開通測試憑證。驗證測試結果後再開通正式訪問權限。