签名成功
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
"password": "order_check_test1",
|
||||
"maxCaptchaAttempts": 3,
|
||||
"autoSync": {
|
||||
"enabled": true,
|
||||
"enabled": false,
|
||||
"intervalMinutes": 3,
|
||||
"pages": 2,
|
||||
"pageSize": 20
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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×tamp=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×tamp=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×tamp=1778670308&version=1.0rste57w8rsubsnxsb384ur3u9kn5fzhr',
|
||||
)
|
||||
})
|
||||
|
||||
test('encryptOpen91Cards matches documented AES ECB base64 sample', () => {
|
||||
const cards = [
|
||||
{
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -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×tamp=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×tamp=1777867200&version=1.0your_secret_key
|
||||
```
|
||||
|
||||
## 9. 业务处理规则
|
||||
|
||||
@@ -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×tamp=1777867260&userId=1001&version=1.0your_secret_key
|
||||
your_secret_keyorderNo=P91KS202605040001×tamp=1777867260&version=1.0your_secret_key
|
||||
```
|
||||
|
||||
## 9. 业务处理规则
|
||||
|
||||
@@ -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
@@ -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`、商户号、内部配置项或未传入的可选字段。
|
||||
|
||||
需要实现:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user