@alipay/alipay-aipay 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alipay/alipay-aipay",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "支付宝 AI 支付技能包 - 一键安装 Agent Skill,并后台准备 alipay-cli 环境",
5
5
  "type": "module",
6
6
  "bin": {
@@ -412,6 +412,8 @@ bash modules/scripts/ar_sign_apply.sh --product apppay --sales-code "I1080300001
412
412
 
413
413
  **查询应用**:执行 `bash modules/scripts/app.sh list --product-type "$PRODUCT_TYPE" --sales-code "$SALES_CODE"`。APP支付会按 `--product-type apppay` 或 `--sales-code I1080300001000041313` 自动查询 `MOBILEAPP`。
414
414
 
415
+ APP支付新建 `MOBILEAPP` 时,`appSign` 仅表示 Android 应用签名摘要字段。采集 `appSign` 时只向用户索要其 Android 应用对应的签名摘要值,禁止引导用户通过支付宝开放平台官方密钥生成工具、签名工具或格式转换工具获取;阶段 4 的应用公钥生成引导只适用于 `createKeyConfirmPage` 的 `publicKey`,不得套用到 `appSign`。
416
+
415
417
  ```bash
416
418
  # 按量付费/网站支付
417
419
  bash modules/scripts/app.sh list --product-type "<aipay|webpay>" --sales-code "<当前产品码>"
@@ -430,7 +432,7 @@ bash modules/scripts/app.sh create --product-type "apppay" --sales-code "I108030
430
432
  bash modules/scripts/app.sh create --product-type "apppay" --sales-code "I1080300001000041313" --mobile-platform "ALL" --bundle-id "<bundleId>" --app-package "<appPackage>" --app-sign "<appSign>"
431
433
  ```
432
434
 
433
- **创建应用**:执行 `bash modules/scripts/app.sh create --product-type "$PRODUCT_TYPE" --sales-code "$SALES_CODE"`。APP支付会自动创建 `MOBILEAPP`,但只在进入 5.3 创建应用阶段后采集移动应用平台信息:`IOS` 需要传 `--bundle-id`,`ANDROID` 需要传 `--app-package` 和 `--app-sign`,`ALL` 需要三项都传。字段缺失时先向用户补问,禁止用最小入参创建 `MOBILEAPP`。
435
+ **创建应用**:执行 `bash modules/scripts/app.sh create --product-type "$PRODUCT_TYPE" --sales-code "$SALES_CODE"`。APP支付会自动创建 `MOBILEAPP`,但只在进入 5.3 创建应用阶段后采集移动应用平台信息:`IOS` 需要传 `--bundle-id`,`ANDROID` 需要传 `--app-package` 和 `--app-sign`,`ALL` 需要三项都传。字段缺失时先向用户补问,禁止用最小入参创建 `MOBILEAPP`。补问 `appSign` 时只索要 Android 应用签名摘要值,不引导用户使用支付宝开放平台官方密钥/签名工具。
434
436
 
435
437
  **设置公钥**:执行 `bash modules/scripts/app.sh key <appId> <用户提供的公钥>`。
436
438
 
@@ -549,7 +551,7 @@ SUCCESS=$(echo "$BUSINESS" | jq -r '.success // false')
549
551
  ```
550
552
 
551
553
  > 内存状态只保存流程上下文和非敏感产物引用。禁止写入私钥、公钥原文、签名串、授权 token、完整支付表单或其他业务凭据。
552
- > `appSign` 指 Android 应用签名摘要字段,只在应用创建阶段使用,不是支付签名串或私钥;不得把支付签名串、私钥或其他业务凭据写入内存。
554
+ > `appSign` 指 Android 应用签名摘要字段,只在应用创建阶段使用,不是应用公钥、支付签名串或私钥;不得把支付签名串、私钥或其他业务凭据写入内存,也不得引导用户通过支付宝开放平台官方密钥/签名工具获取。
553
555
 
554
556
  **productType 与 productName 对应关系**:
555
557
  | productName | productType | 说明 |
@@ -78,6 +78,8 @@
78
78
  - `mobilePlatform=ANDROID`:必须同时传 `appPackage` 和 `appSign`。
79
79
  - `mobilePlatform=ALL`:必须同时传 `bundleId`、`appPackage` 和 `appSign`。
80
80
  - 用户说“bundle_id”时,映射到脚本参数 `--bundle-id`,MCP 请求字段为 `bundleId`。
81
+ - `appSign` 是 Android 应用签名摘要字段,只在创建 `MOBILEAPP` 时作为 `createApplication` 入参采集;它不是应用公钥、支付签名串或私钥。
82
+ - 采集 `appSign` 时只要求用户提供其 Android 应用对应的签名摘要值;不得引导用户通过支付宝开放平台官方密钥生成工具、签名工具或格式转换工具获取,也不得把阶段 4 的应用公钥生成引导套用到 `appSign`。
81
83
  - 不确定字段值时不要编造,也不要用包名、签名摘要或 bundleId 的占位符调用 `createApplication`。
82
84
 
83
85
  ---
@@ -275,6 +277,8 @@ APP支付缺少平台或对应字段时,先向用户补问,禁止调用 `cre
275
277
  | 创建成功 | 获取 appId,进入阶段 4 设置应用公钥 |
276
278
  | 创建失败 | 返回错误,停止后续流程 |
277
279
 
280
+ 如果 `createApplication` 返回 `APP_MAX_ERROR` / `应用数量达到上限`,不要继续调整 `appSign`、`bundleId`、`mobilePlatform` 等创建参数,也不要重复新建。应明确告知用户当前主体应用数量已达到上限,优先返回阶段 2 复用已有 `ON_LINE` 应用;如必须新建,引导用户前往支付宝开放平台处理应用配额或联系支付宝技术支持。
281
+
278
282
  ---
279
283
 
280
284
  ## 阶段 4: 设置应用公钥
@@ -150,11 +150,15 @@
150
150
  }
