# 应用接入协议

> 在当前网站访问 `/help/login` 查看用户字段映射，`/help/integration-api` 查看完整登录/授权接口参数与响应；机器契约为 [OpenAPI](/help/integration-api)。

## OAuth2 / OIDC

管理员在「应用接入」、开发者在「我的应用」创建应用，配置精确回调URL、scope与public_client，保存首次显示的secret和API Key。公共客户端不能打包secret；机密客户端仅在服务端保存。

发现文档：`GET /.well-known/openid-configuration`；公钥：`GET /.well-known/jwks.json`。issuer为APP_ORIGIN，生产必须HTTPS。

1. 生成随机state、nonce和43–128位URL安全code_verifier，challenge为SHA256(verifier)的无填充Base64URL。
2. 浏览器跳转 `/oauth/authorize`，参数response_type=code、client_id、redirect_uri、scope、state、nonce、code_challenge、code_challenge_method=S256。
3. 登录同意后回调携带code和state，客户端必须核对state。
4. POST表单至 `/oauth/token`：grant_type=authorization_code、code、client_id、redirect_uri、code_verifier。机密客户端还需client_secret_basic或client_secret_post认证。授权码60秒有效、一次使用。
5. ID Token使用JWKS校验RS256、kid、iss、aud、exp、nonce，不能仅解码。访问令牌放入Authorization: Bearer调用 `/oauth/userinfo`。
6. 申请offline_access才能刷新。刷新后原子替换旧令牌，重放会撤销令牌族。退出调用 `/oauth/revoke`；用户可在控制台撤销应用同意。

升级到包含在线传统客户端兼容选项的版本时，先运行数据库迁移（`015_application_legacy_oauth.sql`），再启动新版服务和前端。

对于已开启 PKCE 但不发送 nonce 的机密客户端，可在管理员“应用接入 → 编辑”或开发者“我的应用 → 编辑配置”中开启“兼容不发送 nonce 的 OIDC 应用”。仅对该应用生效，保存后新的授权请求立即生效，无需修改环境变量或重启。默认关闭，公开客户端不能开启；非空 nonce 仍须为16–512字符，PKCE要求不变。

对于 Cloudreve、Gitea 等无法发送 PKCE 和 nonce、但能安全保存 Client Secret 的服务端应用，可开启“传统 OIDC 客户端兼容（不发送 PKCE / nonce）”。该设置同样按应用保存并立即生效。缺少 PKCE 只在 challenge 与 method 均未提供时兼容；一旦提供就必须是 S256 且在换取令牌时匹配 verifier。公开客户端不能开启此选项。

支持prompt=consent/login/none。建议使用成熟OIDC客户端库；本项目未取得第三方符合性认证。

## 授权查询与扣减

POST `/api/v1/auth/check`，JSON含authorization_id、binding_value。POST `/api/v1/auth/consume`，另含quantity与idempotency_key（8–128字符）。同一业务重试使用同一幂等键，不能在客户端自行扣余额。

服务端使用X-API-Key，仅访问对应应用授权；不能将API Key公开分发。用户客户端使用获准license:read/consume scope的OAuth访问令牌。域名/设备绑定要求binding_value已验证，且受数量和解绑冷却约束。

## 激活码、设备与域名授权

管理员按授权商品批量生成激活码，只需设置数量、可选的兑换截止时间和批次备注。系统在生成时自动锁定商品当时的授权规则，包括授权类型、时长、额度以及设备或域名绑定上限；之后修改商品不会改变已经发出的激活码权益。激活码按批次管理，可再次查看、复制或导出尚未使用的激活码，并可追踪兑换用户、兑换时间和对应授权的使用情况。新激活码以哈希用于兑换核验，同时加密保存用于有权限的管理员再次查看；升级前生成且只保存哈希的旧激活码无法恢复原文，但仍按原兼容规则兑换。

用户可以在认证中心工作台提交 `POST /authorizations/activate`，也可以让接入应用使用当前用户的 OAuth Access Token 调用同一路径。应用内兑换必须申请 `license:activate` scope，请求只提交 `{"code":"AC-..."}`；认证中心从令牌确定用户和应用，并只允许兑换属于该应用的激活码。应用 API Key 只能证明应用身份，不能单独代表用户兑换。成功响应同时返回值相同的 `license_key` 与 `authorization_id`，并返回 `application_id`、`auth_type`、绑定上限和额度。接入方保存 `license_key` 用于后续授权校验；激活码只用于一次兑换。

