@alipay/alipay-aipay 1.0.2 → 1.2.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.
Files changed (67) hide show
  1. package/dist/commands/install.d.ts.map +1 -1
  2. package/dist/commands/install.js +71 -22
  3. package/dist/commands/install.js.map +1 -1
  4. package/dist/commands/prepare-tools.d.ts.map +1 -1
  5. package/dist/commands/prepare-tools.js +16 -4
  6. package/dist/commands/prepare-tools.js.map +1 -1
  7. package/dist/commands/update.d.ts.map +1 -1
  8. package/dist/commands/update.js +9 -9
  9. package/dist/commands/update.js.map +1 -1
  10. package/dist/index.js +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/utils/alipay-cli.d.ts +6 -0
  13. package/dist/utils/alipay-cli.d.ts.map +1 -1
  14. package/dist/utils/alipay-cli.js +38 -22
  15. package/dist/utils/alipay-cli.js.map +1 -1
  16. package/dist/utils/file.d.ts +9 -2
  17. package/dist/utils/file.d.ts.map +1 -1
  18. package/dist/utils/file.js +33 -3
  19. package/dist/utils/file.js.map +1 -1
  20. package/dist/utils/gpg.d.ts.map +1 -1
  21. package/dist/utils/gpg.js +97 -53
  22. package/dist/utils/gpg.js.map +1 -1
  23. package/dist/utils/jq.d.ts.map +1 -1
  24. package/dist/utils/jq.js +6 -9
  25. package/dist/utils/jq.js.map +1 -1
  26. package/dist/utils/npm.d.ts.map +1 -1
  27. package/dist/utils/npm.js +2 -6
  28. package/dist/utils/npm.js.map +1 -1
  29. package/dist/utils/shell.d.ts +15 -2
  30. package/dist/utils/shell.d.ts.map +1 -1
  31. package/dist/utils/shell.js +216 -12
  32. package/dist/utils/shell.js.map +1 -1
  33. package/package.json +3 -4
  34. package/skill/SKILL.md +77 -11
  35. package/skill/VERSION +1 -0
  36. package/skill/references/integration/flow.md +154 -136
  37. package/skill/references/integration/modules/alipay-sdk-reminder.md +29 -13
  38. package/skill/references/integration/modules/checklist.md +49 -19
  39. package/skill/references/integration/modules/code-examples/csharp/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +3 -3
  40. package/skill/references/integration/modules/code-examples/csharp/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.cs +27 -13
  41. package/skill/references/integration/modules/code-examples/java/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +3 -3
  42. package/skill/references/integration/modules/code-examples/java/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemoController.java +31 -12
  43. package/skill/references/integration/modules/code-examples/nodejs/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +5 -4
  44. package/skill/references/integration/modules/code-examples/nodejs/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.js +69 -37
  45. package/skill/references/integration/modules/code-examples/php/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +3 -3
  46. package/skill/references/integration/modules/code-examples/php/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.php +38 -17
  47. package/skill/references/integration/modules/code-examples/python/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +3 -3
  48. package/skill/references/integration/modules/code-examples/python/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.py +31 -18
  49. package/skill/references/integration/modules/interface-guide.md +7 -3
  50. package/skill/references/integration/modules/sandbox/a2m-sandbox-test.md +77 -48
  51. package/skill/references/integration/modules/sandbox/alipay-sandbox-tool.md +8 -5
  52. package/skill/references/integration/modules/sandbox/sandbox-setup-guide.md +157 -117
  53. package/skill/references/integration/modules/scripts/local_402_sandbox_pay.py +35 -4
  54. package/skill/references/normal/alipay-cli-env.md +13 -11
  55. package/skill/references/normal/self-update.md +59 -0
  56. package/skill/references/onboarding/flow.md +202 -282
  57. package/skill/references/onboarding/modules/app-release.md +91 -35
  58. package/skill/references/onboarding/modules/authorization.md +49 -22
  59. package/skill/references/onboarding/modules/error-handling.md +19 -14
  60. package/skill/references/onboarding/modules/product-sign.md +23 -20
  61. package/skill/references/onboarding/modules/scripts/app.sh +139 -40
  62. package/skill/references/onboarding/modules/scripts/auth.sh +183 -101
  63. package/skill/references/onboarding/modules/scripts/error_handler.sh +148 -18
  64. package/skill/references/onboarding/modules/scripts/query_sign_status.sh +17 -11
  65. package/skill/references/onboarding/modules/scripts/service.sh +208 -37
  66. package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +1 -1
  67. package/skill/references/onboarding/modules/service-registration.md +72 -29
@@ -9,9 +9,9 @@
9
9
  # auth confirm [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>]
10
10
  # auth mismatch [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>]
11
11
  # 返回值:
12
- # init: AUTH_FLOW:SKIP | AUTH_FLOW:READY
12
+ # init: AUTH_FLOW:SKIP | AUTH_FLOW:READY | AUTH_FLOW:SCOPE_MISMATCH | AUTH_FLOW:MCC_MISMATCH | AUTH_FLOW:FAILED
13
13
  # confirm: AUTH_FLOW:AUTH_SUCCESS | AUTH_FLOW:PENDING | AUTH_FLOW:EXPIRED | AUTH_FLOW:SCOPE_MISMATCH | AUTH_FLOW:MCC_MISMATCH | AUTH_FLOW:FAILED