151
151
  ```
152
152
 
153
+ > ⚠️ 有些 MCP 返回会把真实业务响应包在 `data.response` 或 `errorContext.errorStack[]` 下。错误检测必须优先定位业务错误对象:既要兼容 `errorCode: "xxx"`,也要兼容 `errorCode: { "code": "xxx", "desc": "...", "message": "...", "errorScene": "...", "errorSpecific": "..." }`。错误信息应从同一错误对象提取 `errorMessage` / `errorMsg` / `errorCode.desc` / `errorCode.message` / `bizTips` / `errorScene` / `errorSpecific`,不得只读取顶层字段后兜底为“未知错误”。
154
+
153
155
  ### 处理原则
154
156
  ```
155
157
  ✅ errorCode 存在 → 展示 errorMessage
156
158
  ✅ 如有 bizTips → 一并展示给用户
157
159
  ✅ 如有 checkedError → 逐条展示字段级校验错误描述
160
+ ✅ 如有 errorScene / errorSpecific → 一并展示,便于技术支持定位
161
+ ✅ createApplication 返回 APP_MAX_ERROR → 告知用户应用数量达到上限,引导复用已有上线应用或前往支付宝开放平台处理配额
158
162
  ✅ needRetry=true → 告知用户可以重试
159
163
  ❌ 禁止:忽略 errorCode 继续执行
160
164
  ❌ 禁止:向用户暴露技术性内部错误
@@ -211,8 +215,8 @@ SUCCESS=$(echo "$BUSINESS" | jq -r '.success // false')
211
215
  │ 动作:退出登录 → 重新授权 │
212
216
  ├─────────────────────────────────────────────────────────────────┤
213
217
  │ 📋 BUSINESS_ERROR │
214
- │ 关键词:errorCode 字段存在 │
215
- │ 动作:展示错误信息 + bizTips + checkedError │
218
+ │ 关键词:errorCode 字段存在(含 data.response 或 errorStack) │
219
+ │ 动作:展示错误信息 + bizTips + checkedError + errorScene │
216
220
  ├─────────────────────────────────────────────────────────────────┤
