@alipay/alipay-aipay 1.2.2 → 1.4.9

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 (98) hide show
  1. package/README.md +5 -0
  2. package/dist/commands/install.d.ts.map +1 -1
  3. package/dist/commands/install.js +13 -12
  4. package/dist/commands/install.js.map +1 -1
  5. package/dist/commands/uninstall.d.ts.map +1 -1
  6. package/dist/commands/uninstall.js +17 -9
  7. package/dist/commands/uninstall.js.map +1 -1
  8. package/dist/commands/update.d.ts.map +1 -1
  9. package/dist/commands/update.js +5 -5
  10. package/dist/commands/update.js.map +1 -1
  11. package/dist/index.js +4 -4
  12. package/dist/index.js.map +1 -1
  13. package/dist/utils/config.d.ts +5 -3
  14. package/dist/utils/config.d.ts.map +1 -1
  15. package/dist/utils/config.js +34 -12
  16. package/dist/utils/config.js.map +1 -1
  17. package/dist/utils/file.d.ts +8 -0
  18. package/dist/utils/file.d.ts.map +1 -1
  19. package/dist/utils/file.js +161 -9
  20. package/dist/utils/file.js.map +1 -1
  21. package/dist/utils/jq.d.ts.map +1 -1
  22. package/dist/utils/jq.js +8 -0
  23. package/dist/utils/jq.js.map +1 -1
  24. package/package.json +6 -5
  25. package/skill/SKILL.md +15 -169
  26. package/skill/VERSION +1 -1
  27. package/skill/references/integration/flow.md +321 -292
  28. package/skill/references/integration/modules/alipay-sdk-reminder.md +5 -3
  29. package/skill/references/integration/modules/checklist.md +41 -27
  30. package/skill/references/integration/modules/code-examples/csharp/1-/351/200/232/347/224/250/346/216/245/345/217/243//345/274/202/346/255/245/351/200/232/347/237/245/345/244/204/347/220/206/344/273/243/347/240/201/347/244/272/344/276/213.md +199 -0
  31. 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 +8 -4
  32. package/skill/references/integration/modules/code-examples/csharp/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +6 -2
  33. package/skill/references/integration/modules/code-examples/csharp/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.cs +3 -1
  34. package/skill/references/integration/modules/code-examples/java/1-/351/200/232/347/224/250/346/216/245/345/217/243//345/274/202/346/255/245/351/200/232/347/237/245/345/244/204/347/220/206/344/273/243/347/240/201/347/244/272/344/276/213.md +184 -0
  35. 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 +4 -1
  36. package/skill/references/integration/modules/code-examples/java/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +5 -2
  37. package/skill/references/integration/modules/code-examples/java/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemoController.java +3 -1
  38. package/skill/references/integration/modules/code-examples/nodejs/1-/351/200/232/347/224/250/346/216/245/345/217/243//345/274/202/346/255/245/351/200/232/347/237/245/345/244/204/347/220/206/344/273/243/347/240/201/347/244/272/344/276/213.md +135 -0
  39. 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 +16 -10
  40. package/skill/references/integration/modules/code-examples/nodejs/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +10 -4
  41. package/skill/references/integration/modules/code-examples/nodejs/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.js +2 -1
  42. package/skill/references/integration/modules/code-examples/php/1-/351/200/232/347/224/250/346/216/245/345/217/243//345/274/202/346/255/245/351/200/232/347/237/245/345/244/204/347/220/206/344/273/243/347/240/201/347/244/272/344/276/213.md +157 -0
  43. 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 +4 -1
  44. package/skill/references/integration/modules/code-examples/php/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +5 -2
  45. package/skill/references/integration/modules/code-examples/php/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.php +2 -1
  46. package/skill/references/integration/modules/code-examples/python/1-/351/200/232/347/224/250/346/216/245/345/217/243//345/274/202/346/255/245/351/200/232/347/237/245/345/244/204/347/220/206/344/273/243/347/240/201/347/244/272/344/276/213.md +128 -0
  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 +4 -1
  48. package/skill/references/integration/modules/code-examples/python/3-APP/346/224/257/344/273/230/APP/346/224/257/344/273/230/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +5 -1
  49. package/skill/references/integration/modules/code-examples/python/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.py +4 -2
  50. package/skill/references/integration/modules/interface-contract/aipay-interface-contract.md +53 -0
  51. package/skill/references/integration/modules/interface-contract/apppay-interface-contract.md +45 -0
  52. package/skill/references/integration/modules/interface-contract/common-trade-contract.md +42 -0
  53. package/skill/references/integration/modules/interface-contract/notify-url-contract.md +68 -0
  54. package/skill/references/integration/modules/interface-contract/webpay-interface-contract.md +44 -0
  55. package/skill/references/integration/modules/interface-guide.md +37 -72
  56. package/skill/references/integration/modules/product-decision.md +10 -27
  57. package/skill/references/integration/modules/sandbox/a2m-sandbox-test.md +62 -94
  58. package/skill/references/integration/modules/sandbox/alipay-sandbox-tool.md +43 -40
  59. package/skill/references/integration/modules/sandbox/sandbox-setup-guide.md +93 -114
  60. package/skill/references/integration/modules/scripts/local_402_sandbox_pay.py +357 -147
  61. package/skill/references/integration/modules/scripts/sandbox_config.sh +299 -0
  62. package/skill/references/integration/modules/troubleshooting-flow.md +28 -0
  63. package/skill/references/normal/alipay-cli-env.md +15 -5
  64. package/skill/references/normal/full-process-routing.md +80 -0
  65. package/skill/references/normal/json/customer-messages.json +937 -0
  66. package/skill/references/normal/json/policy-rules.json +179 -0
  67. package/skill/references/normal/rejection-guide.md +11 -6
  68. package/skill/references/normal/scripts/alipay_cli_env.mjs +29 -0
  69. package/skill/references/normal/scripts/alipay_cli_env.sh +21 -0
  70. package/skill/references/normal/scripts/common.sh +87 -0
  71. package/skill/references/normal/scripts/detect_dev_tool.sh +5 -1
  72. package/skill/references/normal/scripts/integration_message_runner.mjs +801 -0
  73. package/skill/references/normal/scripts/onboarding_message_runner.mjs +436 -0
  74. package/skill/references/normal/scripts/open_official_url.sh +82 -0
  75. package/skill/references/normal/scripts/platform_compat.mjs +39 -0
  76. package/skill/references/normal/scripts/project_route_inspector.sh +132 -0
  77. package/skill/references/normal/scripts/python_script_runner.mjs +56 -0
  78. package/skill/references/normal/scripts/render_customer_message.mjs +1263 -0
  79. package/skill/references/normal/scripts/self_update.mjs +160 -0
  80. package/skill/references/normal/scripts/telemetry.mjs +1278 -0
  81. package/skill/references/normal/self-update.md +18 -51
  82. package/skill/references/onboarding/flow.md +381 -283
  83. package/skill/references/onboarding/modules/app-release.md +132 -179
  84. package/skill/references/onboarding/modules/authorization.md +53 -64
  85. package/skill/references/onboarding/modules/error-handling.md +31 -34
  86. package/skill/references/onboarding/modules/product-sign.md +62 -66
  87. package/skill/references/onboarding/modules/scripts/app.sh +397 -158
  88. package/skill/references/onboarding/modules/scripts/ar_sign_apply.sh +38 -22
  89. package/skill/references/onboarding/modules/scripts/auth.sh +479 -167
  90. package/skill/references/onboarding/modules/scripts/download_key_tool.sh +157 -0
  91. package/skill/references/onboarding/modules/scripts/error_handler.sh +100 -130
  92. package/skill/references/onboarding/modules/scripts/network_retry.sh +96 -0
  93. package/skill/references/onboarding/modules/scripts/query_sign_status.sh +17 -70
  94. package/skill/references/onboarding/modules/scripts/service.sh +153 -89
  95. package/skill/references/onboarding/modules/scripts/sign_status_parser.sh +58 -0
  96. package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +16 -14
  97. package/skill/references/onboarding/modules/service-registration.md +67 -72
  98. package/skill/references/runtime-entry.md +62 -0
