AI 对接 Skill
把下面这段提示词发给你的 AI 编程助手,它会读取完整对接规范并完成接入。
rekitdev 支持接入登录、COS 存储、支付和服务号通知。请按照 skill 对接:https://rekitdev.com/skill.md
Rekit · 开放平台对接(AI Agent 完整版)
本 skill 面向 其它 AI agent / 产品站开发:读完后应能写出可运行的对接代码,不要自建登录页、自配公众号网页授权、自发明鉴权。
公开直链(无需登录,含本文件 + reference):
https://rekitdev.com/skill.md实现时优先遵循下文「最小实现」;字段级契约以文末「接口参考」为准(已拼入本文档)。
0. Agent 开工前必须拿到的材料
运营在 Rekit 后台配好站点后,应交给对接方(或你)至少:
| 材料 | 示例 | 用途 |
|---|---|---|
| 站点 slug | weibo / rekitdev | 登录跳转 site=;与 Key 绑定 |
| 站点 API Key | sk_... | 仅服务端 X-Api-Key |
| 已开通权限 | email_notify / storage / wechat_notify / payments / cdn / captcha | 未开通 → HTTP 403 |
| 产品站对外 URL / 域名白名单 | https://wb.example.com | 登录回跳、支付 success/cancel 校验 |
| 基址(可默认) | 见下表 | 环境变量 |
生产基址(默认):
| 变量 | 值 | 谁用 |
|---|---|---|
NEXT_PUBLIC_SNGZS_AUTH_API_URL | https://login.rekitdev.com | 浏览器跳转登录(须 NEXT_PUBLIC_) |
SNGZS_API_URL | https://api.rekitdev.com | 服务端开放 API |
NEXT_PUBLIC_SNGZS_CAPTCHA_API_URL | https://api.rekitdev.com | 浏览器加载 captcha SDK / 获取 token |
SNGZS_PAYMENTS_API_URL | https://pay.rekitdev.com | 服务端支付 |
SNGZS_SITE_API_KEY | 站点 Key | 服务端;禁止进前端 bundle |
NEXT_PUBLIC_BASE_URL | https://你的站 | 拼 callback / return_to |
本地端口:login 9102,pay 9104,api 9106,admin API 8000。
仅有 Key、没有 slug / 域名白名单 / 权限绑定:登录回不了 token,开放接口会 403/503。
1. 硬规则(违反即对接错误)
- 两套鉴权不要混 用户身份:
Authorization: Bearer <access_token>→ 只打login.(如/api/v1/me) 站点能力:X-Api-Key: <站点 Key>→ 打api./pay.(邮件、图床开放预签名、公众号通知、支付) - 登录只跳托管门户,产品站不自建注册/登录页,不自配公众号网页授权 AppID。
- 微信内 H5 用
/login/mp;浏览器通用用/login。不要用产品站域去做微信 OAuth。 - Key 永不进浏览器;开放能力请求放在服务端 / BFF。
- 图床:拿预签名后客户端 直传 COS;读图用返回的
url/static.CDN,文件不经产品站中转。 - 支付:优先 Checkout Session;
success_url/cancel_url的 host 必须在该站域名白名单内。
2. 子域名一览
| 子域 | 用途 | 产品站怎么用 |
|---|---|---|
login. | 托管登录 + 用户 JWT | 浏览器 302;Bearer |
api. | 邮件 / 图床 / 公众号通知 | 服务端 X-Api-Key |
pay. | 支付 Checkout / 下单 | 服务端 X-Api-Key |
static. | CDN 读图 | 公开 URL |
3. 登录对接(必做)
3.1 流程
用户点登录
→ 302 {LOGIN}/login?return_to={CALLBACK}&site={slug}&lang=zh|en
(微信内 H5 → /login/mp,参数相同)
→ 用户在 login. 完成邮箱或微信
→ login. 302 → {CALLBACK}?login_code=...
→ 本站 callback 服务端 POST {LOGIN}/api/v1/auth/exchange 换 access_token
→ 写 HttpOnly Cookie,再 302 到业务 path(returnTo)login. 不再把签好的 JWT 明文放进跳转 URL(会留在浏览器历史 / 服务端访问日志 / Referer,且没有来源校验时任何人拿到别人的回跳链接就能把自己的 登录态嫁接到受害者浏览器上)。改为回传一次性 login_code(单次消费、90 秒内有效), 由 产品站服务端(不是浏览器)拿它换真正的 access_token。仍在用旧版 ?access_token=... 直读方式的接入方必须升级 callback 实现,否则登录会失败。{CALLBACK} 推荐:https://你的站/api/auth/callback?returnTo=/account (returnTo 为本站相对路径,须以 / 开头、禁止 //。)
site=<slug>:要回传登录态时必填。return_to 的 host 必须在该站「域名 / 对外 URL」白名单;否则登录成功但 不会 302 带回 login_code。
3.2 跳转 URL 模板
# 浏览器 / 通用
{LOGIN}/login?return_to={encodeURIComponent(CALLBACK)}&site={slug}&lang=zh
# 微信内 H5(专用;非微信打开只提示「请在微信内打开」)
{LOGIN}/login/mp?return_to={encodeURIComponent(CALLBACK)}&site={slug}&lang=zh
# 微信内 H5 · 静默(snsapi_base,无授权页;推荐微信内首选)
{LOGIN}/login/mp?silent=1&return_to={encodeURIComponent(CALLBACK)}&site={slug}&lang=zh检测微信内:/MicroMessenger/i.test(navigator.userAgent)。
silent=1(静默登录):走 snsapi_base 网页授权,微信内不弹授权页——已在平台注册过的 用户(同开放平台主体、unionid 对齐者)零点击直接回跳 ?login_code=...;仅「平台全新」用户 由平台回调自动跳一次 snsapi_userinfo 补昵称头像后回跳。接入方只需带 silent=1,其余 (callback 收 login_code → POST /api/v1/auth/exchange 换 token)与非静默完全一致。仅微信内可用。 静默登录会把用户的公众号 openid 落到平台账号上,这正是内联 JSAPI 支付所依赖的 openid 来源(见 §8)。
3.3 本站最小实现
- 登录入口:按上表 302(或
window.location.href)。 GET /api/auth/callback(本站,服务端处理,不能是纯前端页面): 读 query:login_code(必填)、returnTo(相对路径) 服务端(不经浏览器)POST {LOGIN}/api/v1/auth/exchange,body{"code": login_code};
成功返回 {access_token, expires_in, user},失败(code 过期/已用过)返回 400,需引导用户重新登录
- 写 Cookie(参考名
sngzs_user_token):HttpOnly; Path=/; SameSite=Lax;(HTTPS 加Secure;可按主域设Domain) Max-Age = max(60, expires_in)- 302 到
returnTo - 鉴权:服务端用 Cookie 里的 token 调
GET {LOGIN}/api/v1/me,头:Authorization: Bearer <token> 响应:{ "user": { id, nickname, avatar_url, background_url, email, has_password, has_wechat_web, has_mp, unionid } } (没有 mp_openid 字段;只有 has_mp + unionid。发模板消息用的公众号 openid 见 §5。)
- 登出:清 Cookie。
参考实现(本仓库):rekitdev/src/lib/wechat-login.ts、rekitdev/src/pages/api/auth/callback.ts、rekitdev/src/lib/auth-session.ts、rekitdev/src/lib/sngzs-client.ts。
3.4 平台侧微信配置(对接方知悉,一般由运营做)
- 开放平台「网站应用」授权回调域、公众号「网页授权域名」均填
login.你的域名(如login.rekitdev.com),不是产品站域名。 - 使用
/login/mp时后台须启用「公众号网页授权」。 - 小程序与公众号须同开放平台主体,才能用
unionid对齐账号。
3.5 用户资料 / 账号绑定(Bearer)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/me | 当前用户 |
| PATCH | /api/v1/me | {nickname?, avatar_url?, background_url?};URL 须 http(s) |
| POST | /api/v1/me/bind-email/code | {email} |
| POST | /api/v1/me/bind-email | {email, code, password};密码 ≥6;邮箱占用 409 |
| POST | /api/v1/me/unbind-email | 解绑前须已绑微信 |
| POST | /api/v1/me/password | {current_password, new_password} |
| GET | /api/v1/me/bind-wechat/web/start?return_to= | 返回 {state, authorize_url, redirect_uri} |
| POST | /api/v1/me/unbind-wechat | {channel:"web"};解绑前须有邮箱密码 |
完整表见 reference。
4. 开放 API 通用约定
X-Api-Key: <SNGZS_SITE_API_KEY>
Content-Type: application/json| HTTP | 含义 |
|---|---|
| 401 | 缺 Key / Key 无效 / Bearer 无效 |
| 403 | 站点停用或未开通该权限 |
| 400 | 参数错误 / 业务拒绝(见 detail) |
| 409 | 冲突:JSAPI payer_mp_openid_missing / jsapi_appid_mismatch,应回退扫码 |
| 422 | 请求体校验失败 |
| 429 | 超限:每站点每类通知 120 次/分钟 |
| 502 | 上游失败(如 SMTP) |
| 503 | 平台或站点未绑定配置(SMTP / COS / 支付 / 微信等) |
统一错误外壳: 所有对外错误响应体恒为 {"detail": {"code": "...", "message": "..."}}(平台网关层统一)。code 为稳定机器码(如 api_key_missing、permission_denied、site_provider_unbound、validation_error),可用于多语言映射;message 为兜底人读文案。接入方只需解析这一种形状。
权限 ID:storage、cdn、wechat_notify、email_notify、payments、captcha。
站点自检(对接第一步): GET {SNGZS_API_URL}/api/v1/site,只需 X-Api-Key,无权限门槛。返回各能力的 permission(权限位)与 ready(权限位 且 后台已绑配置,才是真正可调用);ready: false 时 missing 精确说明缺什么——可直接转给运营当工单,不必逐个功能试出 503。存储另带 read_mode(public = 存 unsigned_url 永久链;signed = 带 CDN 鉴权、仅即传即用)与 cdn_auth_enabled(后台原始开关);支付另带 providers(可用渠道,与下单同一判定)与 webhook_configured(false = 到账不推送;后台配了 webhook_url 才有,且后配不补历史)。完整响应形状见文末接口参考。
5. Captcha(captcha)— api.
Rekit 托管登录页已内置 captcha。产品站如果自有表单也要防注册/登录/敏感动作,可复用同一套。
浏览器侧只使用公开站点 slug,不暴露 API Key:
<script src="https://api.rekitdev.com/rc/v1/rekit.min.js"></script>
<div id="captcha"></div>
<script>
const rk = ReKitCaptcha.init({
api: "https://api.rekitdev.com/rc/v1/s/你的站点slug"
});
rk.render(document.getElementById("captcha"), {
onToken(token) {
// 随业务表单一起提交给你的服务端
}
});
</script>服务端校验 token(必须用 X-Api-Key,且 token 只能消费一次):
POST /api/v1/captcha/verify
X-Api-Key: <SNGZS_SITE_API_KEY>
Content-Type: application/json
{ "token": "<browser token>" }成功:{"ok":true,"site":"你的站点slug","sub":"rekit-pass|rekit-verify","score":0.9,"expires_at":...}。 失败或重放:400 captcha_invalid。站点未开权限:403 permission_denied。
6. 公众号通知(wechat_notify)— api.
基址: {SNGZS_API_URL} 鉴权: X-Api-Key + 权限 wechat_notify 收件人身份: 统一用 unionid(接入方共享同一开放平台主体,只拿得到 unionid;公众号 openid 只有公众号自己能拿)。平台内部用 members 表映射到投递 openid。 限流: 每站点 120 次/分钟/类,超限 429 rate_limited。 路径: /api/v1/notify/*。
5.0 前置:拿到 unionid + 用户须关注
通知与查关注以 unionid 为唯一收件人身份。能用 unionid 的前提是: 你站点的公众号、你的小程序/网站应用,绑定在同一个微信开放平台账号下 (同一用户在各应用里才会拿到同一 unionid)。这是接入前一次性配置—— 小程序/网站应用侧由你自己绑(开放平台「管理中心」),平台公众号侧由运营绑; 没绑齐,unionid 拿不到或对不上,查关注/通知对该用户永久不可用且报错与 "未关注"不可区分(只能靠平台后台告警发现)。
三条渠道拿到 unionid(按你的接入形态选一条):
| 接入形态 | 怎么拿 unionid |
|---|---|
| 小程序自有登录(主画像) | 小程序 wx.login() → 你的服务端 jscode2session → 响应直接含 unionid(前提:小程序已绑开放平台) |
| 网站自有微信登录 | 你的网站应用 OAuth(snsapi_login)→ access_token + openid → sns/userinfo 响应含 unionid |
| 全托管 Rekit 登录 | 登录后 GET /api/v1/me(或 auth/exchange 响应)的 user.unionid,平台已替你落库 |
关注是投递前提: 模板消息只能发给已关注该站点绑定服务号的用户。
- 用户该关注哪个号:运营在后台给你站点绑定的那套公众号——向运营要公众号名称/二维码(平台不托管关注页;
/wechat/subscribe/{app_id}是一次性订阅消息授权页,与关注无关)。 - 用户关注后平台回调自动落库 unionid→openid,随后
subscribe-status即可查到(通常秒级)。 - 产品站推荐形态:发通知前
subscribe-status预检;subscribed !== true时在通知设置/订单页展示该服务号的关注入口(公众号资料卡/二维码),关注后再发。 - 用户明明已关注却始终
subscribed:false/wechat_member_not_found:几乎必然是公众号未绑开放平台(平台拿不到 unionid),联系运营核查绑定。
5.1 查是否关注
GET /api/v1/notify/subscribe-status?unionid=oYYYYQuery 传 unionid。
成功:
{
"ok": true,
"openid": "公众号openid",
"subscribed": true,
"nickname": "昵称或null",
"unionid": "unionid或null"
}关键约束:
- 平台用
members表把unionid→ 公众号openid(用户须曾关注过,并由公众号事件/回填写入)。 - 库中无该
unionid(从未关注)→ 返回{"subscribed": false}(不再报错),据此判断即可。 - 实时结果以微信
user/info的subscribe==1为准,不是只读本地缓存。
推荐用法(产品站):
- 直接用登录用户的
unionid查。 subscribed !== true时不要发模板消息(发也发不出去)。
5.2 发「工单状态」模板消息
POST /api/v1/notify/work-order-status
Content-Type: application/json
{
"unionid": "oYYYY",
"project_name": "你的产品名",
"status": "账号过期",
"customer_name": "用户昵称",
"time": "2026-07-17 18:00",
"url": "https://你的站/notify?scene=expired",
"client_msg_id": "expired:123"
}| 字段 | 必填 | 说明 |
|---|---|---|
unionid | 是 | 接入方共享 unionid;平台内部映射到投递 openid |
project_name | 是 | ≤100 |
status | 是 | ≤100;映射到 short_thing 时微信侧常 ≤5 字 |
customer_name | 是 | ≤100 |
time | 否 | YYYY-MM-DD HH:MM 或带秒;不传用服务器当前时间 |
url | 否 | 点击跳转 |
client_msg_id | 否 | 防重 |
成功:{ "ok": true, "msgid": "..." } 收件人 unionid 从未关注公众号:400 wechat_member_not_found(发送前用 5.1 预检)
模板字段映射由运营在公众号配置里设置;产品站只传上述语义字段。
7. 邮件(email_notify)— api.
POST /api/v1/notify/email
{
"to": "a@b.com",
"subject": "主题",
"html": "<p>正文</p>"
}成功:{ "ok": true } 站点须在后台「站点接入」绑定 SMTP;未绑定 → 503 site_email_unbound。 注册验证码由 login 服务自己发信,不要走本接口。
8. 图床(storage)— api.
7.1 站点开放预签名(Api-Key,不计入用户配额)
POST /api/v1/storage/open/presign
{
"filename": "a.webp",
"content_type": "image/webp",
"content_length": 12345,
"subdir": "products/thumbs"
}content_type仅:image/jpeg|image/png|image/webp|image/gifcontent_length:必填(字节,与 PUT body 长度一致);缺失 → 400content_length_required(不传则预签名 URL 不带长度约束,客户端可 PUT 任意大小对象)。用户 Bearer 预签名同样必填- 单文件 ≤ 5MB
- 响应:
{ key, put_url, url, unsigned_url, expires_in }(秒,通常 300) - 客户端对
put_url做 HTTP PUT,Header:Content-Type与申请时一致;若预签名绑定了长度则 body 长度必须等于content_length - 读图用
url(可能带 CDN 鉴权)或unsigned_url
7.2 登录用户资料图(Bearer,计入用户配额 ≤15MB)
POST /api/v1/storage/presign
Authorization: Bearer <access_token>
{ "filename":"a.webp", "content_type":"image/webp", "content_length":12345, "subdir":"avatars" }subdir 为 avatars / backgrounds 时,换绑后由 PATCH /api/v1/me 删除旧对象。
9. 支付 Checkout(payments)— pay.
POST /api/v1/payments/open/checkout/sessions
{
"mode": "payment",
"success_url": "https://你的站/checkout/success?session_id={CHECKOUT_SESSION_ID}",
"cancel_url": "https://你的站/pricing",
"user_id": 123,
"external_customer_id": "站点自管客户标识(与 user_id 二选一)",
"client_reference_id": "本地订单号",
"customer_email": "a@b.com",
"metadata": { "plan_type": "personal-monthly" },
"line_items": [{
"quantity": 1,
"price_data": {
"currency": "cny",
"unit_amount": 4900,
"product_data": {
"name": "个人版 - 月付",
"description": "有效期1年,不自动续费",
"unit_label": "/ 月",
"features": ["权益1", "权益2"]
}
}
}]
}- 付款方双轨:
user_id(login./me的平台用户id)与external_customer_id(站点自管客户标识)二选一;未接 Rekit 登录用后者即可 - 目前 仅支持恰好 1 条
line_items;unit_amount单位为 分 - 响应含
id、url、status、payment_status、amount_total等 - 浏览器 302 到
url(如https://pay.rekitdev.com/c/pay/cs_...) - 回站后履约:
GET /api/v1/payments/open/checkout/sessions/{id}仅当 payment_status === "paid" 时履约。可手动过期:POST .../sessions/{id}/expire。
底层直连(兼容,一般不必用):
POST /api/v1/payments/open/orders
{ "provider": "wechat", "amount_cents": 100, "subject": "标题", "external_customer_id": "cust_123" }provider:wechat | alipay。付款方 user_id 与 external_customer_id 二选一(都没传 → 422)。 站点须绑定对应支付配置。响应含 order_id / out_trade_no / code_url(微信 NATIVE 扫码)/ pay_url(支付宝)。
查订单状态(站点 Key,webhook 之外的轮询兜底):
GET /api/v1/payments/open/orders/{order_id}返回 status(pending | paid | ...)等;仅当 paid 时履约。
8.2 微信内联 JSAPI(产品站自己页面直接拉起付款,不跳转、不扫码)
微信内(MicroMessenger)希望在产品站自己的页面点一下直接弹微信付款,用这个。前提:站点 微信支付配置的 appid 必须等于平台公众号 appid(同一服务号),运营在后台绑定即可。
JSAPI 需 Rekit 微信身份:付款人 openid 由平台按 user_id 从 User.mp_openid 解析(产品站不接触 openid),前置该用户先完成 Rekit 微信登录(§3 的 silent=1 即可)。用 external_customer_id 的外部客户 拿不到公众号 openid,JSAPI 不可用,请走 NATIVE 扫码 / 支付宝 / §8 的 Checkout Session。
POST /api/v1/payments/open/orders
X-Api-Key: <站点 Key>
{ "provider": "wechat", "jsapi": true, "amount_cents": 100, "subject": "标题", "user_id": 123 }成功响应含 prepay_params,产品站在自己页面拉起:
// 必须在微信内置浏览器中;prepay_params 为服务端返回的对象
WeixinJSBridge.invoke('getBrandWCPayRequest', order.prepay_params, function (res) {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
// 用户已支付:以服务端为准履约——等 webhook order.paid,或轮询 GET /open/orders/{id}
} else {
// 取消或失败,提示重试
}
});- 到账仍以服务端为准:靠 webhook
order.paid(§8.1)+GET /open/orders/{order_id}兜底,
不要只信前端 getBrandWCPayRequest:ok。
409 {code:"payer_mp_openid_missing"}:该用户无公众号 openid(如仅邮箱登录)→ 引导其先做
微信登录,或回退 NATIVE 扫码 / 跳转 §8 的 Checkout Session。
409 {code:"jsapi_appid_mismatch"}:站点支付 appid≠公众号 appid,运营需在后台改配;此前回退扫码。- 不传
jsapi→ 维持 NATIVE 扫码(返回code_url);alipay忽略该字段。
8.1 Webhook —— 到账主动推送(推荐主力,轮询作兜底)
配置:后台「站点接入」→ 站点详情填 Webhook URL(https)、复制 Webhook Secret(站点详情随时可查看复制)。留空则不推送、退回轮询。
到账时 Rekit 向该地址 POST 一个签名事件(门铃);产品站验签后即发货,不必守着轮询。轮询 GET .../sessions/{id} 仍保留作兜底(保险锁)——万一 webhook 漏收,靠它补齐。两条腿都用,不丢单。
请求头:
| 头 | 说明 |
|---|---|
X-Rekit-Event-Id | 事件唯一 id(evt_...),用于去重 |
X-Rekit-Event-Type | order.paid(ping 为后台测试事件) |
X-Rekit-Timestamp | Unix 秒,用于防重放 |
X-Rekit-Signature | sha256=<hmac>,见下方验签 |
Body(order.paid):
{ "id":"evt_ab12…", "type":"order.paid", "created":1710000000,
"data":{ "order_id":123, "out_trade_no":"…", "provider":"wechat",
"amount_cents":4900, "currency":"cny", "user_id":456, "external_customer_id":null, "site_id":1,
"checkout_session_id":"cs_…", "client_reference_id":"你的本地单号",
"metadata":{…}, "paid_at":"2026-07-18T09:00:00+00:00" } }验签:hex( HMAC_SHA256(webhook_secret, "<X-Rekit-Timestamp>.<原始 body>") ) 应等于 X-Rekit-Signature 去掉 sha256= 前缀。务必用原始请求体字节验签,勿先 JSON 反序列化再拼回。
// Node.js / Express(express.raw 拿原始 body)
import crypto from "crypto";
app.post("/api/rekit/webhook", express.raw({ type: "*/*" }), (req, res) => {
const ts = req.get("X-Rekit-Timestamp") || "";
const sig = (req.get("X-Rekit-Signature") || "").replace(/^sha256=/, "");
const expect = crypto.createHmac("sha256", process.env.REKIT_WEBHOOK_SECRET)
.update(`${ts}.${req.body.toString("utf8")}`).digest("hex");
if (sig.length !== expect.length ||
!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expect))) {
return res.status(400).end(); // 验签失败
}
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.status(400).end(); // 防重放
const evt = JSON.parse(req.body.toString("utf8"));
// 幂等:按 evt.id 或 data.order_id 去重后再发货
fulfillOnce(evt);
res.status(200).end(); // 必须回 2xx,否则会重试
});重试与幂等:非 2xx / 超时 → 按退避重试(30s→2m→10m→30m→2h→6h→12h,约 8 次后置 failed,后台可「重发」)。同一笔到账只推一次事件,但重试可能让同一 event_id 到达多次——产品站务必按 event_id 或 order_id 幂等发货(至少一次语义)。
联调:后台站点详情「发送测试事件」会推一条 type:"ping",可用来验证地址连通 + 验签逻辑。
10. 多租户边界
- 用户账号是 平台级 SSO(邮箱/微信全局唯一;JWT 不含
site_id)。 - 开放能力按 站点 Key + 权限 隔离;支付订单带
site_id。 - 登录回跳 / Checkout URL 按站点域名白名单校验。
- 微信模板消息使用该站点绑定的公众号配置取 access_token。
11. 运营后台一次性配置(对接方 checklist)
- 「站点接入」:建站 → 填域名/对外 URL → 复制 API Key → 开权限 → 绑定邮件/存储/支付/公众号(按需)。
- 「邮件通知」:SMTP → 站点选用。
- 「对象存储」:COS;
public_base= CDN(如https://static.你的域名);直传域名勿填 CDN。 - 支付:商户配置 → 站点绑定;(推荐)填 Webhook URL + 复制 Webhook Secret,让到账主动推送产品站(§8.1)。
- 登录:微信回调域 =
login.;系统设置secret_key/auth_public_base_url等各服务共用。
12. 给其它 AI 的提示词
请先完整读取对接 skill(含文末接口参考),再按其中流程实现产品站对接,不要自建登录或公众号网页授权:
https://rekitdev.com/skill.md
我将提供:站点 slug、API Key、已开通权限、产品站 BASE_URL。
请实现:登录跳转 + callback 写 Cookie、/me、以及我开通权限对应的开放 API。13. 实现检查清单
- 对接第一步调
GET /api/v1/site自检;ready: false的项按missing找运营补齐 - 登录只跳
login./login或/login/mp,带site+return_to - callback 收
login_code,服务端POST /api/v1/auth/exchange换access_token/expires_in,再写 HttpOnly Cookie(不要在浏览器端/前端代码里直接读 URL 上的 token) - 用户 API 用 Bearer;开放 API 用服务端
X-Api-Key - Key 未进前端;权限已开
- 查关注 / 发模板:路径与字段按 §5;对外传
unionid(推荐),无需公众号 openid - 发模板前用
subscribe-status预检;unionid 从未关注 →subscribed:false/ 发模板400 wechat_member_not_found - 支付付款方:接了登录传
user_id,否则传external_customer_id(二选一) - 邮件/存储/支付站点已绑定配置
- 图床直传对象存储;支付用 Checkout 且 success/cancel host 在白名单
- (推荐)配 Webhook:验签
X-Rekit-Signature、按event_id/order_id幂等发货、回 2xx;轮询作兜底 - 微信授权回调域均为
login.域名
接口参考(完整契约)
产品站 / AI agent 按需查阅。鉴权:用户接口用 Authorization: Bearer;带权限 ID 的接口用 X-Api-Key。
基址:
- login. →
https://login.rekitdev.com(或NEXT_PUBLIC_SNGZS_AUTH_API_URL) - api. →
https://api.rekitdev.com(或SNGZS_API_URL) - pay. →
https://pay.rekitdev.com(或SNGZS_PAYMENTS_API_URL)
错误体统一形态:{"detail": {"code": "...", "message": "..."}}(平台在网关层统一外壳,接入方只需解析这一种)。
login. — 浏览器门户(非 JSON)
| 方法 | 路径 | Query | 说明 |
|---|---|---|---|
| GET | /login | return_to(回跳 URL)、site(slug)、lang=zh|en | 托管登录(邮箱/微信门户) |
| GET | /login/mp | 同上 + silent=1(可选) | 微信内公众号 H5 登录;非微信仅提示。silent=1→snsapi_base 静默(老用户零点击,新用户自动一次 userinfo 补资料),回传同样是 login_code |
| GET | /login/wechat | 同上 | 门户混入口(微信内→公众号,否则→PC 扫码);产品站 H5 请用 /login/mp |
| GET | /register | 同上 | 托管注册(回传同样是 login_code) |
| GET | /forgot-password | return_to 等 | 托管找回密码 |
登录成功后(site 有效且 return_to host 在白名单):
302 → {return_to}?login_code={一次性码}return_to 可自带 query(如 .../callback?returnTo=%2Fmember),平台仅追加 login_code。产品站服务端再用 POST /api/v1/auth/exchange {code: login_code} 换 access_token + user(一次性、90s 有效)——原始 token 不出现在浏览器 URL 里。
login. — JSON API
邮箱认证(一般由托管页使用;产品站通常不必直调)
| 方法 | 路径 | Body | 成功要点 |
|---|---|---|---|
| POST | /api/v1/auth/email-code | {email, purpose:"register"|"reset"} | 发验证码 |
| POST | /api/v1/auth/register | {email, code, password} | 含 access_token、expires_in、user |
| POST | /api/v1/auth/login | {email, password} | 同上 |
| POST | /api/v1/auth/reset-password | {email, code, password} | 重置密码 |
密码长度 ≥ 6。
配置与微信登录(唯一入口:托管门户)
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/app-config | 微信登录/支付是否启用 |
| GET | /login?return_to=&site= | 托管登录门户(PC 扫码 / 邮箱);登录后 302 回 return_to?login_code=… |
| GET | /login/mp?return_to=&site=&silent=1 | 微信内 H5 公众号登录;silent=1 走 snsapi_base 静默(无授权页) |
| POST | /api/v1/auth/exchange | {code}(回跳带回的 login_code)→ access_token + user(一次性换票) |
平台回调 /api/v1/auth/wechat/{web,mp,mp/silent}/callback 由门户内部使用,产品站不直接调用。
唯一路子: 浏览器 /login,微信内 H5 /login/mp?silent=1,回跳拿 login_code → POST /api/v1/auth/exchange 换 token。不要在产品站自拼微信授权 URL,也不存在 state/authorize-url 这类底层入口。
当前用户(Bearer)
GET /api/v1/me
{
"user": {
"id": 1,
"nickname": "昵称",
"avatar_url": "https://...",
"background_url": "https://...",
"email": "a@b.com",
"has_password": true,
"has_wechat_web": false,
"has_mp": true,
"unionid": "oUnion..."
}
}注意:响应 不含 mp_openid;仅有 has_mp。公众号 openid 请用 api. subscribe-status 解析并自行缓存。
PATCH /api/v1/me
Body(均可选,至少一项有意义):
{ "nickname": "...", "avatar_url": "https://...", "background_url": "https://..." }avatar_url/background_url须http://或https://,或空串清空nickname非空- 换绑资料图时平台会删除旧 COS 对象(若属于本平台存储)
邮箱绑定 / 密码 / 微信
| 方法 | 路径 | Body / Query | 说明 |
|---|---|---|---|
| POST | /api/v1/me/bind-email/code | {email} | 返回 {expires_in} |
| POST | /api/v1/me/bind-email | {email, code, password} | 邮箱占用 409 |
| POST | /api/v1/me/unbind-email | — | 须已绑微信 |
| POST | /api/v1/me/password | {current_password, new_password} | 须已设密码 |
| GET | /api/v1/me/bind-wechat/web/start | return_to= | {state, authorize_url, redirect_uri} |
| POST | /api/v1/me/unbind-wechat | {channel:"web"} | 须已有邮箱密码 |
api. — 站点自检
权限: 无(只需 X-Api-Key 有效、站点启用)
GET /api/v1/site
返回本站点身份与各能力就绪状态——对接第一步先调它。permission = 权限位;ready = 权限位 且 后台已绑定可用配置;missing = 未就绪的精确原因(可直接作为给运营的工单)。
{
"site": {"slug": "yoursite", "name": "YourSite", "domain": "your.site.com"},
"capabilities": {
"storage": {
"permission": true,
"ready": true,
"missing": null,
"read_mode": "public",
"cdn_auth_enabled": false,
"public_base": "https://static.example.com"
},
"wechat_notify": {
"permission": true,
"ready": false,
"missing": "未绑定公众号配置,或所绑公众号已停用,请联系运营在后台「站点接入」绑定"
},
"email_notify": {"permission": true, "ready": true, "missing": null},
"captcha": {"permission": true, "ready": true, "missing": null},
"payments": {
"permission": true,
"ready": true,
"missing": null,
"providers": ["wechat", "alipay"],
"webhook_configured": true
}
},
"docs": "https://rekitdev.com/skill.md"
}| 字段 | 说明 |
|---|---|
capabilities.*.ready | 权限位 + 后台已绑配置且启用,才是真正可调用 |
storage.read_mode | public:unsigned_url 是永久链;signed:url 带 CDN 鉴权(默认 3600s 过期、无续期端点,仅即传即用) |
storage.cdn_auth_enabled | 后台原始开关,与 read_mode 可能不一致(开了鉴权但站点无 cdn 权限时,生效仍是 public) |
payments.providers | 可用渠道(绑定 + 启用 + 配置完整,与下单运行时同一判定) |
payments.webhook_configured | false = 到账不推送(后台配 webhook_url 后才有;后配不补历史,靠轮询兜底) |
api. — 邮件
api. — Captcha
权限: captcha 浏览器协议: /rc/v1/s/{site_slug}(SDK 自动调用 handshake / assess / challenge / verify) SDK: {SNGZS_API_URL}/rc/v1/rekit.min.js
POST /api/v1/captcha/verify
服务端用站点 Key 校验浏览器拿到的 token。默认 consume:true,同一 token 只能成功一次。
{ "token": "...", "action": "可选", "consume": true }成功:
{ "ok": true, "site": "yoursite", "sub": "rekit-pass", "score": 0.92, "expires_at": 1710000000 }失败:400 captcha_invalid;未开权限:403 permission_denied。
权限: email_notify 头: X-Api-Key 路径: /api/v1/notify/*。
POST /api/v1/notify/email
{ "to": "a@b.com", "subject": "主题", "html": "<p>正文</p>" }| 字段 | 约束 |
|---|---|
to | 3–254 |
subject | 1–200 |
html | 非空 |
成功:{"ok": true} 未绑定 SMTP:503 site_email_unbound SMTP 失败:502
api. — 公众号通知
权限: wechat_notify 头: X-Api-Key 收件人身份: 统一用 unionid(接入方共享同一开放平台主体,只拿得到 unionid;公众号 openid 只有公众号自己拿得到)。平台内部用 members 表映射到投递 openid。 限流: 每站点 120 次/分钟/类,超限 429 rate_limited。 路径: /api/v1/notify/*。
GET /api/v1/notify/subscribe-status
Query:
| 参数 | 说明 |
|---|---|
unionid | 平台用 members 表解析到公众号 openid |
成功:
{
"ok": true,
"openid": "oMp...",
"subscribed": true,
"nickname": "昵称",
"unionid": "oUnion..."
}失败 / 边界:
| 情况 | HTTP / 返回 |
|---|---|
| 未传 unionid | 422 |
| unionid 从未关注(members 无记录) | 200 {"subscribed": false}(不再报错) |
| 微信 API 错误 | 400 |
| Key / 权限 | 401 / 403 |
subscribed:微信 cgi-bin/user/info 的 subscribe == 1(实时)。
POST /api/v1/notify/work-order-status
{
"unionid": "oUnion...",
"project_name": "你的产品名",
"status": "账号过期",
"customer_name": "用户昵称",
"time": "2026-07-17 18:00",
"url": "https://站/notify?scene=expired",
"client_msg_id": "可选防重ID"
}| 字段 | 必填 | 约束 |
|---|---|---|
unionid | 是 | 接入方共享 unionid;平台内部映射到投递 openid |
project_name | 是 | ≤100 |
status | 是 | ≤100;short_thing 模板常限制约 5 字 |
customer_name | 是 | ≤100 |
time | 否 | YYYY-MM-DD HH:MM / HH:MM:SS / YYYY/MM/DD HH:MM |
url | 否 | 点击跳转 |
client_msg_id | 否 | 防重 |
成功:{"ok": true, "msgid": "..."} time 格式非法:400 invalid_time_format 收件人 unionid 从未关注公众号:400 wechat_member_not_found(先用 subscribe-status 预检)
api. — 图床
GET /api/v1/storage/config(可无 Key)
{
"enabled": true,
"public_base": "https://static.example.com",
"cdn_auth_enabled": false,
"path_prefix": "...",
"max_single_upload_bytes": 5242880,
"max_user_storage_bytes": 15728640
}POST /api/v1/storage/open/presign — 权限 storage + Api-Key
请求:
{
"filename": "a.webp",
"content_type": "image/webp",
"content_length": 12345,
"subdir": "products/thumbs"
}| 字段 | 必填 | 说明 |
|---|---|---|
filename | 否 | 默认 upload.webp |
content_type | 否 | 默认 image/webp;仅 jpeg/png/webp/gif |
content_length | 是 | 字节;与 PUT body 一致;缺失 → 400 content_length_required;校验 ≤5MB |
subdir | 否 | ≤128;落在 {path_prefix}/{site_slug}/ 下 |
响应:
{
"key": "prefix/slug/subdir/uuid.webp",
"put_url": "https://cos.../...",
"url": "https://static.../...",
"unsigned_url": "https://static.../...",
"expires_in": 300
}站点未绑定存储:503 site_storage_unbound。
客户端:PUT put_url,Content-Type 与申请一致。
POST /api/v1/storage/presign — Bearer 用户上传
同上 body(content_length 同样必填);计入用户配额(总 ≤15MB)。 响应额外含 quota: {used_bytes, max_bytes, max_single_bytes}。
GET /api/v1/storage/quota — Bearer
{ "used_bytes": 0, "max_bytes": 15728640, "max_single_bytes": 5242880 }pay. — 支付
权限: payments(开放接口) 头: X-Api-Key(开放接口)
POST /api/v1/payments/open/checkout/sessions
请求:
{
"mode": "payment",
"success_url": "https://站/success?session_id={CHECKOUT_SESSION_ID}",
"cancel_url": "https://站/pricing",
"user_id": 123,
"external_customer_id": "站点自管客户标识(与 user_id 二选一)",
"client_reference_id": "local-order-1",
"customer_email": "a@b.com",
"metadata": { "plan_type": "monthly" },
"line_items": [
{
"quantity": 1,
"price_data": {
"currency": "cny",
"unit_amount": 4900,
"product_data": {
"name": "商品名",
"description": "描述",
"unit_label": "/ 月",
"features": ["特性"],
"validity": "",
"activate_note": "",
"renewal_note": ""
}
}
}
]
}约束:
mode目前仅"payment"line_items恰好 1 条;unit_amount> 0,单位 分- 付款方双轨:
user_id(Rekit 登录用户)与external_customer_id(站点自管客户标识)二选一;未接 Rekit 登录用后者即可 success_url/cancel_urlhost 须在站点白名单metadata值为 string 字典
响应要点(Stripe 风格):
{
"id": "cs_...",
"object": "checkout.session",
"url": "https://pay.../c/pay/cs_...",
"status": "open",
"payment_status": "unpaid",
"mode": "payment",
"amount_total": 4900,
"currency": "cny",
"success_url": "...",
"cancel_url": "...",
"client_reference_id": "...",
"customer_email": "...",
"metadata": {},
"line_items": [],
"expires_at": 1710000000,
"created": 1710000000
}浏览器跳转 url。履约前再查 Session。
GET /api/v1/payments/open/checkout/sessions/{id}
同站点 Key 可查。payment_status === "paid" 时履约。
POST /api/v1/payments/open/checkout/sessions/{id}/expire
手动过期未支付 Session。
托管页(浏览器,非 JSON)
| 路径 | 说明 |
|---|---|
GET /c/pay/{session_id} | 单页收银台:确认订单 / 选支付方式 / 内联二维码 / 就地切换(/wait 旧链接 302 回本页) |
Webhook —— 到账主动推送(order.paid)
站点配 webhook_url(后台「站点接入」)后,到账时 Rekit POST 签名事件到该地址。头 X-Rekit-Event-Id / X-Rekit-Event-Type / X-Rekit-Timestamp / X-Rekit-Signature: sha256=<hmac>;签名 = HMAC_SHA256(webhook_secret, "<timestamp>.<raw_body>") hex。Body data:order_id / out_trade_no / provider / amount_cents / currency / user_id / external_customer_id / site_id / checkout_session_id / client_reference_id / metadata / paid_at。非 2xx 退避重试(~8 次),按 event_id/order_id 幂等发货。详见 SKILL §8.1(含验签示例)。轮询接口仍作兜底。
POST /api/v1/payments/open/orders(兼容直连)
{
"provider": "wechat",
"amount_cents": 100,
"subject": "订单标题",
"user_id": 123,
"external_customer_id": "站点自管客户标识(与 user_id 二选一)"
}provider:wechat | alipay。付款方 user_id 与 external_customer_id 二选一(都没传 → 422)。 传了 user_id 但用户不存在:404 user_not_registered(用 external_customer_id 则不涉及)。未绑定渠道:503。 响应含 order_id / out_trade_no / code_url(微信 NATIVE)/ pay_url(支付宝)。
微信内联 JSAPI:加 "jsapi": true(provider=wechat)→ 响应含 prepay_params({appId,timeStamp, nonceStr,package,signType,paySign}),产品站在自己页面 WeixinJSBridge.invoke('getBrandWCPayRequest', prepay_params, cb) 拉起。付款人 openid 只能来自 Rekit 微信登录(平台按 user_id 从 User.mp_openid 解析,见 §3 silent=1;该用户须已通过 Rekit 微信登录)。开放下单 body 没有 payer_openid 字段;用 external_customer_id 的外部客户拿不到公众号 openid, JSAPI 不可用,请走 NATIVE 扫码 / 支付宝 / 托管收银台。错误:409 payer_mp_openid_missing、 409 jsapi_appid_mismatch(支付 appid≠公众号 appid)→ 均应回退扫码。
GET /api/v1/payments/open/orders/{order_id}(站点 Key,轮询兜底)
按 site_id 隔离取单 → 返回 status(pending|paid|...)等;仅 paid 履约。webhook order.paid 主推之外的兜底腿。订单不存在或跨站:404 order_not_found。
POST /api/v1/payments/open/orders/{order_id}/cancel(站点 Key)
取消 pending 订单(与下单/查单同一条开放路)。按 site_id 隔离,防跨站。订单不存在或跨站:404 order_not_found。
/api/v1/payments/open/* 一套(站点 Key)。无 Bearer 版订单接口。支付结果异步通知路径(平台内部验签,产品站一般不对接): /api/v1/payments/notify/{provider}/{config_id}。
权限目录
| id | 标签 | 服务 |
|---|---|---|
storage | 图床上传 | api |
cdn | CDN 鉴权(影响返回的 url 是否签名) | api |
wechat_notify | 公众号查关注 / 模板消息 | api |
email_notify | 邮件通知 | api |
payments | 支付 | pay |
captcha | 人机验证 | api |
产品站 Cookie / Callback 约定(参考)
| Cookie | 用途 |
|---|---|
sngzs_user_token | 产品站 HttpOnly 存 access_token(参考站约定名) |
sngzs_auth_return | 可选,暂存登录后相对路径 |
说明:login. 服务自身也可能 Set-Cookie 名为 user_token;产品站应以 callback 写入本站 Cookie 为准,不要依赖跨域读 login. 的 Cookie。
Callback query:login_code(一次性消费、90s 有效)、returnTo(相对路径)。站点服务端用 login_code 调 POST /api/v1/auth/exchange 换取 access_token(旧 ?access_token= 直回协议已废止)。
参考代码:rekitdev/src/lib/auth-session.ts、rekitdev/src/pages/api/auth/callback.ts、rekitdev/src/lib/sngzs-client.ts。