Chandler.work 合作伙伴接入指南

文档版本: v3.2
更新时间: 2026-08-11
适用域名: chandler.work
API 基础路径: https://api.chandler.work

当前可用性说明:短信验证码功能(手机 OTP 登录/注册/找回密码、绑定手机)因运营商签名审核暂未开放,相关接口返回 403 auth.sms_disabled;邮箱验证码功能(登录、找回密码)已完整提供,见 2.8。短信功能开放时间以 GET /v1/auth/capabilitiessms_otp_enabled 为准。

支付渠道:目前支持微信支付,两种接入模式——特约商户模式(推荐;平台统一管理渠道参数,完成资质进件即可收款)和直连模式(使用自有微信商户号,在控制台自行配置渠道参数)。支付宝暂未开放

本文档面向合作伙伴开发者,完整覆盖从注册账号 → 填写资质 → 创建应用 → 配置支付 → API 对接的全流程。既包含 Web 控制台操作步骤,也包含对应的 API 调用细节。


目录

  1. 快速概览
  2. 注册与登录
  3. 填写合作伙伴资质信息
  4. 创建 OAuth 应用
  5. 开通收款(特约商户)
  6. 实现 SSO 登录
  7. 实现支付流程
  8. 订单与退款管理
  9. 团队与应用管理
  10. 财务与账单
  11. 对账与分析
  12. API Key 与安全日志
  13. 错误处理
  14. 安全最佳实践
  15. API 速查表
  16. 常见问题

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. 注册与登录

账号能力提供两种使用方式,二选一或混合均可

  1. 平台托管页面:直接使用平台提供的注册/登录页面(2.1),无需开发;
  2. 自行实现界面(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 当前仅支持 emailphone 暂未开放,见上)。
  • 响应固定为 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_disabled403 auth.sms_disabled(目标为手机号且短信功能未开放)。

找回密码:目前仅支持邮箱重置流程(见 2.7)。短信验证码重置密码(POST /v1/auth/phone/forgot-password + POST /v1/auth/phone/reset-password,API-only)将在短信签名审核通过后开放,届时绑定了手机号的账号可直接在客户端调用。

2.9 微信扫码登录与微信绑定

平台支持微信扫码登录,既有用户绑定微信后即可在所有接入应用扫码登录;新用户扫码注册时**必须提供邮箱或手机(二选一)**作为账号恢复通道。两种使用方式:

  1. 方式 A:平台托管扫码页(零开发)——登录页提供「微信扫码登录」入口,扫码确认后自动完成登录与授权,无需任何接入工作。
  2. 方式 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/sendtarget_type=phonepurpose=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 字符

操作流程:

  1. 点击 「保存」 — 保存当前填写的信息(状态为 draft
  2. 点击 「提交审核」 — 提交给平台审核(状态变为 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_idclient_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 平台进件流程

  1. 你在第 3 章提交资质并通过审批
  2. 平台为你进件特约商户(微信进件 API 自动提交为主、人工为辅,1-3 个工作日;进件材料自动取自你的资质信息,无需重复准备)
  3. 微信审核通过后平台管理员激活你的特约商户
  4. 激活后你的应用立即具备收款能力,资金由微信支付直接清算到你进件时登记的银行账户

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-paymentGET .../test-payment/status?order_no=...

5.5 发起测试支付(Web 端)

特约商户激活后,可在合作伙伴后台 概览/partner/dashboard)页面点击 「发起测试支付」 验证收款链路:

  1. 选择已激活特约商户的应用
  2. 点击 创建 0.01 元测试订单
  3. 进入收银台完成支付,并确认你的 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)、namepictureemailemail_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 做显式校验。

本文档持续更新,请以最新版本为准。