Chandler.work 合作伙伴接入指南
文档版本: v3.2
更新时间: 2026-08-11
适用域名:chandler.work
API 基础路径:https://api.chandler.work当前可用性说明:短信验证码功能(手机 OTP 登录/注册/找回密码、绑定手机)因运营商签名审核暂未开放,相关接口返回
403 auth.sms_disabled;邮箱验证码功能(登录、找回密码)已完整提供,见 2.8。短信功能开放时间以GET /v1/auth/capabilities的sms_otp_enabled为准。支付渠道:目前支持微信支付,两种接入模式——特约商户模式(推荐;平台统一管理渠道参数,完成资质进件即可收款)和直连模式(使用自有微信商户号,在控制台自行配置渠道参数)。支付宝暂未开放。
本文档面向合作伙伴开发者,完整覆盖从注册账号 → 填写资质 → 创建应用 → 配置支付 → API 对接的全流程。既包含 Web 控制台操作步骤,也包含对应的 API 调用细节。
目录
- 快速概览
- 注册与登录
- 填写合作伙伴资质信息
- 创建 OAuth 应用
- 开通收款(特约商户)
- 实现 SSO 登录
- 实现支付流程
- 订单与退款管理
- 团队与应用管理
- 财务与账单
- 对账与分析
- API Key 与安全日志
- 错误处理
- 安全最佳实践
- API 速查表
- 常见问题
1. 快速概览
1.1 平台简介
Chandler.work 持有微信支付服务商资质和中国清算协会收单服务商备案,为合作伙伴提供:
| 能力 | 说明 |
|---|---|
| 统一用户系统 | 完整的注册、登录、注销、找回密码后端能力,提供公开 API(见第 2 章);可嵌入平台托管页面,也可由合作伙伴自行实现界面调用 API |
| OAuth 2.0 SSO | 标准 OAuth2 流程,用户授权后获取身份信息 |
| 子商户开通与统一收款 | 我们为你开通微信支付子商户,用户付款直达你的账户 |
| 应用目录 | 应用市场展示,增加曝光与获客 |
1.2 接入流程总览
注册账号 → 验证邮箱 → 填写资质 → 平台审批+开通子商户 → 创建应用 → 技术对接 → 联调测试 → 上线
合作伙伴不需要申请微信支付商户号、不需要配置任何支付密钥。平台持有微信支付服务商资质,为你开通特约商户(子商户),资金由微信支付直接清算至你的银行账户,平台按约定费率从交易中自动分账收取技术服务费。
1.3 环境信息
| 环境 | 地址 | 用途 |
|---|---|---|
| 生产环境 | https://api.chandler.work |
正式接入 |
| 测试环境 | 向平台申请 | 本地开发联调 |
1.4 通用约定
所有 API 响应遵循统一信封格式:
{
"requestId": "req_xxx",
"data": { },
"error": null
}
错误响应:
{
"requestId": "req_xxx",
"data": null,
"error": {
"code": "auth.invalid_credentials",
"message": "邮箱或密码错误"
}
}
2. 注册与登录
账号能力提供两种使用方式,二选一或混合均可:
- 平台托管页面:直接使用平台提供的注册/登录页面(2.1),无需开发;
- 自行实现界面(API 集成):注册、登录、注销、找回密码等全部账号功能都有公开 API(无需预先登录即可调用),合作伙伴完全可以自建注册页/登录页/注销流程,在自己的产品界面里调用 Chandler API 完成——token 由 API 返回,后续接口鉴权与托管页面一致。下方 2.2-2.8 的接口即供此类集成使用。
账号 API 均返回统一的
access_token/refresh_token(后续所有接口的鉴权凭证);注销(2.5)会吊销 refresh token,登出全部设备可调/v1/auth/logout-all。
2.1 Web 端注册
访问合作伙伴注册页面:https://app.chandler.work/partner/register
填写以下信息:
| 字段 | 说明 | 要求 |
|---|---|---|
| 显示名称 | 你的名称 | 可选 |
| 邮箱 | 登录账号 | 必填,需有效邮箱 |
| 密码 | 登录密码 | 至少 8 位字符 |
| 同意协议 | 勾选隐私政策和用户协议 | 必须勾选 |
注册成功后自动登录。注册后系统会自动发送验证邮件,合作伙伴功能(应用管理、收款、订单、财务)需要完成邮箱验证后才能使用——请查收邮件(含垃圾箱)并完成验证;未验证时后台会引导你重发邮件或输入验证码。
2.2 API 注册
curl -X POST https://api.chandler.work/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "dev@your-company.com",
"password": "Your-Strong-Pass123!",
"display_name": "Your Company Dev",
"agree_policies": true
}'
响应:
{
"requestId": "req_xxx",
"data": {
"access_token": "eyJ...",
"refresh_token": "rt_xxx",
"token_type": "Bearer",
"expires_in": 3600,
"user": {
"id": "usr_xxx",
"email": "dev@your-company.com",
"display_name": "Your Company Dev",
"email_verified": false,
"mfa_enabled": false,
"is_admin": false,
"created_at": "2026-07-25T10:00:00Z"
}
}
}
重要:保存返回的 access_token,后续所有 API 调用需要它。
2.3 API 登录
curl -X POST https://api.chandler.work/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "dev@your-company.com",
"password": "Your-Strong-Pass123!"
}'
MFA 场景:如果用户启用了双因素认证,登录会返回 HTTP 428:
{
"requestId": "req_xxx",
"data": { "mfa_required": true, "mfa_token": "..." },
"error": null
}
此时需要使用返回的 mfa_token 调用 MFA 验证接口完成登录。
除账号密码外,也支持邮箱验证码登录(无密码场景),见 2.8;支持微信扫码登录(方式 A 平台托管页 / 方式 B 合作伙伴内嵌扫码)与微信绑定,见 2.9。手机短信验证码暂未开放。
2.4 刷新 Token
Access Token 有效期为 1 小时,过期后使用 Refresh Token 续期:
curl -X POST https://api.chandler.work/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token={REFRESH_TOKEN}"
2.5 登出
curl -X POST https://api.chandler.work/v1/auth/logout \
-H "Content-Type: application/json" \
-d '{"refresh_token": "{REFRESH_TOKEN}"}'
2.6 修改密码
已登录用户可直接通过 API 修改密码,无需跳转网站(适用于 PC/移动客户端内嵌的账号设置页):
curl -X POST https://api.chandler.work/v1/auth/change-password \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"old_password": "Old-Pass123!",
"new_password": "New-Strong-Pass456!"
}'
成功响应 200:
{ "requestId": "req_xxx", "data": { "status": "ok" }, "error": null }
错误响应:
| HTTP | error.code |
说明 |
|---|---|---|
| 401 | auth.invalid_credentials |
旧密码错误 |
| 422 | auth.weak_password |
新密码不合规:8–255 字符,且小写/大写/数字/符号 4 类至少覆盖 3 类 |
| 401 | token.invalid |
未登录或 token 无效 |
重要:修改密码成功后,该用户的所有 refresh token 被吊销、所有已签发的 access token 立即失效(含当前请求使用的这个)。客户端应引导用户用新密码重新登录。
该接口要求活跃用户会话(Bearer JWT 或 Web 会话 cookie),长期 API Key 不能用于修改密码。
2.7 忘记与重置密码
用户忘记密码时走邮件重置流程,两步均为公开接口(无需登录):
第一步:请求重置邮件
curl -X POST https://api.chandler.work/v1/auth/forgot-password \
-H "Content-Type: application/json" \
-d '{"email": "dev@your-company.com"}'
响应固定为 202 {"status":"accepted"}——无论邮箱是否存在都返回相同结果(防账号枚举)。系统会向该邮箱发送包含重置验证码(token)的邮件。
第二步:用邮件中的 token 设置新密码
curl -X POST https://api.chandler.work/v1/auth/reset-password \
-H "Content-Type: application/json" \
-d '{
"token": "{RESET_TOKEN}",
"new_password": "New-Strong-Pass456!"
}'
错误响应:400 token.invalid(token 无效或已过期)、422 auth.weak_password(密码策略同 2.6)、403 auth.user_disabled(账号被禁用)。
重置成功后同样吊销该用户的全部 token。客户端若希望全程不跳出应用,可自行处理邮件链接的 deep link,或引导用户将邮件中的 token 粘贴到客户端内。
2.8 验证码登录(邮箱)
除账号密码外,你的客户端可以直接调用邮箱验证码接口完成登录,两步均为公开接口(无需登录):
手机短信验证码暂未开放:平台短信签名正在运营商审核中,
target_type=phone以及/v1/auth/phone/*系列接口当前一律返回403 auth.sms_disabled;审核通过后这些接口无需变更即可直接使用。客户端可调用GET /v1/auth/capabilities查询sms_otp_enabled的实时状态,据此决定是否展示手机短信入口。
第一步:发送验证码
curl -X POST https://api.chandler.work/v1/auth/otp/send \
-H "Content-Type: application/json" \
-d '{"target": "user@example.com", "target_type": "email"}'
target_type当前仅支持email(phone暂未开放,见上)。- 响应固定为
200 {"status":"sent"}——目标不存在或不可用时同样返回成功(防账号枚举),只是不会真的发码。 - 验证码 6 位数字,默认 10 分钟有效,单码最多尝试 3 次,验证成功或重发即作废。
- 发送频率:发送接口按来源 IP 限流(默认 10 次/分钟)。客户端应在用户点击「发送验证码」后进入 60 秒倒计时再允许重发,触发 429 时按
Retry-After退避(见 13.2)。
第二步:验证码登录
curl -X POST https://api.chandler.work/v1/auth/otp/login \
-H "Content-Type: application/json" \
-d '{"target": "user@example.com", "target_type": "email", "code": "123456"}'
成功响应与密码登录一致(access_token / refresh_token / user)。MFA 场景:如果用户启用了双因素认证,验证码校验通过后同样返回 HTTP 428,data 中携带 mfa_token(与 2.3 密码登录的 MFA 挑战完全一致):
{
"requestId": "req_xxx",
"data": { "mfa_required": true, "mfa_token": "..." },
"error": null
}
此时需携带 mfa_token 和验证器 App 的 6 位动态码调用 POST /v1/auth/mfa/verify 完成登录。错误响应:400 request.invalid(目标格式错误)、401 auth.invalid_credentials(验证码错误或已过期)、403 auth.user_disabled、403 auth.sms_disabled(目标为手机号且短信功能未开放)。
找回密码:目前仅支持邮箱重置流程(见 2.7)。短信验证码重置密码(POST /v1/auth/phone/forgot-password + POST /v1/auth/phone/reset-password,API-only)将在短信签名审核通过后开放,届时绑定了手机号的账号可直接在客户端调用。
2.9 微信扫码登录与微信绑定
平台支持微信扫码登录,既有用户绑定微信后即可在所有接入应用扫码登录;新用户扫码注册时**必须提供邮箱或手机(二选一)**作为账号恢复通道。两种使用方式:
- 方式 A:平台托管扫码页(零开发)——登录页提供「微信扫码登录」入口,扫码确认后自动完成登录与授权,无需任何接入工作。
- 方式 B:合作伙伴内嵌扫码(API 集成)——在自己的登录页渲染二维码,通过扫码会话 API 完成登录与授权,交互体验更好。
方式 B 完整流程:
第一步:创建绑定 OAuth 参数的扫码会话
curl -X POST https://api.chandler.work/v1/oauth/wechat/qr-sessions \
-H "Content-Type: application/json" \
-d '{
"client_id": "{CLIENT_ID}",
"redirect_uri": "https://your-domain.com/auth/callback",
"scope": "openid profile",
"state": "{随机字符串}",
"code_challenge": "{PKCE 挑战值,可选}",
"code_challenge_method": "S256"
}'
响应:
{
"requestId": "req_xxx",
"data": {
"session_id": "wxqr_xxx",
"qr_url": "https://open.weixin.qq.com/connect/qrconnect?...",
"status": "pending",
"browser_token": "bt_xxx",
"expires_at": "2026-08-11T10:07:00Z"
}
}
- 创建时会校验
client_id+redirect_uri是否在白名单内(与 6.2 授权页同一套校验),校验失败返回400 oauth.invalid_client/400 oauth.invalid_redirect_uri。 browser_token同时以 HttpOnly cookie 下发(同域可用);跨域合作伙伴请使用响应体中的browser_token,通过X-WeChat-Browser-Token请求头轮询会话。
第二步:展示二维码并轮询状态(qr_url 在本地渲染为二维码,不经第三方服务)
curl "https://api.chandler.work/v1/auth/wechat/qr-sessions/{session_id}" \
-H "X-WeChat-Browser-Token: {browser_token}"
| 状态 | 含义 | 处理 |
|---|---|---|
pending |
等待用户扫码 | 继续轮询(建议 2 秒间隔) |
needs_bind |
该微信未绑定任何账号 | 新用户:引导注册(见下方「扫码注册」) |
confirmed |
扫码确认完成 | 进入第三步 |
expired / cancelled |
二维码过期/取消 | 重新创建会话 |
第三步:完成登录并授权
# 浏览器重定向(推荐):
# GET https://api.chandler.work/v1/oauth/wechat/complete?session_id={session_id}
# 服务端消费一次性 exchange_code(浏览器无需持有),设置登录 cookie 后
# 302 跳转到 consent 授权页;MFA 账号先展示 TOTP 挑战页。
complete 完成后,后续与标准 OAuth 授权码流程完全一致:用户同意授权 → 浏览器跳回你的 redirect_uri?code=... → 用 code 换 token(见 6.3)。
扫码注册(新用户,强制邮箱或手机二选一)
新用户首次扫码时会话状态为 needs_bind,需先注册账号(exchange_code 由扫码会话生成):
curl -X POST https://api.chandler.work/v1/auth/wechat/register \
-H "Content-Type: application/json" \
-d '{
"session_id": "{session_id}",
"exchange_code": "{exchange_code}",
"display_name": "微信昵称(可选)",
"email": "user@example.com"
}'
- 邮箱路径(
email):建号后自动发送验证邮件,响应携带verification_required: true,需用户完成邮箱验证后使用完整能力(与 2.1 邮箱注册一致)。 - 手机路径(
phone+phone_otp):需先调POST /v1/auth/otp/send(target_type=phone、purpose=register)获取验证码,注册时一并提交phone_otp完成验证(短信签名审核通过后可用)。 - 邮箱和手机必须二选一:两者都为空或同时提供,均返回
400 auth.wechat_contact_required。
绑定微信(既有用户)
- Web 端:账号安全页「绑定微信」区块(
/user/security#wechat),扫码 → 确认 → 输入登录密码或身份验证码完成二次授权 → 绑定成功。 - API:
curl -X POST https://api.chandler.work/v1/me/identities/wechat \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"session_id": "{session_id}",
"current_password": "{登录密码}" # 或 reauth_code: "{身份验证码}"
}'
- 一个微信 unionid 全局唯一(已绑定其他账号时返回
409 auth.identity_taken);解绑后重新绑定走同一流程。 exchange_code可省略——服务端会从会话中解析,浏览器无需持有一次性兑换码。
3. 填写合作伙伴资质信息
3.1 Web 端操作
登录后访问 https://app.chandler.work/partner/profile,填写公司资质信息:
| 字段 | 说明 | 要求 |
|---|---|---|
| 公司名称 | 企业全称 | 必填,最长 128 字符 |
| 统一社会信用代码 | 营业执照编号 | 可选,最长 32 字符 |
| 联系人 | 技术/业务负责人 | 可选,最长 64 字符 |
| 联系邮箱 | 联系邮箱 | 可选,最长 128 字符 |
| 联系电话 | 联系电话 | 可选,最长 32 字符 |
| 资质备注 | 其他说明 | 可选,最长 1024 字符 |
操作流程:
- 点击 「保存」 — 保存当前填写的信息(状态为
draft) - 点击 「提交审核」 — 提交给平台审核(状态变为
submitted)
3.2 上传资质材料
在资质信息页面下方可以上传资质材料:
- 支持格式:PDF、JPG、PNG
- 单个文件不超过 10MB
- 材料类型:营业执照、其他材料
3.3 API 提交资质信息
保存资质信息:
curl -X PUT https://api.chandler.work/v1/me/partner-profile \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"company_name": "Your Company Name",
"credit_code": "91110108xxx",
"contact_name": "技术负责人",
"contact_email": "dev@your-company.com",
"contact_phone": "13800138000",
"license_note": "营业执照编号等"
}'
提交审批:
curl -X POST https://api.chandler.work/v1/me/partner-profile/submit \
-H "Authorization: Apikey {API_KEY}"
查看资质状态:
curl https://api.chandler.work/v1/me/partner-profile \
-H "Authorization: Apikey {API_KEY}"
3.4 审批状态流转
draft → submitted → under_review → approved / rejected
| 状态 | 说明 |
|---|---|
draft |
草稿,可自由编辑 |
submitted |
已提交,等待平台审核 |
under_review |
审核中 |
approved |
审批通过,可使用全部功能 |
rejected |
驳回,需修改后重新提交 |
审批通过后平台会为你的应用完成特约商户进件与激活(见第 5 章),随后即可使用全部合作伙伴功能。
4. 创建 OAuth 应用
4.1 Web 端操作
访问 https://app.chandler.work/partner/apps,点击 「创建应用」:
| 字段 | 说明 | 要求 |
|---|---|---|
| 应用名称 | 你的应用名称 | 必填 |
| 回调地址 | OAuth 授权完成后的跳转地址 | 必填,如 https://your-domain.com/callback |
创建成功后显示 client_id 和 client_secret。
重要:
client_secret仅在创建时返回一次,请立即复制保存。
4.2 API 创建应用
curl -X POST https://api.chandler.work/v1/me/oauth/clients \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "Your App Name",
"redirect_uris": ["https://your-domain.com/auth/callback"],
"scopes": "openid profile email"
}'
响应(client_secret 仅返回一次):
{
"requestId": "req_xxx",
"data": {
"client_id": "cld_xxxxxxxxxxxx",
"client_secret": "csec_xxxxxxxxxxxx",
"name": "Your App Name",
"redirect_uris": ["https://your-domain.com/auth/callback"],
"scopes": "openid profile email",
"status": "active"
}
}
4.3 查看应用列表
curl https://api.chandler.work/v1/me/oauth/clients \
-H "Authorization: Apikey {API_KEY}"
4.4 更新应用信息
curl -X PUT https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID} \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated App Name",
"redirect_uris": ["https://your-domain.com/auth/callback"],
"scopes": "openid profile email",
"notify_url": "https://your-domain.com/webhook/payment"
}'
其中 notify_url 为你的支付结果 webhook 地址(特约商户模式的支付通知会推送到这里,见 7.4;不传则保持原值,传空字符串则清除)。
「回调地址」与「支付回调地址」的区别——两个词都出现在应用设置里,但方向、调用方、配置位置完全不同:
回调地址(redirect_uri) 支付回调地址(notify_url) 用途 OAuth 授权完成后的浏览器跳转,携带授权码 code(见第 6 章)支付结果的服务器间 webhook,验签后入账履约(见 7.4) 调用方 最终用户的浏览器 chandler 服务端 网络要求 与用户浏览器可达即可;本地开发允许 http://localhost必须公网可达的 HTTPS(微信通知经 chandler 转发) 配置位置 应用设置「回调地址」(本表 redirect_uris)特约商户模式:本表 notify_url;直连模式:「配置支付」页支付配置内(见 5.4)
4.5 重置 client_secret
如果 client_secret 泄露,可以重新生成:
curl -X POST https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/secret \
-H "Authorization: Apikey {API_KEY}"
4.6 禁用/启用应用
curl -X POST https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/status \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{"status": "disabled"}'
4.7 删除应用
curl -X DELETE https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID} \
-H "Authorization: Apikey {API_KEY}"
删除后应用将无法继续使用 SSO 和支付能力,但历史订单和配置保留用于审计。
4.8 配置应用目录信息
用户在授权页和应用市场看到的信息由此配置:
curl -X PUT https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/directory \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "Your App Name",
"description": "一句话介绍你的应用",
"category": "productivity",
"website_url": "https://your-domain.com"
}'
5. 开通收款(特约商户模式)
这是唯一需要平台参与的步骤:平台为你在微信支付侧进件特约商户(子商户),并把 sub_mchid 登记到你的应用下。你不需要申请微信支付商户号、不需要配置任何商户密钥或回调域名——下单、回调、退款、对账全部由平台以服务商身份统一处理。
5.1 平台进件流程
- 你在第 3 章提交资质并通过审批
- 平台为你进件特约商户(微信进件 API 自动提交为主、人工为辅,1-3 个工作日;进件材料自动取自你的资质信息,无需重复准备)
- 微信审核通过后平台管理员激活你的特约商户
- 激活后你的应用立即具备收款能力,资金由微信支付直接清算到你进件时登记的银行账户
5.2 查询特约商户状态
Web 端:合作伙伴后台「资质信息」页可查看特约商户状态(待进件 / 已激活 / 已停用,sub_mchid 脱敏显示)。
API:
curl "https://api.chandler.work/v1/me/sub-merchant?client_id={CLIENT_ID}" \
-H "Authorization: Apikey {API_KEY}"
响应:
{
"requestId": "req_xxx",
"data": {
"sub_mchid": "1900****09",
"company_name": "Your Company",
"status": "active",
"created_at": "2026-07-25T10:00:00Z"
}
}
5.3 技术服务费
平台按应用维度约定的服务费率(通常 2%-5%,如 2.5%)从你的每笔交易中自动分账收取技术服务费——这是微信支付官方分账产品完成的清算,资金不经平台中转:
- 用户支付 100 元 → 微信支付清算:97.5 元直达你的账户,2.5 元分账至平台
- 费率由平台在「客户管理」中为你的应用配置,接入时与平台确认
- 你无需任何操作,对账时可按订单查看分账金额
5.4 直连模式(自有商户号)
如果你已经拥有自己的微信支付商户号、希望资金直接清算到自己账户,可以选择直连模式:在「支付配置」中填写自有商户凭证,平台代你调用渠道接口,资金由微信直接清算到你的商户号,平台不经手资金。
适用场景:已有自己的微信支付商户号、希望资金直接清算到自己账户的合作伙伴。
配置路径:合作伙伴后台 → 应用 → 配置支付(/partner/apps/{client_id}/payment),按向导填写:
| 字段 | 说明 |
|---|---|
mch_id |
微信支付商户号 |
merchant_serial_no |
商户证书序列号 |
app_id |
公众号 / 小程序 AppID |
api_key |
APIv3 密钥 |
api_cert |
商户 API 证书私钥(PEM 格式) |
wechat_public_key_serial |
微信支付公钥 ID(PUB_KEY_ID_...)。公钥模式账户必填;证书模式账户留空 |
wechat_public_key_pem |
微信支付公钥(PEM 格式)。公钥模式账户必填;证书模式账户留空 |
notify_url |
支付结果回调地址(需公网可达) |
公钥模式 vs 证书模式:微信新签发的商户号多采用「微信支付公钥」模式,不再提供平台证书(GET /v3/certificates 对这些账户返回 404 RESOURCE_NOT_EXISTS)。公钥模式账户需在「微信商户平台 → 账户中心 → API 安全 → 微信支付公钥」页面下载公钥并查看公钥 ID,填入上表两个字段;平台用它验签支付结果回调。证书模式账户两字段留空即可。保存配置时的凭证校验对两种模式通用(不再依赖平台证书接口)。
模式优先级规则:同一应用若已有 active 状态特约商户,订单一律走特约商户模式(直连配置不生效);未建档特约商户的应用走直连模式;特约商户被 停用(disabled) 时回落到直连模式——应用若配置了自己的直连支付渠道参数,可继续用直连收款;已建档但处于进件流程(pending / applying)时下单会被 409 拒绝(kill switch 语义,见 13.1)。
支付结果通知:直连模式的支付结果 webhook 使用支付配置里的 notify_url;特约商户模式则使用应用设置的 notify_url(见 4.4)。
技术服务费:直连模式的技术服务费结算方式以与平台的约定为准(平台不经手资金,不自动分账)。
直连模式合同门控:使用直连模式收款前,需与平台线下签署直连支付月度服务费合同(灏曦),由平台管理员在后台确认同意后才可开通。未开通时,直连模式下单返回 409 payment.direct_contract_required;特约商户模式不受此门控影响。
API 配置:也可直接调用接口保存(密钥服务端加密存储,查询仅返回 has_api_key / has_api_cert 等脱敏字段):
curl -X POST https://api.chandler.work/v1/me/payment-configs \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"client_id": "{CLIENT_ID}",
"channel": "wechat",
"mch_id": "YOUR_MCH_ID",
"merchant_serial_no": "YOUR_MERCHANT_SERIAL_NO",
"app_id": "YOUR_WX_APP_ID",
"api_key": "YOUR_API_KEY",
"api_cert": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
"notify_url": "https://your-domain.com/webhook/payment",
"mode": "test"
}'
直连模式需要你自行完成微信支付商户入驻与 API 证书管理;大多数合作伙伴推荐特约商户模式(5.1-5.3)。
从其他应用复制配置:同一商户号要在多个应用间复用时,无需逐字段重填——「支付配置」页顶部的 「从其他应用复制」 会列出你有权限的其他应用的 active 配置(只显示商户号等脱敏字段),选择后一键复制,密钥由服务端直接搬运(不经过浏览器)。复制与手动保存走完全相同的校验与审批流程;复制后请核对 app_id(源应用的 AppID 会一并复制,目标应用若使用不同的小程序/公众号需修改后重新保存)。
多应用共用同一商户号时,微信支付页展示的收款方是商户号的商户简称(各应用一致),各应用通过下单时的
subject(商品名)区分展示;平台侧订单、对账、导出始终按应用各自归属,微信侧交易账单也可用appid字段拆分。
测试支付(直连配置):保存配置后,「支付配置」页的 「测试支付」 卡片可发起一笔 0.01 元的真实微信支付(生成扫码二维码),支付成功即证明商户号、证书与回调链路端到端可用;mode: test 的配置不会发起真实支付,仅返回模拟目标。测试订单带 payment_config_test 来源标记,支付成功后资金进入你的商户号,可在订单列表中退款(见 8.4)。对应 API:POST /v1/me/payment-configs/{client_id}/{channel}/test-payment 与 GET .../test-payment/status?order_no=...。
5.5 发起测试支付(Web 端)
特约商户激活后,可在合作伙伴后台 概览(/partner/dashboard)页面点击 「发起测试支付」 验证收款链路:
- 选择已激活特约商户的应用
- 点击 创建 0.01 元测试订单
- 进入收银台完成支付,并确认你的
notify_url收到了支付结果通知
特约商户模式没有
mode: test开关——测试直接使用真实小额订单,支付成功后可在后台发起退款(见 8.4)。
5.6 多应用共用子商户号
如果你有多个应用且属于同一经营主体(同一张营业执照),只需进件一次:第一个应用正常走完 5.1 的进件流程拿到子商户号后,告知平台为你的其余应用复用该子商户号即可——平台管理员在后台登记时可直接选择"复用已有子商户号",激活后即刻收款,无需重复进件、重复准备材料。
- 共用前提:所有共用的应用必须属于同一合作伙伴主体;平台会校验子商户号只能在你的应用之间复用,无法(也不允许)挂到其他合作伙伴名下。
- 统计不串账:订单、对账、导出、服务费账单始终按应用各自归属,共用子商户号不影响任何报表口径。
- 风险提示:停用某个应用的特约商户不影响其他共用的应用;但如果微信侧冻结了该子商户号本身,所有共用的应用会同时失去收款能力。业务体量或风险差异较大的应用,建议仍分别进件。
- 不同经营主体(不同营业执照)必须各自进件,不能共用。
6. 实现 SSO 登录
6.1 OAuth 2.0 授权码流程
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 用户点击 │────▶│ 跳转授权页│────▶│ 用户授权 │────▶│ 回调你的 │
│ 登录按钮 │ │chandler │ │ chandler │ │ 回调地址 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│
▼
┌──────────┐
│ 用 code │
│ 换 token │
└──────────┘
6.2 引导用户到授权页
https://api.chandler.work/v1/oauth/authorize?
response_type=code
&client_id={CLIENT_ID}
&redirect_uri={URL_ENCODED_REDIRECT_URI}
&scope=openid%20profile%20email
&state={随机字符串}
参数说明:
| 参数 | 必填 | 说明 |
|---|---|---|
response_type |
是 | 固定值 code |
client_id |
是 | 步骤 4 获取的 client_id |
redirect_uri |
是 | URL 编码的回调地址,需与注册时一致 |
scope |
否 | 空格分隔的权限列表 |
state |
推荐 | 防 CSRF 的随机字符串 |
6.3 处理回调
用户授权后,浏览器跳转到你的回调地址:
https://your-domain.com/auth/callback?code=AUTH_CODE&state=random-state
验证 state 后,用 code 换取 token:
curl -X POST https://api.chandler.work/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code={AUTH_CODE}" \
-d "client_id={CLIENT_ID}" \
-d "client_secret={CLIENT_SECRET}" \
-d "redirect_uri=https://your-domain.com/auth/callback"
响应:
{
"access_token": "eyJ...",
"refresh_token": "rt_xxx",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile email"
}
6.4 获取用户信息
curl https://api.chandler.work/v1/oauth/userinfo \
-H "Authorization: Bearer {ACCESS_TOKEN}"
响应:
{
"sub": "usr_xxx",
"email": "user@example.com",
"email_verified": true,
"name": "张三",
"picture": "https://cdn.chandler.work/avatar/xxx.jpg"
}
6.5 刷新 Token
curl -X POST https://api.chandler.work/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token={REFRESH_TOKEN}" \
-d "client_id={CLIENT_ID}" \
-d "client_secret={CLIENT_SECRET}"
6.6 SSO 接入示例代码
Python (Flask):
import secrets
from flask import Flask, redirect, request, session
@app.route("/login")
def login():
state = secrets.token_urlsafe(32)
session["oauth_state"] = state
auth_url = (
f"https://api.chandler.work/v1/oauth/authorize"
f"?response_type=code"
f"&client_id={CLIENT_ID}"
f"&redirect_uri={REDIRECT_URI}"
f"&scope=openid profile email"
f"&state={state}"
)
return redirect(auth_url)
@app.route("/auth/callback")
def callback():
# 验证 state
if request.args.get("state") != session.get("oauth_state"):
return "State mismatch", 403
# 用 code 换 token
code = request.args.get("code")
token_resp = exchange_code_for_token(code)
# 获取用户信息
user_info = get_user_info(token_resp["access_token"])
# 绑定或创建本地用户
user = bind_or_create_user(user_info)
# 保存 token 用于后续 API 调用
save_user_tokens(user.id, token_resp)
return redirect("/dashboard")
Node.js (Express):
const crypto = require('crypto');
app.get('/login', (req, res) => {
const state = crypto.randomBytes(32).toString('hex');
req.session.oauthState = state;
const authUrl = new URL('https://api.chandler.work/v1/oauth/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', CLIENT_ID);
authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('state', state);
res.redirect(authUrl.toString());
});
app.get('/auth/callback', async (req, res) => {
if (req.query.state !== req.session.oauthState) {
return res.status(403).send('State mismatch');
}
const tokenResp = await exchangeCodeForToken(req.query.code);
const userInfo = await getUserInfo(tokenResp.access_token);
// 绑定或创建本地用户
await bindOrCreateUser(userInfo);
res.redirect('/dashboard');
});
7. 实现支付流程
7.0 商品定价(可选)
SKU 管理方式由你决定,两种方式平台都支持:可以完全在自有系统管理 SKU,平台仅作为支付通道发起收款、不解读你的商品;也可以选择把 SKU 与价格登记到平台,由平台服务端按登记价格权威定价。平台不维护全局商品目录,是否登记 SKU 完全由你自行选择:
- 自行管理 SKU(不登记到平台):直接调用
POST /v1/pay/orders,不传sku_id,金额由你传的amount决定(见 7.2)。平台仅作为支付通道发起收款,不解读你的商品。 - 登记 SKU + 价格版本(可选):若你希望金额由服务端权威定价——下单传
sku_id,金额按当前有效价核算,客户端提交的amount/currency被忽略,杜绝篡改——可以在平台登记 SKU 与价格版本,见下方 API(Web:合作伙伴后台 → 应用 → 定价)。改价通过新建价格版本完成,历史订单金额不受影响。此方案仅影响金额核算,不要求你把商品信息完整迁入平台。
① 创建 SKU(owner/admin 权限,可选)
curl -X POST https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/skus \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{"code":"vip_month","name":"VIP 月卡"}'
响应 201,返回 SKU 对象(含 id,即下单用的 sku_id)。code 在应用内唯一,重复返回 409 catalog.sku_exists。
② 创建价格版本(owner/admin 权限)
curl -X POST https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/skus/{SKU_ID}/prices \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"amount": 3000,
"billing_interval": "once",
"effective_at": "2026-08-01T00:00:00+08:00"
}'
| 字段 | 必填 | 说明 |
|---|---|---|
amount |
是 | 金额,单位:分(3000 = 30 元),≥ 0 |
currency |
否 | 默认 CNY |
billing_interval |
是 | 仅 once(一次性)。平台不支持订阅类/周期类商品 |
effective_at |
是 | 生效时间(RFC3339) |
expires_at |
否 | 过期时间,不传为长期有效 |
新版本生效后,与其时间段重叠的旧版本自动过期(已被替换),历史订单金额不变。
③ 查询 SKU 与价格
# SKU 列表(每个 SKU 带当前有效价 active_price)
curl https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/skus \
-H "Authorization: Apikey {API_KEY}"
# 某个 SKU 的价格历史
curl https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/skus/{SKU_ID}/prices \
-H "Authorization: Apikey {API_KEY}"
④ 停售 / 恢复在售(owner/admin 权限)
curl -X POST https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/skus/{SKU_ID}/status \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{"status":"inactive"}'
停售的 SKU 下单返回 409 catalog.sku_inactive;无有效价格的 SKU 下单返回 409 catalog.price_not_found。
会员期等业务数据仍用 partner_data / 应用 attributes 自行履约。
7.1 支付流程图
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 用户选择 │────▶│ 创建订单 │────▶│ 获取支付 │────▶│ 展示支付 │
│ 商品下单 │ │ │ │ 二维码 │ │ 二维码 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│
▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 业务系统 │◀────│ 收到支付 │◀────│ 异步通知 │◀────│ 用户扫码 │
│ 更新状态 │ │ 结果通知 │ │ chandler │ │ 支付 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
7.2 创建订单
认证:订单类接口走角色鉴权(权限 = 调用账号在目标应用上的角色),推荐使用 API Key(服务端长期凭证,见 12.2),Bearer 用户 token 亦可:
# 推荐: API Key(服务端集成, 见 12.2)
curl -X POST https://api.chandler.work/v1/pay/orders \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"application_id": "{CLIENT_ID}",
"merchant_order_no": "order_20260725_001",
"channel": "wechat",
"amount": 19900,
"currency": "CNY",
"subject": "VIP 会员月卡",
"source": "app",
"partner_data": {
"product_id": "vip_monthly",
"product_name": "VIP 会员月卡",
"quota": "30天"
}
}'
Authorization: Bearer {USER_ACCESS_TOKEN}(JWT 用户会话)同样可用;两种方式等价,按你的调用方身份任选其一,详见 12.2。
参数说明:
| 字段 | 必填 | 说明 |
|---|---|---|
application_id |
是 | 你的客户端 ID |
merchant_order_no |
是 | 你的商户订单号(唯一) |
sku_id |
否 | 伙伴 SKU ID(见 7.0)。传了则由服务端按该 SKU 当前有效价权威定价,amount/currency 被忽略;不传保持客户端传价 |
channel |
否 | 目前仅支持 wechat(默认) |
amount |
是* | 金额,单位:分(19900 = 199 元)。*传了 sku_id 时无需提交 |
currency |
否 | 默认 CNY |
subject |
是 | 商品描述 |
source |
否 | 来源标记,用于数据分析 |
partner_data |
否 | 你的自定义业务数据(JSON 对象,≤16KB),平台原样存储、不做任何解读,详见 8.2 |
响应:
{
"requestId": "req_xxx",
"data": {
"platform_order_no": "ord_xxxxxxxxxxxx",
"amount": 19900,
"currency": "CNY",
"subject": "VIP 会员月卡"
}
}
7.3 获取支付参数
认证同 7.2(API Key 或 Bearer 用户 token 均可):
curl -X POST https://api.chandler.work/v1/pay/orders/{PLATFORM_ORDER_NO}/prepay \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json"
响应:
{
"channel": "wechat",
"platform_order_no": "ord_xxx",
"code_url": "weixin://wxpay/...",
"h5_url": "https://wx.tenpay.com/..."
}
前端展示二维码或跳转支付:
// 微信支付 - 展示二维码
if (prepay.channel === "wechat" && prepay.code_url) {
QRCode.toCanvas(canvas, prepay.code_url);
}
7.4 接收支付结果通知
Chandler.work 会向你配置的 notify_url 发送 POST 请求(通过 4.4 的更新应用接口配置)。通知请求带有签名头 X-Chandler-Signature,务必验签后再处理。
第一步:生成验签密钥(只需一次)。密钥 = sha256_hex(client_secret),即对应用的 client_secret 做一次 SHA-256、取十六进制字符串(小写 64 字符)。生成后建议存为环境变量(如 CHANDLER_HMAC_KEY):
# macOS / Linux(注意 printf 不要带换行)
printf '%s' "$CHANDLER_CLIENT_SECRET" | shasum -a 256 | awk '{print $1}'
# Node.js
node -e 'console.log(require("crypto").createHash("sha256").update(process.env.CHANDLER_CLIENT_SECRET).digest("hex"))'
# Python
python3 -c 'import hashlib,os;print(hashlib.sha256(os.environ["CHANDLER_CLIENT_SECRET"].encode()).hexdigest())'
第二步:对每个通知请求验签。签名值 = hex(HMAC-SHA256(key=上一步的密钥, message=请求体原始字节)),与 X-Chandler-Signature 头做常量时间比较。关键点:必须对原始请求体字节计算 HMAC,不要解析 JSON 后重新序列化(字段顺序、空白差异会导致验签失败):
import hashlib, hmac, os
HMAC_KEY = os.environ["CHANDLER_HMAC_KEY"] # = sha256_hex(client_secret)
def verify_signature(raw_body: bytes, signature_header: str) -> bool:
expected = hmac.new(HMAC_KEY.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header)
const crypto = require("crypto");
const HMAC_KEY = process.env.CHANDLER_HMAC_KEY; // = sha256_hex(client_secret)
function verifySignature(rawBody, signatureHeader) {
const expected = crypto.createHmac("sha256", HMAC_KEY).update(rawBody).digest("hex");
const received = signatureHeader || "";
return expected.length === received.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}
// Express 注意:用 express.raw({ type: "application/json" }) 拿原始 body,
// 不能用 express.json() 解析后的对象重新序列化。
验签只是第一道防线,处理前仍建议用 7.5 的订单查询接口确认订单真实状态。
通知体示例:
{
"platform_order_no": "ord_xxx",
"merchant_order_no": "order_20260725_001",
"channel_order_no": "4200001234567890",
"amount": 19900,
"currency": "CNY",
"status": "paid",
"partner_data": {
"product_id": "vip_monthly",
"product_name": "VIP 会员月卡",
"quota": "30天"
}
}
channel_order_no 为渠道侧订单号(可能缺省);partner_data 为下单时(或之后通过 8.2 的接口)写入的自定义数据,未设置时为 null。
必须实现幂等处理,以 platform_order_no 为唯一键:
@app.route("/webhook/payment", methods=["POST"])
def payment_webhook():
data = request.json
platform_order_no = data["platform_order_no"]
# 幂等检查:已处理则直接返回成功
if is_order_processed(platform_order_no):
return "OK"
# 验证签名(务必先验签,见上面的 verify_signature 实现)
if not verify_signature(request.get_data(), request.headers.get("X-Chandler-Signature", "")):
abort(401)
# 更新订单状态
update_order_status(
platform_order_no=platform_order_no,
status=data["status"],
)
# 执行业务逻辑(开通权限等)
fulfill_order(platform_order_no)
return "OK"
7.5 查询订单状态
curl https://api.chandler.work/v1/pay/orders/{PLATFORM_ORDER_NO} \
-H "Authorization: Apikey {API_KEY}"
该接口实际是公开的(平台订单号即能力凭证,与收银台扫码页同源),不带 token 也可调用。服务端因此可以用它做主动对账兜底:当支付结果通知(7.4)疑似丢失时,直接查询订单状态,若已是
paid则按通知同等逻辑补入账,而不是被动等待重试或人工重发(8.6)。
8. 订单与退款管理
8.1 查询订单列表
curl "https://api.chandler.work/v1/me/orders?client_id={CLIENT_ID}&page=1&limit=20" \
-H "Authorization: Apikey {API_KEY}"
支持筛选参数:
status— 订单状态筛选from/to— 时间范围(RFC3339 或 YYYY-MM-DD)partner_product_id— 按partner_data.product_id精确匹配筛选(见 8.2,可用于"查询购买过某产品的所有订单")
8.2 订单自定义业务数据(partner_data)
每笔订单可携带一个你自定义的 JSON 对象 partner_data:平台原样存储、不解读语义,内容完全由你定义——典型用法是记录产品 ID/名称、套餐额度、有效期等业务信息。
规则:
- 必须是 JSON 对象(不能是数组或标量),序列化后 ≤ 16KB
- 下单时可传入(见 7.2);支付结果通知、订单列表/详情、CSV 导出中都会原样带回
partner_data是订单上唯一允许你事后修改的字段,通过下面的接口整体覆盖:
curl -X PUT "https://api.chandler.work/v1/me/orders/{PLATFORM_ORDER_NO}/partner-data?client_id={CLIENT_ID}" \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"partner_data": {
"product_id": "vip_yearly",
"product_name": "VIP 会员年卡",
"quota_change": "月卡升级为年卡"
}
}'
该接口需要应用的 admin 及以上角色;每次修改都会记录安全日志(仅记录操作,不记录数据内容)。
8.3 导出订单
curl "https://api.chandler.work/v1/me/orders/export?client_id={CLIENT_ID}&from=2026-07-01&to=2026-07-31" \
-H "Authorization: Apikey {API_KEY}" \
-o orders.csv
返回 CSV 文件,最多 50,000 行。超出时响应头包含 X-Export-Truncated: true。
8.4 创建退款
curl -X POST https://api.chandler.work/v1/pay/refunds \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"client_id": "{CLIENT_ID}",
"platform_order_no": "ord_xxx",
"amount": 19900,
"reason": "用户申请退款"
}'
8.5 查询退款列表
curl "https://api.chandler.work/v1/pay/refunds?client_id={CLIENT_ID}&page=1&limit=20" \
-H "Authorization: Apikey {API_KEY}"
8.6 重新发送支付通知
如果支付回调通知丢失,可以手动触发重发:
curl -X POST "https://api.chandler.work/v1/me/orders/{PLATFORM_ORDER_NO}/notify?client_id={CLIENT_ID}" \
-H "Authorization: Apikey {API_KEY}"
9. 团队与应用管理
9.1 邀请应用成员
多人协作开发同一应用时,可以邀请团队成员(各角色权限见 14.4)。
推荐:Web 控制台操作 —— 「应用管理」列表点击应用的「成员」进入团队管理页:输入邮箱和角色发送邀请、查看成员列表(显示昵称/邮箱与角色)、移除成员、查看邀请记录并取消待接受的邀请。邀请管理与移除仅 owner 可用,其余角色只读成员列表。
也可以用 API 完成同样操作:
# 邀请成员(owner)
curl -X POST https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/invites \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"email": "teammate@your-company.com",
"role": "developer"
}'
# 查看成员列表(viewer 及以上;每项含 user_id、email、display_name、role、created_at)
curl https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/members \
-H "Authorization: Apikey {API_KEY}"
# 移除成员(owner)
curl -X DELETE https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/members/{USER_ID} \
-H "Authorization: Apikey {API_KEY}"
被邀请人会收到含邀请链接的邮件,登录后接受邀请:
curl -X POST https://api.chandler.work/v1/me/oauth/invites/accept \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{"token": "{INVITE_TOKEN}"}'
9.2 管理授权用户
查看哪些用户通过你的应用完成了 SSO 登录,必要时可撤销其授权:
# 查看授权用户列表(每个用户会内联返回你写入的 attributes,未设置时为 null)
curl https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/users \
-H "Authorization: Apikey {API_KEY}"
# 撤销用户的授权
curl -X DELETE https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/grants/{USER_ID} \
-H "Authorization: Apikey {API_KEY}"
9.3 用户自定义属性(attributes)
每个应用可以为自己的授权用户各存一个自定义 JSON 对象 attributes:平台原样存储、不解读语义,内容完全由你定义——典型用法是记录该用户的产品订阅情况、有效期/过期时间、套餐额度等。属性按应用隔离:同一用户在不同应用下的属性互不可见、互不影响。
规则同 partner_data:必须是 JSON 对象,≤ 16KB;用户必须已通过你的应用完成授权(否则 404)。
# 写入/整体覆盖某用户的自定义属性(需要 admin 及以上角色)
curl -X PUT https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/users/{USER_ID}/attributes \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"attributes": {
"product_id": "vip_yearly",
"valid_from": "2026-07-27",
"valid_until": "2027-07-27",
"auto_renew": true
}
}'
# 读取(viewer 及以上角色;未设置时 attributes 为 null)
curl https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/users/{USER_ID}/attributes \
-H "Authorization: Apikey {API_KEY}"
典型闭环:支付回调到达 → 用 8.2 的接口把产品信息写入订单
partner_data→ 把该用户的订阅有效期写入attributes→ 之后用GET /v1/me/orders?partner_product_id=xxx拉取购买某产品的全部订单,再结合用户属性即可得到"产品 → 用户列表 + 有效期"。续费成功后重复写入即可更新有效期。
9.4 应用市场与用户反馈
你的应用可通过目录配置(见 4.8)上架应用市场。浏览应用市场(公开接口):
curl https://api.chandler.work/v1/directory/apps
查看用户评价、处理用户投诉:
# 查看应用评价
curl https://api.chandler.work/v1/directory/apps/{CLIENT_ID}/reviews
# 查看应用投诉
curl https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/complaints \
-H "Authorization: Apikey {API_KEY}"
# 更新投诉状态
curl -X PUT https://api.chandler.work/v1/me/oauth/clients/{CLIENT_ID}/complaints/{COMPLAINT_ID} \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"status": "resolved",
"note": "已处理"
}'
10. 财务与账单
平台按约定费率从交易中自动分账收取技术服务费(见 5.3),相关账单、发票与付款记录通过以下接口查询。
10.1 查看服务费账单
curl "https://api.chandler.work/v1/me/billing/fees?client_id={CLIENT_ID}&from=2026-07" \
-H "Authorization: Apikey {API_KEY}"
响应:
{
"client_id": "cld_xxx",
"fee_bps": 300,
"months": [
{
"month": "2026-07",
"gmv_amount": 1990000,
"refund_amount": 0,
"net_amount": 1990000,
"fee_amount": 59700
}
]
}
其中 fee_bps 为约定的服务费率(万分比,300 = 3%),fee_amount 为当月分账收取的服务费(单位:分)。
10.2 申请发票
curl -X POST https://api.chandler.work/v1/me/billing/invoices \
-H "Authorization: Apikey {API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"client_id": "{CLIENT_ID}",
"period": "2026-07",
"type": "company",
"title": "Your Company Name",
"tax_no": "91110108xxx",
"email": "finance@your-company.com"
}'
10.3 查看付款记录
curl "https://api.chandler.work/v1/me/billing/payments?client_id={CLIENT_ID}" \
-H "Authorization: Apikey {API_KEY}"
11. 对账与分析
11.1 查询对账结果
平台每日与微信支付渠道对账,结果可按应用查询:
curl "https://api.chandler.work/v1/me/reconciliations?client_id={CLIENT_ID}" \
-H "Authorization: Apikey {API_KEY}"
11.2 导出对账数据
curl "https://api.chandler.work/v1/me/reconciliations/export?client_id={CLIENT_ID}" \
-H "Authorization: Apikey {API_KEY}"
11.3 支付分析
curl "https://api.chandler.work/v1/me/analytics/payments?client_id={CLIENT_ID}" \
-H "Authorization: Apikey {API_KEY}"
11.4 用户留存分析
curl "https://api.chandler.work/v1/me/analytics/retention?client_id={CLIENT_ID}" \
-H "Authorization: Apikey {API_KEY}"
11.5 来源分析
创建订单时传入的 source 字段(见 7.2)会用于来源分析:
curl "https://api.chandler.work/v1/me/analytics/sources?client_id={CLIENT_ID}" \
-H "Authorization: Apikey {API_KEY}"
12. API Key 与安全日志
12.1 创建 API Key
合作伙伴可以创建长期 API Key 用于服务端调用(无需每次刷新 Token)。
推荐:Web 控制台创建 —— 登录后进入「合作伙伴中心」(https://app.chandler.work/partner),左侧导航点击「API Key」,输入名称创建。Key 原文只在创建成功时完整显示一次,请立即复制保存;该页面同时支持查看每个 Key 的创建/最近使用时间和吊销。
也可以用 API 创建(name 必填):
curl -X POST https://api.chandler.work/v1/me/api-keys \
-H "Authorization: Bearer {ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"name": "aivlog-refund"}'
响应中的 key 字段是 Key 原文,仅此一次返回(服务端只存哈希):
{
"data": {
"id": "key_xxx",
"name": "aivlog-refund",
"key": "dGhpcy1pcy1hbi1leGFtcGxlLWtleS1kb250LXVzZQ",
"created_at": "2026-08-10T08:00:00Z"
}
}
权限模型:API Key 归属创建它的账号,调用接口时的权限 = 该账号在目标应用上的角色。例如调用退款接口(8.4)要求创建者在应用的成员角色为 finance 及以上(见 14.4),典型做法是给服务端建一个专用账号、只授 finance 角色,再用它创建 API Key。
12.2 使用 API Key 认证
服务端集成推荐使用 API Key:它是长期凭证,无需每次刷新 token,泄露后可在控制台吊销;权限模型与 JWT 完全一致(权限 = 创建账号在目标应用上的角色,见上方)。本文档的合作伙伴接口示例(订单、退款、支付配置、财务、对账等)默认使用 Authorization: Apikey {API_KEY}。
两种等价的携带方式,适用于所有按角色鉴权的合作伙伴接口(订单、退款、财务等):
# 方式一:X-API-Key 头
curl "https://api.chandler.work/v1/me/orders?client_id={CLIENT_ID}" \
-H "X-API-Key: {API_KEY}"
# 方式二:Authorization: Apikey(注意不是 Bearer)
curl "https://api.chandler.work/v1/me/orders?client_id={CLIENT_ID}" \
-H "Authorization: Apikey {API_KEY}"
例外:API Key 管理本身(12.1/12.3)以及改密、MFA、身份绑定、会话管理等账号安全类接口只接受登录会话(JWT),API Key 调用会返回 401——这是刻意设计:一个泄露的长期 Key 不能再制造更多 Key 或改动账号安全设置。
12.3 撤销 API Key
在「合作伙伴中心 → API Key」页面点击吊销即可,立即生效;也可以调接口:
curl -X DELETE https://api.chandler.work/v1/me/api-keys/{KEY_ID} \
-H "Authorization: Bearer {ACCESS_TOKEN}"
12.4 查看安全日志
查看账号的登录、密钥变更等安全事件:
curl https://api.chandler.work/v1/me/security-logs \
-H "Authorization: Bearer {ACCESS_TOKEN}"
13. 错误处理
13.1 常见错误码
所有错误响应遵循统一信封格式(见 1.4),error.code 用于程序化处理:
| 错误码 | HTTP 状态码 | 说明 |
|---|---|---|
auth.invalid_credentials |
401 | 邮箱或密码错误(修改密码时为旧密码错误) |
auth.weak_password |
422 | 密码不合规:8–255 字符,4 类字符至少覆盖 3 类 |
token.invalid |
400/401 | token 无效或已过期(重置密码 token / access token) |
auth.user_disabled |
403 | 账号已被禁用 |
auth.otp_delivery_failed |
503 | 验证码投递失败(邮件渠道异常),请稍后重试 |
auth.sms_disabled |
403 | 短信验证码功能未开放(签名审核中),请改用邮箱验证码 |
partner.profile_not_approved |
403 | 合作伙伴资料未通过审批 |
client.not_found |
404 | 应用不存在,或当前账号与该应用无任何关系(不泄露应用是否存在) |
client.forbidden |
403 | 已是应用成员但角色不足,无法执行该操作 |
order.amount_invalid |
400 | 订单金额无效 |
order.not_found |
404 | 订单不存在 |
order.not_refundable |
409 | 订单不可退款 |
payment.direct_contract_required |
409 | 直连支付需先与平台签署月度服务费合同(由管理员在后台开通),特约商户模式不受影响 |
wallet.insufficient_balance |
400 | 余额不足 |
rate_limit_exceeded |
429 | 请求频率超限 |
13.2 重试策略
- 429 Too Many Requests:读取
Retry-After头,等待后重试 - 5xx 服务器错误:指数退避重试(建议最多 3 次)
- 4xx 客户端错误:不重试,修复请求后重新发起
13.3 幂等设计
支付相关操作必须支持幂等。创建订单等写操作建议携带 Idempotency-Key,防止网络重试导致重复创建:
# 使用 Idempotency-Key 防止重复创建
curl -X POST https://api.chandler.work/v1/pay/orders \
-H "Idempotency-Key: {唯一请求ID}" \
...
14. 安全最佳实践
14.1 密钥管理
- ✅
client_secret仅保存在服务端环境变量或密钥管理服务 - ✅ 定期轮换 API 密钥
- ❌ 不要将密钥写入前端代码、Git 仓库或日志
14.2 回调安全
- ✅ 验证回调请求的签名
- ✅ 回调地址使用 HTTPS
- ✅ 实现幂等处理
- ❌ 不要将敏感信息暴露在 URL 参数中
14.3 Token 安全
- ✅ Access Token 保存在服务端 Session
- ✅ Refresh Token 定期轮换
- ❌ 不要在前端存储敏感 Token
14.4 团队角色权限
合作伙伴支持多角色团队管理:
| 角色 | 权限 |
|---|---|
owner |
全部权限(隐含,OAuth 客户端创建者) |
admin |
管理应用、支付配置、退款、成员 |
finance |
创建/执行退款、查看订单和对账 |
operator |
重新发送通知、查看订单 |
developer |
保存/回滚支付配置、查看订单 |
viewer |
只读查看所有数据 |
应用管理类写操作要求 admin 及以上角色(/me/oauth/clients/* 的更新、状态、密钥等)。写操作角色不足时返回 403(client.forbidden);当前账号与目标应用无任何关系时统一返回 404(client.not_found,防止枚举应用是否存在)。
15. API 速查表
认证
| 功能 | 方法 | 路径 |
|---|---|---|
| 注册 | POST | /v1/auth/register |
| 登录 | POST | /v1/auth/login |
| 登出 | POST | /v1/auth/logout |
| 全部设备登出 | POST | /v1/auth/logout-all |
| 修改密码 | POST | /v1/auth/change-password |
| 忘记密码 | POST | /v1/auth/forgot-password |
| 重置密码 | POST | /v1/auth/reset-password |
| 发送登录验证码(邮箱) | POST | /v1/auth/otp/send |
| 验证码登录 | POST | /v1/auth/otp/login |
| 认证功能开关查询 | GET | /v1/auth/capabilities |
| 发送短信重置验证码(暂未开放) | POST | /v1/auth/phone/forgot-password |
| 短信验证码重置密码(暂未开放) | POST | /v1/auth/phone/reset-password |
| 发送验证邮件 | POST | /v1/auth/send-verification-email |
| 验证邮箱 | POST | /v1/auth/verify-email |
授权(OAuth SSO)
| 功能 | 方法 | 路径 |
|---|---|---|
| 授权页 | GET | /v1/oauth/authorize |
| Token 交换/刷新 | POST | /v1/oauth/token |
| 撤销授权 | POST | /v1/oauth/revoke |
| 用户信息 | GET | /v1/oauth/userinfo |
资质与材料
| 功能 | 方法 | 路径 |
|---|---|---|
| 合作伙伴资料 | GET/PUT | /v1/me/partner-profile |
| 提交审批 | POST | /v1/me/partner-profile/submit |
| 上传材料 | POST | /v1/me/partner/documents |
| 材料列表 | GET | /v1/me/partner/documents |
| 删除材料 | DELETE | /v1/me/partner/documents/{id} |
| 下载材料 | GET | /v1/me/partner/documents/{id}/download |
应用管理
| 功能 | 方法 | 路径 |
|---|---|---|
| 创建 OAuth 客户端 | POST | /v1/me/oauth/clients |
| 列出 OAuth 客户端 | GET | /v1/me/oauth/clients |
更新 OAuth 客户端(含 notify_url) |
PUT | /v1/me/oauth/clients/{id} |
| 删除客户端 | DELETE | /v1/me/oauth/clients/{id} |
| 重置密钥 | POST | /v1/me/oauth/clients/{id}/secret |
| 禁用/启用 | POST | /v1/me/oauth/clients/{id}/status |
| 应用目录配置 | PUT | /v1/me/oauth/clients/{id}/directory |
| 邀请成员 | POST | /v1/me/oauth/clients/{id}/invites |
| 邀请列表 | GET | /v1/me/oauth/clients/{id}/invites |
| 取消/重发邀请 | POST | /v1/me/oauth/clients/{id}/invites/{invite_id}/cancel /resend |
| 接受邀请 | POST | /v1/me/oauth/invites/accept |
| 成员列表/移除 | GET/DELETE | /v1/me/oauth/clients/{id}/members /{user_id} |
| 授权用户列表(含用户属性) | GET | /v1/me/oauth/clients/{id}/users |
| 读/写用户自定义属性 | GET/PUT | /v1/me/oauth/clients/{id}/users/{user_id}/attributes |
| 撤销用户授权 | DELETE | /v1/me/oauth/clients/{id}/grants/{user_id} |
| 投诉列表 | GET | /v1/me/oauth/clients/{id}/complaints |
| 更新投诉状态 | PUT | /v1/me/oauth/clients/{id}/complaints/{complaint_id} |
收款
| 功能 | 方法 | 路径 |
|---|---|---|
| 特约商户状态 | GET | /v1/me/sub-merchant |
订单
| 功能 | 方法 | 路径 |
|---|---|---|
| 创建订单 | POST | /v1/pay/orders |
| 获取支付参数 | POST | /v1/pay/orders/{no}/prepay |
| 查询订单 | GET | /v1/pay/orders/{no} |
订单列表(支持 partner_product_id 筛选) |
GET | /v1/me/orders |
| 订单详情 | GET | /v1/me/orders/{no} |
| 更新订单扩展字段 | PUT | /v1/me/orders/{no}/partner-data |
| 导出订单 | GET | /v1/me/orders/export |
| 重发通知 | POST | /v1/me/orders/{no}/notify |
退款
| 功能 | 方法 | 路径 |
|---|---|---|
| 申请退款 | POST | /v1/pay/refunds |
| 查询退款 | GET | /v1/pay/refunds/{refund_no} |
| 退款列表 | GET | /v1/me/refunds |
| 退款详情 | GET | /v1/me/refunds/{refund_no} |
| 执行退款 | POST | /v1/me/refunds/{refund_no}/execute |
| 导出退款 | GET | /v1/me/refunds/export |
财务
| 功能 | 方法 | 路径 |
|---|---|---|
| 服务费账单 | GET | /v1/me/billing/fees |
| 申请发票 | POST | /v1/me/billing/invoices |
| 发票列表 | GET | /v1/me/billing/invoices |
| 付款记录 | GET | /v1/me/billing/payments |
对账
| 功能 | 方法 | 路径 |
|---|---|---|
| 对账结果 | GET | /v1/me/reconciliations |
| 导出对账 | GET | /v1/me/reconciliations/export |
分析
| 功能 | 方法 | 路径 |
|---|---|---|
| 支付分析 | GET | /v1/me/analytics/payments |
| 用户留存 | GET | /v1/me/analytics/retention |
| 来源分析 | GET | /v1/me/analytics/sources |
其他
| 功能 | 方法 | 路径 |
|---|---|---|
| API Key 列表/创建 | GET/POST | /v1/me/api-keys |
| 撤销 API Key | DELETE | /v1/me/api-keys/{key_id} |
| 安全日志 | GET | /v1/me/security-logs |
| 应用市场 | GET | /v1/directory/apps |
| 应用详情 | GET | /v1/directory/apps/{client_id} |
| 应用评价 | GET | /v1/directory/apps/{client_id}/reviews |
| 健康检查 | GET | /health |
| JWKS | GET | /.well-known/jwks.json |
| OpenAPI Spec | GET | /openapi.yaml |
平台管理端(Admin,仅平台运营使用)
| 功能 | 方法 | 路径 |
|---|---|---|
| 特约商户列表/登记 | GET/POST | /v1/admin/sub-merchants |
| 更新特约商户 | PUT | /v1/admin/sub-merchants/{id} |
| 激活/停用特约商户 | POST | /v1/admin/sub-merchants/{id}/activate /disable |
| 设置应用服务费率 | PUT | /v1/admin/applications/{client_id}/fee |
| 微信支付服务商配置 | GET | /v1/admin/wechat-sp |
完整 API 文档:https://api.chandler.work/docs
16. 常见问题
Q: 回调地址(redirect_uri)可以是内网地址吗?
A: 本地开发可以。redirect_uris 允许登记 http://localhost(含 127.0.0.1)用于开发联调;生产环境必须使用 HTTPS。它与支付回调是两个东西,见 4.4 的对照表。
Q: 支付回调地址(notify_url)可以是内网地址吗?
A: 不可以。支付通知是 chandler 服务端发起的 POST,notify_url 必须是公网可访问的 HTTPS 地址。本地开发可使用 ngrok、cloudflare tunnel 等工具。
Q: 如何测试支付?
A: 特约商户模式没有沙箱开关,直接在合作伙伴后台「概览」页发起 0.01 元测试支付(见 5.5),验证收款与回调链路后可发起退款。
Q: client_secret 泄露了怎么办?
A: 立即通过 API 重新生成:POST /v1/me/oauth/clients/{id}/secret。
Q: 用户在 chandler.work 注册后,我的应用需要再创建账号吗?
A: 通过 OAuth 获取用户信息后,建议在你的系统中创建或绑定一个本地账号,保存 user_id 用于业务关联。
Q: 金额单位是什么?
A: 所有金额字段单位为分(人民币最小单位)。例如 19900 分 = 199.00 元。
Q: 支持哪些支付方式?
A: 目前仅支持微信支付(扫码支付、H5 支付、JSAPI 支付),两种接入模式:特约商户模式(平台统一管理渠道参数,见第 5 章)和直连模式(自有商户号,见 5.4)。支付宝暂未开放。
Q: 如何记录我自己的产品、套餐、订阅有效期等业务数据?
A: 用两个自定义字段,平台只存储不解读:订单级的 partner_data(见 8.2,可下单时传入、支付后修改、按 partner_product_id 筛选)和用户级的 attributes(见 9.3,按应用隔离)。有效期、续费后的时间更新等业务状态由你自己的系统维护。
Q: 我是电商平台,每个订单价格都不一样,怎么接?
A: 直接用 POST /v1/pay/orders,不传 sku_id 即可逐单自定义金额(amount 仅校验大于 0,单位分)。商品名/订单摘要放 subject(会展示给付款人,≤255 字节),结构化商品信息放 partner_data(JSON 对象,≤16KB)——它会在订单详情、CSV 导出、支付 webhook 中原样返回,且可用 GET /v1/me/orders?partner_product_id=xxx 按商品筛选订单做统计(见 8.2)。注意不要用「充值」(topup)类接口承载电商订单,那是平台自营钱包充值专用,金额固定且订单不计入你的应用。
Q: 用户的分销佣金怎么发给用户(提现)?
A: 两条路径,按佣金比例选择:
- 佣金 ≤ 订单金额 30%:优先在交易内直接分账(微信支付官方分账产品,资金不过平台、不绕银行、无额外税务环节),这是成本最低的合规方式;
- 需要事后发放或超过 30%:使用微信支付「商家转账」(分销返佣场景)从你的商户号转账到用户零钱。注意三点:特约商户模式下需要你自己在微信支付商户平台开通(公司主体、场景审核,平台作为服务商无法代开通代发起);转账从「运营账户」出资,收款资金需先结算提现到你的对公银行账户、再充值回运营账户,无法直接用未结算收款发放;微信明确拒绝多级分销与"代收再分发"(二清)结构。平台不提供代发佣金服务。
Q: 我有多个应用,每个都要进件申请子商户吗?
A: 同一经营主体下不需要,进件一次即可,其余应用由平台复用同一子商户号(见 5.6)。不同经营主体必须分别进件。
Q: 对接需要多长时间?
A: 如果你的应用已有基本框架,按照本文档操作,最快数天可完成对接。
技术支持
| 渠道 | 信息 |
|---|---|
| 平台地址 | https://chandler.work |
| 合作伙伴后台 | https://app.chandler.work/partner |
| API 文档 | https://api.chandler.work/docs |
| OpenAPI Spec | https://api.chandler.work/openapi.yaml |
| 技术支持 | support@chandler.work |
附录 A:对接避坑指南(实测经验)
本节为真实对接中踩过的坑,供外部伙伴参考。平台官方文档正文为契约,本节为补充。
A.1 端点与回调(正确姿势)
- API 基础路径就是
https://api.chandler.work——所有 API 均部署在该子域(/v1/*、/openapi.yaml、/docs)。如果你在某个地址请求 API 却收到 HTML 页面(SPA fallback),说明地址错了:真实 API 在无 token 时应返回 401 + JSON 错误信封。 - 回调地址必须逐字登记:你的 OAuth 回调(如 NextAuth 的
{BASE}/api/auth/callback/{providerId})必须与合作伙伴后台redirect_uris中登记的完全一致(含协议、端口、路径)。本地联调可用 tunnel 工具(如 ngrok、cloudflare tunnel)获得公网地址并登记。 - token 交换是
application/x-www-form-urlencoded,不是 JSON body。
A.2 使用 NextAuth(next-auth v4)的伙伴注意
next-auth v4 自定义 token.request 的返回值必须是 { tokens: {...} } 嵌套结构——其内部执行 new TokenSet(response.tokens)。按直觉返回扁平的 { access_token, ... } 会让 TokenSet 拿到 undefined,换 token 这一步静默失败且无明显报错。正确返回:
return {
tokens: {
access_token: data.access_token,
refresh_token: data.refresh_token,
token_type: data.token_type,
expires_in: data.expires_in,
},
};
A.3 字段映射与密钥
- userinfo 字段:
sub(用户 ID)、name、picture、email、email_verified。注意与/v1/me(字段为id/display_name/avatar)是两套命名,做映射时别混。 client_secret仅在创建应用时返回一次,立即存入你的密钥管理;丢失只能通过"重置密钥"重新生成(旧 secret 作废)。- 支付 webhook 验签密钥是
sha256_hex(client_secret)(secret 的 SHA-256 十六进制),不是 secret 本身。生成命令与验签代码示例见 7.4。
A.4 测试与金额
- 特约商户模式没有沙箱:用 0.01 元真实订单验证全链路(下单 → 扫码 → 回调 → 对账),测完在后台退款即可。
- 所有金额单位为分(19900 = 199.00 元),不要用浮点表示金额。
- 并发创建用户用 upsert:OAuth 登录回调里"查不到就 create"的写法在并发/重试下会撞唯一约束。
- 模块级
process.env求值:在模块顶层读 env 生成 URL,缺失时会静默产出undefined/v1/...的坏 URL 直到运行时才炸——启动时对必填 env 做显式校验。
本文档持续更新,请以最新版本为准。