This commit is contained in:
2026-03-13 15:51:59 +08:00
parent 4db2386bbf
commit 4e91f4cede
133 changed files with 19502 additions and 37 deletions

View File

@@ -0,0 +1,931 @@
# 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. 统一响应格式
所有接口均返回 JSONHTTP 状态码为 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 | 否 | 指定收款商户 IDSaaS 多商户场景),须为本 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`