@alipay/alipay-aipay 1.2.2 → 1.5.0

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 (104) 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 +9 -5
  25. package/skill/SKILL.md +15 -169
  26. package/skill/VERSION +1 -1
  27. package/skill/references/integration/flow.md +388 -291
  28. package/skill/references/integration/modules/alipay-sdk-reminder.md +5 -3
  29. package/skill/references/integration/modules/checklist.md +54 -32
  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 +52 -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 +38 -71
  56. package/skill/references/integration/modules/product-decision.md +18 -35
  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 +358 -148
  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 +82 -0
  65. package/skill/references/normal/json/customer-messages.json +942 -0
  66. package/skill/references/normal/json/policy-rules.json +179 -0
  67. package/skill/references/normal/rejection-guide.md +12 -7
  68. package/skill/references/normal/scripts/alipay_cli_env.mjs +33 -0
  69. package/skill/references/normal/scripts/alipay_cli_env.sh +55 -0
  70. package/skill/references/normal/scripts/common.sh +151 -0
  71. package/skill/references/normal/scripts/detect_dev_tool.sh +5 -1
  72. package/skill/references/normal/scripts/flow_loader.mjs +241 -0
  73. package/skill/references/normal/scripts/integration_context_runner.mjs +166 -0
  74. package/skill/references/normal/scripts/integration_message_runner.mjs +1023 -0
  75. package/skill/references/normal/scripts/interaction_reply.mjs +133 -0
  76. package/skill/references/normal/scripts/onboarding_discovery_runner.mjs +224 -0
  77. package/skill/references/normal/scripts/onboarding_message_runner.mjs +451 -0
  78. package/skill/references/normal/scripts/onboarding_recovery_runner.mjs +96 -0
  79. package/skill/references/normal/scripts/open_official_url.sh +82 -0
  80. package/skill/references/normal/scripts/platform_compat.mjs +39 -0
  81. package/skill/references/normal/scripts/project_route_inspector.sh +132 -0
  82. package/skill/references/normal/scripts/python_script_runner.mjs +56 -0
  83. package/skill/references/normal/scripts/render_customer_message.mjs +1288 -0
  84. package/skill/references/normal/scripts/self_update.mjs +160 -0
  85. package/skill/references/normal/scripts/telemetry.mjs +1327 -0
  86. package/skill/references/normal/self-update.md +18 -51
  87. package/skill/references/normal/telemetry.md +26 -0
  88. package/skill/references/onboarding/flow.md +421 -309
  89. package/skill/references/onboarding/modules/app-release.md +44 -438
  90. package/skill/references/onboarding/modules/authorization.md +25 -281
  91. package/skill/references/onboarding/modules/error-handling.md +31 -237
  92. package/skill/references/onboarding/modules/product-sign.md +25 -294
  93. package/skill/references/onboarding/modules/scripts/app.sh +445 -169
  94. package/skill/references/onboarding/modules/scripts/ar_sign_apply.sh +47 -28
  95. package/skill/references/onboarding/modules/scripts/auth.sh +484 -167
  96. package/skill/references/onboarding/modules/scripts/download_key_tool.sh +157 -0
  97. package/skill/references/onboarding/modules/scripts/error_handler.sh +101 -130
  98. package/skill/references/onboarding/modules/scripts/network_retry.sh +96 -0
  99. package/skill/references/onboarding/modules/scripts/query_sign_status.sh +20 -71
  100. package/skill/references/onboarding/modules/scripts/service.sh +154 -89
  101. package/skill/references/onboarding/modules/scripts/sign_status_parser.sh +58 -0
  102. package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +34 -20
  103. package/skill/references/onboarding/modules/service-registration.md +20 -317
  104. package/skill/references/runtime-entry.md +63 -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
7
- # list/create 可传 --application-type,未传时按 --product-type/--sales-code 或兼容环境变量推断
6
+ # 调用前置: 脚本通过 error_handler.sh 间接初始化 DEV_TOOL_NAME/PLATFORM_ID
7
+ # 所有 CLI 子命令都接收 --product-type;appId/publicKey 仍使用原位置参数
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> - 设置应用公钥
12
- # app verify-key <appId> [publicKey] - 确认应用公钥已设置
13
- # app audit <appId> - 提交应用审核
14
- # app reuse <appId> - 复用已有应用(查询安全密钥)
11
+ # app key --product-type <type> --defer-open <appId> <publicKey> - 设置应用公钥
12
+ # app open-key-page <appId> - 打开上一次生成的公钥确认页
13
+ # app verify-key --product-type <type> <appId> [publicKey] - 确认应用公钥已设置
14
+ # app verify-key-and-audit --product-type <type> <appId> [publicKey] - 复用最终密钥查询结果并提交审核
15
+ # app audit --product-type <type> <appId> - 提交应用审核
16
+ # app reuse --product-type <type> <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
@@ -182,6 +258,31 @@ validate_app_context() {
182
258
  fi
183
259
  }
184
260
 
