Files
pay-bridge/docs/api-integration-guide.md
2026-03-13 15:51:59 +08:00

932 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`