签名成功

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