同一个 `license:activate` 用户 Access Token 也可以调用下述绑定、兼容确认和解绑路径。认证中心同时按令牌中的用户 ID 与应用 ID 限制目标授权，不能管理该用户在其他应用中的授权：

- `POST /authorizations/:id/bindings`
- `POST /authorizations/:id/bindings/:binding/verify`
- `DELETE /authorizations/:id/bindings/:binding`

设备型授权通过 `POST /authorizations/:id/bindings` 激活设备，binding_type为device、binding_value为应用生成的稳定设备标识。达到上限后拒绝新设备。用户在授权详情中可查看设备并调用 `DELETE /authorizations/:id/bindings/:binding` 取消激活。旧绑定协议中，解绑成功释放绑定名额，仍遵守所属应用配置的冷却时间；绑定名额不代表运行期并发许可。采用严格在线租约的客户端中，解绑会撤销旧租约的续期能力，但不缩短旧客户端已经收到的截止时间；新设备须等旧实例停止并 release，或等原租约实际到期，再成功 acquire 后才能运行。参见[永久授权恢复与单设备在线租约](/help/license-leases)。旧绑定策略迁移为 `010_activation_code_policy.sql`，租约协议另需 `027_authorization_leases.sql`；此说明不表示生产迁移已经执行。

域名型授权提交根域名后立即生效，不需要配置 DNS TXT 记录。提交完整子域名时携带 `parent_binding_id` 指向根域名，子域名必须严格位于该根域名之下。授权检查只接受根域名和用户明确配置的子域名；未配置的其他子域名不会自动获得授权。移除根域名会级联移除它下面的子域名。旧客户端的验证路径继续保留，但只确认旧绑定并立即生效，不再查询 DNS。升级时迁移 `026_domain_binding_immediate.sql` 会把已有待验证域名直接设为有效。

## 浏览器与交易

浏览器接口前缀 `/api/v1`，返回 `{code,message,data}`。用户写请求使用同源Cookie、Origin和X-CSRF-Token，先GET `/api/v1/auth/csrf`。机器授权、OAuth表单和支付通知使用各自认证。

创建订单POST `/orders`，含items（product_id/quantity，可选renewal_authorization_id）、coupon_ids、points、idempotency_key；同一键更换内容返回409。金额一律最小货币单位整数。

POST `/orders/:id/pay` 指定channel，返回跳转、二维码或银行资料。浏览器返回页不代表到账。正式通知地址 `/api/v1/payments/:channel/callback`，各适配器校验签名、商户、金额、币种后事务发放权益。

银行需工作人员录入真实流水和金额，跨订单复用流水被拒绝。取消后晚到账进入独立退款，不正常开通权益。退款须按类型区分：管理员售后金额退款（`after_sales`）支持部分或全部剩余现金/积分退款，保留订单及已履约权益；取消未履约纯服务订单（`cancel_order`）仅退还全部剩余额度；用户原有退货退款会回收软件权益，续期退款按原规则回滚续期增量，原单存在未处理续期时先处理续期。金额退款不等于撤销权益，提交或审核通过也不等于渠道已经退回资金。详情见[浏览器与订单退款接口](/help/browser-api)。普通软件授权订阅由用户主动续期；开发者会员可另行开启仅使用预付余额的自动续费，免费开发者订阅续期金额为 0，均不会直接向支付渠道周期扣款。详情见[开发者会员与应用管理](/help/developer)。

## 发票适配

配置HTTPS endpoint与Bearer token。系统POST JSON，Idempotency-Key和request_id均为申请编号:issue或申请编号:void。适配器须幂等，返回匹配request_id、amount_minor与currency。

请求含operation、invoice_no、title快照、amount_minor、currency、invoice_type、invoice_format、email、shipping_address、order_id。响应status=processing表示等待；issued需真实invoice_no，电子票需pdf_base64，纸票可含tracking_no；作废成功返回voided。详细字段见invoice_adapter.go。

退款可能早于异步开票完成，适配器应能按申请编号取消或查找已开票再红冲。不能仅删除本地PDF代表税务作废。未配置自动服务时使用人工开票与真实文件上传。

## 外部配置

支付、第三方登录、生产SMTP和税务开票都需要实际服务账号、回调域名与沙箱验收。密钥加密存储，读取时屏蔽；不要写入源码或日志。软件版本/S3客户端协议见 [版本管理](/help/releases)。