261
+ init_app_cli_product() {
262
+ local resolved_product_type="${PRODUCT_TYPE:-}"
263
+
264
+ if [ -z "$resolved_product_type" ] && [ -n "${SALES_CODE:-}" ]; then
265
+ resolved_product_type="$(alipay_product_type_for_sales_code "$SALES_CODE")" || return 1
266
+ fi
267
+ if [ -z "$resolved_product_type" ]; then
268
+ resolved_product_type="${ALIPAY_AIPAY_PRODUCT_TYPE:-}"
269
+ fi
270
+ PRODUCT_TYPE="$resolved_product_type"
271
+ validate_app_context || return 1
272
+ init_alipay_cli_product "$PRODUCT_TYPE"
273
+ }
274
+
275
+ prepare_positional_app_cli_context() {
276
+ PRODUCT_TYPE="${ALIPAY_AIPAY_PRODUCT_TYPE:-}"
277
+ if [ "${1:-}" = "--product-type" ]; then
278
+ [ "$#" -ge 2 ] || { echo "❌ 缺少参数值: $1"; return 1; }
279
+ PRODUCT_TYPE="$2"
280
+ shift 2
281
+ fi
282
+ init_app_cli_product || return 1
283
+ APP_POSITIONAL_ARGS=("$@")
284
+ }
285
+
185
286
  resolve_application_type() {
186
287
  validate_app_context || return 1
187
288
 
@@ -213,9 +314,15 @@ query_security_key() {
213
314
  REQUEST_JSON=$(jq -n --arg appId "$APP_ID" '{request:{appId:$appId}}')
214
315
  fi
215
316
 
216
- RESULT=$(export PLATFORM=${DEV_TOOL_NAME} && alipay-cli mcp call apprelease.queryApplicationSecurityKey \
317
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
318
+ run_network_retry RESULT read application_security_key -- alipay-cli mcp call apprelease.queryApplicationSecurityKey \
217
319
  -d "$REQUEST_JSON" \
218
- --json 2>/dev/null)
320
+ --json
321
+ RETRY_RC=$?
322
+ if [ "$RETRY_RC" -ne 0 ]; then
323
+ echo "❌ 应用安全密钥查询因网络或服务异常在自动重试两次后仍未恢复"
324
+ return 1
325
+ fi
219
326
 
220
327
  if ! handle_error "$RESULT"; then
221
328
  return 1
@@ -226,6 +333,7 @@ query_security_key() {
226
333
 
227
334
  if [ "$SUCCESS" != "true" ]; then
228
335
  ERROR_MSG=$(echo "$SECURITY_BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
336
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
229
337
  echo "❌ 查询安全密钥失败: $ERROR_MSG"
230
338
  return 1
231
339
  fi
@@ -235,9 +343,15 @@ query_application_info() {
235
343
  local APP_ID="$1"
236
344
 
237
345
  REQUEST_JSON=$(jq -n --arg appId "$APP_ID" '{request:{appId:$appId}}')
238
- RESULT=$(export PLATFORM=${DEV_TOOL_NAME} && alipay-cli mcp call apprelease.queryApplicationInfo \
346
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
347
+ run_network_retry RESULT read application_info -- alipay-cli mcp call apprelease.queryApplicationInfo \
239
348
  -d "$REQUEST_JSON" \
240
- --json 2>/dev/null)
349
+ --json
350
+ RETRY_RC=$?
351
+ if [ "$RETRY_RC" -ne 0 ]; then
352
+ echo "❌ 应用信息查询因网络或服务异常在自动重试两次后仍未恢复"
353
+ return 1
354
+ fi
241
355
 
242
356
  if ! handle_error "$RESULT"; then
243
357
  return 1
@@ -248,6 +362,7 @@ query_application_info() {
248
362
 
249
363
  if [ "$SUCCESS" != "true" ]; then
250
364
  ERROR_MSG=$(echo "$APPLICATION_INFO_BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
365
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
251
366
  echo "❌ 查询应用信息失败: $ERROR_MSG"
252
367
  return 1
253
368
  fi
@@ -324,20 +439,20 @@ write_alipay_public_key() {
324
439
  fi
325
440
 
326
441
  while [ "$ATTEMPT" -le "$MAX_ATTEMPTS" ]; do
327
- if { mkdir -p "${HOME}/.config" && printf '%s\n' "$ALIPAY_PUBLIC_KEY" > "$KEY_FILE"; } 2>/dev/null; then
442
+ 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
443
  return 0
329
444
  fi
330
445
 
331
446
  if [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; then
332
- echo "⚠️ 支付宝公钥文件写入失败,正在重试(${ATTEMPT}/${MAX_ATTEMPTS})..."
447
+ emit_control "⚠️ 支付宝公钥文件写入失败,正在重试(${ATTEMPT}/${MAX_ATTEMPTS})..."
333
448
  sleep 1
334
449
  fi
335
450
  ATTEMPT=$((ATTEMPT + 1))
336
451
  done
337
452
 
338
- echo "⚠️ 已获取到 RSA2 支付宝公钥,但本地文件写入失败: $KEY_FILE"
339
- echo "📋 这不是公钥查询失败;当前流程会继续,但后续集成时需要手动配置支付宝公钥。"
340
- echo "📋 可检查本地目录权限或设置 HOME 后重新执行导出。"
453
+ emit_control "⚠️ 已获取到 RSA2 支付宝公钥,但本地文件写入失败: $KEY_FILE"
454
+ emit_control "📋 这不是公钥查询失败;当前流程会继续,但后续集成时需要手动配置支付宝公钥。"
455
+ emit_control "📋 可检查本地目录权限或设置 HOME 后重新执行导出。"
341
456
  return 1
342
457
  }
343
458
 
@@ -345,42 +460,50 @@ ensure_rsa2_application_key_ready() {
345
460
  local APP_ID="$1"
346
461
  local PUBLIC_KEY="${2:-}"
347
462
  local ATTEMPT=1
348
- local MAX_ATTEMPTS="${APP_KEY_VERIFY_RETRIES:-6}"
349
- local INTERVAL_SECONDS="${APP_KEY_VERIFY_INTERVAL_SECONDS:-5}"
463
+ local MAX_ATTEMPTS=20
464
+ local INTERVAL_SECONDS=2
465
+ KEY_VERIFY_RESULT=""
350
466
 
351
- if ! [[ "$MAX_ATTEMPTS" =~ ^[1-9][0-9]*$ ]]; then
352
- MAX_ATTEMPTS=6
467
+ if [ "${ALIPAY_AIPAY_RECOVERY_QUERY_ONCE:-0}" = "1" ]; then
468
+ MAX_ATTEMPTS=1
353
469
  fi
354
470
 
355
- if ! [[ "$INTERVAL_SECONDS" =~ ^[0-9]+$ ]]; then
356
- INTERVAL_SECONDS=5
471
+ if [ "${ALIPAY_AIPAY_TEST_MODE:-0}" = "1" ] && [[ "${APP_KEY_VERIFY_INTERVAL_SECONDS:-}" =~ ^[0-9]+$ ]]; then
472
+ INTERVAL_SECONDS="$APP_KEY_VERIFY_INTERVAL_SECONDS"
357
473
  fi
358
474
 
359
475
  while [ "$ATTEMPT" -le "$MAX_ATTEMPTS" ]; do
360
- query_security_key "$APP_ID" "$PUBLIC_KEY" || return 1
476
+ if ! query_security_key "$APP_ID" "$PUBLIC_KEY"; then
477
+ KEY_VERIFY_RESULT="QUERY_FAILED"
478
+ return 1
479
+ fi
361
480
 
362
481
  RSA2_KEY_INFO=$(extract_rsa2_application_key_info)
363
482
 
364
483
  if is_rsa2_application_key_ready "$RSA2_KEY_INFO"; then
484
+ KEY_VERIFY_RESULT="CONFIRMED"
365
485
  return 0
366
486
  fi
367
487
 
368
488
  if [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; then
369
- echo "⏳ 支付宝侧尚未确认到应用公钥生效,正在重试(${ATTEMPT}/${MAX_ATTEMPTS})..."
489
+ emit_control "KEY_POLL_PENDING=${ATTEMPT}/${MAX_ATTEMPTS}"
370
490
  sleep "$INTERVAL_SECONDS"
371
491
  fi
372
492
 
373
493
  ATTEMPT=$((ATTEMPT + 1))
374
494
  done
375
495
 
376
- echo "❌ 应用公钥尚未确认,请先完成公钥设置确认"
496
+ KEY_VERIFY_RESULT="PENDING"
377
497
  return 1
378
498
  }
379
499
 
380
- verify_key_and_export_alipay_public_key() {
381
- local APP_ID="$1"
500
+ render_key_pending() {
501
+ printf '%s' '{}' \
502
+ | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=app.sh node "$RENDERER" application.key.pending --variant DEFAULT
503
+ }
382
504
 
383
- query_security_key "$APP_ID" || return 1
505
+ export_alipay_public_key_from_security_business() {
506
+ local APP_ID="$1"
384
507
 
385
508
  RSA2_KEY_INFO=$(extract_rsa2_application_key_info)
386
509
  RSA2_ALIPAY_KEY_INFO=$(extract_rsa2_alipay_key_info)
@@ -397,17 +520,24 @@ verify_key_and_export_alipay_public_key() {
397
520
  fi
398
521
 
399
522
  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"
523
+ emit_control "✅ 已确认应用公钥并导出支付宝公钥"
524
+ emit_control "支付宝公钥保存路径: ~/.config/${APP_ID}-alipayPublicKey.keytext"
525
+ emit_control "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=EXPORTED"
526
+ emit_control "ALIPAY_PUBLIC_KEY_FILE=~/.config/${APP_ID}-alipayPublicKey.keytext"
404
527
  else
405
- echo "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=MANUAL_CONFIGURATION_REQUIRED"
528
+ emit_control "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=MANUAL_CONFIGURATION_REQUIRED"
406
529
  fi
407
530
 
408
531
  return 0
409
532
  }
410
533
 
534
+ verify_key_and_export_alipay_public_key() {
535
+ local APP_ID="$1"
536
+
537
+ query_security_key "$APP_ID" || return 1
538
+ export_alipay_public_key_from_security_business "$APP_ID"
539
+ }
540
+
411
541
  # ─── app list: 查询已有应用 ──────────────────────────────────────────────────
412
542
  app_list() {
413
543
  parse_app_context_args "$@" || {
@@ -415,11 +545,19 @@ app_list() {
415
545
  exit 1
416
546
  }
417
547
  APP_APPLICATION_TYPE=$(resolve_application_type) || exit 1
548
+ init_app_cli_product || exit 1
418
549
  REQUEST_JSON=$(jq -n --arg applicationType "$APP_APPLICATION_TYPE" '{request:{appTypes:[$applicationType]}}')
419
550
 
420
- RESULT=$(export PLATFORM=${DEV_TOOL_NAME} && alipay-cli mcp call apprelease.queryApplicationList \
551
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
552
+ run_network_retry RESULT read application_list -- alipay-cli mcp call apprelease.queryApplicationList \
421
553
  -d "$REQUEST_JSON" \
422
- --json 2>/dev/null)
554
+ --json
555
+ RETRY_RC=$?
556
+ if [ "$RETRY_RC" -ne 0 ]; then
557
+ echo "❌ 应用列表查询因网络或服务异常在自动重试两次后仍未恢复"
558
+ emit_control "FLOW:ERROR"
559
+ exit 1
560
+ fi
423
561
 
424
562
  # 错误检测
425
563
  if ! handle_error "$RESULT"; then
@@ -432,8 +570,9 @@ app_list() {
432
570
 
433
571
  if [ "$SUCCESS" != "true" ]; then
434
572
  ERROR_MSG=$(echo "$BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
573
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
435
574
  echo "❌ 查询应用列表失败: $ERROR_MSG"
436
- echo "FLOW:ERROR"
575
+ emit_control "FLOW:ERROR"
437
576
  exit 1
438
577
  fi
439
578
 
@@ -448,78 +587,56 @@ app_list() {
448
587
  ' 2>/dev/null)
449
588
  if [ -z "$APP_LIST" ] || ! echo "$APP_LIST" | jq -e 'type == "array"' >/dev/null 2>&1; then
450
589
  echo "❌ 应用列表返回结构异常,无法解析应用列表"
451
- echo "FLOW:ERROR"
590
+ emit_control "FLOW:ERROR"
452
591
  exit 1
453
592
  fi
454
593
  APP_COUNT=$(echo "$APP_LIST" | jq 'length')
594
+ APP_CANDIDATES=$(echo "$APP_LIST" | jq -c '[.[] | {
595
+ appId: ((.appId // "未知") | tostring),
596
+ appName: ((.appName // "未知") | tostring),
597
+ status: ((.status // "未知") | tostring)
598
+ }]')
599
+ ONLINE_APPS=$(echo "$APP_LIST" | jq -r '[.[] | select(.status == "ON_LINE")]')
600
+ NON_ONLINE_APPS=$(echo "$APP_LIST" | jq -r '[.[] | select(.status != "ON_LINE")]')
601
+ ONLINE_COUNT=$(echo "$ONLINE_APPS" | jq 'length')
602
+ NON_ONLINE_COUNT=$(echo "$NON_ONLINE_APPS" | jq 'length')
603
+ ONLINE_COUNT=${ONLINE_COUNT:-0}
604
+ NON_ONLINE_COUNT=${NON_ONLINE_COUNT:-0}
605
+
606
+ if [ "$ONLINE_COUNT" -gt 0 ] && ! echo "$ONLINE_APPS" | jq -e '
607
+ all(.[];
608
+ ((.appId | type) == "string") and
609
+ (.appId | length > 0) and
610
+ ((.appId | test("[\u0000-\u001f\u007f]") | not))
611
+ )
612
+ ' >/dev/null 2>&1; then
613
+ echo "❌ 应用列表返回结构异常:可复用候选 appId 缺失或包含控制字符"
614
+ emit_control "FLOW:ERROR"
615
+ exit 1
616
+ fi
617
+
618
+ if ! printf '%s' "$APP_CANDIDATES" \
619
+ | node "$CANDIDATE_RUNNER" application-candidates --application-type "$APP_APPLICATION_TYPE" >/dev/null 2>&1; then
620
+ echo "❌ 应用候选事实安全校验失败"
621
+ emit_control "FLOW:ERROR"
622
+ exit 1
623
+ fi
624
+ emit_control_field "APP_CANDIDATES_JSON" "$APP_CANDIDATES" || {
625
+ echo "❌ 应用候选事实包含非法控制字符"
626
+ emit_control "FLOW:ERROR"
627
+ exit 1
628
+ }
629
+ emit_control_field "APPLICATION_TYPE" "$APP_APPLICATION_TYPE" || exit 1
455
630
 
456
631
  if [ "$APP_COUNT" -eq 0 ]; then
457
- echo "📋 暂无 ${APP_APPLICATION_TYPE} 应用;记录新建候选分支,等待 Step 4 统一决策"
458
- echo "FLOW:CREATE_NEW"
632
+ emit_control "FLOW:CREATE_NEW"
459
633
  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
634
  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"
635
+ emit_control "FLOW:PENDING_APPLICATIONS"
483
636
  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"
637
+ echo "$ONLINE_APPS" | jq -r '.[] | select((.appId // "") != "") | "APP_CANDIDATE_ID=" + .appId' \
638
+ | while IFS= read -r marker; do emit_control "$marker"; done
639
+ emit_control "FLOW:SELECT"
523
640
  fi
524
641
  fi
525
642
  }
@@ -531,15 +648,27 @@ app_create() {
531
648
  exit 1
532
649
  }
533
650
  APP_APPLICATION_TYPE=$(resolve_application_type) || exit 1
651
+ init_app_cli_product || exit 1
534
652
  validate_mobile_platform_args "$APP_APPLICATION_TYPE" || exit 1
535
653
  REQUEST_JSON=$(build_create_application_request "$APP_APPLICATION_TYPE") || {
536
654
  echo "❌ 创建应用请求参数组装失败"
537
655
  exit 1
538
656
  }
539
657
 
540
- RESULT=$(export PLATFORM=${DEV_TOOL_NAME} && alipay-cli mcp call apprelease.createApplication \
658
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
659
+ run_network_retry RESULT write application_create -- alipay-cli mcp call apprelease.createApplication \
541
660
  -d "$REQUEST_JSON" \
542
- --json 2>/dev/null)
661
+ --json
662
+ RETRY_RC=$?
663
+ if [ "$RETRY_RC" -eq 75 ]; then
664
+ echo "❌ 应用创建响应不明;现有应用列表无法把同类型新应用唯一归属于本次创建,结果标记为 UNKNOWN,禁止自动重复创建"
665
+ emit_control "FLOW:ERROR"
666
+ exit 1
667
+ elif [ "$RETRY_RC" -ne 0 ]; then
668
+ echo "❌ 应用创建因明确未发送的网络异常在自动重试两次后仍未恢复"
669
+ emit_control "FLOW:ERROR"
670
+ exit 1
671
+ fi
543
672
 
544
673
  # 错误检测
545
674
  if ! handle_error "$RESULT"; then
@@ -552,16 +681,17 @@ app_create() {
552
681
 
553
682
  if [ "$SUCCESS" = "true" ]; then
554
683
  APP_ID=$(echo "$BUSINESS" | jq -r '.resultObj.appId // .data.appId // empty')
555
- if [ -z "$APP_ID" ]; then
684
+ if ! validate_control_value "$APP_ID"; then
556
685
  echo "❌ 应用创建返回成功,但未解析到 appId,禁止进入公钥设置流程"
557
- echo "FLOW:ERROR"
686
+ emit_control "FLOW:ERROR"
558
687
  exit 1
559
688
  fi
560
- echo "✅ 应用创建成功"
561
- echo "APPLICATION_TYPE=$APP_APPLICATION_TYPE"
562
- echo "APP_ID=$APP_ID"
689
+ emit_control "✅ 应用创建成功"
690
+ emit_control_field "APPLICATION_TYPE" "$APP_APPLICATION_TYPE" || exit 1
691
+ emit_control_field "APP_ID" "$APP_ID" || exit 1
563
692
  else
564
693
  ERROR_MSG=$(echo "$BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
694
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
565
695
  echo "❌ 应用创建失败: $ERROR_MSG"
566
696
  exit 1
567
697
  fi
@@ -569,9 +699,19 @@ app_create() {
569
699
 
570
700
  # ─── app key: 设置应用公钥 ───────────────────────────────────────────────────
571
701
  app_key() {
702
+ prepare_positional_app_cli_context "$@" || exit 1
703
+ set -- "${APP_POSITIONAL_ARGS[@]}"
704
+ if [ "${1:-}" = "--defer-open" ]; then
705
+ shift
706
+ else
707
+ echo "❌ 参数格式错误:app key 必须使用 --defer-open,确保先展示公钥确认说明和链接,再自动打开页面"
708
+ echo "用法: bash app.sh key --product-type <type> --defer-open <appId> <publicKey>"
709
+ exit 1
710
+ fi
711
+
572
712
  if [ "$#" -ne 2 ] || [[ "${1:-}" == --* ]]; then
573
- echo "❌ 参数格式错误:key 使用位置参数 <appId> <publicKey>,不支持 --app-id 等未定义选项"
574
- echo "用法: bash app.sh key <appId> <publicKey>"
713
+ echo "❌ 参数格式错误:key 使用位置参数 <appId> <publicKey>"
714
+ echo "用法: bash app.sh key --product-type <type> --defer-open <appId> <publicKey>"
575
715
  exit 1
576
716
  fi
577
717
 
@@ -580,7 +720,11 @@ app_key() {
580
720
 
581
721
  if [ -z "$APP_ID" ] || [ -z "$PUBLIC_KEY" ]; then
582
722
  echo "❌ 请提供 appId 和 publicKey"
583
- echo "用法: bash app.sh key <appId> <publicKey>"
723
+ echo "用法: bash app.sh key --product-type <type> --defer-open <appId> <publicKey>"
724
+ exit 1
725
+ fi
726
+ if ! validate_control_value "$APP_ID"; then
727
+ echo "❌ appId 包含非法控制字符"
584
728
  exit 1
585
729
  fi
586
730
 
@@ -589,9 +733,18 @@ app_key() {
589
733
  --arg publicKey "$PUBLIC_KEY" \
590
734
  '{request:{appId:$appId,signType:"RSA2",publicKey:$publicKey}}')
591
735
 
592
- RESULT=$(export PLATFORM=${DEV_TOOL_NAME} && alipay-cli mcp call apprelease.createKeyConfirmPage \
736
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
737
+ run_network_retry RESULT write application_key_confirm_page -- alipay-cli mcp call apprelease.createKeyConfirmPage \
593
738
  -d "$REQUEST_JSON" \
594
- --json 2>/dev/null)
739
+ --json
740
+ RETRY_RC=$?
741
+ if [ "$RETRY_RC" -eq 75 ]; then
742
+ echo "❌ 公钥确认页生成响应不明,结果标记为 UNKNOWN;不得自动生成第二个页面"
743
+ exit 1
744
+ elif [ "$RETRY_RC" -ne 0 ]; then
745
+ echo "❌ 公钥确认页因明确未发送的网络异常在自动重试两次后仍未恢复"
746
+ exit 1
747
+ fi
595
748
 
596
749
  # 错误检测
597
750
  if ! handle_error "$RESULT"; then
@@ -606,50 +759,101 @@ app_key() {
606
759
  CONFIRM_PAGE_URL=$(echo "$BUSINESS" | jq -r '.resultObj.confirmPageUrl // empty')
607
760
 
608
761
  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 "确认完成后,请告诉我\"好了\"继续后续流程。"
762
+ RENDERER="${SCRIPT_DIR}/../../../normal/scripts/render_customer_message.mjs"
763
+ save_app_key_page_state || exit 1
764
+ MESSAGE_INPUT=$(printf '%s\n' "$APP_ID" "$CONFIRM_PAGE_URL" | jq -Rs '
765
+ split("\n") as $v | {appId:$v[0],officialUrl:$v[1]}
766
+ ')
767
+ if ! printf '%s' "$MESSAGE_INPUT" | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=app.sh node "$RENDERER" application.key.page --variant READY; then
768
+ echo "❌ 公钥确认页已生成,但标准消息无法安全输出;结果保持待核验,禁止自动生成第二个页面"
769
+ exit 1
770
+ fi
771
+ emit_control "KEY_PAGE:READY_TO_OPEN"
619
772
  else
620
- echo "❌ 未获取到确认页面链接,请重试"
773
+ echo "❌ 公钥确认页响应缺少 confirmPageUrl,结果标记为 UNKNOWN;不得自动生成第二个页面"
621
774
  exit 1
622
775
  fi
623
776
  else
624
777
  ERROR_MSG=$(echo "$BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
778
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
625
779
  echo "❌ 公钥设置失败: $ERROR_MSG"
626
780
  exit 1
627
781
  fi
628
782
  }
629
783
 
630
- # ─── app audit: 提交应用审核 ─────────────────────────────────────────────────
631
- app_audit() {
784
+ app_open_key_page() {
785
+ local APP_ID="$1" SAVED_APP_ID CONFIRM_PAGE_URL MESSAGE_INPUT OPEN_HELPER OPEN_STATUS
786
+
632
787
  if [ "$#" -ne 1 ] || [[ "${1:-}" == --* ]]; then
633
- echo "❌ 参数格式错误:audit 使用位置参数 <appId>,不支持 --app-id 等未定义选项"
634
- echo "用法: bash app.sh audit <appId>"
788
+ echo "❌ 参数格式错误:open-key-page 使用位置参数 <appId>,不支持 --app-id 等未定义选项"
789
+ echo "用法: bash app.sh open-key-page <appId>"
790
+ exit 1
791
+ fi
792
+ if ! validate_control_value "$APP_ID"; then
793
+ echo "❌ appId 包含非法控制字符"
635
794
  exit 1
636
795
  fi
796
+ if ! validate_app_key_state_file; then
797
+ echo "❌ 缺少待打开的公钥确认页上下文,请重新执行 app.sh key --defer-open <appId> <publicKey>"
798
+ emit_control "FLOW:ERROR"
799
+ return 1
800
+ fi
801
+ if ! jq -e . "$APP_KEY_STATE_FILE" >/dev/null 2>&1; then
802
+ echo "❌ 公钥确认页状态文件不是合法 JSON,已停止"
803
+ emit_control "FLOW:ERROR"
804
+ return 1
805
+ fi
637
806
 
638
- APP_ID="$1"
807
+ SAVED_APP_ID=$(jq -r '.appId // ""' "$APP_KEY_STATE_FILE")
808
+ CONFIRM_PAGE_URL=$(jq -r '.confirmPageUrl // ""' "$APP_KEY_STATE_FILE")
809
+ if [ "$SAVED_APP_ID" != "$APP_ID" ] || [ -z "$CONFIRM_PAGE_URL" ]; then
810
+ echo "❌ 当前 appId 与已生成的公钥确认页上下文不一致,已停止打开"
811
+ emit_control "FLOW:ERROR"
812
+ return 1
813
+ fi
639
814
 
640
- if [ -z "$APP_ID" ]; then
641
- echo "❌ 请提供 appId"
642
- echo "用法: bash app.sh audit <appId>"
643
- exit 1
815
+ MESSAGE_INPUT=$(printf '%s\n' "$APP_ID" "$CONFIRM_PAGE_URL" | jq -Rs '
816
+ split("\n") as $v | {appId:$v[0],officialUrl:$v[1]}
817
+ ')
818
+ if ! printf '%s' "$MESSAGE_INPUT" | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=app.sh node "$RENDERER" application.key.page --variant READY >/dev/null; then
819
+ echo "❌ 公钥确认页 URL 校验失败,已停止打开"
820
+ emit_control "FLOW:ERROR"
821
+ return 1
644
822
  fi
645
823
 
646
- verify_key_and_export_alipay_public_key "$APP_ID" || exit 1
824
+ OPEN_HELPER="${SCRIPT_DIR}/../../../normal/scripts/open_official_url.sh"
825
+ OPEN_STATUS=$(printf '%s\n' "$CONFIRM_PAGE_URL" | bash "$OPEN_HELPER" public-key-confirmation)
826
+ case "$OPEN_STATUS" in OPENED|OPEN_FAILED|GUI_UNAVAILABLE|LINK_ONLY) ;; *) OPEN_STATUS="OPEN_FAILED" ;; esac
827
+ emit_control "KEY_OPEN:${OPEN_STATUS}"
828
+ }
647
829
 
830
+ submit_application_audit() {
831
+ local APP_ID="$1"
648
832
  REQUEST_JSON=$(jq -n --arg appId "$APP_ID" '{request:{appId:$appId}}')
649
833
 
650
- RESULT=$(export PLATFORM=${DEV_TOOL_NAME} && alipay-cli mcp call apprelease.submitApplicationAudit \
834
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
835
+ run_network_retry RESULT write application_audit -- alipay-cli mcp call apprelease.submitApplicationAudit \
651
836
  -d "$REQUEST_JSON" \
652
- --json 2>/dev/null)
837
+ --json
838
+ RETRY_RC=$?
839
+ if [ "$RETRY_RC" -eq 75 ]; then
840
+ if query_application_info "$APP_ID"; then
841
+ ACTUAL_STATUS=$(extract_application_status)
842
+ case "$ACTUAL_STATUS" in
843
+ AUDITING|ON_LINE)
844
+ emit_control "✅ 应用审核提交已由应用信息查询核验成功"
845
+ emit_control_field "APP_ID" "$APP_ID" || return 1
846
+ emit_control "FLOW:AUDIT_SUBMITTED"
847
+ return 0
848
+ ;;
849
+ esac
850
+ fi
851
+ echo "❌ 应用提审响应不明且现有状态无法排除传播延迟,结果标记为 UNKNOWN,禁止自动重复提审"
852
+ exit 1
853
+ elif [ "$RETRY_RC" -ne 0 ]; then
854
+ echo "❌ 应用提审因明确未发送的网络异常在自动重试两次后仍未恢复"
855
+ exit 1
856
+ fi
653
857
 
654
858
  # 错误检测
655
859
  if ! handle_error "$RESULT"; then
@@ -661,21 +865,41 @@ app_audit() {
661
865
  SUCCESS=$(echo "$BUSINESS" | jq -r '.success // false')
662
866
 
663
867
  if [ "$SUCCESS" = "true" ]; then
664
- echo "✅ 应用审核提交成功"
665
- echo "APP_ID=$APP_ID"
666
- echo "FLOW:AUDIT_SUBMITTED"
868
+ emit_control "✅ 应用审核提交成功"
869
+ emit_control_field "APP_ID" "$APP_ID" || return 1
870
+ emit_control "FLOW:AUDIT_SUBMITTED"
667
871
  else
668
872
  ERROR_MSG=$(echo "$BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
873
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
669
874
  echo "❌ 提交审核失败: $ERROR_MSG"
670
875
  exit 1
671
876
  fi
672
877
  }
673
878
 
879
+ # ─── app audit: 提交应用审核 ─────────────────────────────────────────────────
880
+ app_audit() {
881
+ prepare_positional_app_cli_context "$@" || exit 1
882
+ set -- "${APP_POSITIONAL_ARGS[@]}"
883
+ if [ "$#" -ne 1 ] || [[ "${1:-}" == --* ]]; then
884
+ echo "❌ 参数格式错误:audit 使用位置参数 <appId>,不支持 --app-id 等未定义选项"
885
+ echo "用法: bash app.sh audit --product-type <type> <appId>"
886
+ exit 1
887
+ fi
888
+
889
+ APP_ID="$1"
890
+ [ -n "$APP_ID" ] || { echo "❌ 请提供 appId"; echo "用法: bash app.sh audit --product-type <type> <appId>"; exit 1; }
891
+ validate_control_value "$APP_ID" || { echo "❌ appId 包含非法控制字符"; exit 1; }
892
+ verify_key_and_export_alipay_public_key "$APP_ID" || exit 1
893
+ submit_application_audit "$APP_ID"
894
+ }
895
+
674
896
  # ─── app verify-key: 确认应用公钥已设置 ─────────────────────────────────────
675
897
  app_verify_key() {
898
+ prepare_positional_app_cli_context "$@" || exit 1
899
+ set -- "${APP_POSITIONAL_ARGS[@]}"
676
900
  if [ "$#" -lt 1 ] || [ "$#" -gt 2 ] || [[ "${1:-}" == --* ]]; then
677
901
  echo "❌ 参数格式错误:verify-key 使用位置参数 <appId> [publicKey],不支持 --app-id 等未定义选项"
678
- echo "用法: bash app.sh verify-key <appId> [publicKey]"
902
+ echo "用法: bash app.sh verify-key --product-type <type> <appId> [publicKey]"
679
903
  exit 1
680
904
  fi
681
905
 
@@ -684,25 +908,66 @@ app_verify_key() {
684
908
 
685
909
  if [ -z "$APP_ID" ]; then
686
910
  echo "❌ 请提供 appId"
687
- echo "用法: bash app.sh verify-key <appId> [publicKey]"
911
+ echo "用法: bash app.sh verify-key --product-type <type> <appId> [publicKey]"
688
912
  exit 1
689
913
  fi
914
+ validate_control_value "$APP_ID" || { echo "❌ appId 包含非法控制字符"; exit 1; }
690
915
 
691
916
  if ensure_rsa2_application_key_ready "$APP_ID" "$PUBLIC_KEY"; then
692
- echo "✅ 应用公钥已确认"
693
- echo "APP_ID=$APP_ID"
694
- echo "FLOW:KEY_CONFIRMED"
917
+ emit_control "✅ 应用公钥已确认"
918
+ emit_control_field "APP_ID" "$APP_ID" || exit 1
919
+ emit_control "FLOW:KEY_CONFIRMED"
695
920
  else
696
- echo "FLOW:KEY_NOT_CONFIRMED"
921
+ if [ "$KEY_VERIFY_RESULT" = "PENDING" ]; then
922
+ if ! render_key_pending; then
923
+ emit_control "FLOW:ERROR"
924
+ exit 1
925
+ fi
926
+ emit_control "FLOW:KEY_NOT_CONFIRMED"
927
+ else
928
+ emit_control "FLOW:ERROR"
929
+ fi
697
930
  exit 1
698
931
  fi
699
932
  }
700
933
 
934
+ app_verify_key_and_audit() {
935
+ prepare_positional_app_cli_context "$@" || exit 1
936
+ set -- "${APP_POSITIONAL_ARGS[@]}"
937
+ if [ "$#" -lt 1 ] || [ "$#" -gt 2 ] || [[ "${1:-}" == --* ]]; then
938
+ echo "❌ 参数格式错误:verify-key-and-audit 使用位置参数 <appId> [publicKey],不支持 --app-id 等未定义选项"
939
+ echo "用法: bash app.sh verify-key-and-audit --product-type <type> <appId> [publicKey]"
940
+ exit 1
941
+ fi
942
+
943
+ APP_ID="$1"
944
+ PUBLIC_KEY="${2:-}"
945
+ [ -n "$APP_ID" ] || { echo "❌ 请提供 appId"; exit 1; }
946
+ validate_control_value "$APP_ID" || { echo "❌ appId 包含非法控制字符"; exit 1; }
947
+
948
+ if ! ensure_rsa2_application_key_ready "$APP_ID" "$PUBLIC_KEY"; then
949
+ if [ "$KEY_VERIFY_RESULT" = "PENDING" ]; then
950
+ if ! render_key_pending; then
951
+ emit_control "FLOW:ERROR"
952
+ exit 1
953
+ fi
954
+ emit_control "FLOW:KEY_NOT_CONFIRMED"
955
+ else
956
+ emit_control "FLOW:ERROR"
957
+ fi
958
+ exit 1
959
+ fi
960
+ export_alipay_public_key_from_security_business "$APP_ID" || exit 1
961
+ submit_application_audit "$APP_ID"
962
+ }
963
+
701
964
  # ─── app reuse: 复用已有应用(查询安全密钥) ──────────────────────────────────
702
965
  app_reuse() {
966
+ prepare_positional_app_cli_context "$@" || exit 1
967
+ set -- "${APP_POSITIONAL_ARGS[@]}"
703
968
  if [ "$#" -ne 1 ] || [[ "${1:-}" == --* ]]; then
704
969
  echo "❌ 参数格式错误:reuse 使用位置参数 <appId>,不支持 --app-id 等未定义选项"
705
- echo "用法: bash app.sh reuse <appId>"
970
+ echo "用法: bash app.sh reuse --product-type <type> <appId>"
706
971
  exit 1
707
972
  fi
708
973
 
@@ -710,19 +975,20 @@ app_reuse() {
710
975
 
711
976
  if [ -z "$APP_ID" ]; then
712
977
  echo "❌ 请提供 appId"
713
- echo "用法: bash app.sh reuse <appId>"
978
+ echo "用法: bash app.sh reuse --product-type <type> <appId>"
714
979
  exit 1
715
980
  fi
981
+ validate_control_value "$APP_ID" || { echo "❌ appId 包含非法控制字符"; exit 1; }
716
982
 
717
983
  if query_application_info "$APP_ID"; then
718
984
  APP_STATUS=$(extract_application_status)
719
985
  if [ "$APP_STATUS" != "ON_LINE" ]; then
720
986
  echo "❌ 仅允许复用 ON_LINE(已上线)应用,当前应用状态: ${APP_STATUS:-未知}"
721
- echo "FLOW:ERROR"
987
+ emit_control "FLOW:ERROR"
722
988
  exit 1
723
989
  fi
724
990
  else
725
- echo "FLOW:ERROR"
991
+ emit_control "FLOW:ERROR"
726
992
  exit 1
727
993
  fi
728
994
 
@@ -732,27 +998,27 @@ app_reuse() {
732
998
  ALIPAY_PUBLIC_KEY=$(extract_alipay_public_key "$RSA2_ALIPAY_KEY_INFO")
733
999
 
734
1000
  if ! is_rsa2_application_key_ready "$RSA2_KEY_INFO"; then
735
- echo "⚠️ 未确认到已生效的 RSA2 应用公钥"
736
- echo "FLOW:REUSE_NO_KEY"
1001
+ emit_control "⚠️ 未确认到已生效的 RSA2 应用公钥"
1002
+ emit_control "FLOW:REUSE_NO_KEY"
737
1003
  exit 1
738
1004
  elif [ -n "$ALIPAY_PUBLIC_KEY" ]; then
739
1005
  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"
1006
+ emit_control "✅ 已获取支付宝公钥"
1007
+ emit_control "支付宝公钥保存路径: ~/.config/${APP_ID}-alipayPublicKey.keytext"
1008
+ emit_control "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=EXPORTED"
1009
+ emit_control "ALIPAY_PUBLIC_KEY_FILE=~/.config/${APP_ID}-alipayPublicKey.keytext"
744
1010
  else
745
- echo "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=MANUAL_CONFIGURATION_REQUIRED"
1011
+ emit_control "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=MANUAL_CONFIGURATION_REQUIRED"
746
1012
  fi
747
- echo "APP_ID=$APP_ID"
748
- echo "FLOW:REUSE_SUCCESS"
1013
+ emit_control_field "APP_ID" "$APP_ID" || exit 1
1014
+ emit_control "FLOW:REUSE_SUCCESS"
749
1015
  else
750
1016
  echo "❌ 已确认应用公钥生效,但未获取到 RSA2 支付宝公钥"
751
- echo "FLOW:ERROR"
1017
+ emit_control "FLOW:ERROR"
752
1018
  exit 1
753
1019
  fi
754
1020
  else
755
- echo "FLOW:ERROR"
1021
+ emit_control "FLOW:ERROR"
756
1022
  exit 1
757
1023
  fi
758
1024
  }
@@ -760,7 +1026,7 @@ app_reuse() {
760
1026
  # ─── 入口分发 ────────────────────────────────────────────────────────────────
761
1027
  APP_COMMAND="${1:-}"
762
1028
  case "$APP_COMMAND" in
763
- list|create|key|verify-key|audit|reuse)
1029
+ list|create|key|verify-key|verify-key-and-audit|audit|reuse)
764
1030
  require_command alipay-cli || exit 1
765
1031
  ;;
766
1032
  esac
@@ -778,10 +1044,18 @@ case "$APP_COMMAND" in
778
1044
  shift
779
1045
  app_key "$@"
780
1046
  ;;
1047
+ open-key-page)
1048
+ shift
1049
+ app_open_key_page "$@"
1050
+ ;;
781
1051
  verify-key)
782
1052
  shift
783
1053
  app_verify_key "$@"
784
1054
  ;;
1055
+ verify-key-and-audit)
1056
+ shift
1057
+ app_verify_key_and_audit "$@"
1058
+ ;;
785
1059
  audit)
786
1060
  shift
787
1061
  app_audit "$@"
@@ -791,14 +1065,16 @@ case "$APP_COMMAND" in
791
1065
  app_reuse "$@"
792
1066
  ;;
793
1067
  *)
794
- echo "用法: bash app.sh <list|create|key|verify-key|audit|reuse> [参数...]"
1068
+ echo "用法: bash app.sh <list|create|key|open-key-page|verify-key|verify-key-and-audit|audit|reuse> [参数...]"
795
1069
  echo ""
796
1070
  echo " app list [--product-type <type> --sales-code <code> --application-type <type>] - 查询已有应用"
797
1071
  echo " app create [--product-type <type> --sales-code <code> --application-type <type>] [--mobile-platform IOS|ANDROID|ALL ...] - 创建新应用"
798
- echo " app key <appId> <pubKey> - 设置公钥"
799
- echo " app verify-key <appId> [pubKey] - 确认公钥设置结果"
800
- echo " app audit <appId> - 提交审核"
801
- echo " app reuse <appId> - 复用应用"
1072
+ echo " app key --product-type <type> --defer-open <appId> <pubKey> - 设置公钥"
1073
+ echo " app open-key-page <appId> - 打开已生成的公钥确认页"
1074
+ echo " app verify-key --product-type <type> <appId> [pubKey] - 确认公钥设置结果"
1075
+ echo " app verify-key-and-audit --product-type <type> <appId> [pubKey] - 确认公钥并提交审核"
1076
+ echo " app audit --product-type <type> <appId> - 提交审核"
1077
+ echo " app reuse --product-type <type> <appId> - 复用应用"
802
1078
  exit 1
803
1079
  ;;
804
1080
  esac