14
- # mismatch: 执行 logout → 调用 auth init 重新生成授权链接(不输出 FLOW 标记,由 auth init 输出 AUTH_FLOW:READY)
14
+ # mismatch: 执行一次 logout 后调用 auth init 重新生成授权链接(不输出 FLOW 标记,由 auth init 输出 AUTH_FLOW:READY)
15
15
  #=============================================================================
16
16
 
17
17
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -194,6 +194,123 @@ run_json_command() {
194
194
  return 1
195
195
  }
196
196
 
197
+ # 校验当前登录会话是否覆盖目标产品 scope 和 MCC。
198
+ # 可复用调用方已经取得的 whoami JSON,避免重复查询。
199
+ validate_current_authorization() {
200
+ local whoami_result="${1:-}"
201
+ local logged_in is_expired granted_scope required_scope
202
+ local scope_match mcc_verify_result mcc_verify_unwrapped mcc_match
203
+ local mcc_error_type mcc_error_code mcc_success
204
+ local granted_scope_item
205
+ local -a granted_scope_items
206
+
207
+ if [ -z "$SALES_CODE" ] || [ -z "$MCC_CODE" ]; then
208
+ echo "❌ 缺少授权校验参数"
209
+ echo "AUTH_FLOW:FAILED"
210
+ return 1
211
+ fi
212
+
213
+ if [ -z "$whoami_result" ]; then
214
+ whoami_result=$(run_json_command "alipay-cli whoami --json" alipay-cli whoami --json) || {
215
+ echo "AUTH_FLOW:FAILED"
216
+ return 1
217
+ }
218
+ fi
219
+
220
+ if ! handle_error "$whoami_result"; then
221
+ echo "AUTH_FLOW:FAILED"
222
+ return 1
223
+ fi
224
+
225
+ logged_in=$(echo "$whoami_result" | jq -r '.data.logged_in // false')
226
+ is_expired=$(echo "$whoami_result" | jq -r '.data.is_expired // false')
227
+ if [ "$logged_in" != "true" ] || [ "$is_expired" = "true" ]; then
228
+ echo "❌ 当前登录状态无效或已过期"
229
+ echo "AUTH_FLOW:FAILED"
230
+ return 1
231
+ fi
232
+
233
+ granted_scope=$(echo "$whoami_result" | jq -r '.data.scope // empty')
234
+ case "$SALES_CODE" in
235
+ "I1080300001000041203") required_scope="fast_instant_trade_pay:write" ;;
236
+ "I1080300001000041313") required_scope="auth_alipay_apppay:write" ;;
237
+ "I1080300001000160457") required_scope="machine_pay:write" ;;
238
+ *)
239
+ echo "❌ 未知产品码: $SALES_CODE"
240
+ echo "AUTH_FLOW:FAILED"
241
+ return 1
242
+ ;;
243
+ esac
244
+
245
+ scope_match="false"
246
+ IFS=',' read -r -a granted_scope_items <<< "$granted_scope"
247
+ for granted_scope_item in "${granted_scope_items[@]}"; do
248
+ granted_scope_item="${granted_scope_item#"${granted_scope_item%%[![:space:]]*}"}"
249
+ granted_scope_item="${granted_scope_item%"${granted_scope_item##*[![:space:]]}"}"
250
+ if [ "$granted_scope_item" = "$required_scope" ]; then
251
+ scope_match="true"
252
+ break
253
+ fi
254
+ done
255
+
256
+ mcc_match="true"
257
+ if [ "$scope_match" = "true" ]; then
258
+ mcc_verify_result=$(run_json_command "alipay-cli mcp call ar-query.queryArInfosBySalesProd" \
259
+ alipay-cli mcp call ar-query.queryArInfosBySalesProd \
260
+ -d "{\"request\":{\"salesProductCodes\":[\"${SALES_CODE}\"]},\"ctx\":{}}" \
261
+ --json) || {
262
+ echo "AUTH_FLOW:FAILED"
263
+ return 1
264
+ }
265
+
266
+ mcc_verify_unwrapped=$(unwrap_mcp "$mcc_verify_result")
267
+ mcc_error_type=$(detect_error "$mcc_verify_result")
268
+ if [ "$mcc_error_type" = "AUTH_MISMATCH" ]; then
269
+ mcc_match="false"
270
+ elif [ "$mcc_error_type" != "SUCCESS" ]; then
271
+ echo "❌ MCC 校验失败"
272
+ echo "AUTH_FLOW:FAILED"
273
+ return 1
274
+ fi
275
+
276
+ # 保留对非标准错误响应的兼容,只在后端明确返回 MCC 未授权时判定不匹配。
277
+ mcc_error_code=$(echo "$mcc_verify_unwrapped" | jq -r '.errorCode // .data.errorCode // ""' 2>/dev/null)
278
+ mcc_success=$(echo "$mcc_verify_unwrapped" | jq -r '.success // "null"' 2>/dev/null)
279
+ if [ "$mcc_success" = "false" ] && [ -n "$mcc_error_code" ] && \
280
+ echo "$mcc_verify_unwrapped" | jq -r '.errorMessage // .data.errorMessage // .error.message // ""' 2>/dev/null | grep -qi "mccCode.*is not auth"; then
281
+ mcc_match="false"
282
+ elif echo "$mcc_verify_unwrapped" | grep -qi "mccCode.*is not auth"; then
283
+ mcc_match="false"
284
+ fi
285
+ fi
286
+
287
+ if [ "$scope_match" = "true" ] && [ "$mcc_match" = "true" ]; then
288
+ echo "✅ 授权范围校验通过(scope + MCC)"
289
+ return 0
290
+ fi
291
+
292
+ echo ""
293
+ echo "⚠️ 授权范围不满足"
294
+ if [ "$scope_match" != "true" ]; then
295
+ echo "📋 原因:scope 权限不满足"
296
+ echo "🤖 需要权限: $required_scope"
297
+ echo "🤖 已授权 scope: $granted_scope"
298
+ fi
299
+ if [ "$mcc_match" != "true" ]; then
300
+ echo "📋 原因:经营类目未授权"
301
+ echo "🤖 当前类目: $MCC_CODE"
302
+ fi
303
+
304
+ echo ""
305
+ echo "📋 必须通过 auth.sh mismatch 退出当前登录并重新授权"
306
+ if [ "$scope_match" != "true" ]; then
307
+ echo "AUTH_FLOW:SCOPE_MISMATCH"
308
+ else
309
+ echo "AUTH_FLOW:MCC_MISMATCH"
310
+ fi
311
+ return 1
312
+ }
313
+
197
314
  # ─── auth init: 检查登录状态 + 执行登录 + 构建授权链接 + 输出授权信息 ────────