@@ -3,24 +3,28 @@
3
3
  # 脚本名称: app.sh
4
4
  # 功能描述: 应用发布全流程 - 查询/复用 + 创建 + 公钥设置 + 提交审核
5
5
  # 调用位置: list 用于 Step 3.1 前置资源查询;其他命令用于 Step 5.3 应用发布
6
- # 调用前置: 脚本通过 error_handler.sh 间接初始化 DEV_TOOL_NAME
6
+ # 调用前置: 脚本通过 error_handler.sh 间接初始化 DEV_TOOL_NAME/PLATFORM_ID
7
7
  # list/create 可传 --application-type,未传时按 --product-type/--sales-code 或兼容环境变量推断
8
8
  # 用法:
9
9
  # app list [--product-type <type> --sales-code <code> --application-type <type>] - 查询已有应用并判断复用/新建
10
10
  # app create [--product-type <type> --sales-code <code> --application-type <type>] [--mobile-platform IOS|ANDROID|ALL ...] - 创建新应用
11
- # app key <appId> <publicKey> - 设置应用公钥
11
+ # app key --defer-open <appId> <publicKey> - 设置应用公钥
12
+ # app open-key-page <appId> - 打开上一次生成的公钥确认页
12
13
  # app verify-key <appId> [publicKey] - 确认应用公钥已设置
14
+ # app verify-key-and-audit <appId> [publicKey] - 复用最终密钥查询结果并提交审核
13
15
  # app audit <appId> - 提交应用审核
14
16
  # app reuse <appId> - 复用已有应用(查询安全密钥)
15
17
  # 返回值:
16
- # list: FLOW:CREATE_NEW | FLOW:SELECT | FLOW:PENDING_APPLICATIONS | FLOW:ERROR
17
- # create: 成功输出 APP_ID=xxx;失败 exit 1,返回结构异常时同时输出 FLOW:ERROR
18
+ # list: stdout 成功时为空;stderr 输出带统一内部前缀的候选 JSON、FLOW 和候选 ID
19
+ # create: 成功在 stderr 输出 APP_ID=xxx;失败 exit 1,返回结构异常时同时输出 FLOW:ERROR
18
20
  # key: 输出 confirmPageUrl
19
21
  # audit: 输出审核结果
20
- # reuse: FLOW:REUSE_SUCCESS | FLOW:REUSE_NO_KEY | FLOW:ERROR
22
+ # reuse: stderr 输出 FLOW:REUSE_SUCCESS | FLOW:REUSE_NO_KEY | FLOW:ERROR
21
23
  #=============================================================================
22
24
 
23
25
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
26
+ RENDERER="${SCRIPT_DIR}/../../../normal/scripts/render_customer_message.mjs"
27
+ CANDIDATE_RUNNER="${SCRIPT_DIR}/../../../normal/scripts/onboarding_message_runner.mjs"
24
28
 
25
29
  # 一次性 source error_handler(含 unwrap_mcp)
26
30
  if [ -f "${SCRIPT_DIR}/error_handler.sh" ]; then
@@ -29,8 +33,80 @@ else
29
33
  echo "❌ 缺少错误处理脚本: ${SCRIPT_DIR}/error_handler.sh"
30
34
  exit 1
31
35
  fi
36
+ source "${SCRIPT_DIR}/network_retry.sh"
32
37
 
33
38
  require_command jq || exit 1
39
+ require_command node || exit 1
40
+
41
+ APP_KEY_STATE_OWNER="$(id -u 2>/dev/null || printf '%s' "${USER:-unknown}" | tr -c 'A-Za-z0-9_-' '_')"
42
+ APP_KEY_STATE_BASE="${TMPDIR:-/tmp}"
43
+ APP_KEY_STATE_BASE="${APP_KEY_STATE_BASE%/}"
44
+ if [ -n "${ALIPAY_APP_KEY_STATE_FILE:-}" ]; then
45
+ APP_KEY_STATE_FILE="$ALIPAY_APP_KEY_STATE_FILE"
46
+ APP_KEY_STATE_DIR="$(dirname "$APP_KEY_STATE_FILE")"
47
+ APP_KEY_STATE_DIR_MANAGED=false
48
+ else
49
+ APP_KEY_STATE_DIR="${APP_KEY_STATE_BASE}/alipay-aipay-app-key-${APP_KEY_STATE_OWNER}"
50
+ APP_KEY_STATE_FILE="${APP_KEY_STATE_DIR}/key-page-state.json"
51
+ APP_KEY_STATE_DIR_MANAGED=true
52
+ fi
53
+
54
+ app_key_secure_mode() {
55
+ local target="$1" expected="$2" mode
56
+ if mode=$(stat -f '%Lp' "$target" 2>/dev/null); then
57
+ :
58
+ elif mode=$(stat -c '%a' "$target" 2>/dev/null); then
59
+ :
60
+ else
61
+ return 1
62
+ fi
63
+ [ "$mode" = "$expected" ]
64
+ }
65
+
66
+ prepare_app_key_state_dir() {
67
+ if [ "$APP_KEY_STATE_DIR_MANAGED" = true ]; then
68
+ if [ -L "$APP_KEY_STATE_DIR" ] || { [ -e "$APP_KEY_STATE_DIR" ] && [ ! -d "$APP_KEY_STATE_DIR" ]; }; then
69
+ echo "❌ 公钥确认页临时目录不安全,已停止" >&2
70
+ return 1
71
+ fi
72
+ mkdir -p "$APP_KEY_STATE_DIR" || return 1
73
+ chmod 700 "$APP_KEY_STATE_DIR" 2>/dev/null || return 1
74
+ app_key_secure_mode "$APP_KEY_STATE_DIR" 700 || return 1
75
+ else
76
+ [ -d "$APP_KEY_STATE_DIR" ] && [ ! -L "$APP_KEY_STATE_DIR" ] || {
77
+ echo "❌ 指定的公钥确认页状态目录不可用或不安全,已停止" >&2
78
+ return 1
79
+ }
80
+ fi
81
+ }
82
+
83
+ validate_app_key_state_file() {
84
+ [ -f "$APP_KEY_STATE_FILE" ] && [ ! -L "$APP_KEY_STATE_FILE" ] || return 1
85
+ chmod 600 "$APP_KEY_STATE_FILE" 2>/dev/null || return 1
86
+ app_key_secure_mode "$APP_KEY_STATE_FILE" 600
87
+ }
88
+
89
+ save_app_key_page_state() {
90
+ local temp_state
91
+ require_command mktemp || return 1
92
+ prepare_app_key_state_dir || return 1
93
+ if [ -L "$APP_KEY_STATE_FILE" ] || { [ -e "$APP_KEY_STATE_FILE" ] && [ ! -f "$APP_KEY_STATE_FILE" ]; }; then
94
+ echo "❌ 公钥确认页状态路径不是安全的普通文件,已停止" >&2
95
+ return 1
96
+ fi
97
+ temp_state=$(mktemp "${APP_KEY_STATE_DIR}/.key-page-state.XXXXXX") || return 1
98
+ chmod 600 "$temp_state" 2>/dev/null || { rm -f "$temp_state"; return 1; }
99
+ jq -n \
100
+ --arg appId "$APP_ID" \
101
+ --arg confirmPageUrl "$CONFIRM_PAGE_URL" \
102
+ '{appId:$appId,confirmPageUrl:$confirmPageUrl}' > "$temp_state" || { rm -f "$temp_state"; return 1; }
103
+ mv "$temp_state" "$APP_KEY_STATE_FILE" || { rm -f "$temp_state"; return 1; }
104
+ validate_app_key_state_file || {
105
+ rm -f "$APP_KEY_STATE_FILE"
106
+ echo "❌ 无法确认公钥确认页状态文件权限为 0600,已停止" >&2
107
+ return 1
108
+ }
109
+ }
34
110
 
