签名成功

This commit is contained in:
yml2213
2026-05-13 19:16:19 +08:00
parent b120924263
commit c2fd9078b9
11 changed files with 57 additions and 39 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
"password": "order_check_test1",
"maxCaptchaAttempts": 3,
"autoSync": {
"enabled": true,
"enabled": false,
"intervalMinutes": 3,
"pages": 2,
"pageSize": 20
+3 -3
View File
@@ -1,13 +1,13 @@
{
"syncFromCreatedAt": "2026-05-02T10:53:28.550Z",
"lastRunStartedAt": "2026-05-04T14:03:19.832Z",
"lastRunFinishedAt": "2026-05-04T14:03:20.530Z",
"lastRunStartedAt": "2026-05-11T14:09:54.989Z",
"lastRunFinishedAt": "2026-05-11T14:09:56.241Z",
"lastRunStatus": "success",
"lastErrorMessage": "",
"fetchedCount": 40,
"syncedCount": 0,
"ignoredCount": 40,
"lastOrderCreatedAt": "2026-05-04T14:02:50.000Z",
"lastOrderCreatedAt": "2026-05-11T14:09:50.000Z",
"watchMode": "normal",
"watchOrders": []
}
@@ -24,7 +24,7 @@ export async function createOpen91Order(payload = {}, { requestId = '' } = {}) {
assertOpen91CreatePayload(normalized, config)
assertOpen91Timestamp(normalized.timestamp, config.timestampToleranceSeconds)
assertOpen91Signature(normalized, config)
assertOpen91Signature(payload, config)
const now = nowIso()
const items = [
@@ -26,7 +26,7 @@ export async function queryOpen91Order(payload = {}, { requestId = '' } = {}) {
assertOpen91QueryPayload(normalized, config)
assertOpen91Timestamp(normalized.timestamp, config.timestampToleranceSeconds)
assertOpen91Signature(normalized, config)
assertOpen91Signature(payload, config)
const order = await findOrderByPlatformOrderId({
provider: OPEN_91_PROVIDER,
+8 -13
View File
@@ -28,13 +28,6 @@ export function getOpen91Config() {
export function assertOpen91Config() {
const config = getOpen91Config()
if (!config.userId) {
throw createHttpError('91卡券 userId 未配置', {
statusCode: 500,
errorCode: 'open91_missing_user_id',
})
}
if (!config.secret) {
throw createHttpError('91卡券 secret 未配置', {
statusCode: 500,
@@ -154,13 +147,15 @@ export function assertOpen91Timestamp(timestamp, toleranceSeconds = assertOpen91
}
}
export function buildOpen91SignSource(params = {}, { userId, secret } = assertOpen91Config()) {
const entries = Object.entries({
...params,
userId: normalizeOpen91String(userId),
})
export function buildOpen91SignSource(params = {}, { secret } = assertOpen91Config()) {
const entries = Object.entries(params)
.filter(([key]) => key !== 'sign')
.sort(([left], [right]) => left.localeCompare(right))
.sort(([left], [right]) => {
if (left === right) {
return 0
}
return left < right ? -1 : 1
})
const queryString = entries
.map(([key, value]) => `${key}=${stringifyOpen91SignValue(value)}`)
@@ -10,7 +10,7 @@ import {
verifyOpen91Signature,
} from './shared.js'
test('signOpen91Payload matches documented md5 signing rule', () => {
test('signOpen91Payload signs only request body fields', () => {
const config = {
userId: '1001',
secret: 'rste57w8rsubsnxsb384ur3u9kn5fzhr',
@@ -28,7 +28,7 @@ test('signOpen91Payload matches documented md5 signing rule', () => {
const source = buildOpen91SignSource(payload, config)
assert.equal(
source,
'rste57w8rsubsnxsb384ur3u9kn5fzhrbuyNum=1&callbackUrl=https://cb.example.com/notify/91/order&maxAmount=0.0000&orderNo=P91KS202605040001&productNo=KS-CLOUD-SKU-001&timestamp=1777867200&userId=1001&version=1.0rste57w8rsubsnxsb384ur3u9kn5fzhr',
'rste57w8rsubsnxsb384ur3u9kn5fzhrbuyNum=1&callbackUrl=https://cb.example.com/notify/91/order&maxAmount=0.0000&orderNo=P91KS202605040001&productNo=KS-CLOUD-SKU-001&timestamp=1777867200&version=1.0rste57w8rsubsnxsb384ur3u9kn5fzhr',
)
const sign = signOpen91Payload(payload, config)
@@ -36,6 +36,26 @@ test('signOpen91Payload matches documented md5 signing rule', () => {
assert.equal(verifyOpen91Signature({ ...payload, sign }, config), true)
})
test('signOpen91Payload does not add missing optional fields or configured userId', () => {
const config = {
userId: '1001',
secret: 'rste57w8rsubsnxsb384ur3u9kn5fzhr',
}
const payload = {
orderNo: '2613300083829561',
productNo: '183999074510936',
buyNum: 1,
maxAmount: '0.01',
timestamp: 1778670308,
version: '1.0',
}
assert.equal(
buildOpen91SignSource(payload, config),
'rste57w8rsubsnxsb384ur3u9kn5fzhrbuyNum=1&maxAmount=0.01&orderNo=2613300083829561&productNo=183999074510936&timestamp=1778670308&version=1.0rste57w8rsubsnxsb384ur3u9kn5fzhr',
)
})
test('encryptOpen91Cards matches documented AES ECB base64 sample', () => {
const cards = [
{
-1
View File
@@ -1 +0,0 @@
+5 -4
View File
@@ -39,11 +39,13 @@ POST https://221329.cc.cd/api/v1/open/91/orders/create
签名规则采用 签名规则示例 中的约定:
- `sign` 外,所有参数按字段名 ASCII 升序排序
- 仅使用请求 JSON body 中实际传入的参数参与签名
-`sign` 外,所有 body 参数按字段名 ASCII 升序排序;
- 使用 `key=value&key=value` 方式拼接;
- 前后拼接商户密钥;
-`MD5`,输出 32 位大写字符串;
- 空值参数参与签名
- 已传入的空值参数参与签名
- 不要额外加入 `userId`、商户号、内部配置项或未传入的可选字段。
## 6. 请求参数
@@ -77,13 +79,12 @@ POST https://221329.cc.cd/api/v1/open/91/orders/create
假设:
- `userId = 1001`
- 商户密钥为:`your_secret_key`
则源串示例如下:
```text
your_secret_keybuyNum=1&callbackUrl=https://cb.example.com/notify/91/order&maxAmount=0.0000&orderNo=P91KS202605040001&productNo=KS-CLOUD-SKU-001&timestamp=1777867200&userId=1001&version=1.0your_secret_key
your_secret_keybuyNum=1&callbackUrl=https://cb.example.com/notify/91/order&maxAmount=0.0000&orderNo=P91KS202605040001&productNo=KS-CLOUD-SKU-001&timestamp=1777867200&version=1.0your_secret_key
```
## 9. 业务处理规则
+5 -4
View File
@@ -33,11 +33,13 @@ POST https://221329.cc.cd/api/v1/open/91/orders/query
签名规则采用 2.签名规则示例 中的约定:
- `sign` 外,所有参数按字段名 ASCII 升序排序
- 仅使用请求 JSON body 中实际传入的参数参与签名
-`sign` 外,所有 body 参数按字段名 ASCII 升序排序;
- 使用 `key=value&key=value` 方式拼接;
- 前后拼接商户密钥;
-`MD5`,输出 32 位大写字符串;
- 空值参数参与签名
- 已传入的空值参数参与签名
- 不要额外加入 `userId`、商户号、内部配置项或未传入的可选字段。
## 6. 请求参数
@@ -63,13 +65,12 @@ POST https://221329.cc.cd/api/v1/open/91/orders/query
假设:
- `userId = 1001`
- 商户密钥为:`your_secret_key`
则源串示例如下:
```text
your_secret_keyorderNo=P91KS202605040001&timestamp=1777867260&userId=1001&version=1.0your_secret_key
your_secret_keyorderNo=P91KS202605040001&timestamp=1777867260&version=1.0your_secret_key
```
## 9. 业务处理规则
+4 -3
View File
@@ -37,7 +37,8 @@ https://你的域名/api/v1/open/91
签名规则采用当前文档里的示例规则:
- `sign` 外,所有参数按字段名 ASCII 升序排序
- 仅使用请求 JSON body 中实际传入的参数参与签名
-`sign` 外,所有 body 参数按字段名 ASCII 升序排序;
- 使用 `key=value&key=value` 方式拼接;
- 前后拼接商户密钥;
-`MD5`,输出 32 位大写字符串。
@@ -48,7 +49,8 @@ https://你的域名/api/v1/open/91
- `charset`: `UTF-8`
- `timestamp`: 10 位秒级 Unix 时间戳
- `version`: `1.0`
- 空值参数是否参与签名:`参与`
- 已传入的空值参数是否参与签名:`参与`
- 不参与签名:`sign`、未传入的可选字段、`userId`、商户号、内部配置项
## 5. 业务字段映射
@@ -296,7 +298,6 @@ Content-Type: application/json;charset=utf-8
- 异步卡密下单 URL
- 查询订单 URL
- `userId`
- 商户密钥
- 签名算法是否按本文档固定
- `cards``cardNo` 直接展示链接是否按预期显示
+7 -6
View File
@@ -109,12 +109,13 @@ platforms: {
签名规则沿用文档约定:
1. `sign` 外,所有参数按 ASCII 升序排序
2. 拼接为 QueryString
3. 前后加商户密钥
4. 计算大写 MD5
5. `userId` 不在请求体中,但参与签名
6. 空值参数参与签名
1. 仅使用请求 JSON body 中实际传入的参数参与签名
2. `sign` 外,所有 body 参数按 ASCII 升序排序
3. 拼接为 QueryString
4. 前后加商户密钥
5. 计算大写 MD5
6. 已传入的空值参数参与签名
7. 不额外加入 `userId`、商户号、内部配置项或未传入的可选字段。
需要实现: