SDK

35pay 提供三种官方 SDK:JavaScript / TypeScript、Python、Go。所有 SDK 都是对 REST API 的轻封装,方法名一致。挑你后端用的语言即可。

SDK 仅服务端使用。Secret Key 不要出现在浏览器、移动端、公开仓库。

JavaScript / TypeScript

包名 @35m/sdk,发布在 npm。Node.js 18+,含 TypeScript 类型定义。

安装

npm i @35m/sdk
# 或
pnpm add @35m/sdk

用法

import { createPay, Pay, PayError } from '@35m/sdk'

const pay = createPay({
  apiKey: process.env.PAY_SECRET_KEY!,   // sk_test_xxx 或 sk_live_xxx
  // baseUrl: 'https://pay.35team.com', // 自托管时改这里
  // timeoutMs: 15000,
})

// 创建支付
const session = await pay.createCheckout({
  amount: 9900,
  currency: 'CNY',
  description: '追思视频套餐',
  metadata: { orderId: 'order_123' },
  // successUrl 不传:客户付完落到 35pay 收据页(适合 PaymentLink 等简单场景)
  // 传了:sync 中转模式 —— 客户落地时 webhook 已处理,本地业务可直接读 paid,零 polling
  successUrl: 'https://your.site/orders/123',
})

return Response.redirect(session.url, 303)

// 查询会话
const s = await pay.getSession('sess_01HXYZ...')
if (s.status === 'paid') console.log('paid via', s.record?.provider)

// 验证 webhook 签名(详见 webhooks 文档)
Pay.verifyWebhookSignature(rawBody, sigHeader, secret)

错误处理

try {
  await pay.createCheckout({ amount: 9900, currency: 'CNY' })
} catch (e) {
  if (e instanceof PayError) {
    console.error('35pay error', e.status, e.body)
  }
  throw e
}

Python

PyPI 包名 35m-sdk,模块名 pay35,Python 3.9+。 同步版零依赖(仅用 stdlib urllib),异步版可选装 httpx

安装

# 同步版(FastAPI 同步路由 / Flask / Django sync 视图)
pip install 35m-sdk

# 含异步(FastAPI / Starlette / Litestar / aiohttp)
pip install '35m-sdk[async]'

同步用法

example.py
import os
from pay35 import Pay, PayError

pay = Pay(api_key=os.environ["PAY_KEY"])

# 创建支付
session = pay.create_checkout(
    amount=9900,
    currency="CNY",
    description="追思视频套餐",
    metadata={"order_id": "order_123"},
    success_url="https://your.site/orders/123",
)
# session = {"id": ..., "url": ..., "status": "pending", ...}

# 查询会话
s = pay.get_session("sess_01HXYZ...")

# 验证 webhook 签名
ok = Pay.verify_webhook_signature(raw_body, sig_header, secret)

异步用法(AsyncPay)

fastapi_example.py
from pay35 import AsyncPay

# 推荐:context manager,自动 aclose
async def create_payment():
    async with AsyncPay(api_key=os.environ["PAY_KEY"]) as pay:
        return await pay.create_checkout(amount=9900, currency="CNY")

# 或长生命周期,shutdown 时显式 aclose
pay = AsyncPay(api_key=os.environ["PAY_KEY"])
@app.on_event("shutdown")
async def shutdown():
    await pay.aclose()
AsyncPay 复用一个 httpx.AsyncClient,比每请求新建连接快得多。 长进程(FastAPI / Litestar)建议保留 client,shutdown 时 aclose()。 短脚本用 async with 即可。

Go

单文件 pay35.go,零依赖(仅用 stdlib),Go 1.21+。

安装

# 推荐:go get(GitHub release 后可用)
go get github.com/guo2001china/35m-go@v0.1.0

# 或直接 vendor 单文件(零依赖,1 个 .go)
curl -O https://pay.35team.com/sdk/go/pay35.go

用法

main.go
package main

import (
    "log"
    "net/http"
    "os"

    pay35 "github.com/guo2001china/35m-go"
)

func handler(w http.ResponseWriter, r *http.Request) {
    pay := pay35.New(pay35.Config{APIKey: os.Getenv("PAY_KEY")})

    session, err := pay.CreateCheckout(pay35.CheckoutInput{
        Amount:     9900,
        Currency:   "CNY",
        Metadata:   map[string]any{"orderId": "order_123"},
        SuccessURL: "https://your.site/orders/123",
    })
    if err != nil {
        log.Println(err); http.Error(w, "pay error", 500); return
    }
    http.Redirect(w, r, session.URL, http.StatusSeeOther)
}

// Webhook
func webhook(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    sig := r.Header.Get("X-35pay-Signature")
    if !pay35.VerifyWebhookSignature(string(body), sig, webhookSecret) {
        http.Error(w, "bad signature", 401); return
    }
    // ...
}

方法对照

三种 SDK 方法名风格略有差异(语言习惯),但语义一致:

createCheckout
POST /api/v1/checkout
JS: pay.createCheckout(input) Python: pay.create_checkout(...) Go: pay.CreateCheckout(input)。返回 session 对象,把 url 给用户跳过去。
getSession
GET /api/v1/sessions/:id
查询会话状态。已支付的会话会带 record 字段。
refund
POST /api/v1/refunds
JS: pay.refund(recordId, amount?, reason?) Python: pay.refund(record_id, amount=None, reason=None) Go: pay.Refund(pay35.RefundInput{...})amount 留空表示全额退款。详见 REST API 退款
verifyWebhookSignature
static / 顶层函数
验签静态方法。详见 Webhooks

多 ProviderConfig 路由

如果你给同一种 provider 配了多份凭证(如「主号」「个体户副号」),SDK 默认走每种 provider 标了默认的那份。要显式指定,把 providerHints 作为一个额外字段塞进 input:

// JS / TS(类型上是额外字段,as cast 即可)
await pay.createCheckout({
  amount: 9900,
  currency: 'CNY',
  ...({ providerHints: { wechat: '个体户副号' } } as object),
})
python
# Python:pay35 字段固定,直接调内部 _request 透传额外字段
pay._request("POST", "/api/v1/checkout", {
    "amount": 9900, "currency": "CNY",
    "providerHints": {"wechat": "个体户副号"},
})

# AsyncPay 同理:
await pay._request("POST", "/api/v1/checkout", {...})

Go SDK 同样可以扩 CheckoutInput 加一个字段,或者直接用 http.Client 发请求。 语义和路由规则见 REST API · providerHints。 三个 SDK 后续会把这个字段加进类型定义。

其他语言

没有官方 SDK 的语言(PHP / Ruby / Java / .NET 等)请直接调 REST API。 签名验证算法是标准 HMAC-SHA256,所有语言都有内置库。