198
315
  auth_init() {
199
316
  SCOPE=""
@@ -230,26 +347,42 @@ auth_init() {
230
347
  fi
231
348
 
232
349
  # Step 1: 检查登录状态
233
- CHECK_RESULT=$(run_json_command "alipay-cli whoami --json" alipay-cli whoami --json) || exit 1
350
+ CHECK_RESULT=$(run_json_command "alipay-cli whoami --json" alipay-cli whoami --json) || {
351
+ echo "AUTH_FLOW:FAILED"
352
+ return 1
353
+ }
354
+ if ! handle_error "$CHECK_RESULT"; then
355
+ echo "AUTH_FLOW:FAILED"
356
+ return 1
357
+ fi
234
358
  LOGGED_IN=$(echo "$CHECK_RESULT" | jq -r '.data.logged_in // false')
235
359
  IS_EXPIRED=$(echo "$CHECK_RESULT" | jq -r '.data.is_expired // false')
236
360
 
237
361
  if [ "$LOGGED_IN" = "true" ] && [ "$IS_EXPIRED" = "false" ]; then
362
+ validate_current_authorization "$CHECK_RESULT" || return 1
238
363
  DEVICE_CODE=""
239
364
  save_auth_state
240
- echo "✅ 已处于登录状态,可继续后续流程"
365
+ echo "✅ 当前登录及授权范围有效,可继续后续流程"
241
366
  echo "AUTH_FLOW:SKIP"
242
367
  return 0
243
368
  fi
244
369
 
245
370
  # Step 2: 执行登录
246
- LOGIN_RESULT=$(run_json_command "alipay-cli login --non-interactive --scope" alipay-cli login --non-interactive --scope "$SCOPE" --json) || exit 1
371
+ LOGIN_RESULT=$(run_json_command "alipay-cli login --non-interactive --scope" alipay-cli login --non-interactive --scope "$SCOPE" --json) || {
372
+ echo "AUTH_FLOW:FAILED"
373
+ return 1
374
+ }
375
+ if ! handle_error "$LOGIN_RESULT"; then
376
+ echo "AUTH_FLOW:FAILED"
377
+ return 1
378
+ fi
247
379
  LOGIN_STATUS=$(echo "$LOGIN_RESULT" | jq -r '.data.status // ""')
248
380
 
249
381
  if [ "$LOGIN_STATUS" = "already_logged_in" ]; then
382
+ validate_current_authorization || return 1
250
383
  DEVICE_CODE=""
251
384
  save_auth_state
252
- echo "✅ 已处于登录状态,可继续后续流程"
385
+ echo "✅ 当前登录及授权范围有效,可继续后续流程"
253
386
  echo "AUTH_FLOW:SKIP"
254
387
  return 0
255
388
  fi
@@ -269,8 +402,21 @@ auth_init() {
269
402
  exit 1
270
403
  fi
271
404
 
272
- # Step 5: 构建授权链接(禁止使用 CLI 返回的 verification_url)
273
- BROWSER_URL="https://aipay.alipay.com/cli-auth?deviceCode=${DEVICE_CODE}&productCode=${SALES_CODE}&mccCode=${MCC_CODE}&platform=${DEV_TOOL_NAME}"
405
+ # Step 5: 构建并校验授权链接(禁止使用 CLI 返回的 verification_url)
406
+ BROWSER_URL="https://aipay.alipay.com/cli-auth?deviceCode=${DEVICE_CODE}&productCode=${SALES_CODE}&mccCode=${MCC_CODE}"
407
+ if [ -n "${DEV_TOOL_NAME:-}" ] && [ "$DEV_TOOL_NAME" != "unknown" ]; then
408
+ AUTH_PLATFORM=$(jq -nr --arg value "$DEV_TOOL_NAME" '$value|@uri')
409
+ BROWSER_URL="${BROWSER_URL}&platform=${AUTH_PLATFORM}"
410
+ fi
411
+ if [[ "$BROWSER_URL" != https://aipay.alipay.com/cli-auth\?* ]] ||
412
+ [[ "$BROWSER_URL" != *"deviceCode=${DEVICE_CODE}"* ]] ||
413
+ [[ "$BROWSER_URL" != *"productCode=${SALES_CODE}"* ]] ||
414
+ [[ "$BROWSER_URL" != *"mccCode=${MCC_CODE}"* ]] ||
415
+ [[ "$BROWSER_URL" == *"opengw.alipay.com/oauth/device"* ]]; then
416
+ echo "❌ 授权链接构建失败,缺少必要参数或误用了 CLI verification_url,已停止。"
417
+ echo "📋 正确格式: https://aipay.alipay.com/cli-auth?deviceCode=...&productCode=...&mccCode=..."
418
+ exit 1
419
+ fi
274
420
 
275
421
  # Step 6: 保存参数到状态文件(供 confirm/mismatch 使用)
276
422
  save_auth_state
@@ -304,7 +450,8 @@ auth_init() {
304
450
  echo ""
305
451
  echo "⚠️ 安全提示:请核对授权页面显示的确认码是否与上方一致,如不一致,请勿授权,立即停止操作!"
306
452
  echo ""
307
- echo "🌐 授权链接:[点击跳转进行授权](${BROWSER_URL})(无法跳链时,请复制链接到网页浏览器打开)"
453
+ echo "提示:无法跳链时,请复制下方链接到网页浏览器打开。"
454
+ echo "🌐 授权链接:[点击跳转进行授权](${BROWSER_URL})"
308
455
  echo ""
309
456
  echo "请在完成授权后告诉我\"好了\"继续后续流程。"
310
457
  echo ""
@@ -349,103 +496,29 @@ auth_confirm() {
349
496
  echo "AUTH_FLOW:EXPIRED"
350
497
  return 0
351
498
  else
352
- echo "❌ 授权确认失败"
353
- echo "$LOGIN_COMPLETE_RESULT" | jq -r '.data.error.message // .error.message // "未知错误"' 2>/dev/null
499
+ if handle_error "$LOGIN_COMPLETE_RESULT"; then
500
+ echo "❌ 授权确认失败:返回结果未包含可识别的完成状态"
501
+ fi
354
502
  cleanup_auth_state
355
503
  echo "AUTH_FLOW:FAILED"
356
504
  return 1
357
505
  fi
358
506
 
359
- # Step 3: Scope 权限校验
507
+ # Step 3: 复用 init 的 scope + MCC 校验
360
508
  WHOAMI_RESULT=$(run_json_command "alipay-cli whoami --json" alipay-cli whoami --json) || {
361
509
  echo "AUTH_FLOW:FAILED"
362
510
  return 1
363
511
  }
364
- GRANTED_SCOPE=$(echo "$WHOAMI_RESULT" | jq -r '.data.scope // empty')
365
-
366
- case "$SALES_CODE" in
367
- "I1080300001000041203") REQUIRED_SCOPE="fast_instant_trade_pay:write" ;;
368
- "I1080300001000041313") REQUIRED_SCOPE="auth_alipay_apppay:write" ;;
369
- "I1080300001000160457") REQUIRED_SCOPE="machine_pay:write" ;;
370
- *) echo "❌ 未知产品码: $SALES_CODE"; return 1 ;;
371
- esac
372
-
373
- SCOPE_MATCH="false"
374
- if echo "$GRANTED_SCOPE" | grep -Fq "$REQUIRED_SCOPE"; then
375
- SCOPE_MATCH="true"
376
- fi
377
-
378
- # Step 4: MCC 一致性校验
379
- MCC_VERIFY_RESULT=$(run_json_command "alipay-cli mcp call ar-query.queryArInfosBySalesProd" \
380
- alipay-cli mcp call ar-query.queryArInfosBySalesProd \
381
- -d "{\"request\":{\"salesProductCodes\":[\"${SALES_CODE}\"]},\"ctx\":{}}" \
382
- --json) || {
383
- echo "AUTH_FLOW:FAILED"
384
- return 1
385
- }
386
-
387
- # 先解包 MCP 信封再精确解析
388
- MCC_VERIFY_UNWRAPPED=$(unwrap_mcp "$MCC_VERIFY_RESULT")
389
-
390
- MCC_MATCH="true"
391
- MCC_ERROR_TYPE=$(detect_error "$MCC_VERIFY_RESULT")
392
- if [ "$MCC_ERROR_TYPE" = "AUTH_MISMATCH" ]; then
393
- MCC_MATCH="false"
394
- elif [ "$MCC_ERROR_TYPE" != "SUCCESS" ]; then
395
- echo "❌ MCC 校验失败"
396
- echo "AUTH_FLOW:FAILED"
397
- return 1
398
- fi
399
-
400
- # 精确解析 errorCode 字段判断 MCC 授权异常,避免 grep 全文匹配误判
401
- MCC_ERROR_CODE=$(echo "$MCC_VERIFY_UNWRAPPED" | jq -r '.errorCode // .data.errorCode // ""' 2>/dev/null)
402
- MCC_SUCCESS=$(echo "$MCC_VERIFY_UNWRAPPED" | jq -r '.success // "null"' 2>/dev/null)
403
- # 优先路径:success=false 且 errorCode 存在,且 errorMessage 精确包含 mccCode is not auth
404
- # 这条路径确保仅在后端明确返回授权类错误时才判定为 MCC 不匹配,避免正常数据误触发
405
- if [ "$MCC_SUCCESS" = "false" ] && [ -n "$MCC_ERROR_CODE" ] && echo "$MCC_VERIFY_UNWRAPPED" | jq -r '.errorMessage // .data.errorMessage // .error.message // ""' 2>/dev/null | grep -qi "mccCode.*is not auth"; then
406
- MCC_MATCH="false"
407
- # 兜底路径:解包后的全文包含 "mccCode is not auth"(覆盖非 JSON 格式的错误响应)
408
- elif echo "$MCC_VERIFY_UNWRAPPED" | grep -qi "mccCode.*is not auth"; then
409
- MCC_MATCH="false"
410
- fi
411
-
412
- # Step 5: 判断校验结果
413
- if [ "$SCOPE_MATCH" = "true" ] && [ "$MCC_MATCH" = "true" ]; then
414
- echo "✅ 授权范围校验通过(scope + MCC)"
415
- cleanup_auth_state
416
- echo "AUTH_FLOW:AUTH_SUCCESS"
417
- return 0
418
- fi
419
-
420
- # Step 6: 校验不通过,输出原因
421
- echo ""
422
- echo "⚠️ 授权范围不满足,正在退出登录..."
423
-
424
- if [ "$SCOPE_MATCH" != "true" ]; then
425
- echo "📋 原因:scope 权限不满足"
426
- echo "🤖 需要权限: $REQUIRED_SCOPE"
427
- echo "🤖 已授权 scope: $GRANTED_SCOPE"
428
- fi
429
- if [ "$MCC_MATCH" != "true" ]; then
430
- echo "📋 原因:经营类目未授权"
431
- echo "🤖 当前类目: $MCC_CODE"
432
- fi
433
-
434
- LOGOUT_RESULT=$(export PLATFORM=${DEV_TOOL_NAME} && alipay-cli logout --json 2>/dev/null)
435
- echo "✅ 已退出当前登录"
436
- echo ""
437
- echo "📋 需要重新执行授权流程"
438
-
439
- if [ "$SCOPE_MATCH" != "true" ]; then
440
- echo "AUTH_FLOW:SCOPE_MISMATCH"
441
- else
442
- echo "AUTH_FLOW:MCC_MISMATCH"
443
- fi
444
- return 1
512
+ validate_current_authorization "$WHOAMI_RESULT" || return 1
513
+ cleanup_auth_state
514
+ echo "AUTH_FLOW:AUTH_SUCCESS"
515
+ return 0
445
516
  }
446
517
 
447
- # ─── auth mismatch: 授权范围不满足 → logout + 重新授权 ───────────────────────
518
+ # ─── auth mismatch: 授权范围不满足 → logout → 重新授权 ──────────────────────
448
519
  auth_mismatch() {
520
+ local logout_result logout_error_type
521
+
449
522
  # 优先使用显式传入的非敏感上下文;未传时再从状态文件兜底恢复。
450
523
  restore_auth_context_or_state "$@" || exit 1
451
524
 
@@ -454,19 +527,28 @@ auth_mismatch() {
454
527
  return 1
455
528
  fi
456
529
 
457
- echo "⚠️ 授权范围不满足,正在退出登录..."
458
-
459
- # Step 1: 退出登录
460
- LOGOUT_RESULT=$(export PLATFORM=${DEV_TOOL_NAME} && alipay-cli logout --json 2>/dev/null)
461
- echo "📋 已退出当前登录"
462
-
463
- # Step 2: 根据 salesCode 构造正确的 scope
530
+ # Step 1: 根据 salesCode 构造正确的 scope;目标无效时不退出当前登录。
464
531
  SCOPE=$(get_scope "$SALES_CODE")
465
532
  if [ -z "$SCOPE" ]; then
466
533
  echo "❌ 未知产品码: $SALES_CODE"
467
534
  return 1
468
535
  fi
469
536
 
537
+ # Step 2: mismatch 是授权不匹配恢复路径中唯一执行 logout 的入口。
538
+ echo "⚠️ 授权范围不满足,正在退出当前登录并准备重新授权..."
539
+ logout_result=$(run_json_command "alipay-cli logout --json" alipay-cli logout --json) || {
540
+ echo "❌ 退出当前登录失败,已停止重新授权"
541
+ echo "AUTH_FLOW:FAILED"
542
+ return 1
543
+ }
544
+ logout_error_type=$(detect_error "$logout_result")
545
+ if [ "$logout_error_type" != "SUCCESS" ]; then
546
+ echo "❌ 退出当前登录失败,已停止重新授权"
547
+ echo "AUTH_FLOW:FAILED"
548
+ return 1
549
+ fi
550
+ echo "✅ 已退出当前登录"
551
+
470
552
  # Step 3: 调用 auth init 执行登录 + 构建授权链接 + 输出授权信息
471
553
  # DEV_TOOL_NAME 已 export,子进程可继承
472
554
  echo "📋 请重新扫码授权,新的授权将包含正确的权限范围"
@@ -23,9 +23,87 @@ fi
23
23
  # { "content": [ { "text": "<业务JSON>", "type": "text" } ] }
24
24
  # 业务 JSON(含 success / resultObj / errorCode)被包在 content[0].text 里。
25
25
  # 本函数负责解包:若检测到信封结构,提取 content[0].text;否则原样返回。
26
+ # 当 stdout 混有日志时,只接受唯一可确定的 MCP 信封或 JSON;候选不唯一时
27
+ # 保留原始文本,让后续错误检测安全阻断,禁止猜测业务响应。
26
28
  #
27
29
  # 对于非 mcp call 的命令(如 alipay-cli login / whoami),返回不带信封,
28
30
  # 保证这些命令的原样透传。
31
+ extract_unique_json() {
32
+ local RAW="$1"
33
+ printf '%s' "$RAW" | jq -Rsc '
34
+ explode as $chars |
35
+ reduce range(0; ($chars | length)) as $index (
36
+ {
37
+ depth: 0,
38
+ start: null,
39
+ in_string: false,
40
+ escaped: false,
41
+ raw_candidates: []
42
+ };
43
+ ($chars[$index]) as $char |
44
+ if .depth == 0 then
45
+ if $char == 123 or $char == 91 then
46
+ .depth = 1 |
47
+ .start = $index |
48
+ .in_string = false |
49
+ .escaped = false
50
+ else
51
+ .
52
+ end
53
+ elif .in_string then
54
+ if .escaped then
55
+ .escaped = false
56
+ elif $char == 92 then
57
+ .escaped = true
58
+ elif $char == 34 then
59
+ .in_string = false
60
+ else
61
+ .
62
+ end
63
+ elif $char == 34 then
64
+ .in_string = true
65
+ elif $char == 123 or $char == 91 then
66
+ .depth += 1
67
+ elif $char == 125 or $char == 93 then
68
+ .depth -= 1 |
69
+ if .depth == 0 then
70
+ ($chars[.start:($index + 1)] | implode) as $candidate |
71
+ .raw_candidates += [$candidate] |
72
+ .start = null
73
+ else
74
+ .
75
+ end
76
+ else
77
+ .
78
+ end
79
+ ) |
80
+ [
81
+ .raw_candidates[] |
82
+ fromjson? |
83
+ select(type == "object" or type == "array")
84
+ ] |
85
+ unique as $candidates |
86
+ (
87
+ $candidates |
88
+ map(select(
89
+ type == "object" and
90
+ (.content | type) == "array" and
91
+ any(.content[]?; (.text? | type) == "string")
92
+ )) |
93
+ unique
94
+ ) as $envelopes |
95
+ if ($envelopes | length) == 1 then
96
+ {status: "ok", value: $envelopes[0]}
97
+ elif ($envelopes | length) == 0 and ($candidates | length) == 1 then
98
+ {status: "ok", value: $candidates[0]}
99
+ elif ($candidates | length) == 0 then
100
+ {status: "none"}
101
+ else
102
+ {status: "ambiguous"}
103
+ end
104
+ ' 2>/dev/null
105
+ }
106
+
29
107
  unwrap_mcp() {
30
108
  local RAW="$1"
31
109
  if [ -z "$RAW" ]; then
@@ -33,17 +111,30 @@ unwrap_mcp() {
33
111
  return
34
112
  fi
35
113
 
36
- if ! echo "$RAW" | jq -e . >/dev/null 2>&1; then
37
- echo "$RAW"
38
- return
39
- fi
114
+ local ANALYSIS STATUS JSON
115
+ ANALYSIS=$(extract_unique_json "$RAW")
116
+ STATUS=$(echo "$ANALYSIS" | jq -r '.status // "none"' 2>/dev/null)
117
+ case "$STATUS" in
118
+ ok)
119
+ JSON=$(echo "$ANALYSIS" | jq -c '.value' 2>/dev/null)
120
+ ;;
121
+ ambiguous)
122
+ echo "CLI 输出包含多个 JSON 候选,无法唯一解析"
123
+ echo "$RAW"
124
+ return
125
+ ;;
126
+ *)
127
+ echo "$RAW"
128
+ return
129
+ ;;
130
+ esac
40
131
 
41
132
  local TEXT
42
- TEXT=$(echo "$RAW" | jq -r 'if (.content | type == "array") and (.content[0].text != null) then .content[0].text else empty end' 2>/dev/null)
133
+ TEXT=$(echo "$JSON" | jq -r 'if (.content | type == "array") and (.content[0].text != null) then .content[0].text else empty end' 2>/dev/null)
43
134
  if [ -n "$TEXT" ]; then
44
135
  echo "$TEXT"
45
136
  else
46
- echo "$RAW"
137
+ echo "$JSON"
47
138
  fi
48
139
  }
49
140
 
@@ -103,11 +194,36 @@ extract_error_message() {
103
194
  fi
104
195
 
105
196
  echo "$JSON" | jq -r '
106
- [
107
- .. | objects |
108
- (.errorMessage?, .errorMsg?, .message?, (.error? | objects | .message?)) |
109
- select(. != null and (tostring != ""))
110
- ][0] // "未知错误"
197
+ if .data.success? == false then
198
+ [
199
+ .data.subMsg?,
200
+ .data.msg?,
201
+ .subMsg?,
202
+ .errorMessage?,
203
+ .errorMsg?,
204
+ .message?,
205
+ (.error? | objects | .message?),
206
+ .msg?,
207
+ (
208
+ .. | objects |
209
+ (.errorMessage?, .errorMsg?, .message?, (.error? | objects | .message?))
210
+ )
211
+ ] |
212
+ [
213
+ .[] |
214
+ select(
215
+ . != null and
216
+ (tostring != "") and
217
+ ((tostring | ascii_downcase) != "success")
218
+ )
219
+ ][0] // "未知错误"
220
+ else
221
+ [
222
+ .. | objects |
223
+ (.errorMessage?, .errorMsg?, .message?, (.error? | objects | .message?)) |
224
+ select(. != null and (tostring != ""))
225
+ ][0] // "未知错误"
226
+ end
111
227
  ' 2>/dev/null
112
228
  }
113
229
 
@@ -244,6 +360,8 @@ detect_error() {
244
360
 
245
361
  # ─── MCP 认证错误处理 ──────────────────────────────────────────────────────
246
362
  handle_mcp_auth_error() {
363
+ local LOGOUT_RESULT LOGOUT_EXIT_CODE LOGOUT_ERROR_TYPE LOGOUT_ERROR_MSG
364
+
247
365
  echo ""
248
366
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
249
367
  echo "🔐 未登录或授权已过期"
@@ -255,7 +373,22 @@ handle_mcp_auth_error() {
255
373
  echo "📋 说明:Authorization is empty(认证信息为空)"
256
374
  echo ""
257
375
  echo "🔄 正在退出当前登录..."
258
- export PLATFORM=${DEV_TOOL_NAME:-unknown} && alipay-cli logout --json 2>/dev/null
376
+ LOGOUT_RESULT=$(PLATFORM=${DEV_TOOL_NAME:-unknown} alipay-cli logout --json 2>&1)
377
+ LOGOUT_EXIT_CODE=$?
378
+ LOGOUT_ERROR_TYPE=$(detect_error "$LOGOUT_RESULT")
379
+
380
+ if [ "$LOGOUT_EXIT_CODE" -ne 0 ] || [ "$LOGOUT_ERROR_TYPE" != "SUCCESS" ]; then
381
+ echo ""
382
+ echo "❌ 退出当前登录失败,已停止重新授权"
383
+ if echo "$LOGOUT_RESULT" | jq -e . >/dev/null 2>&1; then
384
+ LOGOUT_ERROR_MSG=$(extract_error_message "$LOGOUT_RESULT")
385
+ echo "📋 错误信息:$LOGOUT_ERROR_MSG"
386
+ elif [ -n "$LOGOUT_RESULT" ]; then
387
+ echo "📋 错误信息:$LOGOUT_RESULT"
388
+ fi
389
+ echo "📋 请先重试退出登录;确认退出成功后再重新授权。"
390
+ return 1
391
+ fi
259
392
 
260
393
  echo ""
261
394
  echo "✅ 已退出登录状态"
@@ -265,7 +398,7 @@ handle_mcp_auth_error() {
265
398
  echo " 2. 您使用支付宝扫码授权"
266
399
  echo " 3. 授权成功后继续当前操作"
267
400
  echo ""
268
- echo "请回复「继续」开始重新授权,或「退出」结束流程。"
401
+ echo "📌 产品、MCC 和授权范围未变化时,沿用已有确认立即重新生成授权链接;发生变化时先重新确认受影响内容。"
269
402
  }
270
403
 
271
404
  # ─── MCP 服务不可用处理 ────────────────────────────────────────────────────
@@ -322,11 +455,8 @@ handle_auth_mismatch() {
322
455
 
323
456
  echo ""
324
457
  echo ""
325
-
326
- echo "🔄 正在退出当前登录..."
327
- export PLATFORM=${DEV_TOOL_NAME:-unknown} && alipay-cli logout --json 2>/dev/null
328
-
329
- echo "✅ 已退出登录,请重新执行登录授权流程"
458
+ echo "⛔ 已停止当前操作和后续调用"
459
+ echo "📋 请调用 auth.sh mismatch;该命令会统一退出当前登录并使用正确范围重新授权"
330
460
  }
331
461
 
332
462
  # ─── 后端业务错误处理(完整透出错误信息) ──────────────────────────────────
@@ -2,7 +2,7 @@
2
2
  #=============================================================================
3
3
  # 脚本名称: query_sign_status.sh
4
4
  # 功能描述: 查询签约状态并根据结果判断后续流程
5
- # 调用位置: Step 3.1 签约状态查询
5
+ # 调用位置: Step 3.1 状态与资源查询
6
6
  # 调用前置: 脚本通过 error_handler.sh 间接初始化 DEV_TOOL_NAME;需传入 --sales-code,或兼容使用 SALES_CODE 环境变量
7
7
  # 用法: bash query_sign_status.sh --sales-code <code> [--product-type aipay|webpay|apppay]
8
8
  # 返回值: 输出签约状态判断结果和处理建议
@@ -73,15 +73,15 @@ emit_signed_flow() {
73
73
  case "$SALES_CODE" in
74
74
  "I1080300001000041203")
75
75
  echo "FLOW:PC_WEB_SIGNED"
76
- # 网站支付已生效或已提交:跳过签约提交,直接进入应用发布
76
+ # 网站支付已生效或已提交:Step 4 只处理应用决策,Step 5 跳过签约提交
77
77
  ;;
78
78
  "I1080300001000041313")
79
79
  echo "FLOW:APP_SIGNED"
80
- # APP支付已生效或已提交:跳过签约提交,直接进入应用发布
80
+ # APP支付已生效或已提交:Step 4 只处理应用决策,Step 5 跳过签约提交
81
81
  ;;
82
82
  "I1080300001000160457")
83
83
  echo "FLOW:AI_PAY_SIGNED"
84
- # 按量付费已生效或已提交:跳过签约提交,直接进入服务注册 + 应用发布
84
+ # 按量付费已生效或已提交:Step 4 处理服务/应用决策,Step 5 跳过签约提交
85
85
  ;;
86
86
  esac
87
87
  }
@@ -99,8 +99,14 @@ fi
99
99
  # 解包 MCP 信封后解析业务字段
100
100
  BUSINESS=$(unwrap_mcp "$RESULT")
101
101
 
102
- # 使用 // [] 安全提取数组,判断签约状态;解析失败必须阻断,避免误判为未签约
103
- AR_LIST=$(echo "$BUSINESS" | jq -c '.resultObj.arInfoList // []' 2>/dev/null)
102
+ # 只有明确返回数组才可判断签约状态;缺少字段必须阻断,避免误判为未签约
103
+ AR_LIST=$(echo "$BUSINESS" | jq -c '
104
+ if (.resultObj | type) == "object" and (.resultObj.arInfoList | type) == "array" then
105
+ .resultObj.arInfoList
106
+ else
107
+ null
108
+ end
109
+ ' 2>/dev/null)
104
110
  if [ -z "$AR_LIST" ] || ! echo "$AR_LIST" | jq -e 'type == "array"' >/dev/null 2>&1; then
105
111
  echo "❌ 签约状态返回结构异常,无法解析 arInfoList"
106
112
  exit 1
@@ -108,7 +114,7 @@ fi
108
114
  AR_COUNT=$(echo "$AR_LIST" | jq 'length')
109
115
 
110
116
  if [ "$AR_COUNT" -eq 0 ]; then
111
- echo "📋 未签约 (NOT_SIGNED),进入资料采集流程"
117
+ echo "📋 签约状态: 未签约 (NOT_SIGNED);继续 Step 4 一次性资料与资源决策"
112
118
  echo "SIGN_STATUS=NOT_SIGNED"
113
119
 
114
120
  case "$SALES_CODE" in
@@ -122,16 +128,16 @@ if [ "$AR_COUNT" -eq 0 ]; then
122
128
  ;;
123
129
  "I1080300001000160457")
124
130
  echo "FLOW:AI_PAY_NOT_SIGNED"
125
- # 按量付费未签约:跳过 Step 4,进入 Step 5.1 签约,再继续服务注册 + 应用发布
131
+ # 按量付费未签约:Step 4 无需页面图片,但仍完成服务/应用决策
126
132
  ;;
127
133
  esac
128
134
  else
129
- # 使用 // [] 避免迭代 null 报错
135
+ # AR_LIST 已在上方确认为数组
130
136
  HAS_EFFECTIVE=$(echo "$AR_LIST" | jq -r '[.[] | select(.arStatus == "02")] | length')
131
137
  HAS_SUBMITTED=$(echo "$AR_LIST" | jq -r '[.[] | select(.arStatus == "01")] | length')
132
138
 
133
139
  if [ "$HAS_EFFECTIVE" -gt 0 ]; then
134
- echo "✅ 已签约(已生效),跳过资料采集"
140
+ echo "✅ 签约已生效;无需签约材料,继续 Step 4 资源决策"
135
141
  echo "SIGN_STATUS=SIGNED_EFFECTIVE"
136
142
  emit_signed_flow
137
143
  elif [ "$HAS_SUBMITTED" -gt 0 ]; then
@@ -139,7 +145,7 @@ else
139
145
  echo "SIGN_STATUS=SIGN_SUBMITTED"
140
146
  emit_signed_flow
141
147
  else
142
- echo "📋 其他签约状态,进入资料采集流程"
148
+ echo "📋 其他签约状态;保留已取得的只读结果并将签约分支标记为待核验"
143
149
  echo "SIGN_STATUS=OTHER_STATUS"
144
150
  echo "FLOW:OTHER_STATUS"
145
151
  fi