35
111
  parse_app_context_args() {
36
112
  while [[ $# -gt 0 ]]; do
@@ -73,7 +149,7 @@ validate_mobile_platform_args() {
73
149
  fi
74
150
  if [ "$APP_APPLICATION_TYPE" = "MOBILEAPP" ]; then
75
151
  echo "❌ 创建 MOBILEAPP 必须传 --mobile-platform IOS|ANDROID|ALL" >&2
76
- echo "📋 IOS 需同时传 --bundle-id;ANDROID 需同时传 --app-package 和 --app-sign;ALL 需三项都传" >&2
152
+ echo "📋 IOS 需同时传 --bundle-id;ANDROID 需同时传 --app-package 和 --app-sign;ALL 需三项都传;HarmonyOS 移动应用不支持通过 Skill 创建" >&2
77
153
  return 1
78
154
  fi
79
155
  return 0
@@ -112,7 +188,7 @@ validate_mobile_platform_args() {
112
188
  fi
113
189
  ;;
114
190
  *)
115
- echo "❌ --mobile-platform 参数值无效: ${MOBILE_PLATFORM}(仅支持 IOS|ANDROID|ALL)" >&2
191
+ echo "❌ --mobile-platform 参数值无效: ${MOBILE_PLATFORM}(仅支持 IOS|ANDROID|ALL;HarmonyOS 移动应用需前往支付宝开放平台控制台创建)" >&2
116
192
  return 1
117
193
  ;;
118
194
  esac
@@ -213,9 +289,15 @@ query_security_key() {
213
289
  REQUEST_JSON=$(jq -n --arg appId "$APP_ID" '{request:{appId:$appId}}')
214
290
  fi
215
291
 
216
- RESULT=$(export PLATFORM=${DEV_TOOL_NAME} && alipay-cli mcp call apprelease.queryApplicationSecurityKey \
292
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
293
+ run_network_retry RESULT read application_security_key -- alipay-cli mcp call apprelease.queryApplicationSecurityKey \
217
294
  -d "$REQUEST_JSON" \
218
- --json 2>/dev/null)
295
+ --json
296
+ RETRY_RC=$?
297
+ if [ "$RETRY_RC" -ne 0 ]; then
298
+ echo "❌ 应用安全密钥查询因网络或服务异常在自动重试两次后仍未恢复"
299
+ return 1
300
+ fi
219
301
 
220
302
  if ! handle_error "$RESULT"; then
221
303
  return 1
@@ -226,6 +308,7 @@ query_security_key() {
226
308
 
227
309
  if [ "$SUCCESS" != "true" ]; then
228
310
  ERROR_MSG=$(echo "$SECURITY_BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
311
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
229
312
  echo "❌ 查询安全密钥失败: $ERROR_MSG"
230
313
  return 1
231
314
  fi
@@ -235,9 +318,15 @@ query_application_info() {
235
318
  local APP_ID="$1"
236
319
 
237
320
  REQUEST_JSON=$(jq -n --arg appId "$APP_ID" '{request:{appId:$appId}}')
238
- RESULT=$(export PLATFORM=${DEV_TOOL_NAME} && alipay-cli mcp call apprelease.queryApplicationInfo \
321
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
322
+ run_network_retry RESULT read application_info -- alipay-cli mcp call apprelease.queryApplicationInfo \
239
323
  -d "$REQUEST_JSON" \
240
- --json 2>/dev/null)
324
+ --json
325
+ RETRY_RC=$?
326
+ if [ "$RETRY_RC" -ne 0 ]; then
327
+ echo "❌ 应用信息查询因网络或服务异常在自动重试两次后仍未恢复"
328
+ return 1
329
+ fi
241
330
 
242
331
  if ! handle_error "$RESULT"; then
243
332
  return 1
@@ -248,6 +337,7 @@ query_application_info() {
248
337
 
249
338
  if [ "$SUCCESS" != "true" ]; then
250
339
  ERROR_MSG=$(echo "$APPLICATION_INFO_BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
340
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
251
341
  echo "❌ 查询应用信息失败: $ERROR_MSG"
252
342
  return 1
253
343
  fi
@@ -324,20 +414,20 @@ write_alipay_public_key() {
324
414
  fi
325
415
 
326
416
  while [ "$ATTEMPT" -le "$MAX_ATTEMPTS" ]; do
327
- if { mkdir -p "${HOME}/.config" && printf '%s\n' "$ALIPAY_PUBLIC_KEY" > "$KEY_FILE"; } 2>/dev/null; then
417
+ if { mkdir -p "${HOME}/.config" && chmod 700 "${HOME}/.config" && (umask 077; printf '%s\n' "$ALIPAY_PUBLIC_KEY" > "$KEY_FILE") && chmod 600 "$KEY_FILE"; } 2>/dev/null; then
328
418
  return 0
329
419
  fi
330
420
 
331
421
  if [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; then
332
- echo "⚠️ 支付宝公钥文件写入失败,正在重试(${ATTEMPT}/${MAX_ATTEMPTS})..."
422
+ emit_control "⚠️ 支付宝公钥文件写入失败,正在重试(${ATTEMPT}/${MAX_ATTEMPTS})..."
333
423
  sleep 1
334
424
  fi
335
425
  ATTEMPT=$((ATTEMPT + 1))
336
426
  done
337
427
 
338
- echo "⚠️ 已获取到 RSA2 支付宝公钥,但本地文件写入失败: $KEY_FILE"
339
- echo "📋 这不是公钥查询失败;当前流程会继续,但后续集成时需要手动配置支付宝公钥。"
340
- echo "📋 可检查本地目录权限或设置 HOME 后重新执行导出。"
428
+ emit_control "⚠️ 已获取到 RSA2 支付宝公钥,但本地文件写入失败: $KEY_FILE"
429
+ emit_control "📋 这不是公钥查询失败;当前流程会继续,但后续集成时需要手动配置支付宝公钥。"
430
+ emit_control "📋 可检查本地目录权限或设置 HOME 后重新执行导出。"
341
431
  return 1
342
432
  }
343
433
 
@@ -345,42 +435,50 @@ ensure_rsa2_application_key_ready() {
345
435
  local APP_ID="$1"
346
436
  local PUBLIC_KEY="${2:-}"
347
437
  local ATTEMPT=1
348
- local MAX_ATTEMPTS="${APP_KEY_VERIFY_RETRIES:-6}"
349
- local INTERVAL_SECONDS="${APP_KEY_VERIFY_INTERVAL_SECONDS:-5}"
438
+ local MAX_ATTEMPTS=20
439
+ local INTERVAL_SECONDS=2
440
+ KEY_VERIFY_RESULT=""
350
441
 
351
- if ! [[ "$MAX_ATTEMPTS" =~ ^[1-9][0-9]*$ ]]; then
352
- MAX_ATTEMPTS=6
442
+ if [ "${ALIPAY_AIPAY_RECOVERY_QUERY_ONCE:-0}" = "1" ]; then
443
+ MAX_ATTEMPTS=1
353
444
  fi
354
445
 
355
- if ! [[ "$INTERVAL_SECONDS" =~ ^[0-9]+$ ]]; then
356
- INTERVAL_SECONDS=5
446
+ if [ "${ALIPAY_AIPAY_TEST_MODE:-0}" = "1" ] && [[ "${APP_KEY_VERIFY_INTERVAL_SECONDS:-}" =~ ^[0-9]+$ ]]; then
447
+ INTERVAL_SECONDS="$APP_KEY_VERIFY_INTERVAL_SECONDS"
357
448
  fi
358
449
 
359
450
  while [ "$ATTEMPT" -le "$MAX_ATTEMPTS" ]; do
360
- query_security_key "$APP_ID" "$PUBLIC_KEY" || return 1
451
+ if ! query_security_key "$APP_ID" "$PUBLIC_KEY"; then
452
+ KEY_VERIFY_RESULT="QUERY_FAILED"
453
+ return 1
454
+ fi
361
455
 
362
456
  RSA2_KEY_INFO=$(extract_rsa2_application_key_info)
363
457
 
364
458
  if is_rsa2_application_key_ready "$RSA2_KEY_INFO"; then
459
+ KEY_VERIFY_RESULT="CONFIRMED"
365
460
  return 0
366
461
  fi
367
462
 
368
463
  if [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; then
369
- echo "⏳ 支付宝侧尚未确认到应用公钥生效,正在重试(${ATTEMPT}/${MAX_ATTEMPTS})..."
464
+ emit_control "KEY_POLL_PENDING=${ATTEMPT}/${MAX_ATTEMPTS}"
370
465
  sleep "$INTERVAL_SECONDS"
371
466
  fi
372
467
 
373
468
  ATTEMPT=$((ATTEMPT + 1))
374
469
  done
375
470
 
376
- echo "❌ 应用公钥尚未确认,请先完成公钥设置确认"
471
+ KEY_VERIFY_RESULT="PENDING"
377
472
  return 1
378
473
  }
379
474
 
380
- verify_key_and_export_alipay_public_key() {
381
- local APP_ID="$1"
475
+ render_key_pending() {
476
+ printf '%s' '{}' \
477
+ | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=app.sh node "$RENDERER" application.key.pending --variant DEFAULT
478
+ }
382
479
 
383
- query_security_key "$APP_ID" || return 1
480
+ export_alipay_public_key_from_security_business() {
481
+ local APP_ID="$1"
384
482
 
385
483
  RSA2_KEY_INFO=$(extract_rsa2_application_key_info)
386
484
  RSA2_ALIPAY_KEY_INFO=$(extract_rsa2_alipay_key_info)
@@ -397,17 +495,24 @@ verify_key_and_export_alipay_public_key() {
397
495
  fi
398
496
 
399
497
  if write_alipay_public_key "$APP_ID" "$ALIPAY_PUBLIC_KEY"; then
400
- echo "✅ 已确认应用公钥并导出支付宝公钥"
401
- echo "支付宝公钥保存路径: ~/.config/${APP_ID}-alipayPublicKey.keytext"
402
- echo "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=EXPORTED"
403
- echo "ALIPAY_PUBLIC_KEY_FILE=~/.config/${APP_ID}-alipayPublicKey.keytext"
498
+ emit_control "✅ 已确认应用公钥并导出支付宝公钥"
499
+ emit_control "支付宝公钥保存路径: ~/.config/${APP_ID}-alipayPublicKey.keytext"
500
+ emit_control "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=EXPORTED"
501
+ emit_control "ALIPAY_PUBLIC_KEY_FILE=~/.config/${APP_ID}-alipayPublicKey.keytext"
404
502
  else
405
- echo "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=MANUAL_CONFIGURATION_REQUIRED"
503
+ emit_control "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=MANUAL_CONFIGURATION_REQUIRED"
406
504
  fi
407
505
 
408
506
  return 0
409
507
  }
410
508
 
509
+ verify_key_and_export_alipay_public_key() {
510
+ local APP_ID="$1"
511
+
512
+ query_security_key "$APP_ID" || return 1
513
+ export_alipay_public_key_from_security_business "$APP_ID"
514
+ }
515
+
411
516
  # ─── app list: 查询已有应用 ──────────────────────────────────────────────────
412
517
  app_list() {
413
518
  parse_app_context_args "$@" || {
@@ -417,9 +522,16 @@ app_list() {
417
522
  APP_APPLICATION_TYPE=$(resolve_application_type) || exit 1
418
523
  REQUEST_JSON=$(jq -n --arg applicationType "$APP_APPLICATION_TYPE" '{request:{appTypes:[$applicationType]}}')
419
524
 
420
- RESULT=$(export PLATFORM=${DEV_TOOL_NAME} && alipay-cli mcp call apprelease.queryApplicationList \
525
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
526
+ run_network_retry RESULT read application_list -- alipay-cli mcp call apprelease.queryApplicationList \
421
527
  -d "$REQUEST_JSON" \
422
- --json 2>/dev/null)
528
+ --json
529
+ RETRY_RC=$?
530
+ if [ "$RETRY_RC" -ne 0 ]; then
531
+ echo "❌ 应用列表查询因网络或服务异常在自动重试两次后仍未恢复"
532
+ emit_control "FLOW:ERROR"
533
+ exit 1
534
+ fi
423
535
 
424
536
  # 错误检测
425
537
  if ! handle_error "$RESULT"; then
@@ -432,8 +544,9 @@ app_list() {
432
544
 
433
545
  if [ "$SUCCESS" != "true" ]; then
434
546
  ERROR_MSG=$(echo "$BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
547
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
435
548
  echo "❌ 查询应用列表失败: $ERROR_MSG"
436
- echo "FLOW:ERROR"
549
+ emit_control "FLOW:ERROR"
437
550
  exit 1
438
551
  fi
439
552
 
@@ -448,78 +561,56 @@ app_list() {
448
561
  ' 2>/dev/null)
449
562
  if [ -z "$APP_LIST" ] || ! echo "$APP_LIST" | jq -e 'type == "array"' >/dev/null 2>&1; then
450
563
  echo "❌ 应用列表返回结构异常,无法解析应用列表"
451
- echo "FLOW:ERROR"
564
+ emit_control "FLOW:ERROR"
452
565
  exit 1
453
566
  fi
454
567
  APP_COUNT=$(echo "$APP_LIST" | jq 'length')
568
+ APP_CANDIDATES=$(echo "$APP_LIST" | jq -c '[.[] | {
569
+ appId: ((.appId // "未知") | tostring),
570
+ appName: ((.appName // "未知") | tostring),
571
+ status: ((.status // "未知") | tostring)
572
+ }]')
573
+ ONLINE_APPS=$(echo "$APP_LIST" | jq -r '[.[] | select(.status == "ON_LINE")]')
574
+ NON_ONLINE_APPS=$(echo "$APP_LIST" | jq -r '[.[] | select(.status != "ON_LINE")]')
575
+ ONLINE_COUNT=$(echo "$ONLINE_APPS" | jq 'length')
576
+ NON_ONLINE_COUNT=$(echo "$NON_ONLINE_APPS" | jq 'length')
577
+ ONLINE_COUNT=${ONLINE_COUNT:-0}
578
+ NON_ONLINE_COUNT=${NON_ONLINE_COUNT:-0}
579
+
580
+ if [ "$ONLINE_COUNT" -gt 0 ] && ! echo "$ONLINE_APPS" | jq -e '
581
+ all(.[];
582
+ ((.appId | type) == "string") and
583
+ (.appId | length > 0) and
584
+ ((.appId | test("[\u0000-\u001f\u007f]") | not))
585
+ )
586
+ ' >/dev/null 2>&1; then
587
+ echo "❌ 应用列表返回结构异常:可复用候选 appId 缺失或包含控制字符"
588
+ emit_control "FLOW:ERROR"
589
+ exit 1
590
+ fi
591
+
592
+ if ! printf '%s' "$APP_CANDIDATES" \
593
+ | node "$CANDIDATE_RUNNER" application-candidates --application-type "$APP_APPLICATION_TYPE" >/dev/null 2>&1; then
594
+ echo "❌ 应用候选事实安全校验失败"
595
+ emit_control "FLOW:ERROR"
596
+ exit 1
597
+ fi
598
+ emit_control_field "APP_CANDIDATES_JSON" "$APP_CANDIDATES" || {
599
+ echo "❌ 应用候选事实包含非法控制字符"
600
+ emit_control "FLOW:ERROR"
601
+ exit 1
602
+ }
603
+ emit_control_field "APPLICATION_TYPE" "$APP_APPLICATION_TYPE" || exit 1
455
604
 
456
605
  if [ "$APP_COUNT" -eq 0 ]; then
457
- echo "📋 暂无 ${APP_APPLICATION_TYPE} 应用;记录新建候选分支,等待 Step 4 统一决策"
458
- echo "FLOW:CREATE_NEW"
606
+ emit_control "FLOW:CREATE_NEW"
459
607
  else
460
- # 实际返回的状态字段值是 "ON_LINE"(带下划线)
461
- ONLINE_APPS=$(echo "$APP_LIST" | jq -r '[.[] | select(.status == "ON_LINE")]')
462
- NON_ONLINE_APPS=$(echo "$APP_LIST" | jq -r '[.[] | select(.status != "ON_LINE")]')
463
- ONLINE_COUNT=$(echo "$ONLINE_APPS" | jq 'length')
464
- NON_ONLINE_COUNT=$(echo "$NON_ONLINE_APPS" | jq 'length')
465
- ONLINE_COUNT=${ONLINE_COUNT:-0}
466
- NON_ONLINE_COUNT=${NON_ONLINE_COUNT:-0}
467
-
468
608
  if [ "$ONLINE_COUNT" -eq 0 ]; then
469
- echo "📋 当前已有以下尚未上线的 ${APP_APPLICATION_TYPE} 应用:"
470
- echo ""
471
- echo "| 应用ID | 应用名称 | 实际状态 |"
472
- echo "|--------|----------|----------|"
473
- for i in $(seq 0 $((NON_ONLINE_COUNT-1))); do
474
- APP=$(echo "$NON_ONLINE_APPS" | jq ".[$i]")
475
- APP_ID=$(echo "$APP" | jq -r '.appId // "未知"')
476
- APP_NAME=$(echo "$APP" | jq -r '.appName // "未知"')
477
- APP_STATUS=$(echo "$APP" | jq -r '.status // "未知"')
478
- echo "| $APP_ID | $APP_NAME | $APP_STATUS |"
479
- done
480
- echo ""
481
- echo "以上应用当前不可复用。请等待应用上线,或在了解现有应用状态后明确选择新建。"
482
- echo "FLOW:PENDING_APPLICATIONS"
609
+ emit_control "FLOW:PENDING_APPLICATIONS"
483
610
  else
484
- echo "📋 发现您已有以下上线 ${APP_APPLICATION_TYPE} 应用:"
485
- echo ""
486
- echo "| 序号 | 应用ID | 应用名称 | 状态 |"
487
- echo "|------|--------|----------|------|"
488
-
489
- for i in $(seq 0 $((ONLINE_COUNT-1))); do
490
- APP=$(echo "$ONLINE_APPS" | jq ".[$i]")
491
- APP_ID=$(echo "$APP" | jq -r '.appId // "未知"')
492
- APP_NAME=$(echo "$APP" | jq -r '.appName // "未知"')
493
- echo "| $((i+1)) | $APP_ID | $APP_NAME | 已上线 |"
494
- done
495
-
496
- if [ "$NON_ONLINE_COUNT" -gt 0 ]; then
497
- echo ""
498
- echo "另有以下尚未上线的同类型应用,当前不可复用:"
499
- echo ""
500
- echo "| 应用ID | 应用名称 | 实际状态 |"
501
- echo "|--------|----------|----------|"
502
- for i in $(seq 0 $((NON_ONLINE_COUNT-1))); do
503
- APP=$(echo "$NON_ONLINE_APPS" | jq ".[$i]")
504
- APP_ID=$(echo "$APP" | jq -r '.appId // "未知"')
505
- APP_NAME=$(echo "$APP" | jq -r '.appName // "未知"')
506
- APP_STATUS=$(echo "$APP" | jq -r '.status // "未知"')
507
- echo "| $APP_ID | $APP_NAME | $APP_STATUS |"
508
- done
509
- echo ""
510
- echo "新建前请结合以上状态避免重复创建。"
511
- fi
512
-
513
- echo ""
514
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
515
- echo " 请选择操作:"
516
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
517
- echo ""
518
- echo " 【复用】输入序号 (1-$ONLINE_COUNT) → 复用对应应用"
519
- echo " 【新建】输入 \"新建\" → 创建新应用"
520
- echo ""
521
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
522
- echo "FLOW:SELECT"
611
+ echo "$ONLINE_APPS" | jq -r '.[] | select((.appId // "") != "") | "APP_CANDIDATE_ID=" + .appId' \
612
+ | while IFS= read -r marker; do emit_control "$marker"; done
613
+ emit_control "FLOW:SELECT"
523
614
  fi
524
615
  fi
525
616
  }
@@ -537,9 +628,20 @@ app_create() {
537
628
  exit 1
538
629
  }
539
630
 
540
- RESULT=$(export PLATFORM=${DEV_TOOL_NAME} && alipay-cli mcp call apprelease.createApplication \
631
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
632
+ run_network_retry RESULT write application_create -- alipay-cli mcp call apprelease.createApplication \
541
633
  -d "$REQUEST_JSON" \
542
- --json 2>/dev/null)
634
+ --json
635
+ RETRY_RC=$?
636
+ if [ "$RETRY_RC" -eq 75 ]; then
637
+ echo "❌ 应用创建响应不明;现有应用列表无法把同类型新应用唯一归属于本次创建,结果标记为 UNKNOWN,禁止自动重复创建"
638
+ emit_control "FLOW:ERROR"
639
+ exit 1
640
+ elif [ "$RETRY_RC" -ne 0 ]; then
641
+ echo "❌ 应用创建因明确未发送的网络异常在自动重试两次后仍未恢复"
642
+ emit_control "FLOW:ERROR"
643
+ exit 1
644
+ fi
543
645
 
544
646
  # 错误检测
545
647
  if ! handle_error "$RESULT"; then
@@ -552,16 +654,17 @@ app_create() {
552
654
 
553
655
  if [ "$SUCCESS" = "true" ]; then
554
656
  APP_ID=$(echo "$BUSINESS" | jq -r '.resultObj.appId // .data.appId // empty')
555
- if [ -z "$APP_ID" ]; then
657
+ if ! validate_control_value "$APP_ID"; then
556
658
  echo "❌ 应用创建返回成功,但未解析到 appId,禁止进入公钥设置流程"
557
- echo "FLOW:ERROR"
659
+ emit_control "FLOW:ERROR"
558
660
  exit 1
559
661
  fi
560
- echo "✅ 应用创建成功"
561
- echo "APPLICATION_TYPE=$APP_APPLICATION_TYPE"
562
- echo "APP_ID=$APP_ID"
662
+ emit_control "✅ 应用创建成功"
663
+ emit_control_field "APPLICATION_TYPE" "$APP_APPLICATION_TYPE" || exit 1
664
+ emit_control_field "APP_ID" "$APP_ID" || exit 1
563
665
  else
564
666
  ERROR_MSG=$(echo "$BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
667
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
565
668
  echo "❌ 应用创建失败: $ERROR_MSG"
566
669
  exit 1
567
670
  fi
@@ -569,9 +672,17 @@ app_create() {
569
672
 
570
673
  # ─── app key: 设置应用公钥 ───────────────────────────────────────────────────
571
674
  app_key() {
675
+ if [ "${1:-}" = "--defer-open" ]; then
676
+ shift
677
+ else
678
+ echo "❌ 参数格式错误:app key 必须使用 --defer-open,确保先展示公钥确认说明和链接,再自动打开页面"
679
+ echo "用法: bash app.sh key --defer-open <appId> <publicKey>"
680
+ exit 1
681
+ fi
682
+
572
683
  if [ "$#" -ne 2 ] || [[ "${1:-}" == --* ]]; then
573
- echo "❌ 参数格式错误:key 使用位置参数 <appId> <publicKey>,不支持 --app-id 等未定义选项"
574
- echo "用法: bash app.sh key <appId> <publicKey>"
684
+ echo "❌ 参数格式错误:key 使用位置参数 <appId> <publicKey>"
685
+ echo "用法: bash app.sh key --defer-open <appId> <publicKey>"
575
686
  exit 1
576
687
  fi
577
688
 
@@ -580,7 +691,11 @@ app_key() {
580
691
 
581
692
  if [ -z "$APP_ID" ] || [ -z "$PUBLIC_KEY" ]; then
582
693
  echo "❌ 请提供 appId 和 publicKey"
583
- echo "用法: bash app.sh key <appId> <publicKey>"
694
+ echo "用法: bash app.sh key --defer-open <appId> <publicKey>"
695
+ exit 1
696
+ fi
697
+ if ! validate_control_value "$APP_ID"; then
698
+ echo "❌ appId 包含非法控制字符"
584
699
  exit 1
585
700
  fi
586
701
 
@@ -589,9 +704,18 @@ app_key() {
589
704
  --arg publicKey "$PUBLIC_KEY" \
590
705
  '{request:{appId:$appId,signType:"RSA2",publicKey:$publicKey}}')
591
706
 
592
- RESULT=$(export PLATFORM=${DEV_TOOL_NAME} && alipay-cli mcp call apprelease.createKeyConfirmPage \
707
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
708
+ run_network_retry RESULT write application_key_confirm_page -- alipay-cli mcp call apprelease.createKeyConfirmPage \
593
709
  -d "$REQUEST_JSON" \
594
- --json 2>/dev/null)
710
+ --json
711
+ RETRY_RC=$?
712
+ if [ "$RETRY_RC" -eq 75 ]; then
713
+ echo "❌ 公钥确认页生成响应不明,结果标记为 UNKNOWN;不得自动生成第二个页面"
714
+ exit 1
715
+ elif [ "$RETRY_RC" -ne 0 ]; then
716
+ echo "❌ 公钥确认页因明确未发送的网络异常在自动重试两次后仍未恢复"
717
+ exit 1
718
+ fi
595
719
 
596
720
  # 错误检测
597
721
  if ! handle_error "$RESULT"; then
@@ -606,50 +730,101 @@ app_key() {
606
730
  CONFIRM_PAGE_URL=$(echo "$BUSINESS" | jq -r '.resultObj.confirmPageUrl // empty')
607
731
 
608
732
  if [ -n "$CONFIRM_PAGE_URL" ]; then
609
- echo "📋 应用公钥设置中..."
610
- echo ""
611
- echo "请点击以下链接确认公钥设置:"
612
- echo ""
613
- echo "提示:无法跳链时,请复制下方链接到网页浏览器打开。"
614
- echo "[点击确认公钥设置]($CONFIRM_PAGE_URL)"
615
- echo ""
616
- echo "链接有效期为10分钟。"
617
- echo ""
618
- echo "确认完成后,请告诉我\"好了\"继续后续流程。"
733
+ RENDERER="${SCRIPT_DIR}/../../../normal/scripts/render_customer_message.mjs"
734
+ save_app_key_page_state || exit 1
735
+ MESSAGE_INPUT=$(printf '%s\n' "$APP_ID" "$CONFIRM_PAGE_URL" | jq -Rs '
736
+ split("\n") as $v | {appId:$v[0],officialUrl:$v[1]}
737
+ ')
738
+ if ! printf '%s' "$MESSAGE_INPUT" | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=app.sh node "$RENDERER" application.key.page --variant READY; then
739
+ echo "❌ 公钥确认页已生成,但标准消息无法安全输出;结果保持待核验,禁止自动生成第二个页面"
740
+ exit 1
741
+ fi
742
+ emit_control "KEY_PAGE:READY_TO_OPEN"
619
743
  else
620
- echo "❌ 未获取到确认页面链接,请重试"
744
+ echo "❌ 公钥确认页响应缺少 confirmPageUrl,结果标记为 UNKNOWN;不得自动生成第二个页面"
621
745
  exit 1
622
746
  fi
623
747
  else
624
748
  ERROR_MSG=$(echo "$BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
749
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
625
750
  echo "❌ 公钥设置失败: $ERROR_MSG"
626
751
  exit 1
627
752
  fi
628
753
  }
629
754
 
630
- # ─── app audit: 提交应用审核 ─────────────────────────────────────────────────
631
- app_audit() {
755
+ app_open_key_page() {
756
+ local APP_ID="$1" SAVED_APP_ID CONFIRM_PAGE_URL MESSAGE_INPUT OPEN_HELPER OPEN_STATUS
757
+
632
758
  if [ "$#" -ne 1 ] || [[ "${1:-}" == --* ]]; then
633
- echo "❌ 参数格式错误:audit 使用位置参数 <appId>,不支持 --app-id 等未定义选项"
634
- echo "用法: bash app.sh audit <appId>"
759
+ echo "❌ 参数格式错误:open-key-page 使用位置参数 <appId>,不支持 --app-id 等未定义选项"
760
+ echo "用法: bash app.sh open-key-page <appId>"
761
+ exit 1
762
+ fi
763
+ if ! validate_control_value "$APP_ID"; then
764
+ echo "❌ appId 包含非法控制字符"
635
765
  exit 1
636
766
  fi
767
+ if ! validate_app_key_state_file; then
768
+ echo "❌ 缺少待打开的公钥确认页上下文,请重新执行 app.sh key --defer-open <appId> <publicKey>"
769
+ emit_control "FLOW:ERROR"
770
+ return 1
771
+ fi
772
+ if ! jq -e . "$APP_KEY_STATE_FILE" >/dev/null 2>&1; then
773
+ echo "❌ 公钥确认页状态文件不是合法 JSON,已停止"
774
+ emit_control "FLOW:ERROR"
775
+ return 1
776
+ fi
637
777
 
638
- APP_ID="$1"
778
+ SAVED_APP_ID=$(jq -r '.appId // ""' "$APP_KEY_STATE_FILE")
779
+ CONFIRM_PAGE_URL=$(jq -r '.confirmPageUrl // ""' "$APP_KEY_STATE_FILE")
780
+ if [ "$SAVED_APP_ID" != "$APP_ID" ] || [ -z "$CONFIRM_PAGE_URL" ]; then
781
+ echo "❌ 当前 appId 与已生成的公钥确认页上下文不一致,已停止打开"
782
+ emit_control "FLOW:ERROR"
783
+ return 1
784
+ fi
639
785
 
640
- if [ -z "$APP_ID" ]; then
641
- echo "❌ 请提供 appId"
642
- echo "用法: bash app.sh audit <appId>"
643
- exit 1
786
+ MESSAGE_INPUT=$(printf '%s\n' "$APP_ID" "$CONFIRM_PAGE_URL" | jq -Rs '
787
+ split("\n") as $v | {appId:$v[0],officialUrl:$v[1]}
788
+ ')
789
+ if ! printf '%s' "$MESSAGE_INPUT" | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=app.sh node "$RENDERER" application.key.page --variant READY >/dev/null; then
790
+ echo "❌ 公钥确认页 URL 校验失败,已停止打开"
791
+ emit_control "FLOW:ERROR"
792
+ return 1
644
793
  fi
645
794
 
646
- verify_key_and_export_alipay_public_key "$APP_ID" || exit 1
795
+ OPEN_HELPER="${SCRIPT_DIR}/../../../normal/scripts/open_official_url.sh"
796
+ OPEN_STATUS=$(printf '%s\n' "$CONFIRM_PAGE_URL" | bash "$OPEN_HELPER" public-key-confirmation)
797
+ case "$OPEN_STATUS" in OPENED|OPEN_FAILED|GUI_UNAVAILABLE|LINK_ONLY) ;; *) OPEN_STATUS="OPEN_FAILED" ;; esac
798
+ emit_control "KEY_OPEN:${OPEN_STATUS}"
799
+ }
647
800
 
801
+ submit_application_audit() {
802
+ local APP_ID="$1"
648
803
  REQUEST_JSON=$(jq -n --arg appId "$APP_ID" '{request:{appId:$appId}}')
649
804
 
650
- RESULT=$(export PLATFORM=${DEV_TOOL_NAME} && alipay-cli mcp call apprelease.submitApplicationAudit \
805
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
806
+ run_network_retry RESULT write application_audit -- alipay-cli mcp call apprelease.submitApplicationAudit \
651
807
  -d "$REQUEST_JSON" \
652
- --json 2>/dev/null)
808
+ --json
809
+ RETRY_RC=$?
810
+ if [ "$RETRY_RC" -eq 75 ]; then
811
+ if query_application_info "$APP_ID"; then
812
+ ACTUAL_STATUS=$(extract_application_status)
813
+ case "$ACTUAL_STATUS" in
814
+ AUDITING|ON_LINE)
815
+ emit_control "✅ 应用审核提交已由应用信息查询核验成功"
816
+ emit_control_field "APP_ID" "$APP_ID" || return 1
817
+ emit_control "FLOW:AUDIT_SUBMITTED"
818
+ return 0
819
+ ;;
820
+ esac
821
+ fi
822
+ echo "❌ 应用提审响应不明且现有状态无法排除传播延迟,结果标记为 UNKNOWN,禁止自动重复提审"
823
+ exit 1
824
+ elif [ "$RETRY_RC" -ne 0 ]; then
825
+ echo "❌ 应用提审因明确未发送的网络异常在自动重试两次后仍未恢复"
826
+ exit 1
827
+ fi
653
828
 
654
829
  # 错误检测
655
830
  if ! handle_error "$RESULT"; then
@@ -661,16 +836,32 @@ app_audit() {
661
836
  SUCCESS=$(echo "$BUSINESS" | jq -r '.success // false')
662
837
 
663
838
  if [ "$SUCCESS" = "true" ]; then
664
- echo "✅ 应用审核提交成功"
665
- echo "APP_ID=$APP_ID"
666
- echo "FLOW:AUDIT_SUBMITTED"
839
+ emit_control "✅ 应用审核提交成功"
840
+ emit_control_field "APP_ID" "$APP_ID" || return 1
841
+ emit_control "FLOW:AUDIT_SUBMITTED"
667
842
  else
668
843
  ERROR_MSG=$(echo "$BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
844
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
669
845
  echo "❌ 提交审核失败: $ERROR_MSG"
670
846
  exit 1
671
847
  fi
672
848
  }
673
849
 
850
+ # ─── app audit: 提交应用审核 ─────────────────────────────────────────────────
851
+ app_audit() {
852
+ if [ "$#" -ne 1 ] || [[ "${1:-}" == --* ]]; then
853
+ echo "❌ 参数格式错误:audit 使用位置参数 <appId>,不支持 --app-id 等未定义选项"
854
+ echo "用法: bash app.sh audit <appId>"
855
+ exit 1
856
+ fi
857
+
858
+ APP_ID="$1"
859
+ [ -n "$APP_ID" ] || { echo "❌ 请提供 appId"; echo "用法: bash app.sh audit <appId>"; exit 1; }
860
+ validate_control_value "$APP_ID" || { echo "❌ appId 包含非法控制字符"; exit 1; }
861
+ verify_key_and_export_alipay_public_key "$APP_ID" || exit 1
862
+ submit_application_audit "$APP_ID"
863
+ }
864
+
674
865
  # ─── app verify-key: 确认应用公钥已设置 ─────────────────────────────────────
675
866
  app_verify_key() {
676
867
  if [ "$#" -lt 1 ] || [ "$#" -gt 2 ] || [[ "${1:-}" == --* ]]; then
@@ -687,17 +878,54 @@ app_verify_key() {
687
878
  echo "用法: bash app.sh verify-key <appId> [publicKey]"
688
879
  exit 1
689
880
  fi
881
+ validate_control_value "$APP_ID" || { echo "❌ appId 包含非法控制字符"; exit 1; }
690
882
 
691
883
  if ensure_rsa2_application_key_ready "$APP_ID" "$PUBLIC_KEY"; then
692
- echo "✅ 应用公钥已确认"
693
- echo "APP_ID=$APP_ID"
694
- echo "FLOW:KEY_CONFIRMED"
884
+ emit_control "✅ 应用公钥已确认"
885
+ emit_control_field "APP_ID" "$APP_ID" || exit 1
886
+ emit_control "FLOW:KEY_CONFIRMED"
695
887
  else
696
- echo "FLOW:KEY_NOT_CONFIRMED"
888
+ if [ "$KEY_VERIFY_RESULT" = "PENDING" ]; then
889
+ if ! render_key_pending; then
890
+ emit_control "FLOW:ERROR"
891
+ exit 1
892
+ fi
893
+ emit_control "FLOW:KEY_NOT_CONFIRMED"
894
+ else
895
+ emit_control "FLOW:ERROR"
896
+ fi
697
897
  exit 1
698
898
  fi
699
899
  }
700
900
 
901
+ app_verify_key_and_audit() {
902
+ if [ "$#" -lt 1 ] || [ "$#" -gt 2 ] || [[ "${1:-}" == --* ]]; then
903
+ echo "❌ 参数格式错误:verify-key-and-audit 使用位置参数 <appId> [publicKey],不支持 --app-id 等未定义选项"
904
+ echo "用法: bash app.sh verify-key-and-audit <appId> [publicKey]"
905
+ exit 1
906
+ fi
907
+
908
+ APP_ID="$1"
909
+ PUBLIC_KEY="${2:-}"
910
+ [ -n "$APP_ID" ] || { echo "❌ 请提供 appId"; exit 1; }
911
+ validate_control_value "$APP_ID" || { echo "❌ appId 包含非法控制字符"; exit 1; }
912
+
913
+ if ! ensure_rsa2_application_key_ready "$APP_ID" "$PUBLIC_KEY"; then
914
+ if [ "$KEY_VERIFY_RESULT" = "PENDING" ]; then
915
+ if ! render_key_pending; then
916
+ emit_control "FLOW:ERROR"
917
+ exit 1
918
+ fi
919
+ emit_control "FLOW:KEY_NOT_CONFIRMED"
920
+ else
921
+ emit_control "FLOW:ERROR"
922
+ fi
923
+ exit 1
924
+ fi
925
+ export_alipay_public_key_from_security_business "$APP_ID" || exit 1
926
+ submit_application_audit "$APP_ID"
927
+ }
928
+
701
929
  # ─── app reuse: 复用已有应用(查询安全密钥) ──────────────────────────────────
702
930
  app_reuse() {
703
931
  if [ "$#" -ne 1 ] || [[ "${1:-}" == --* ]]; then
@@ -713,16 +941,17 @@ app_reuse() {
713
941
  echo "用法: bash app.sh reuse <appId>"
714
942
  exit 1
715
943
  fi
944
+ validate_control_value "$APP_ID" || { echo "❌ appId 包含非法控制字符"; exit 1; }
716
945
 
717
946
  if query_application_info "$APP_ID"; then
718
947
  APP_STATUS=$(extract_application_status)
719
948
  if [ "$APP_STATUS" != "ON_LINE" ]; then
720
949
  echo "❌ 仅允许复用 ON_LINE(已上线)应用,当前应用状态: ${APP_STATUS:-未知}"
721
- echo "FLOW:ERROR"
950
+ emit_control "FLOW:ERROR"
722
951
  exit 1
723
952
  fi
724
953
  else
725
- echo "FLOW:ERROR"
954
+ emit_control "FLOW:ERROR"
726
955
  exit 1
727
956
  fi
728
957
 
@@ -732,27 +961,27 @@ app_reuse() {
732
961
  ALIPAY_PUBLIC_KEY=$(extract_alipay_public_key "$RSA2_ALIPAY_KEY_INFO")
733
962
 
734
963
  if ! is_rsa2_application_key_ready "$RSA2_KEY_INFO"; then
735
- echo "⚠️ 未确认到已生效的 RSA2 应用公钥"
736
- echo "FLOW:REUSE_NO_KEY"
964
+ emit_control "⚠️ 未确认到已生效的 RSA2 应用公钥"
965
+ emit_control "FLOW:REUSE_NO_KEY"
737
966
  exit 1
738
967
  elif [ -n "$ALIPAY_PUBLIC_KEY" ]; then
739
968
  if write_alipay_public_key "$APP_ID" "$ALIPAY_PUBLIC_KEY"; then
740
- echo "✅ 已获取支付宝公钥"
741
- echo "支付宝公钥保存路径: ~/.config/${APP_ID}-alipayPublicKey.keytext"
742
- echo "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=EXPORTED"
743
- echo "ALIPAY_PUBLIC_KEY_FILE=~/.config/${APP_ID}-alipayPublicKey.keytext"
969
+ emit_control "✅ 已获取支付宝公钥"
970
+ emit_control "支付宝公钥保存路径: ~/.config/${APP_ID}-alipayPublicKey.keytext"
971
+ emit_control "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=EXPORTED"
972
+ emit_control "ALIPAY_PUBLIC_KEY_FILE=~/.config/${APP_ID}-alipayPublicKey.keytext"
744
973
  else
745
- echo "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=MANUAL_CONFIGURATION_REQUIRED"
974
+ emit_control "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=MANUAL_CONFIGURATION_REQUIRED"
746
975
  fi
747
- echo "APP_ID=$APP_ID"
748
- echo "FLOW:REUSE_SUCCESS"
976
+ emit_control_field "APP_ID" "$APP_ID" || exit 1
977
+ emit_control "FLOW:REUSE_SUCCESS"
749
978
  else
750
979
  echo "❌ 已确认应用公钥生效,但未获取到 RSA2 支付宝公钥"
751
- echo "FLOW:ERROR"
980
+ emit_control "FLOW:ERROR"
752
981
  exit 1
753
982
  fi
754
983
  else
755
- echo "FLOW:ERROR"
984
+ emit_control "FLOW:ERROR"
756
985
  exit 1
757
986
  fi
758
987
  }
@@ -760,7 +989,7 @@ app_reuse() {
760
989
  # ─── 入口分发 ────────────────────────────────────────────────────────────────
761
990
  APP_COMMAND="${1:-}"
762
991
  case "$APP_COMMAND" in
763
- list|create|key|verify-key|audit|reuse)
992
+ list|create|key|verify-key|verify-key-and-audit|audit|reuse)
764
993
  require_command alipay-cli || exit 1
765
994
  ;;
766
995
  esac
@@ -778,10 +1007,18 @@ case "$APP_COMMAND" in
778
1007
  shift
779
1008
  app_key "$@"
780
1009
  ;;
1010
+ open-key-page)
1011
+ shift
1012
+ app_open_key_page "$@"
1013
+ ;;
781
1014
  verify-key)
782
1015
  shift
783
1016
  app_verify_key "$@"
784
1017
  ;;
1018
+ verify-key-and-audit)
1019
+ shift
1020
+ app_verify_key_and_audit "$@"
1021
+ ;;
785
1022
  audit)
786
1023
  shift
787
1024
  app_audit "$@"
@@ -791,12 +1028,14 @@ case "$APP_COMMAND" in
791
1028
  app_reuse "$@"
792
1029
  ;;
793
1030
  *)
794
- echo "用法: bash app.sh <list|create|key|verify-key|audit|reuse> [参数...]"
1031
+ echo "用法: bash app.sh <list|create|key|open-key-page|verify-key|verify-key-and-audit|audit|reuse> [参数...]"
795
1032
  echo ""
796
1033
  echo " app list [--product-type <type> --sales-code <code> --application-type <type>] - 查询已有应用"
797
1034
  echo " app create [--product-type <type> --sales-code <code> --application-type <type>] [--mobile-platform IOS|ANDROID|ALL ...] - 创建新应用"
798
- echo " app key <appId> <pubKey> - 设置公钥"
1035
+ echo " app key --defer-open <appId> <pubKey> - 设置公钥"
1036
+ echo " app open-key-page <appId> - 打开已生成的公钥确认页"
799
1037
  echo " app verify-key <appId> [pubKey] - 确认公钥设置结果"
1038
+ echo " app verify-key-and-audit <appId> [pubKey] - 确认公钥并提交审核"
800
1039
  echo " app audit <appId> - 提交审核"
801
1040
  echo " app reuse <appId> - 复用应用"
802
1041
  exit 1