首次调用 API

SpacePay 商户 API 用来创建支付、查询订单、发起退款和管理结算。接口使用 HTTPS + JSON,金额以整数分存储,同时返回小数金额。

!本环境的余额为虚拟资金,不接入真实银行卡或支付渠道,结算出款为模拟流程。接口语义、签名与幂等行为与生产实现一致,可用于完整联调。

接入流程

入驻商户

登录 SpacePay,在「我的 → 商户后台」提交入驻申请,等待运营审核。

创建 API Key

审核通过后,在商户后台「API 密钥」创建 sp_live_…。密钥只显示一次,服务端只保存哈希。

创建支付

调用 POST /api/v1/payments,把返回的 checkout_url 交给付款人。

处理 Webhook

付款完成后收到 payment.succeeded,校验签名并更新订单状态。

接口地址与鉴权

项目值
接口地址https://your-domain/api/v1
当前部署:
鉴权方式Authorization: Bearer sp_live_<key_id>_<secret>,或请求头 X-API-Key
数据格式Content-Type: application/json,UTF-8
金额入参 amount 为十进制字符串(如 "12.34");响应同时返回 amount_cents(整数分)
时间毫秒时间戳(UNIX epoch, ms)
币种当前仅支持 USD
i密钥是服务端凭据,务必放在服务端环境变量中,不要写进浏览器、移动端包体或 Git 仓库。怀疑泄露时在商户后台立即吊销。

发起第一笔支付

把 SPACEPAY_KEY 设置为你的密钥,然后发送请求:

export SPACEPAY_KEY="sp_live_xxxxxxxxxxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyy"

curl "https://your-domain/api/v1/payments" \
  -X POST \
  -H "Authorization: Bearer $SPACEPAY_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "amount": "12.34",
    "reference": "order-2026-0001",
    "description": "拿铁两杯",
    "customer_email": "[email protected]",
    "metadata": { "sku": "latte" },
    "success_url": "https://shop.example/paid"
  }'

返回示例:

{
  "ok": true,
  "payment": {
    "id": "pay_1",
    "numeric_id": 1,
    "reference": "order-2026-0001",
    "amount_cents": 1234,
    "amount": "12.34",
    "currency": "USD",
    "status": "CREATED",
    "refunded_cents": 0,
    "refundable_cents": 1234,
    "description": "拿铁两杯",
    "customer_email": "[email protected]",
    "metadata": { "sku": "latte" },
    "checkout_url": "https://your-domain/pay.html?token=…",
    "created_at": 1762800000000,
    "expires_at": 1762801800000
  }
}

让付款人完成支付

把 checkout_url 交给付款人的浏览器。付款人登录 SpacePay、输入 6 位支付 PIN 后完成付款;支付单在 30 分钟内有效。

不要只依赖付款人跳回 success_url 判断支付成功——跳转可能被中断。以 Webhook 为准,必要时再调用 查询支付 确认。

验证 Webhook

付款成功后,SpacePay 会向商户后台配置的 HTTPS 地址推送 payment.succeeded。请求头 X-SpacePay-Signature 形如 t=时间戳,v1=签名,签名是 HMAC-SHA256(webhook_secret, 时间戳 + "." + 原始请求体)。

import crypto from 'node:crypto';

function verify(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(signatureHeader.split(',').map((p) => p.split('=')));
  const expected = crypto.createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

完整的事件与重试规则见 Webhook 与签名。

下一步