217
221
  │ ⚠️ CLI_ERROR │
218
222
  │ 关键词:success=false 但无 errorCode │
@@ -47,6 +47,136 @@ unwrap_mcp() {
47
47
  fi
48
48
  }
49
49
 
50
+ # ─── JSON 错误字段提取 ─────────────────────────────────────────────────────
51
+ # 部分 MCP 后端会把真实业务响应包在 data/response 或 errorContext.errorStack 下。
52
+ # 下面的提取函数优先定位同一个错误对象,避免 success=false 时丢失真实错误体。
53
+ extract_first_named_field() {
54
+ local JSON="$1"
55
+ local FIELD="$2"
56
+ echo "$JSON" | jq -r --arg field "$FIELD" '
57
+ [.. | objects | .[$field]? | select(. != null and (tostring != ""))][0] // ""
58
+ ' 2>/dev/null
59
+ }
60
+
61
+ extract_error_object() {
62
+ echo "$1" | jq -c '
63
+ [
64
+ .. | objects |
65
+ select(
66
+ ((.errorCode? | type) == "string" and .errorCode != "") or
67
+ ((.errorCode? | type) == "object" and ((.errorCode.code? // "") != ""))
68
+ )
69
+ ][0] // empty
70
+ ' 2>/dev/null
71
+ }
72
+
73
+ extract_error_code() {
74
+ local ERROR_OBJECT
75
+ ERROR_OBJECT=$(extract_error_object "$1")
76
+ if [ -n "$ERROR_OBJECT" ]; then
77
+ echo "$ERROR_OBJECT" | jq -r '
78
+ if (.errorCode | type) == "string" then .errorCode
79
+ elif (.errorCode | type) == "object" then (.errorCode.code // "")
80
+ else ""
81
+ end
82
+ ' 2>/dev/null
83
+ return
84
+ fi
85
+
86
+ echo ""
87
+ }
88
+
89
+ extract_error_message() {
90
+ local JSON="$1"
91
+ local ERROR_OBJECT
92
+ ERROR_OBJECT=$(extract_error_object "$JSON")
93
+ if [ -n "$ERROR_OBJECT" ]; then
94
+ echo "$ERROR_OBJECT" | jq -r '
95
+ .errorMessage //
96
+ .errorMsg //
97
+ .error.message //
98
+ .message //
99
+ (if (.errorCode | type) == "object" then (.errorCode.desc // .errorCode.message) else empty end) //
100
+ "未知错误"
101
+ ' 2>/dev/null
102
+ return
103
+ fi
104
+
105
+ echo "$JSON" | jq -r '
106
+ [
107
+ .. | objects |
108
+ (.errorMessage?, .errorMsg?, .message?, (.error? | objects | .message?)) |
109
+ select(. != null and (tostring != ""))
110
+ ][0] // "未知错误"
111
+ ' 2>/dev/null
112
+ }
113
+
114
+ extract_biz_tips() {
115
+ local ERROR_OBJECT
116
+ ERROR_OBJECT=$(extract_error_object "$1")
117
+ if [ -n "$ERROR_OBJECT" ]; then
118
+ echo "$ERROR_OBJECT" | jq -r '.bizTips // ""' 2>/dev/null
119
+ return
120
+ fi
121
+
122
+ extract_first_named_field "$1" "bizTips"
123
+ }
124
+
125
+ extract_need_retry() {
126
+ local JSON="$1"
127
+ local ERROR_OBJECT
128
+ ERROR_OBJECT=$(extract_error_object "$JSON")
129
+ if [ -n "$ERROR_OBJECT" ]; then
130
+ echo "$ERROR_OBJECT" | jq -r 'if .needRetry == true then "true" else "false" end' 2>/dev/null
131
+ return
132
+ fi
133
+
134
+ echo "$JSON" | jq -r '
135
+ if any(.. | objects; .needRetry? == true) then "true" else "false" end
136
+ ' 2>/dev/null
137
+ }
138
+
139
+ extract_error_object_field() {
140
+ local JSON="$1"
141
+ local FIELD="$2"
142
+ local ERROR_OBJECT
143
+ ERROR_OBJECT=$(extract_error_object "$JSON")
144
+ if [ -n "$ERROR_OBJECT" ]; then
145
+ echo "$ERROR_OBJECT" | jq -r --arg field "$FIELD" '
146
+ .[$field] //
147
+ (if (.errorCode | type) == "object" then (.errorCode[$field] // "") else "" end)
148
+ ' 2>/dev/null
149
+ return
150
+ fi
151
+
152
+ extract_first_named_field "$JSON" "$FIELD"
153
+ }
154
+
155
+ extract_success_state() {
156
+ local JSON="$1"
157
+ echo "$JSON" | jq -r '
158
+ if any(.. | objects; .success? == false) then "false"
159
+ elif any(.. | objects; .success? == true) then "true"
160
+ else "null"
161
+ end
162
+ ' 2>/dev/null
163
+ }
164
+
165
+ extract_checked_errors() {
166
+ local JSON="$1"
167
+ echo "$JSON" | jq -c '
168
+ [
169
+ .. | objects |
170
+ (
171
+ .checkedError?,
172
+ (.resultObj? | objects | .checkedError?),
173
+ (.data? | objects | .checkedError?)
174
+ ) |
175
+ select(. != null)
176
+ ][0] // empty
177
+ ' 2>/dev/null
178
+ }
179
+
50
180
  # ─── 统一错误检测 ──────────────────────────────────────────────────────────
51
181
  # 参数: $1 - CLI 执行结果文本(原始输出,含信封或不含)
52
182
  # 返回: MCP_AUTH_ERROR | MCP_SERVICE_ERROR | AUTH_MISMATCH | SERVICE_UNSTABLE | ERROR:xxx | CLI_ERROR:xxx | SUCCESS
@@ -94,18 +224,17 @@ detect_error() {
94
224
  fi
95
225
 
96
226
  # 5. 通用业务错误(从解包后的 JSON 中提取 errorCode)
97
- local ERROR_CODE=$(echo "$UNWRAPPED" | jq -r '.errorCode // .data.errorCode // ""' 2>/dev/null)
227
+ local ERROR_CODE=$(extract_error_code "$UNWRAPPED")
98
228
  if [ -n "$ERROR_CODE" ] && [ "$ERROR_CODE" != "null" ]; then
99
229
  echo "ERROR:$ERROR_CODE"
100
230
  return
101
231
  fi
102
232
 
103
233
  # 6. CLI 命令本身的错误(success: false,从解包后的 JSON 提取)
104
- # 注意: jq 的 // 对 false 视为 falsy 会触发 alternative,所以不能写 .success // "true"
105
- # 必须先将 false 转为字符串再判断
106
- local SUCCESS=$(echo "$UNWRAPPED" | jq -r 'if .success == false then "false" elif .success == true then "true" else "null" end' 2>/dev/null)
234
+ # 注意: MCP 业务响应可能嵌套在 data/response 下,因此递归检测 success=false。
235
+ local SUCCESS=$(extract_success_state "$UNWRAPPED")
107
236
  if [ "$SUCCESS" = "false" ]; then
108
- local ERROR_MSG=$(echo "$UNWRAPPED" | jq -r '.error.message // .errorMessage // .message // "未知错误"' 2>/dev/null)
237
+ local ERROR_MSG=$(extract_error_message "$UNWRAPPED")
109
238
  echo "CLI_ERROR:$ERROR_MSG"
110
239
  return
111
240
  fi
@@ -214,14 +343,20 @@ handle_backend_error() {
214
343
  echo "📋 错误码:$ERROR_CODE"
215
344
 
216
345
  # 提取错误信息
217
- local ERROR_MSG=$(echo "$UNWRAPPED" | jq -r '.errorMessage // .data.errorMessage // .error.message // .message // "未知错误"' 2>/dev/null)
218
- local BIZ_TIPS=$(echo "$UNWRAPPED" | jq -r '.bizTips // .data.bizTips // ""' 2>/dev/null)
219
- local NEED_RETRY=$(echo "$UNWRAPPED" | jq -r '.needRetry // .data.needRetry // "false"' 2>/dev/null)
346
+ local ERROR_MSG=$(extract_error_message "$UNWRAPPED")
347
+ local BIZ_TIPS=$(extract_biz_tips "$UNWRAPPED")
348
+ local NEED_RETRY=$(extract_need_retry "$UNWRAPPED")
349
+ local ERROR_SCENE=$(extract_error_object_field "$UNWRAPPED" "errorScene")
350
+ local ERROR_SPECIFIC=$(extract_error_object_field "$UNWRAPPED" "errorSpecific")
220
351
 
221
352
  echo "📋 错误信息:$ERROR_MSG"
222
353
 
354
+ if [ -n "$ERROR_SCENE" ] || [ -n "$ERROR_SPECIFIC" ]; then
355
+ echo "📋 错误场景:${ERROR_SCENE:-未知} / ${ERROR_SPECIFIC:-未知}"
356
+ fi
357
+
223
358
  # 透出 checkedError 中的详细错误描述(如 ar-sign.apply 返回的字段级校验错误)
224
- local CHECKED_ERRORS=$(echo "$UNWRAPPED" | jq -r '.resultObj.checkedError // .data.checkedError // empty' 2>/dev/null)
359
+ local CHECKED_ERRORS=$(extract_checked_errors "$UNWRAPPED")
225
360
  if [ -n "$CHECKED_ERRORS" ] && [ "$CHECKED_ERRORS" != "null" ] && [ "$CHECKED_ERRORS" != "[]" ]; then
226
361
  echo ""
227
362
  echo "📋 详细校验错误:"
@@ -235,6 +370,11 @@ handle_backend_error() {
235
370
  echo "💡 提示:$BIZ_TIPS"
236
371
  fi
237
372
 
373
+ if [ "$ERROR_CODE" = "APP_MAX_ERROR" ]; then
374
+ echo ""
375
+ echo "📌 处理建议:当前主体下应用数量已达到上限,无法继续新建应用。请优先复用已有上线应用;如必须新建,请前往支付宝开放平台处理应用配额或联系支付宝技术支持。"
376
+ fi
377
+
238
378
  if [ "$NEED_RETRY" = "true" ]; then
239
379
  echo ""
240
380
  echo "🔄 此错误可重试,请回复「重试」重新执行"
@@ -291,11 +431,11 @@ handle_error() {
291
431
  local MSG="${ERROR_TYPE#CLI_ERROR:}"
292
432
  echo "❌ 命令执行失败:$MSG"
293
433
  # CLI_ERROR 也透出 bizTips 和 checkedError(如果有的话)
294
- local CLI_BIZ_TIPS=$(echo "$_UNWRAPPED" | jq -r '.bizTips // .data.bizTips // ""' 2>/dev/null)
434
+ local CLI_BIZ_TIPS=$(extract_biz_tips "$_UNWRAPPED")
295
435
  if [ -n "$CLI_BIZ_TIPS" ] && [ "$CLI_BIZ_TIPS" != "null" ] && [ "$CLI_BIZ_TIPS" != "" ]; then
296
436
  echo "💡 $CLI_BIZ_TIPS"
297
437
  fi
298
- local CLI_CHECKED=$(echo "$_UNWRAPPED" | jq -r '.resultObj.checkedError // .data.checkedError // empty' 2>/dev/null)
438
+ local CLI_CHECKED=$(extract_checked_errors "$_UNWRAPPED")
299
439
  if [ -n "$CLI_CHECKED" ] && [ "$CLI_CHECKED" != "null" ] && [ "$CLI_CHECKED" != "[]" ]; then
300
440
  echo "📋 详细校验错误:"
301
441
  echo "$CLI_CHECKED" | jq -r '.[]? | .errorDesc // .message // . // empty' 2>/dev/null | while IFS= read -r line; do