# Pay-Bridge 支付网关接入文档 | 版本 | 日期 | 说明 | |------|------|------| | v1.2 | 2026-02-28 | 新增商户进件接口(SaaS 多商户场景)、统一下单支持 merchant_id | | v1.1 | 2026-02-28 | 更新接入凭证申请方式 | | v1.0 | 2026-02-28 | 初始版本 | --- ## 目录 1. [接入准备](#1-接入准备) 2. [请求规范](#2-请求规范) 3. [签名算法](#3-签名算法) 4. [统一响应格式](#4-统一响应格式) 5. [支付接口](#5-支付接口) - 5.1 [统一下单](#51-统一下单) - 5.2 [查询订单](#52-查询订单) - 5.3 [关闭订单](#53-关闭订单) - 5.4 [申请退款](#54-申请退款) - 5.5 [查询退款](#55-查询退款) 6. [商户进件接口](#6-商户进件接口)(SaaS 多商户场景) - 6.1 [创建商户](#61-创建商户) - 6.2 [上传证件文件](#62-上传证件文件) - 6.3 [提交进件申请](#63-提交进件申请) - 6.4 [查询审核状态](#64-查询审核状态) - 6.5 [查询商户详情](#65-查询商户详情) - 6.6 [查询商户列表](#66-查询商户列表) 7. [异步通知](#7-异步通知) 8. [订单状态说明](#8-订单状态说明) 9. [错误码对照表](#9-错误码对照表) 10. [最佳实践](#10-最佳实践) 11. [签名示例代码](#11-签名示例代码) --- ## 1. 接入准备 ### 1.1 获取接入凭证 接入凭证由**平台管理员**在管理后台创建。请联系平台管理员,按以下步骤为你的系统开通接入权限: 1. 管理员登录管理后台,进入「**接入应用**」模块 2. 点击「新建应用」,填写应用名称(如:商城系统、ERP 系统) 3. 系统自动生成 `app_id` 和 `app_secret`,**Secret 仅在创建时展示一次** 4. 管理员将凭证告知接入方 > **重要提示**:`app_secret` 创建后无法再次查看,如遗失需由管理员在后台执行「重置密钥」操作,旧密钥立即失效。 获得的凭证如下: | 参数 | 示例 | 说明 | |------|------|------| | `app_id` | `app_2602280a3f1b7c2d` | 应用唯一标识,格式为 `app_` + 日期 + 随机串 | | `app_secret` | `A3F8C2D1E4B7F9A0...` | 64 位十六进制字符串,用于请求签名,**请妥善保管,切勿泄露或提交至代码仓库** | ### 1.2 接口基础地址 | 环境 | 地址 | |------|------| | 生产环境 | `https://pay.your-domain.com` | | 沙箱环境 | `https://sandbox-pay.your-domain.com` | --- ## 2. 请求规范 ### 2.1 基本要求 - 协议:HTTPS - 方法:POST(下单/退款/关闭)、GET(查询) - 编码:UTF-8 - Content-Type:`application/json` ### 2.2 公共请求头 所有请求必须携带以下 Header: | Header | 类型 | 必填 | 说明 | |--------|------|------|------| | `X-App-Id` | string | 是 | 平台分配的 app_id | | `X-Timestamp` | string | 是 | 当前 Unix 时间戳(秒),与服务器时间差须在 **5 分钟**以内 | | `X-Sign` | string | 是 | 请求签名,见[签名算法](#3-签名算法) | | `Content-Type` | string | 是 | `application/json` | **示例:** ```http POST /api/v1/pay/unified-order HTTP/1.1 Host: pay.your-domain.com Content-Type: application/json X-App-Id: app_20260101001 X-Timestamp: 1740700800 X-Sign: a3f8c2d1e4b7f9a0c5d2e8f1b4a7c3d6e9f2b5a8c1d4e7f0a3b6c9d2e5f8a1b4 ``` --- ## 3. 签名算法 ### 3.1 算法说明 签名算法为 **HMAC-SHA256**,签名结果为 **十六进制小写字符串**。 ### 3.2 签名步骤 **第一步**:拼接签名原文 ``` 签名原文 = app_id + timestamp + requestBody ``` - `app_id`:Header 中的 X-App-Id - `timestamp`:Header 中的 X-Timestamp - `requestBody`:请求体原始 JSON 字符串(GET 请求时为空字符串 `""`) **第二步**:HMAC-SHA256 签名 ``` sign = HMAC-SHA256(签名原文, app_secret) ``` **第三步**:Hex 编码,填入 `X-Sign` Header ### 3.3 注意事项 - 请求体 JSON 中字段顺序不影响签名,但序列化后的**字节内容必须与实际发送的 body 完全一致** - 时间戳精度为秒,不是毫秒 - GET 请求 body 为空字符串参与签名 --- ## 4. 统一响应格式 所有接口均返回 JSON,HTTP 状态码为 200(业务错误也返回 200,通过 `code` 字段区分)。 **成功响应:** ```json { "code": "0", "message": "success", "data": { ... }, "trace_id": "7f3a2b1c4d5e6f7a" } ``` **失败响应:** ```json { "code": "30004", "message": "退款金额超过可退金额", "trace_id": "7f3a2b1c4d5e6f7a" } ``` | 字段 | 类型 | 说明 | |------|------|------| | `code` | string | `"0"` 为成功,其他为错误码,见[错误码表](#9-错误码对照表) | | `message` | string | 提示信息 | | `data` | object | 业务数据,仅成功时返回 | | `trace_id` | string | 请求追踪 ID,排查问题时提供给平台 | --- ## 5. 支付接口 ### 5.1 统一下单 **接口地址** ``` POST /api/v1/pay/unified-order ``` **请求参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `merchant_order_no` | string | 是 | 商户系统订单号,同一 app_id 下唯一,最长 64 位 | | `pay_method` | string | 是 | 支付方式,见下方枚举 | | `amount` | int64 | 是 | 订单金额,单位**分**,最小 1 | | `subject` | string | 是 | 商品描述,最长 256 位 | | `notify_url` | string | 是 | 支付结果异步通知地址,须为合法 HTTPS URL | | `expire_minutes` | int | 否 | 订单有效期(分钟),默认 30 分钟 | | `profit_sharing_amount` | int64 | 否 | 分润金额(分),0 表示不分润 | | `extra` | object | 否 | 支付方式附加参数,见下方说明 | | `merchant_id` | string | 否 | 指定收款商户 ID(SaaS 多商户场景),须为本 app_id 下已进件审核通过的商户 | **`pay_method` 枚举值** | 值 | 说明 | |----|------| | `WECHAT_JSAPI` | 微信公众号支付 | | `WECHAT_H5` | 微信 H5 支付 | | `WECHAT_NATIVE` | 微信扫码支付 | | `WECHAT_MINI` | 微信小程序支付 | | `ALIPAY` | 支付宝扫码支付 | | `QUICK_PAY` | 快捷支付 | **`extra` 附加参数说明** | 支付方式 | 参数 | 说明 | |---------|------|------| | `WECHAT_JSAPI` | `openid`(必填) | 用户在公众号的 openid | | `WECHAT_MINI` | `openid`(必填) | 用户在小程序的 openid | | `WECHAT_JSAPI` / `WECHAT_MINI` | `sub_appid`(可选) | 子商户公众号/小程序 AppID | **请求示例** ```json { "merchant_order_no": "ORD20260228001", "pay_method": "WECHAT_JSAPI", "amount": 9900, "subject": "会员充值 - 月卡", "notify_url": "https://your-server.com/callback/pay", "expire_minutes": 15, "extra": { "openid": "oxxxxxxxxxxxxxxxxxxxxxxx" } } ``` **成功响应 `data`** | 字段 | 类型 | 说明 | |------|------|------| | `trade_no` | string | 平台交易号,格式 `PAYyyMMddXXXXXXXX` | | `pay_credential` | object | 支付凭证,透传给前端拉起支付,格式因支付方式不同而异 | | `is_idempotent` | bool | `true` 表示该订单号已存在,返回的是已有订单信息 | **响应示例** ```json { "code": "0", "message": "success", "data": { "trade_no": "PAY26022800000001", "pay_credential": { "appId": "wx1234567890", "timeStamp": "1740700800", "nonceStr": "abc123xyz", "package": "prepay_id=wx28...", "signType": "RSA", "paySign": "..." }, "is_idempotent": false } } ``` > **幂等说明**:使用相同的 `merchant_order_no` 重复调用,不会重复下单,会直接返回已有订单的支付凭证,并且 `is_idempotent` 为 `true`。 --- ### 5.2 查询订单 **接口地址** ``` GET /api/v1/pay/query/{trade_no} ``` **路径参数** | 参数 | 类型 | 说明 | |------|------|------| | `trade_no` | string | 平台交易号(下单时返回的 `trade_no`) | **请求示例** ```http GET /api/v1/pay/query/PAY26022800000001 ``` **成功响应 `data`** | 字段 | 类型 | 说明 | |------|------|------| | `trade_no` | string | 平台交易号 | | `merchant_order_no` | string | 商户订单号 | | `pay_method` | string | 支付方式 | | `amount` | int64 | 订单金额(分) | | `status` | string | 订单状态,见[订单状态说明](#8-订单状态说明) | | `channel_trade_no` | string | 渠道侧交易号 | | `pay_time` | string | 支付成功时间,ISO8601 格式,未支付时为 null | | `created_at` | string | 订单创建时间,ISO8601 格式 | **响应示例** ```json { "code": "0", "message": "success", "data": { "trade_no": "PAY26022800000001", "merchant_order_no": "ORD20260228001", "pay_method": "WECHAT_JSAPI", "amount": 9900, "status": "PAID", "channel_trade_no": "4200002345202602280000000001", "pay_time": "2026-02-28T10:05:00Z", "created_at": "2026-02-28T10:00:00Z" } } ``` --- ### 5.3 关闭订单 关闭仍在支付中的订单,已支付订单不可关闭(请使用退款接口)。 **接口地址** ``` POST /api/v1/pay/close ``` **请求参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `trade_no` | string | 是 | 平台交易号 | **请求示例** ```json { "trade_no": "PAY26022800000001" } ``` **成功响应** ```json { "code": "0", "message": "success" } ``` --- ### 5.4 申请退款 **接口地址** ``` POST /api/v1/pay/refund ``` **请求参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `trade_no` | string | 是 | 平台交易号 | | `refund_amount` | int64 | 是 | 退款金额(分),不得超过原订单金额 | | `reason` | string | 否 | 退款原因,最长 256 位 | | `notify_url` | string | 否 | 退款结果异步通知地址 | **请求示例** ```json { "trade_no": "PAY26022800000001", "refund_amount": 9900, "reason": "用户申请退款", "notify_url": "https://your-server.com/callback/refund" } ``` **成功响应 `data`** | 字段 | 类型 | 说明 | |------|------|------| | `refund_no` | string | 平台退款单号 | | `trade_no` | string | 关联的平台交易号 | | `refund_amount` | int64 | 退款金额(分) | | `status` | string | 退款状态:`PENDING` / `PROCESSING` / `SUCCESS` / `FAILED` | | `channel_refund_no` | string | 渠道侧退款单号 | **响应示例** ```json { "code": "0", "message": "success", "data": { "refund_no": "REF26022800000001", "trade_no": "PAY26022800000001", "refund_amount": 9900, "status": "PROCESSING", "channel_refund_no": "50300807092026022800000000001" } } ``` > **注意**:退款为异步处理,状态 `PROCESSING` 表示已提交渠道,最终结果通过异步通知推送,建议同时通过查询接口轮询确认。 --- ### 5.5 查询退款 **接口地址** ``` GET /api/v1/pay/refund/query/{refund_no} ``` **路径参数** | 参数 | 类型 | 说明 | |------|------|------| | `refund_no` | string | 平台退款单号(申请退款时返回的 `refund_no`) | **成功响应 `data`** | 字段 | 类型 | 说明 | |------|------|------| | `refund_no` | string | 平台退款单号 | | `trade_no` | string | 关联的平台交易号 | | `refund_amount` | int64 | 退款金额(分) | | `status` | string | 退款状态 | | `channel_refund_no` | string | 渠道侧退款单号 | | `refund_time` | string | 退款成功时间,ISO8601 格式,未完成时为 null | --- ## 6. 商户进件接口 > 适用场景:你的系统是 **SaaS 平台**,平台上的客户需要先完成进件(企业入网)才能以自己名义收款。 > > 所有进件接口与支付接口使用**相同的 HMAC 鉴权**(`app_id` + `app_secret`),且数据完全按 `app_id` 隔离——你只能看到自己名下的商户。 ### 典型流程 ``` 1. 创建商户记录(获取 merchant_id) ↓ 2. 上传营业执照等证件文件(获取 file_id) ↓ 3. 提交进件申请(将 file_id 填入 submit_data) ↓ 4. 轮询查询审核状态,直至 APPROVED ↓ 5. 统一下单时传入 merchant_id,指定该商户收款 ``` --- ### 6.1 创建商户 **接口地址** ``` POST /api/v1/merchant ``` **请求参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `merchant_id` | string | 是 | 你方系统的商户唯一标识,最长 32 位,由调用方自定义 | | `merchant_name` | string | 是 | 商户名称 | | `license_no` | string | 否 | 营业执照号 | | `legal_person` | string | 否 | 法定代表人姓名 | | `bank_account` | string | 否 | 结算银行账号(脱敏存储) | **请求示例** ```json { "merchant_id": "tenant_0001", "merchant_name": "示例科技有限公司", "license_no": "91310000XXXXXXXXXX", "legal_person": "张三", "bank_account": "6222021234567890123" } ``` **成功响应 `data`** ```json { "merchant_id": "tenant_0001" } ``` --- ### 6.2 上传证件文件 **接口地址** ``` POST /api/v1/merchant/upload-file ``` **请求格式**:`multipart/form-data` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `file` | file | 是 | 证件图片,支持 JPG / PNG / PDF | | `file_media_type` | string | 是 | 文件类型代码,由渠道定义,如 `01`=营业执照、`02`=身份证正面 | | `channel_code` | string | 否 | 渠道代码,默认 `HEEPAY` | **成功响应 `data`** ```json { "file_id": "3000000001234567" } ``` > 上传返回的 `file_id` 在提交进件时填入 `submit_data`,有效期以渠道规定为准(通常 48 小时内使用)。 --- ### 6.3 提交进件申请 **接口地址** ``` POST /api/v1/merchant/{merchant_id}/apply ``` **路径参数** | 参数 | 说明 | |------|------| | `merchant_id` | 创建商户时使用的商户 ID | **请求参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `channel_code` | string | 是 | 渠道代码,如 `HEEPAY` | | `submit_data` | object | 否 | 渠道要求的进件业务参数,内容因渠道而异,详见各渠道对接手册 | **请求示例** ```json { "channel_code": "HEEPAY", "submit_data": { "business_license_no": "91310000XXXXXXXXXX", "business_license_copy": "3000000001234567", "id_card_front": "3000000001234568", "id_card_back": "3000000001234569", "contact_name": "张三", "contact_phone": "138XXXXXXXX", "bank_account_no": "6222021234567890123", "bank_name": "中国工商银行" } } ``` **成功响应 `data`** ```json { "application_id": "APP1a2b3c4d5e6f7a8b" } ``` --- ### 6.4 查询审核状态 **接口地址** ``` GET /api/v1/merchant/{merchant_id}/audit ``` **成功响应 `data`** | 字段 | 类型 | 说明 | |------|------|------| | `application_id` | string | 进件申请 ID | | `merchant_id` | string | 商户 ID | | `channel_code` | string | 渠道代码 | | `audit_status` | string | 审核状态,见下方枚举 | | `reject_reason` | string | 拒绝原因,仅 `REJECTED` 时返回 | | `submitted_at` | string | 提交时间 | | `audited_at` | string | 审核完成时间,未完成时为 null | **`audit_status` 枚举值** | 值 | 说明 | |----|------| | `SUBMITTING` | 提交中,正在调用渠道 | | `REVIEWING` | 审核中,渠道人工审核 | | `APPROVED` | 审核通过,商户可正常收款 | | `REJECTED` | 审核拒绝,查看 `reject_reason` 修改后重新提交 | **响应示例** ```json { "code": "0", "message": "success", "data": { "application_id": "APP1a2b3c4d5e6f7a8b", "merchant_id": "tenant_0001", "channel_code": "HEEPAY", "audit_status": "APPROVED", "submitted_at": "2026-02-28T10:00:00Z", "audited_at": "2026-02-28T14:30:00Z" } } ``` --- ### 6.5 查询商户详情 **接口地址** ``` GET /api/v1/merchant/{merchant_id} ``` **成功响应 `data`**:返回完整商户对象,字段同创建时入参,额外包含: | 字段 | 类型 | 说明 | |------|------|------| | `status` | string | 商户状态:`PENDING` / `ACTIVE` / `FROZEN` / `REJECTED` | | `channel_merchant_id` | string | 渠道侧商户 ID,进件审核通过后由渠道下发 | | `created_at` | string | 创建时间 | > 如传入的 `merchant_id` 不属于当前 `app_id`,接口返回 404。 --- ### 6.6 查询商户列表 **接口地址** ``` GET /api/v1/merchant ``` **Query 参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `status` | string | 否 | 按状态过滤:`PENDING` / `ACTIVE` / `FROZEN` / `REJECTED` | | `limit` | int | 否 | 每页数量,默认 20 | | `offset` | int | 否 | 偏移量,默认 0 | **响应示例** ```json { "code": "0", "message": "success", "data": { "list": [ { "merchant_id": "tenant_0001", "merchant_name": "示例科技有限公司", "status": "ACTIVE", "created_at": "2026-02-28T10:00:00Z" } ], "limit": 20, "offset": 0 } } ``` --- ### 6.7 指定商户收款(统一下单扩展) 商户进件通过后,在[统一下单](#51-统一下单)时传入 `merchant_id`,平台会将该商户的渠道收款账号注入下单参数,实现**分账到具体商户**: ```json { "merchant_order_no": "ORD20260228002", "pay_method": "WECHAT_JSAPI", "amount": 9900, "subject": "订单支付", "notify_url": "https://your-server.com/callback/pay", "merchant_id": "tenant_0001", "extra": { "openid": "oxxxxxxxxxxxxxxxxxxxxxxx" } } ``` > `merchant_id` 必须属于当前 `app_id`,且商户状态为 `ACTIVE`,否则返回 `30001`。 --- ## 7. 异步通知 ### 7.1 支付成功通知 用户支付成功后,平台将向下单时提供的 `notify_url` 发送 POST 请求。 **通知内容** ```json { "trade_no": "PAY26022800000001", "merchant_order_no": "ORD20260228001", "status": "PAID", "amount": 9900, "pay_method": "WECHAT_JSAPI", "channel_trade_no": "4200002345202602280000000001", "pay_time": "2026-02-28T10:05:00Z" } ``` ### 7.2 退款结果通知 退款处理完成后,平台将向退款时提供的 `notify_url` 发送 POST 请求。 **通知内容** ```json { "refund_no": "REF26022800000001", "trade_no": "PAY26022800000001", "merchant_order_no": "ORD20260228001", "refund_amount": 9900, "status": "SUCCESS", "refund_time": "2026-02-28T10:30:00Z" } ``` ### 7.3 通知应答规则 **接收通知后,你的服务器必须在 10 秒内响应以下内容:** ``` HTTP 200 Body: success ``` 返回任何其他内容,或超时未响应,平台将认为通知失败并重试。 ### 7.4 重试策略 | 第 N 次 | 延迟 | |--------|------| | 1 | 立即 | | 2 | 15 秒后 | | 3 | 30 秒后 | | 4 | 1 分钟后 | | 5 | 5 分钟后 | | 6 | 30 分钟后 | | 7 | 1 小时后 | | 8 | 6 小时后 | | 9 | 12 小时后 | 超过 9 次仍失败将停止重试,请通过**查询接口**主动同步最终状态。 ### 7.5 防重处理 通知可能因网络原因重复发送,请以 `trade_no` 为唯一键做幂等处理,避免重复发货或重复入账。 --- ## 8. 订单状态说明 ### 8.1 交易订单状态 | 状态 | 说明 | |------|------| | `CREATING` | 创建中,正在调用渠道下单 | | `PAYING` | 待支付,等待用户付款 | | `PAID` | 支付成功 | | `CLOSED` | 已关闭 | | `FAILED` | 支付失败 | | `CREATE_FAILED` | 下单失败,渠道返回错误 | | `REFUNDED` | 已全额退款 | ### 8.2 退款状态 | 状态 | 说明 | |------|------| | `PENDING` | 待处理 | | `PROCESSING` | 处理中,已提交渠道 | | `SUCCESS` | 退款成功 | | `FAILED` | 退款失败,可联系平台处理 | --- ## 9. 错误码对照表 | 错误码 | HTTP 状态码 | 说明 | 处理建议 | |--------|------------|------|---------| | `0` | 200 | 成功 | — | | `10001` | 400 | 参数校验失败 | 检查请求参数格式和必填项 | | `10002` | 400 | 缺少必填参数 | 补充缺少的参数 | | `10003` | 400 | 不支持的支付方式 | 检查 pay_method 是否正确 | | `10004` | 400 | 金额非法 | 金额须为正整数(分) | | `20001` | 401 | 签名验证失败 | 检查签名算法和 app_secret | | `20002` | 401 | 应用不存在或已禁用 | 联系平台确认 app_id 状态 | | `30001` | 422 | 订单不存在 | 检查 trade_no 是否正确 | | `30002` | 422 | 订单已支付 | 勿重复支付 | | `30003` | 422 | 订单已关闭 | 需重新下单 | | `30004` | 422 | 退款金额超过可退金额 | 检查退款金额 | | `30009` | 422 | 订单未支付,无法退款 | 确认订单已支付后再退款 | | `30010` | 422 | 退款单不存在 | 检查 refund_no 是否正确 | | `40001` | 502 | 渠道下单失败 | 稍后重试,仍失败请联系平台 | | `40002` | 502 | 渠道退款失败 | 稍后重试,仍失败请联系平台 | | `40003` | 502 | 渠道调用超时 | 稍后重试 | | `50099` | 500 | 系统内部错误 | 记录 trace_id 联系平台排查 | --- ## 10. 最佳实践 ### 10.1 下单流程建议 ``` 1. 用户确认支付 ↓ 2. 你的服务端调用【统一下单】接口 ↓ 3. 返回 pay_credential 给前端 ↓ 4. 前端调用微信/支付宝 SDK 拉起支付 ↓ 5. 接收异步通知 → 更新订单状态 → 返回 "success" ↓ 6. (兜底)若通知未收到,定时调用【查询订单】接口轮询 ``` ### 10.2 安全注意事项 - `app_secret` 只在服务端使用,**禁止出现在前端代码或 App 客户端中** - 异步通知的 IP 建议加入白名单(联系平台获取出口 IP 段) - 通知接收后务必**先返回 `success`,再处理业务逻辑**,避免超时导致重复通知 ### 10.3 金额单位 所有金额字段均以**分**为单位,整数类型: - 1 元 = `100` - 9.9 元 = `990` - 99.99 元 = `9999` ### 10.4 时区 所有时间字段均为 **UTC 时间**,ISO8601 格式,例如 `2026-02-28T10:05:00Z`。 --- ## 11. 签名示例代码 ### Go ```go import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" "strconv" "time" ) func sign(appID, appSecret, body string) (timestamp, sign string) { ts := strconv.FormatInt(time.Now().Unix(), 10) raw := appID + ts + body mac := hmac.New(sha256.New, []byte(appSecret)) mac.Write([]byte(raw)) return ts, hex.EncodeToString(mac.Sum(nil)) } ``` ### Python ```python import hmac import hashlib import time def sign(app_id: str, app_secret: str, body: str) -> tuple[str, str]: timestamp = str(int(time.time())) raw = app_id + timestamp + body signature = hmac.new( app_secret.encode("utf-8"), raw.encode("utf-8"), hashlib.sha256 ).hexdigest() return timestamp, signature ``` ### Java ```java import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; public static String[] sign(String appId, String appSecret, String body) throws Exception { String timestamp = String.valueOf(System.currentTimeMillis() / 1000); String raw = appId + timestamp + body; Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); byte[] bytes = mac.doFinal(raw.getBytes(StandardCharsets.UTF_8)); StringBuilder sb = new StringBuilder(); for (byte b : bytes) sb.append(String.format("%02x", b)); return new String[]{timestamp, sb.toString()}; } ``` ### PHP ```php function sign(string $appId, string $appSecret, string $body): array { $timestamp = (string)time(); $raw = $appId . $timestamp . $body; $signature = hash_hmac('sha256', $raw, $appSecret); return [$timestamp, $signature]; } ``` --- 如有问题请联系平台技术支持,并提供接口返回的 `trace_id`。