開發者 受控試點
OpenAPI支持工作台
API v1 以非托管為設計原則

接受穩定幣付款。
資金始終由你掌控。

MainPay 創建精確金額的帳單,監測商家控制的錢包,匹配鏈上轉帳併發送簽名 Webhook。無需轉移真實資金即可測試完整流程。

MainPay 從不托管資金。不執行扣款、出款、分帳、托管或網關退款。付款直接結算至企業控制的錢包。

快速接入收銀台

在服務端創建訂單,在客戶自己的錢包中付款。

原生 REST API 可直接使用內置的 fetch,無需 SDK。前端只接收公開收銀台 URL。對於額度充值,由你的應用在驗證 invoice.paid 後管理並更新餘額。

typescript
// 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。示例需要 curljq

01

設置環境

接入時使用測試模式。測試對象在結構上與真實結算及帳目隔離。

bash
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
02

查找收款錢包

選擇有效錢包的 public_id,並確認其幣種和網路與計劃創建的帳單一致。

bash
curl --fail-with-body -s "$MAINPAY_API_BASE/wallets" \
  -H "X-API-Key: $MAINPAY_API_KEY" | jq
03

創建帳單

每個獨立的創建請求都應使用唯一的冪等鍵。因傳輸問題重試時,復用相同的鍵和請求體。

bash
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
201{ "public_id": "…", "livemode": false, "status": "pending", "crypto_amount": "500.00", "hosted_url": "https://mainpay.com/pay/…" }
04

模擬檢測與結算

模擬器使用與真實付款相同的狀態轉換和事件服務,但不會寫入真實鏈上憑證。

bash
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
05

獲取權威狀態

Webhook 僅用於通知。集成需要最新狀態時,請主動獲取帳單。

bash
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 示例同樣是完整支持的接入方式。

typescript
// 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_…

正式密鑰

監測資金向你自己錢包的真實結算。通過測試試點後才會開通正式訪問權限。

http
X-API-Key: mp_test_xxxxxxxxxxxxxxxxxxxxxxxx

密鑰僅展示一次,以雜湊形式存儲,限定權限範圍,可立即撤銷,並按工作空間和模式隔離。切勿在瀏覽器代碼中暴露密鑰。

接口參考

精簡的 API 接口

POST/invoices創建帳單201
GET/invoices通過游標分頁列出帳單200
GET/invoices/{public_id}獲取權威帳單狀態200
POST/invoices/{public_id}/cancel取消待付款帳單200
GET/checkout/{public_id}獲取適合向付款方展示的收銀台資訊200
POST/checkout/{public_id}/events記錄匿名收銀台操作202
GET/wallets列出有效收款錢包200
POST/test/invoices/{public_id}/pay模擬測試付款200
OpenAPI 3.1 規範下載 YAML →

API 行為

適合付款運營的安全預設設置

01

十進制字符串

金額始終序列化為字符串,不使用浮點數。

02

冪等創建

相同的重試請求返回原始帳單。請求體變化時返回 409

03

游標分頁

列表返回 datahas_morenext_cursor

04

按密鑰限流

每個滾動分鐘允許 100 次請求,瞬時上限為每秒 20 次請求。

05

請求標識

每個響應都包含 X-Request-Id,用於支持與追蹤。

06

保護付款方隱私的收銀台

公開收銀台不包含客戶郵箱、內部 ID 或付款歷史。

Webhook

先驗證,再處理一次。

MainPay 使用 t=<unix>,v1=<hmac> 對原始請求體的精確字節簽名。拒絕過期時間戳,使用恆定時間比較,並根據穩定的事件 id 去重。

javascript
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;
  }
}
1 讀取原始字節2 驗證簽名3 保存事件 ID4 盡快返回 2xx

事件類型

invoice.created帳單已準備好接收付款
invoice.payment_detected匹配的轉帳等待確認
invoice.paid精確金額或可接受金額的付款已確認
invoice.underpaid已確認金額低於接受標準
invoice.overpaid已確認金額超過帳單金額
invoice.expired待付款帳單已到期
invoice.cancelled商家已停止收款

事件至少投遞一次,順序不保證。重試採用持久化退避策略。嵌入的帳單僅是快照,主動獲取的狀態才是權威依據。

錯誤

統一可預期的響應結構

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "not_found",
    "message": "Invoice not found",
    "param": null
  }
}
400無效請求修正請求後再重試。
401身份驗證檢查是否提供了有效的 API 密鑰。
403權限使用正確的模式和憑證權限範圍。
404未找到對象不存在,或不屬於當前工作空間或模式。
409衝突使用相同的鍵和請求體重試冪等請求。
422驗證檢查字段級錯誤列表。
429請求限流等待 Retry-After 指定的時間後,再添加隨機延遲重試。
503暫停創建遵循 Retry-After;獲取操作仍可使用。

受控試點

準備好測試接入了嗎?

連接錢包,配置 Webhook,並聯繫 MainPay 為你的工作空間開通測試憑證。驗證測試結果後再開通正式訪問權限。

申請試點訪問打開控制台