@alipay/alipay-aipay 1.7.0 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +30 -0
  2. package/dist/commands/install-current.js +15 -7
  3. package/dist/commands/install.js +20 -16
  4. package/dist/commands/uninstall.js +8 -3
  5. package/dist/commands/update.js +34 -16
  6. package/dist/index.js +10 -4
  7. package/dist/utils/channels.js +67 -0
  8. package/dist/utils/config.js +15 -11
  9. package/dist/utils/file.js +23 -10
  10. package/dist/utils/npm.js +19 -33
  11. package/package.json +8 -3
  12. package/skill/SKILL.md +2 -0
  13. package/skill/references/normal/json/release-channel.json +1 -0
  14. package/skill/references/normal/scripts/self_update.mjs +62 -17
  15. package/skill/references/normal/self-update.md +3 -1
  16. package/skill-pre/SKILL.md +65 -0
  17. package/skill-pre/VERSION +1 -0
  18. package/skill-pre/references/integration/flow.md +602 -0
  19. package/skill-pre/references/integration/modules/alipay-sdk-reminder.md +281 -0
  20. package/skill-pre/references/integration/modules/checklist.md +154 -0
  21. package/skill-pre/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
  22. package/skill-pre/references/integration/modules/code-examples/csharp/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/345/205/263/351/227/255/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +68 -0
  23. package/skill-pre/references/integration/modules/code-examples/csharp/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +83 -0
  24. package/skill-pre/references/integration/modules/code-examples/csharp/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +80 -0
  25. package/skill-pre/references/integration/modules/code-examples/csharp/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +73 -0
  26. package/skill-pre/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 +62 -0
  27. package/skill-pre/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 +132 -0
  28. package/skill-pre/references/integration/modules/code-examples/csharp/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.md +640 -0
  29. package/skill-pre/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
  30. package/skill-pre/references/integration/modules/code-examples/java/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/345/205/263/351/227/255/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +72 -0
  31. package/skill-pre/references/integration/modules/code-examples/java/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +96 -0
  32. package/skill-pre/references/integration/modules/code-examples/java/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +81 -0
  33. package/skill-pre/references/integration/modules/code-examples/java/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +77 -0
  34. package/skill-pre/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 +69 -0
  35. package/skill-pre/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 +138 -0
  36. package/skill-pre/references/integration/modules/code-examples/java/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemoController.md +623 -0
  37. package/skill-pre/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
  38. package/skill-pre/references/integration/modules/code-examples/nodejs/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/345/205/263/351/227/255/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +51 -0
  39. package/skill-pre/references/integration/modules/code-examples/nodejs/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +63 -0
  40. package/skill-pre/references/integration/modules/code-examples/nodejs/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +59 -0
  41. package/skill-pre/references/integration/modules/code-examples/nodejs/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +54 -0
  42. package/skill-pre/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 +84 -0
  43. package/skill-pre/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 +71 -0
  44. package/skill-pre/references/integration/modules/code-examples/nodejs/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.md +498 -0
  45. package/skill-pre/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
  46. package/skill-pre/references/integration/modules/code-examples/php/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/345/205/263/351/227/255/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +59 -0
  47. package/skill-pre/references/integration/modules/code-examples/php/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +73 -0
  48. package/skill-pre/references/integration/modules/code-examples/php/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +69 -0
  49. package/skill-pre/references/integration/modules/code-examples/php/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +65 -0
  50. package/skill-pre/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 +59 -0
  51. package/skill-pre/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 +110 -0
  52. package/skill-pre/references/integration/modules/code-examples/php/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.md +806 -0
  53. package/skill-pre/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
  54. package/skill-pre/references/integration/modules/code-examples/python/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/345/205/263/351/227/255/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +57 -0
  55. package/skill-pre/references/integration/modules/code-examples/python/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +70 -0
  56. package/skill-pre/references/integration/modules/code-examples/python/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +65 -0
  57. package/skill-pre/references/integration/modules/code-examples/python/1-/351/200/232/347/224/250/346/216/245/345/217/243//347/273/237/344/270/200/346/224/266/345/215/225/344/272/244/346/230/223/351/200/200/346/254/276/346/237/245/350/257/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +62 -0
  58. package/skill-pre/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 +51 -0
  59. package/skill-pre/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 +47 -0
  60. package/skill-pre/references/integration/modules/code-examples/python/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.md +541 -0
  61. package/skill-pre/references/integration/modules/interface-contract/aipay-interface-contract.md +52 -0
  62. package/skill-pre/references/integration/modules/interface-contract/apppay-interface-contract.md +45 -0
  63. package/skill-pre/references/integration/modules/interface-contract/common-trade-contract.md +42 -0
  64. package/skill-pre/references/integration/modules/interface-contract/notify-url-contract.md +68 -0
  65. package/skill-pre/references/integration/modules/interface-contract/webpay-interface-contract.md +44 -0
  66. package/skill-pre/references/integration/modules/interface-guide.md +61 -0
  67. package/skill-pre/references/integration/modules/product-decision.md +73 -0
  68. package/skill-pre/references/integration/modules/sandbox/a2m-sandbox-test.md +235 -0
  69. package/skill-pre/references/integration/modules/sandbox/alipay-sandbox-tool.md +142 -0
  70. package/skill-pre/references/integration/modules/sandbox/sandbox-setup-guide.md +239 -0
  71. package/skill-pre/references/integration/modules/scripts/local_402_sandbox_pay.py +900 -0
  72. package/skill-pre/references/integration/modules/scripts/sandbox_config.sh +347 -0
  73. package/skill-pre/references/integration/modules/troubleshooting-flow.md +28 -0
  74. package/skill-pre/references/normal/alipay-cli-env.md +169 -0
  75. package/skill-pre/references/normal/conversation-telemetry.md +101 -0
  76. package/skill-pre/references/normal/full-process-routing.md +92 -0
  77. package/skill-pre/references/normal/integration-worker.md +90 -0
  78. package/skill-pre/references/normal/json/customer-messages.json +1 -0
  79. package/skill-pre/references/normal/json/managed-jq-assets.json +1 -0
  80. package/skill-pre/references/normal/json/policy-rules.json +1 -0
  81. package/skill-pre/references/normal/json/release-channel.json +1 -0
  82. package/skill-pre/references/normal/rejection-guide.md +41 -0
  83. package/skill-pre/references/normal/scripts/agent_platform.mjs +43 -0
  84. package/skill-pre/references/normal/scripts/alipay_cli_env.mjs +34 -0
  85. package/skill-pre/references/normal/scripts/alipay_cli_env.sh +55 -0
  86. package/skill-pre/references/normal/scripts/alipay_cli_refresh.mjs +129 -0
  87. package/skill-pre/references/normal/scripts/auth_runtime_runner.mjs +60 -0
  88. package/skill-pre/references/normal/scripts/checklist_sections.mjs +43 -0
  89. package/skill-pre/references/normal/scripts/common.sh +365 -0
  90. package/skill-pre/references/normal/scripts/conversation_telemetry.mjs +105 -0
  91. package/skill-pre/references/normal/scripts/detect_dev_tool.sh +79 -0
  92. package/skill-pre/references/normal/scripts/flow_loader.mjs +237 -0
  93. package/skill-pre/references/normal/scripts/integration_context_runner.mjs +151 -0
  94. package/skill-pre/references/normal/scripts/integration_message_runner.mjs +1456 -0
  95. package/skill-pre/references/normal/scripts/interaction_reply.mjs +140 -0
  96. package/skill-pre/references/normal/scripts/managed_jq.mjs +215 -0
  97. package/skill-pre/references/normal/scripts/onboarding_discovery_runner.mjs +387 -0
  98. package/skill-pre/references/normal/scripts/onboarding_message_runner.mjs +1425 -0
  99. package/skill-pre/references/normal/scripts/onboarding_recovery_runner.mjs +100 -0
  100. package/skill-pre/references/normal/scripts/open_official_url.sh +205 -0
  101. package/skill-pre/references/normal/scripts/platform_compat.mjs +42 -0
  102. package/skill-pre/references/normal/scripts/preview_runtime.mjs +151 -0
  103. package/skill-pre/references/normal/scripts/project_route_inspector.sh +132 -0
  104. package/skill-pre/references/normal/scripts/python_script_runner.mjs +58 -0
  105. package/skill-pre/references/normal/scripts/render_customer_message.mjs +1792 -0
  106. package/skill-pre/references/normal/scripts/runtime.mjs +271 -0
  107. package/skill-pre/references/normal/scripts/self_update.mjs +376 -0
  108. package/skill-pre/references/normal/scripts/telemetry.mjs +2102 -0
  109. package/skill-pre/references/normal/self-update.md +7 -0
  110. package/skill-pre/references/normal/telemetry.md +70 -0
  111. package/skill-pre/references/onboarding/assets/keygen.html +927 -0
  112. package/skill-pre/references/onboarding/flow.md +607 -0
  113. package/skill-pre/references/onboarding/modules/app-release.md +90 -0
  114. package/skill-pre/references/onboarding/modules/authorization.md +43 -0
  115. package/skill-pre/references/onboarding/modules/error-handling.md +62 -0
  116. package/skill-pre/references/onboarding/modules/product-sign.md +49 -0
  117. package/skill-pre/references/onboarding/modules/scripts/app.sh +1485 -0
  118. package/skill-pre/references/onboarding/modules/scripts/ar_sign_apply.sh +331 -0
  119. package/skill-pre/references/onboarding/modules/scripts/auth.sh +1040 -0
  120. package/skill-pre/references/onboarding/modules/scripts/download_key_tool.sh +4 -0
  121. package/skill-pre/references/onboarding/modules/scripts/error_handler.sh +841 -0
  122. package/skill-pre/references/onboarding/modules/scripts/mcc.sh +1085 -0
  123. package/skill-pre/references/onboarding/modules/scripts/network_retry.sh +135 -0
  124. package/skill-pre/references/onboarding/modules/scripts/query_sign_status.sh +105 -0
  125. package/skill-pre/references/onboarding/modules/scripts/service.sh +528 -0
  126. package/skill-pre/references/onboarding/modules/scripts/sign_status_parser.sh +58 -0
  127. package/skill-pre/references/onboarding/modules/scripts/upload_screenshots.sh +208 -0
  128. package/skill-pre/references/onboarding/modules/scripts/upload_special_license.sh +195 -0
  129. package/skill-pre/references/onboarding/modules/service-registration.md +40 -0
  130. package/skill-pre/references/runtime-entry.md +99 -0
  131. package/dist/commands/install-current.d.ts +0 -4
  132. package/dist/commands/install-current.d.ts.map +0 -1
  133. package/dist/commands/install-current.js.map +0 -1
  134. package/dist/commands/install.d.ts +0 -10
  135. package/dist/commands/install.d.ts.map +0 -1
  136. package/dist/commands/install.js.map +0 -1
  137. package/dist/commands/prepare-tools.d.ts +0 -5
  138. package/dist/commands/prepare-tools.d.ts.map +0 -1
  139. package/dist/commands/prepare-tools.js.map +0 -1
  140. package/dist/commands/uninstall.d.ts +0 -10
  141. package/dist/commands/uninstall.d.ts.map +0 -1
  142. package/dist/commands/uninstall.js.map +0 -1
  143. package/dist/commands/update.d.ts +0 -10
  144. package/dist/commands/update.d.ts.map +0 -1
  145. package/dist/commands/update.js.map +0 -1
  146. package/dist/index.d.ts +0 -2
  147. package/dist/index.d.ts.map +0 -1
  148. package/dist/index.js.map +0 -1
  149. package/dist/types.d.ts +0 -42
  150. package/dist/types.d.ts.map +0 -1
  151. package/dist/types.js.map +0 -1
  152. package/dist/utils/alipay-cli.d.ts +0 -20
  153. package/dist/utils/alipay-cli.d.ts.map +0 -1
  154. package/dist/utils/alipay-cli.js.map +0 -1
  155. package/dist/utils/config.d.ts +0 -52
  156. package/dist/utils/config.d.ts.map +0 -1
  157. package/dist/utils/config.js.map +0 -1
  158. package/dist/utils/file.d.ts +0 -77
  159. package/dist/utils/file.d.ts.map +0 -1
  160. package/dist/utils/file.js.map +0 -1
  161. package/dist/utils/jq.d.ts +0 -17
  162. package/dist/utils/jq.d.ts.map +0 -1
  163. package/dist/utils/jq.js.map +0 -1
  164. package/dist/utils/npm.d.ts +0 -17
  165. package/dist/utils/npm.d.ts.map +0 -1
  166. package/dist/utils/npm.js.map +0 -1
  167. package/dist/utils/shell.d.ts +0 -24
  168. package/dist/utils/shell.d.ts.map +0 -1
  169. package/dist/utils/shell.js.map +0 -1
@@ -0,0 +1,1485 @@
1
+ #!/bin/bash
2
+ # Application actions: stdout is customer text; control facts use internal stderr.
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ RENDERER="${SCRIPT_DIR}/../../../normal/scripts/render_customer_message.mjs"
6
+ CANDIDATE_RUNNER="${SCRIPT_DIR}/../../../normal/scripts/onboarding_message_runner.mjs"
7
+
8
+ if [ -f "${SCRIPT_DIR}/error_handler.sh" ]; then
9
+ source "${SCRIPT_DIR}/error_handler.sh"
10
+ else
11
+ echo "❌ 缺少错误处理脚本: ${SCRIPT_DIR}/error_handler.sh"
12
+ exit 1
13
+ fi
14
+ source "${SCRIPT_DIR}/network_retry.sh"
15
+
16
+ require_command jq || exit 1
17
+ require_command node || exit 1
18
+
19
+ APP_KEY_STATE_OWNER="$(id -u 2>/dev/null || printf '%s' "${USER:-unknown}" | tr -c 'A-Za-z0-9_-' '_')"
20
+ APP_KEY_STATE_BASE="${TMPDIR:-/tmp}"
21
+ APP_KEY_STATE_BASE="${APP_KEY_STATE_BASE%/}"
22
+ if [ -n "${ALIPAY_APP_KEY_STATE_FILE:-}" ]; then
23
+ APP_KEY_STATE_FILE="$ALIPAY_APP_KEY_STATE_FILE"
24
+ APP_KEY_STATE_DIR="$(dirname "$APP_KEY_STATE_FILE")"
25
+ APP_KEY_STATE_DIR_MANAGED=false
26
+ else
27
+ APP_KEY_STATE_DIR="${APP_KEY_STATE_BASE}/alipay-aipay-app-key-${APP_KEY_STATE_OWNER}"
28
+ APP_KEY_STATE_FILE="${APP_KEY_STATE_DIR}/key-page-state.json"
29
+ APP_KEY_STATE_DIR_MANAGED=true
30
+ fi
31
+
32
+ app_key_secure_mode() {
33
+ local target="$1" expected="$2" mode
34
+ if mode=$(stat -f '%Lp' "$target" 2>/dev/null); then
35
+ :
36
+ elif mode=$(stat -c '%a' "$target" 2>/dev/null); then
37
+ :
38
+ else
39
+ return 1
40
+ fi
41
+ [ "$mode" = "$expected" ]
42
+ }
43
+
44
+ prepare_app_key_state_dir() {
45
+ if [ "$APP_KEY_STATE_DIR_MANAGED" = true ]; then
46
+ if [ -L "$APP_KEY_STATE_DIR" ] || { [ -e "$APP_KEY_STATE_DIR" ] && [ ! -d "$APP_KEY_STATE_DIR" ]; }; then
47
+ echo "❌ 公钥确认页临时目录不安全,已停止" >&2
48
+ return 1
49
+ fi
50
+ mkdir -p "$APP_KEY_STATE_DIR" || return 1
51
+ chmod 700 "$APP_KEY_STATE_DIR" 2>/dev/null || return 1
52
+ app_key_secure_mode "$APP_KEY_STATE_DIR" 700 || return 1
53
+ else
54
+ [ -d "$APP_KEY_STATE_DIR" ] && [ ! -L "$APP_KEY_STATE_DIR" ] || {
55
+ echo "❌ 指定的公钥确认页状态目录不可用或不安全,已停止" >&2
56
+ return 1
57
+ }
58
+ fi
59
+ }
60
+
61
+ validate_app_key_state_file() {
62
+ [ -f "$APP_KEY_STATE_FILE" ] && [ ! -L "$APP_KEY_STATE_FILE" ] || return 1
63
+ chmod 600 "$APP_KEY_STATE_FILE" 2>/dev/null || return 1
64
+ app_key_secure_mode "$APP_KEY_STATE_FILE" 600
65
+ }
66
+
67
+ save_app_key_page_state() {
68
+ local temp_state
69
+ require_command mktemp || return 1
70
+ prepare_app_key_state_dir || return 1
71
+ if [ -L "$APP_KEY_STATE_FILE" ] || { [ -e "$APP_KEY_STATE_FILE" ] && [ ! -f "$APP_KEY_STATE_FILE" ]; }; then
72
+ echo "❌ 公钥确认页状态路径不是安全的普通文件,已停止" >&2
73
+ return 1
74
+ fi
75
+ temp_state=$(mktemp "${APP_KEY_STATE_DIR}/.key-page-state.XXXXXX") || return 1
76
+ chmod 600 "$temp_state" 2>/dev/null || { rm -f "$temp_state"; return 1; }
77
+ jq -n \
78
+ --arg appId "$APP_ID" \
79
+ --arg confirmPageUrl "$CONFIRM_PAGE_URL" \
80
+ '{appId:$appId,confirmPageUrl:$confirmPageUrl}' > "$temp_state" || { rm -f "$temp_state"; return 1; }
81
+ mv "$temp_state" "$APP_KEY_STATE_FILE" || { rm -f "$temp_state"; return 1; }
82
+ validate_app_key_state_file || {
83
+ rm -f "$APP_KEY_STATE_FILE"
84
+ echo "❌ 无法确认公钥确认页状态文件权限为 0600,已停止" >&2
85
+ return 1
86
+ }
87
+ }
88
+
89
+ parse_app_context_args() {
90
+ local SEEN_ARGS=""
91
+ PRODUCT_TYPE="${ALIPAY_AIPAY_PRODUCT_TYPE:-}"
92
+ SALES_CODE=""
93
+ APPLICATION_TYPE=""
94
+ MOBILE_PLATFORM=""
95
+ BUNDLE_ID=""
96
+ APP_PACKAGE=""
97
+ APP_SIGN=""
98
+ while [[ $# -gt 0 ]]; do
99
+ case "${SEEN_ARGS}|" in
100
+ *"|${1}|"*) echo "❌ 参数重复: $1"; return 1 ;;
101
+ esac
102
+ case $1 in
103
+ --product-type)
104
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; return 1; }
105
+ SEEN_ARGS="${SEEN_ARGS}|$1"; PRODUCT_TYPE="$2"; shift 2 ;;
106
+ --sales-code)
107
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; return 1; }
108
+ SEEN_ARGS="${SEEN_ARGS}|$1"; SALES_CODE="$2"; shift 2 ;;
109
+ --application-type)
110
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; return 1; }
111
+ SEEN_ARGS="${SEEN_ARGS}|$1"; APPLICATION_TYPE="$2"; shift 2 ;;
112
+ --mobile-platform)
113
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; return 1; }
114
+ SEEN_ARGS="${SEEN_ARGS}|$1"; MOBILE_PLATFORM=$(printf '%s' "$2" | tr '[:lower:]' '[:upper:]'); shift 2 ;;
115
+ --bundle-id)
116
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; return 1; }
117
+ SEEN_ARGS="${SEEN_ARGS}|$1"; BUNDLE_ID="$2"; shift 2 ;;
118
+ --app-package)
119
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; return 1; }
120
+ SEEN_ARGS="${SEEN_ARGS}|$1"; APP_PACKAGE="$2"; shift 2 ;;
121
+ --app-sign)
122
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; return 1; }
123
+ SEEN_ARGS="${SEEN_ARGS}|$1"; APP_SIGN="$2"; shift 2 ;;
124
+ *)
125
+ echo "❌ 未知参数: $1"
126
+ return 1 ;;
127
+ esac
128
+ done
129
+ }
130
+
131
+ validate_mobile_platform_args() {
132
+ local APP_APPLICATION_TYPE="$1"
133
+
134
+ if [ -z "${MOBILE_PLATFORM:-}" ]; then
135
+ if [ -n "${BUNDLE_ID:-}" ] || [ -n "${APP_PACKAGE:-}" ] || [ -n "${APP_SIGN:-}" ]; then
136
+ echo "❌ bundleId/appPackage/appSign 必须与 --mobile-platform 一起传入" >&2
137
+ return 1
138
+ fi
139
+ if [ "$APP_APPLICATION_TYPE" = "MOBILEAPP" ]; then
140
+ echo "❌ 创建 MOBILEAPP 必须传 --mobile-platform IOS|ANDROID|ALL" >&2
141
+ echo "📋 IOS 需同时传 --bundle-id;ANDROID 需同时传 --app-package 和 --app-sign;ALL 需三项都传;HarmonyOS 移动应用不支持通过 Skill 创建" >&2
142
+ return 1
143
+ fi
144
+ return 0
145
+ fi
146
+
147
+ if [ "$APP_APPLICATION_TYPE" != "MOBILEAPP" ]; then
148
+ echo "❌ --mobile-platform 仅支持 MOBILEAPP 创建场景" >&2
149
+ return 1
150
+ fi
151
+
152
+ case "$MOBILE_PLATFORM" in
153
+ IOS)
154
+ if [ -z "${BUNDLE_ID:-}" ]; then
155
+ echo "❌ mobilePlatform=IOS 时必须传 --bundle-id" >&2
156
+ return 1
157
+ fi
158
+ if [ -n "${APP_PACKAGE:-}" ] || [ -n "${APP_SIGN:-}" ]; then
159
+ echo "❌ mobilePlatform=IOS 时不应传 --app-package 或 --app-sign;如需同时支持 iOS 和 Android,请使用 --mobile-platform ALL" >&2
160
+ return 1
161
+ fi
162
+ ;;
163
+ ANDROID)
164
+ if [ -z "${APP_PACKAGE:-}" ] || [ -z "${APP_SIGN:-}" ]; then
165
+ echo "❌ mobilePlatform=ANDROID 时必须传 --app-package 和 --app-sign" >&2
166
+ return 1
167
+ fi
168
+ if [ -n "${BUNDLE_ID:-}" ]; then
169
+ echo "❌ mobilePlatform=ANDROID 时不应传 --bundle-id;如需同时支持 iOS 和 Android,请使用 --mobile-platform ALL" >&2
170
+ return 1
171
+ fi
172
+ ;;
173
+ ALL)
174
+ if [ -z "${BUNDLE_ID:-}" ] || [ -z "${APP_PACKAGE:-}" ] || [ -z "${APP_SIGN:-}" ]; then
175
+ echo "❌ mobilePlatform=ALL 时必须传 --bundle-id、--app-package 和 --app-sign" >&2
176
+ return 1
177
+ fi
178
+ ;;
179
+ *)
180
+ echo "❌ --mobile-platform 参数值无效: ${MOBILE_PLATFORM}(仅支持 IOS|ANDROID|ALL;HarmonyOS 移动应用需前往支付宝开放平台控制台创建)" >&2
181
+ return 1
182
+ ;;
183
+ esac
184
+ }
185
+
186
+ build_create_application_request() {
187
+ local APP_APPLICATION_TYPE="$1"
188
+
189
+ if [ -z "${MOBILE_PLATFORM:-}" ]; then
190
+ jq -n --arg applicationType "$APP_APPLICATION_TYPE" \
191
+ '{request:{applicationType:$applicationType,createScene:"cli"}}'
192
+ return 0
193
+ fi
194
+
195
+ jq -n \
196
+ --arg applicationType "$APP_APPLICATION_TYPE" \
197
+ --arg mobilePlatform "$MOBILE_PLATFORM" \
198
+ --arg bundleId "${BUNDLE_ID:-}" \
199
+ --arg appPackage "${APP_PACKAGE:-}" \
200
+ --arg appSign "${APP_SIGN:-}" \
201
+ '{
202
+ request: (
203
+ {
204
+ applicationType:$applicationType,
205
+ createScene:"cli",
206
+ mobilePlatform:$mobilePlatform
207
+ }
208
+ + (if $bundleId != "" then {bundleId:$bundleId} else {} end)
209
+ + (if $appPackage != "" then {appPackage:$appPackage} else {} end)
210
+ + (if $appSign != "" then {appSign:$appSign} else {} end)
211
+ )
212
+ }'
213
+ }
214
+
215
+ validate_app_context() {
216
+ local EXPECTED_PRODUCT_TYPE=""
217
+ local EXPECTED_APPLICATION_TYPE=""
218
+
219
+ if [ -n "${PRODUCT_TYPE:-}" ] && [ "$PRODUCT_TYPE" != "aipay" ] && [ "$PRODUCT_TYPE" != "webpay" ] && [ "$PRODUCT_TYPE" != "apppay" ]; then
220
+ echo "❌ --product-type 参数值无效: ${PRODUCT_TYPE}(仅支持 aipay|webpay|apppay)" >&2
221
+ return 1
222
+ fi
223
+
224
+ case "${SALES_CODE:-}" in
225
+ "") ;;
226
+ "I1080300001000160457") EXPECTED_PRODUCT_TYPE="aipay" ;;
227
+ "I1080300001000041203") EXPECTED_PRODUCT_TYPE="webpay" ;;
228
+ "I1080300001000041313") EXPECTED_PRODUCT_TYPE="apppay" ;;
229
+ *) echo "❌ 未知 salesCode: ${SALES_CODE}" >&2; return 1 ;;
230
+ esac
231
+
232
+ if [ -n "$EXPECTED_PRODUCT_TYPE" ] && [ -n "${PRODUCT_TYPE:-}" ] && [ "$PRODUCT_TYPE" != "$EXPECTED_PRODUCT_TYPE" ]; then
233
+ echo "❌ --product-type 与 --sales-code 不匹配:--product-type=${PRODUCT_TYPE}, --sales-code=${SALES_CODE}" >&2
234
+ echo "📋 期望 --product-type=${EXPECTED_PRODUCT_TYPE}" >&2
235
+ return 1
236
+ fi
237
+
238
+ case "${PRODUCT_TYPE:-$EXPECTED_PRODUCT_TYPE}" in
239
+ apppay) EXPECTED_APPLICATION_TYPE="MOBILEAPP" ;;
240
+ aipay|webpay) EXPECTED_APPLICATION_TYPE="WEBAPP" ;;
241
+ esac
242
+
243
+ if [ -n "$EXPECTED_APPLICATION_TYPE" ] && [ -n "${APPLICATION_TYPE:-}" ] && [ "$APPLICATION_TYPE" != "$EXPECTED_APPLICATION_TYPE" ]; then
244
+ echo "❌ --application-type 与产品上下文不匹配:--application-type=${APPLICATION_TYPE}" >&2
245
+ echo "📋 期望 --application-type=${EXPECTED_APPLICATION_TYPE}" >&2
246
+ return 1
247
+ fi
248
+ }
249
+
250
+ init_app_cli_product() {
251
+ local resolved_product_type="${PRODUCT_TYPE:-}"
252
+
253
+ if [ -z "$resolved_product_type" ] && [ -n "${SALES_CODE:-}" ]; then
254
+ resolved_product_type="$(alipay_product_type_for_sales_code "$SALES_CODE")" || return 1
255
+ fi
256
+ if [ -z "$resolved_product_type" ]; then
257
+ resolved_product_type="${ALIPAY_AIPAY_PRODUCT_TYPE:-}"
258
+ fi
259
+ PRODUCT_TYPE="$resolved_product_type"
260
+ validate_app_context || return 1
261
+ init_alipay_cli_product "$PRODUCT_TYPE"
262
+ }
263
+
264
+ prepare_positional_app_cli_context() {
265
+ PRODUCT_TYPE="${ALIPAY_AIPAY_PRODUCT_TYPE:-}"
266
+ if [ "${1:-}" = "--product-type" ]; then
267
+ [ "$#" -ge 2 ] || { echo "❌ 缺少参数值: $1"; return 1; }
268
+ PRODUCT_TYPE="$2"
269
+ shift 2
270
+ fi
271
+ init_app_cli_product || return 1
272
+ APP_POSITIONAL_ARGS=("$@")
273
+ }
274
+
275
+ resolve_application_type() {
276
+ validate_app_context || return 1
277
+
278
+ if [ -n "${APPLICATION_TYPE:-}" ]; then
279
+ case "$APPLICATION_TYPE" in
280
+ WEBAPP|MOBILEAPP) echo "$APPLICATION_TYPE" ;;
281
+ *) echo "❌ APPLICATION_TYPE 参数值无效: ${APPLICATION_TYPE}(仅支持 WEBAPP|MOBILEAPP)" >&2; return 1 ;;
282
+ esac
283
+ return 0
284
+ fi
285
+
286
+ if [ "${PRODUCT_TYPE:-}" = "apppay" ] || [ "${SALES_CODE:-}" = "I1080300001000041313" ]; then
287
+ echo "MOBILEAPP"
288
+ else
289
+ echo "WEBAPP"
290
+ fi
291
+ }
292
+
293
+ query_security_key() {
294
+ local APP_ID="$1"
295
+ local PUBLIC_KEY="${2:-}"
296
+
297
+ if [ -n "$PUBLIC_KEY" ]; then
298
+ REQUEST_JSON=$(jq -n \
299
+ --arg appId "$APP_ID" \
300
+ --arg publicKey "$PUBLIC_KEY" \
301
+ '{request:{appId:$appId,publicKey:$publicKey}}')
302
+ else
303
+ REQUEST_JSON=$(jq -n --arg appId "$APP_ID" '{request:{appId:$appId}}')
304
+ fi
305
+
306
+ require_alipay_cli_local_state_access_or_emit || return "$?"
307
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
308
+ run_network_retry RESULT read application_security_key -- alipay-cli mcp call apprelease.queryApplicationSecurityKey \
309
+ -d "$REQUEST_JSON" \
310
+ --json
311
+ RETRY_RC=$?
312
+ if [ "$RETRY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
313
+ return "$RETRY_RC"
314
+ fi
315
+ if [ "$RETRY_RC" -ne 0 ]; then
316
+ echo "❌ 应用安全密钥查询因网络或服务异常在自动重试两次后仍未恢复"
317
+ return 1
318
+ fi
319
+
320
+ if ! handle_error "$RESULT" "MCP_AUTHENTICATED_CALL"; then
321
+ return 1
322
+ fi
323
+
324
+ SECURITY_BUSINESS=$(unwrap_mcp "$RESULT")
325
+ SUCCESS=$(echo "$SECURITY_BUSINESS" | jq -r '.success // false')
326
+
327
+ if [ "$SUCCESS" != "true" ]; then
328
+ ERROR_MSG=$(echo "$SECURITY_BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
329
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
330
+ echo "❌ 查询安全密钥失败: $ERROR_MSG"
331
+ return 1
332
+ fi
333
+ }
334
+
335
+ query_application_info() {
336
+ local APP_ID="$1"
337
+
338
+ REQUEST_JSON=$(jq -n --arg appId "$APP_ID" '{request:{appId:$appId}}')
339
+ require_alipay_cli_local_state_access_or_emit || return "$?"
340
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
341
+ run_network_retry RESULT read application_info -- alipay-cli mcp call apprelease.queryApplicationInfo \
342
+ -d "$REQUEST_JSON" \
343
+ --json
344
+ RETRY_RC=$?
345
+ if [ "$RETRY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
346
+ return "$RETRY_RC"
347
+ fi
348
+ if [ "$RETRY_RC" -ne 0 ]; then
349
+ echo "❌ 应用信息查询因网络或服务异常在自动重试两次后仍未恢复"
350
+ return 1
351
+ fi
352
+
353
+ if ! handle_error "$RESULT" "MCP_AUTHENTICATED_CALL"; then
354
+ return 1
355
+ fi
356
+
357
+ APPLICATION_INFO_BUSINESS=$(unwrap_mcp "$RESULT")
358
+ SUCCESS=$(echo "$APPLICATION_INFO_BUSINESS" | jq -r '.success // false')
359
+
360
+ if [ "$SUCCESS" != "true" ]; then
361
+ ERROR_MSG=$(echo "$APPLICATION_INFO_BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
362
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
363
+ echo "❌ 查询应用信息失败: $ERROR_MSG"
364
+ return 1
365
+ fi
366
+ }
367
+
368
+ extract_application_status() {
369
+ echo "$APPLICATION_INFO_BUSINESS" | jq -r '
370
+ .resultObj.status //
371
+ empty
372
+ '
373
+ }
374
+
375
+ extract_rsa2_application_key_info() {
376
+ echo "$SECURITY_BUSINESS" | jq -c '
377
+ (
378
+ (.resultObj.securityConfigList // []) +
379
+ (.resultObj.securityKeys // []) +
380
+ (.data.securityConfigList // []) +
381
+ (.data.securityKeys // []) +
382
+ (.resultObj.alipayKeyList // []) +
383
+ (.data.alipayKeyList // [])
384
+ ) |
385
+ map(select(
386
+ .signType == "RSA2" and
387
+ (
388
+ ((.partnerPublicKey // "") != "") or
389
+ ((.certInfoDTO | type) == "object" and ((.certInfoDTO.publicKey // "") != ""))
390
+ )
391
+ )) |
392
+ .[0] // {}
393
+ '
394
+ }
395
+
396
+ extract_rsa2_alipay_key_info() {
397
+ echo "$SECURITY_BUSINESS" | jq -c '
398
+ (
399
+ (.resultObj.alipayKeyList // []) +
400
+ (.data.alipayKeyList // []) +
401
+ (.resultObj.securityKeys // []) +
402
+ (.data.securityKeys // [])
403
+ ) |
404
+ map(select(.signType == "RSA2" and ((.alipayPublicKey // "") != ""))) |
405
+ .[0] // {}
406
+ '
407
+ }
408
+
409
+ extract_alipay_public_key() {
410
+ local RSA2_KEY_INFO="$1"
411
+ echo "$RSA2_KEY_INFO" | jq -r '.alipayPublicKey // empty'
412
+ }
413
+
414
+ is_rsa2_application_key_ready() {
415
+ local RSA2_KEY_INFO="$1"
416
+
417
+ echo "$RSA2_KEY_INFO" | jq -e '
418
+ ((.partnerPublicKey // "") != "") or
419
+ (
420
+ (.certInfoDTO | type) == "object" and
421
+ ((.certInfoDTO.publicKey // "") != "")
422
+ )
423
+ ' >/dev/null 2>&1
424
+ }
425
+
426
+ write_alipay_public_key() {
427
+ local APP_ID="$1"
428
+ local ALIPAY_PUBLIC_KEY="$2"
429
+
430
+ local CONFIG_DIR KEY_FILE
431
+ local ATTEMPT=1
432
+ local MAX_ATTEMPTS="${ALIPAY_PUBLIC_KEY_WRITE_RETRIES:-3}"
433
+ local LOCAL_CONFIG_PERMISSION_EXIT_CODE="${ALIPAY_AIPAY_LOCAL_CONFIG_PERMISSION_EXIT_CODE:-77}"
434
+
435
+ if ! [[ "$MAX_ATTEMPTS" =~ ^[1-9][0-9]*$ ]]; then
436
+ MAX_ATTEMPTS=3
437
+ fi
438
+
439
+ CONFIG_DIR="$(alipay_aipay_config_dir)" || return "$LOCAL_CONFIG_PERMISSION_EXIT_CODE"
440
+ require_alipay_aipay_config_write_access || return "$LOCAL_CONFIG_PERMISSION_EXIT_CODE"
441
+ KEY_FILE="${CONFIG_DIR}/${APP_ID}-alipayPublicKey.keytext"
442
+ if [ -z "${XDG_CONFIG_HOME:-}" ]; then
443
+ ALIPAY_PUBLIC_KEY_FILE_DISPLAY="~/.config/${APP_ID}-alipayPublicKey.keytext"
444
+ else
445
+ ALIPAY_PUBLIC_KEY_FILE_DISPLAY="$KEY_FILE"
446
+ fi
447
+
448
+ while [ "$ATTEMPT" -le "$MAX_ATTEMPTS" ]; do
449
+ if { (umask 077; printf '%s\n' "$ALIPAY_PUBLIC_KEY" > "$KEY_FILE") && chmod 600 "$KEY_FILE"; } 2>/dev/null; then
450
+ return 0
451
+ fi
452
+
453
+ if [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; then
454
+ emit_control "⚠️ 支付宝公钥文件写入失败,正在重试(${ATTEMPT}/${MAX_ATTEMPTS})..."
455
+ sleep 1
456
+ fi
457
+ ATTEMPT=$((ATTEMPT + 1))
458
+ done
459
+
460
+ return 1
461
+ }
462
+
463
+ emit_alipay_public_key_export_retry() {
464
+ emit_control "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=RETRY_WITH_LOCAL_CONFIG_PERMISSION"
465
+ emit_control "APP_FLOW:RETRY_WITH_LOCAL_CONFIG_PERMISSION"
466
+ }
467
+
468
+ write_and_emit_alipay_public_key_export() {
469
+ local APP_ID="$1"
470
+ local ALIPAY_PUBLIC_KEY="$2"
471
+ local WRITE_RC DISPLAY_PATH
472
+ local LOCAL_CONFIG_PERMISSION_EXIT_CODE="${ALIPAY_AIPAY_LOCAL_CONFIG_PERMISSION_EXIT_CODE:-77}"
473
+
474
+ write_alipay_public_key "$APP_ID" "$ALIPAY_PUBLIC_KEY"
475
+ WRITE_RC=$?
476
+ if [ "$WRITE_RC" -eq 0 ]; then
477
+ DISPLAY_PATH="${ALIPAY_PUBLIC_KEY_FILE_DISPLAY:-~/.config/${APP_ID}-alipayPublicKey.keytext}"
478
+ emit_control "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=EXPORTED"
479
+ emit_control_field "ALIPAY_PUBLIC_KEY_FILE" "$DISPLAY_PATH" || return 1
480
+ emit_stage_metric "onboarding.confirm_alipayapp"
481
+ return 0
482
+ fi
483
+
484
+ if [ "$WRITE_RC" -eq "$LOCAL_CONFIG_PERMISSION_EXIT_CODE" ]; then
485
+ emit_alipay_public_key_export_retry
486
+ return "$LOCAL_CONFIG_PERMISSION_EXIT_CODE"
487
+ fi
488
+
489
+ emit_control "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=MANUAL_CONFIGURATION_REQUIRED"
490
+ return 0
491
+ }
492
+
493
+ ensure_rsa2_application_key_ready() {
494
+ local APP_ID="$1"
495
+ local PUBLIC_KEY="${2:-}"
496
+ local ATTEMPT=1
497
+ local MAX_ATTEMPTS=20
498
+ local INTERVAL_SECONDS=2 QUERY_RC
499
+ KEY_VERIFY_RESULT=""
500
+
501
+ if [ "${ALIPAY_AIPAY_RECOVERY_QUERY_ONCE:-0}" = "1" ]; then
502
+ MAX_ATTEMPTS=1
503
+ fi
504
+
505
+ if [ "${ALIPAY_AIPAY_TEST_MODE:-0}" = "1" ] && [[ "${APP_KEY_VERIFY_INTERVAL_SECONDS:-}" =~ ^[0-9]+$ ]]; then
506
+ INTERVAL_SECONDS="$APP_KEY_VERIFY_INTERVAL_SECONDS"
507
+ fi
508
+
509
+ while [ "$ATTEMPT" -le "$MAX_ATTEMPTS" ]; do
510
+ query_security_key "$APP_ID" "$PUBLIC_KEY"
511
+ QUERY_RC=$?
512
+ if [ "$QUERY_RC" -ne 0 ]; then
513
+ if [ "$QUERY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
514
+ KEY_VERIFY_RESULT="LOCAL_FS_PERMISSION"
515
+ return "$QUERY_RC"
516
+ fi
517
+ KEY_VERIFY_RESULT="QUERY_FAILED"
518
+ return 1
519
+ fi
520
+
521
+ RSA2_KEY_INFO=$(extract_rsa2_application_key_info)
522
+
523
+ if is_rsa2_application_key_ready "$RSA2_KEY_INFO"; then
524
+ KEY_VERIFY_RESULT="CONFIRMED"
525
+ return 0
526
+ fi
527
+
528
+ if [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; then
529
+ emit_control "KEY_POLL_PENDING=${ATTEMPT}/${MAX_ATTEMPTS}"
530
+ sleep "$INTERVAL_SECONDS"
531
+ fi
532
+
533
+ ATTEMPT=$((ATTEMPT + 1))
534
+ done
535
+
536
+ KEY_VERIFY_RESULT="PENDING"
537
+ return 1
538
+ }
539
+
540
+ render_key_pending() {
541
+ printf '%s' '{}' \
542
+ | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=app.sh node "$RENDERER" application.key.pending --variant DEFAULT
543
+ }
544
+
545
+ export_alipay_public_key_from_security_business() {
546
+ local APP_ID="$1"
547
+
548
+ RSA2_KEY_INFO=$(extract_rsa2_application_key_info)
549
+ RSA2_ALIPAY_KEY_INFO=$(extract_rsa2_alipay_key_info)
550
+ ALIPAY_PUBLIC_KEY=$(extract_alipay_public_key "$RSA2_ALIPAY_KEY_INFO")
551
+
552
+ if ! is_rsa2_application_key_ready "$RSA2_KEY_INFO"; then
553
+ echo "❌ 应用公钥尚未确认,禁止提交应用审核"
554
+ return 1
555
+ fi
556
+
557
+ if [ -z "$ALIPAY_PUBLIC_KEY" ]; then
558
+ echo "❌ 已确认应用公钥生效,但未获取到 RSA2 支付宝公钥,禁止提交应用审核"
559
+ return 1
560
+ fi
561
+
562
+ write_and_emit_alipay_public_key_export "$APP_ID" "$ALIPAY_PUBLIC_KEY"
563
+ }
564
+
565
+ verify_key_and_export_alipay_public_key() {
566
+ local APP_ID="$1"
567
+ local QUERY_RC
568
+
569
+ query_security_key "$APP_ID"
570
+ QUERY_RC=$?
571
+ [ "$QUERY_RC" -eq 0 ] || return "$QUERY_RC"
572
+ export_alipay_public_key_from_security_business "$APP_ID"
573
+ }
574
+
575
+ app_list() {
576
+ parse_app_context_args "$@" || {
577
+ echo "用法: bash app.sh list [--product-type aipay|webpay|apppay] [--sales-code <code>] [--application-type WEBAPP|MOBILEAPP]"
578
+ exit 1
579
+ }
580
+ APP_APPLICATION_TYPE=$(resolve_application_type) || exit 1
581
+ init_app_cli_product || exit 1
582
+ REQUEST_JSON=$(jq -n --arg applicationType "$APP_APPLICATION_TYPE" '{request:{appTypes:[$applicationType]}}')
583
+
584
+ require_alipay_cli_local_state_access_or_emit || exit "$?"
585
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
586
+ run_network_retry RESULT read application_list -- alipay-cli mcp call apprelease.queryApplicationList \
587
+ -d "$REQUEST_JSON" \
588
+ --json
589
+ RETRY_RC=$?
590
+ if [ "$RETRY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
591
+ exit "$RETRY_RC"
592
+ fi
593
+ if [ "$RETRY_RC" -ne 0 ]; then
594
+ echo "❌ 应用列表查询因网络或服务异常在自动重试两次后仍未恢复"
595
+ emit_control "FLOW:ERROR"
596
+ exit 1
597
+ fi
598
+
599
+ if ! handle_error "$RESULT" "MCP_AUTHENTICATED_CALL"; then
600
+ exit 1
601
+ fi
602
+
603
+ BUSINESS=$(unwrap_mcp "$RESULT")
604
+ SUCCESS=$(echo "$BUSINESS" | jq -r '.success // false')
605
+
606
+ if [ "$SUCCESS" != "true" ]; then
607
+ ERROR_MSG=$(echo "$BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
608
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
609
+ echo "❌ 查询应用列表失败: $ERROR_MSG"
610
+ emit_control "FLOW:ERROR"
611
+ exit 1
612
+ fi
613
+
614
+ APP_LIST=$(echo "$BUSINESS" | jq -c '
615
+ if (.resultObj | type) == "object" and (.resultObj.applicationList | type) == "array" then
616
+ .resultObj.applicationList
617
+ elif (.resultObj | type) == "array" then
618
+ .resultObj
619
+ else
620
+ null
621
+ end
622
+ ' 2>/dev/null)
623
+ if [ -z "$APP_LIST" ] || ! echo "$APP_LIST" | jq -e 'type == "array"' >/dev/null 2>&1; then
624
+ echo "❌ 应用列表返回结构异常,无法解析应用列表"
625
+ emit_control "FLOW:ERROR"
626
+ exit 1
627
+ fi
628
+ APP_COUNT=$(echo "$APP_LIST" | jq 'length')
629
+ APP_CANDIDATES=$(echo "$APP_LIST" | jq -c '[.[] | {
630
+ appId: ((.appId // "未知") | tostring),
631
+ appName: ((.appName // "未知") | tostring),
632
+ status: ((.status // "未知") | tostring)
633
+ }]')
634
+ ONLINE_APPS=$(echo "$APP_LIST" | jq -r '[.[] | select(.status == "ON_LINE")]')
635
+ NON_ONLINE_APPS=$(echo "$APP_LIST" | jq -r '[.[] | select(.status != "ON_LINE")]')
636
+ ONLINE_COUNT=$(echo "$ONLINE_APPS" | jq 'length')
637
+ NON_ONLINE_COUNT=$(echo "$NON_ONLINE_APPS" | jq 'length')
638
+ ONLINE_COUNT=${ONLINE_COUNT:-0}
639
+ NON_ONLINE_COUNT=${NON_ONLINE_COUNT:-0}
640
+
641
+ if [ "$ONLINE_COUNT" -gt 0 ] && ! echo "$ONLINE_APPS" | jq -e '
642
+ all(.[];
643
+ ((.appId | type) == "string") and
644
+ (.appId | length > 0) and
645
+ ((.appId | test("[\u0000-\u001f\u007f]") | not))
646
+ )
647
+ ' >/dev/null 2>&1; then
648
+ echo "❌ 应用列表返回结构异常:可复用候选 appId 缺失或包含控制字符"
649
+ emit_control "FLOW:ERROR"
650
+ exit 1
651
+ fi
652
+
653
+ if ! printf '%s' "$APP_CANDIDATES" \
654
+ | node "$CANDIDATE_RUNNER" application-candidates --application-type "$APP_APPLICATION_TYPE" >/dev/null 2>&1; then
655
+ echo "❌ 应用候选事实安全校验失败"
656
+ emit_control "FLOW:ERROR"
657
+ exit 1
658
+ fi
659
+ emit_control_field "APP_CANDIDATES_JSON" "$APP_CANDIDATES" || {
660
+ echo "❌ 应用候选事实包含非法控制字符"
661
+ emit_control "FLOW:ERROR"
662
+ exit 1
663
+ }
664
+ emit_control_field "APPLICATION_TYPE" "$APP_APPLICATION_TYPE" || exit 1
665
+
666
+ if [ "$APP_COUNT" -eq 0 ]; then
667
+ emit_control "FLOW:CREATE_NEW"
668
+ else
669
+ if [ "$ONLINE_COUNT" -eq 0 ]; then
670
+ emit_control "FLOW:PENDING_APPLICATIONS"
671
+ else
672
+ echo "$ONLINE_APPS" | jq -r '.[] | select((.appId // "") != "") | "APP_CANDIDATE_ID=" + .appId' \
673
+ | while IFS= read -r marker; do emit_control "$marker"; done
674
+ emit_control "FLOW:SELECT"
675
+ fi
676
+ fi
677
+ }
678
+
679
+ app_create() {
680
+ parse_app_context_args "$@" || {
681
+ echo "用法: bash app.sh create [--product-type aipay|webpay|apppay] [--sales-code <code>] [--application-type WEBAPP|MOBILEAPP] [--mobile-platform IOS|ANDROID|ALL --bundle-id <id> --app-package <pkg> --app-sign <sign>]"
682
+ exit 1
683
+ }
684
+ APP_APPLICATION_TYPE=$(resolve_application_type) || exit 1
685
+ init_app_cli_product || exit 1
686
+ validate_mobile_platform_args "$APP_APPLICATION_TYPE" || exit 1
687
+ REQUEST_JSON=$(build_create_application_request "$APP_APPLICATION_TYPE") || {
688
+ echo "❌ 创建应用请求参数组装失败"
689
+ exit 1
690
+ }
691
+
692
+ require_alipay_cli_local_state_access_or_emit || exit "$?"
693
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
694
+ run_network_retry RESULT write application_create -- alipay-cli mcp call apprelease.createApplication \
695
+ -d "$REQUEST_JSON" \
696
+ --json
697
+ RETRY_RC=$?
698
+ if [ "$RETRY_RC" -eq 75 ]; then
699
+ echo "❌ 应用创建响应不明;现有应用列表无法把同类型新应用唯一归属于本次创建,结果标记为 UNKNOWN,禁止自动重复创建"
700
+ emit_control "APPLICATION_CREATE_RESULT=UNKNOWN"
701
+ exit 1
702
+ elif [ "$RETRY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
703
+ exit "$RETRY_RC"
704
+ elif [ "$RETRY_RC" -ne 0 ]; then
705
+ echo "❌ 应用创建因明确未发送的网络异常在自动重试两次后仍未恢复"
706
+ emit_control "APPLICATION_CREATE_RESULT=FAILED"
707
+ exit 1
708
+ fi
709
+
710
+ if ! handle_error "$RESULT" "MCP_AUTHENTICATED_CALL"; then
711
+ emit_control "APPLICATION_CREATE_RESULT=FAILED"
712
+ exit 1
713
+ fi
714
+
715
+ BUSINESS=$(unwrap_mcp "$RESULT")
716
+ SUCCESS=$(echo "$BUSINESS" | jq -r '.success // false')
717
+
718
+ if [ "$SUCCESS" = "true" ]; then
719
+ APP_ID=$(echo "$BUSINESS" | jq -r '.resultObj.appId // .data.appId // empty')
720
+ if ! validate_control_value "$APP_ID"; then
721
+ echo "❌ 应用创建返回成功,但未解析到 appId,禁止进入公钥设置流程"
722
+ emit_control "APPLICATION_CREATE_RESULT=UNKNOWN"
723
+ exit 1
724
+ fi
725
+ emit_control_field "APPLICATION_TYPE" "$APP_APPLICATION_TYPE" || exit 1
726
+ emit_control_field "APP_ID" "$APP_ID" || exit 1
727
+ emit_control "APPLICATION_CREATE_RESULT=SUCCESS"
728
+ else
729
+ ERROR_MSG=$(echo "$BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
730
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
731
+ echo "❌ 应用创建失败: $ERROR_MSG"
732
+ emit_control "APPLICATION_CREATE_RESULT=FAILED"
733
+ exit 1
734
+ fi
735
+ }
736
+
737
+ copy_captured_output() {
738
+ local FILE="$1" LINE
739
+ [ -f "$FILE" ] || return 0
740
+ while IFS= read -r LINE || [ -n "$LINE" ]; do
741
+ printf '%s\n' "$LINE"
742
+ done < "$FILE"
743
+ }
744
+
745
+ extract_unique_control_field() {
746
+ local FILE="$1" NAME="$2" PREFIX="ALIPAY_AIPAY_INTERNAL:${2}=" LINE VALUE="" COUNT=0
747
+ while IFS= read -r LINE || [ -n "$LINE" ]; do
748
+ case "$LINE" in
749
+ "$PREFIX"*)
750
+ COUNT=$((COUNT + 1))
751
+ VALUE="${LINE#"$PREFIX"}"
752
+ ;;
753
+ esac
754
+ done < "$FILE"
755
+ [ "$COUNT" -eq 1 ] && validate_control_value "$VALUE" || return 1
756
+ printf '%s' "$VALUE"
757
+ }
758
+
759
+ load_child_recovery_marker() {
760
+ local FILE="$1" LINE MARKER="" RECOVERY_COUNT=0 PREFIX_COUNT=0
761
+ CHILD_RECOVERY_MARKER=""
762
+ while IFS= read -r LINE || [ -n "$LINE" ]; do
763
+ case "$LINE" in
764
+ "ALIPAY_AIPAY_INTERNAL:CLI_FLOW:"*|"ALIPAY_AIPAY_INTERNAL:AUTH_FLOW:"*)
765
+ PREFIX_COUNT=$((PREFIX_COUNT + 1))
766
+ ;;
767
+ esac
768
+ case "$LINE" in
769
+ "ALIPAY_AIPAY_INTERNAL:CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION"|\
770
+ "ALIPAY_AIPAY_INTERNAL:AUTH_FLOW:AUTH_REQUIRED"|\
771
+ "ALIPAY_AIPAY_INTERNAL:AUTH_FLOW:SCOPE_MISMATCH"|\
772
+ "ALIPAY_AIPAY_INTERNAL:AUTH_FLOW:MCC_MISMATCH"|\
773
+ "ALIPAY_AIPAY_INTERNAL:AUTH_FLOW:RETRY_WITH_NETWORK"|\
774
+ "ALIPAY_AIPAY_INTERNAL:AUTH_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION"|\
775
+ "ALIPAY_AIPAY_INTERNAL:AUTH_FLOW:LOGOUT_STILL_LOGGED_IN"|\
776
+ "ALIPAY_AIPAY_INTERNAL:AUTH_FLOW:FAILED")
777
+ RECOVERY_COUNT=$((RECOVERY_COUNT + 1))
778
+ MARKER="${LINE#ALIPAY_AIPAY_INTERNAL:}"
779
+ ;;
780
+ esac
781
+ done < "$FILE"
782
+ [ "$PREFIX_COUNT" -eq "$RECOVERY_COUNT" ] && [ "$RECOVERY_COUNT" -le 1 ] || return 1
783
+ CHILD_RECOVERY_MARKER="$MARKER"
784
+ }
785
+
786
+ emit_unique_child_recovery_marker() {
787
+ load_child_recovery_marker "$1" || return 1
788
+ [ -n "$CHILD_RECOVERY_MARKER" ] || return 1
789
+ emit_control "$CHILD_RECOVERY_MARKER"
790
+ }
791
+
792
+ captured_line_count() {
793
+ local FILE="$1" EXPECTED="$2" LINE COUNT=0
794
+ while IFS= read -r LINE || [ -n "$LINE" ]; do
795
+ [ "$LINE" = "$EXPECTED" ] && COUNT=$((COUNT + 1))
796
+ done < "$FILE"
797
+ printf '%s' "$COUNT"
798
+ }
799
+
800
+ forward_write_child_outcome() {
801
+ local KIND="$1" STDOUT_FILE="$2" STDERR_FILE="$3" CHILD_RC="$4" EXPECTED_TYPE="${5:-}" ALLOW_SIGNAL="${6:-false}"
802
+ local FIELD SUCCESS RESULT CHILD_APPLICATION_TYPE READY_COUNT STATE_FAILURE_COUNT MESSAGE_COUNT
803
+ case "$KIND" in
804
+ create) FIELD=APPLICATION_CREATE_RESULT; SUCCESS=SUCCESS; FORWARDED_APP_ID="" ;;
805
+ key) FIELD=KEY_PAGE_RESULT; SUCCESS=READY_TO_OPEN ;;
806
+ *) return 1 ;;
807
+ esac
808
+ if ! load_child_recovery_marker "$STDERR_FILE"; then
809
+ emit_control "$FIELD=UNKNOWN"
810
+ return 1
811
+ fi
812
+ RESULT=$(extract_unique_control_field "$STDERR_FILE" "$FIELD" 2>/dev/null || true)
813
+ if [ -n "$CHILD_RECOVERY_MARKER" ]; then
814
+ if [ "$CHILD_RC" -eq 0 ]; then
815
+ emit_control "$FIELD=UNKNOWN"
816
+ else
817
+ case "$CHILD_RECOVERY_MARKER:$RESULT" in
818
+ CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION:) emit_control "$CHILD_RECOVERY_MARKER" ;;
819
+ AUTH_FLOW:*:FAILED) emit_control "$FIELD=FAILED"; emit_control "$CHILD_RECOVERY_MARKER" ;;
820
+ AUTH_FLOW:*:UNKNOWN|AUTH_FLOW:*:) emit_control "$FIELD=UNKNOWN"; emit_control "$CHILD_RECOVERY_MARKER" ;;
821
+ *) emit_control "$FIELD=UNKNOWN" ;;
822
+ esac
823
+ fi
824
+ return 1
825
+ fi
826
+ if [ "$RESULT" = FAILED ]; then
827
+ if [ "$CHILD_RC" -eq 0 ]; then
828
+ emit_control "$FIELD=UNKNOWN"
829
+ else
830
+ MESSAGE_COUNT=$(captured_line_count "$STDERR_FILE" "ALIPAY_AIPAY_INTERNAL:CHILD_CUSTOMER_MESSAGE_RENDERED")
831
+ [ "$MESSAGE_COUNT" -ne 1 ] || copy_captured_output "$STDOUT_FILE"
832
+ emit_control "$FIELD=FAILED"
833
+ fi
834
+ return 1
835
+ fi
836
+ if [ "$RESULT" != "$SUCCESS" ] || { [ "$CHILD_RC" -ne 0 ] && [ "$ALLOW_SIGNAL" != true ]; }; then
837
+ emit_control "$FIELD=UNKNOWN"
838
+ return 1
839
+ fi
840
+ if [ "$KIND" = create ]; then
841
+ FORWARDED_APP_ID=$(extract_unique_control_field "$STDERR_FILE" APP_ID 2>/dev/null || true)
842
+ CHILD_APPLICATION_TYPE=$(extract_unique_control_field "$STDERR_FILE" APPLICATION_TYPE 2>/dev/null || true)
843
+ if [ -s "$STDOUT_FILE" ] || ! validate_control_value "$FORWARDED_APP_ID" || [ "$CHILD_APPLICATION_TYPE" != "$EXPECTED_TYPE" ]; then
844
+ emit_control "$FIELD=UNKNOWN"
845
+ return 1
846
+ fi
847
+ emit_control_field APPLICATION_TYPE "$CHILD_APPLICATION_TYPE" || return 1
848
+ emit_control_field APP_ID "$FORWARDED_APP_ID" || return 1
849
+ emit_control "$FIELD=SUCCESS"
850
+ return 0
851
+ fi
852
+ READY_COUNT=$(captured_line_count "$STDERR_FILE" "ALIPAY_AIPAY_INTERNAL:KEY_PAGE:READY_TO_OPEN")
853
+ STATE_FAILURE_COUNT=$(captured_line_count "$STDERR_FILE" "ALIPAY_AIPAY_INTERNAL:KEY_PAGE_STATE:SAVE_FAILED")
854
+ MESSAGE_COUNT=$(captured_line_count "$STDERR_FILE" "ALIPAY_AIPAY_INTERNAL:CHILD_CUSTOMER_MESSAGE_RENDERED")
855
+ if [ ! -s "$STDOUT_FILE" ] || [ "$READY_COUNT" -ne 1 ] || [ "$STATE_FAILURE_COUNT" -gt 1 ] || [ "$MESSAGE_COUNT" -ne 1 ]; then
856
+ emit_control "$FIELD=UNKNOWN"
857
+ return 1
858
+ fi
859
+ copy_captured_output "$STDOUT_FILE"
860
+ [ "$STATE_FAILURE_COUNT" -eq 0 ] || emit_control "KEY_PAGE_STATE:SAVE_FAILED"
861
+ emit_control "$FIELD=READY_TO_OPEN"
862
+ emit_control "KEY_PAGE:READY_TO_OPEN"
863
+ }
864
+
865
+ fail_application_combination() {
866
+ emit_control "APPLICATION_COMBINATION_RESULT=$1"
867
+ exit 1
868
+ }
869
+
870
+ handle_create_from_snapshot_signal() {
871
+ local EXIT_CODE="$1"
872
+ trap - INT TERM
873
+ case "${COMBINATION_PHASE:-}" in
874
+ PRE_WRITE|LIST|RECHECK) emit_control "APPLICATION_COMBINATION_RESULT=PREWRITE_FAILED" ;;
875
+ CREATE) forward_write_child_outcome create "$CHILD_STDOUT" "$CHILD_STDERR" "$EXIT_CODE" "$APP_APPLICATION_TYPE" true || true ;;
876
+ KEY_NOT_STARTED) emit_control "APPLICATION_COMBINATION_RESULT=KEY_NOT_STARTED" ;;
877
+ KEY) forward_write_child_outcome key "$CHILD_STDOUT" "$CHILD_STDERR" "$EXIT_CODE" "" true || true ;;
878
+ esac
879
+ exit "$EXIT_CODE"
880
+ }
881
+
882
+ # Guarded list -> compare -> create -> key orchestration.
883
+ app_create_from_snapshot() {
884
+ local PREVIOUS_CANDIDATES_JSON="" PREVIOUS_CANDIDATES_SET=false PUBLIC_KEY="" DEFER_OPEN=false
885
+ local PRODUCT_TYPE_SET=false SALES_CODE_SET=false
886
+ local ARG VALUE APP_APPLICATION_TYPE CHILD_RC RECHECK_RESULT RECHECK_MESSAGE APP_ID
887
+ local TMP_ROOT CHILD_STDOUT CHILD_STDERR
888
+ local COMBINATION_PHASE="PRE_WRITE"
889
+ local -a CONTEXT_ARGS=()
890
+
891
+ while [ "$#" -gt 0 ]; do
892
+ ARG="$1"
893
+ case "$ARG" in
894
+ --previous-candidates-json)
895
+ [ "$#" -ge 2 ] || fail_application_combination CONTRACT_ERROR
896
+ [ "$PREVIOUS_CANDIDATES_SET" = false ] || fail_application_combination CONTRACT_ERROR
897
+ PREVIOUS_CANDIDATES_SET=true
898
+ PREVIOUS_CANDIDATES_JSON="$2"
899
+ shift 2
900
+ ;;
901
+ --defer-open)
902
+ DEFER_OPEN=true
903
+ shift
904
+ [ "$#" -eq 1 ] || fail_application_combination CONTRACT_ERROR
905
+ PUBLIC_KEY="$1"
906
+ shift
907
+ ;;
908
+ --product-type|--sales-code|--application-type|--mobile-platform|--bundle-id|--app-package|--app-sign)
909
+ [ "$#" -ge 2 ] || fail_application_combination CONTRACT_ERROR
910
+ [ "$ARG" = "--product-type" ] && PRODUCT_TYPE_SET=true
911
+ [ "$ARG" = "--sales-code" ] && SALES_CODE_SET=true
912
+ VALUE="$2"
913
+ CONTEXT_ARGS+=("$ARG" "$VALUE")
914
+ shift 2
915
+ ;;
916
+ *)
917
+ fail_application_combination CONTRACT_ERROR
918
+ ;;
919
+ esac
920
+ done
921
+
922
+ [ -n "$PREVIOUS_CANDIDATES_JSON" ] \
923
+ && printf '%s' "$PREVIOUS_CANDIDATES_JSON" | jq -e 'type == "array"' >/dev/null 2>&1 \
924
+ || fail_application_combination SNAPSHOT_REFRESH_REQUIRED
925
+ [ "$DEFER_OPEN" = true ] && [ -n "$PUBLIC_KEY" ] || fail_application_combination CONTRACT_ERROR
926
+
927
+ parse_app_context_args "${CONTEXT_ARGS[@]}" >/dev/null 2>&1 \
928
+ || fail_application_combination CONTRACT_ERROR
929
+ [ "$PRODUCT_TYPE_SET" = true ] && [ "$SALES_CODE_SET" = true ] \
930
+ || fail_application_combination CONTRACT_ERROR
931
+ APP_APPLICATION_TYPE=$(resolve_application_type 2>/dev/null) \
932
+ || fail_application_combination CONTRACT_ERROR
933
+ if [ "${MOBILE_PLATFORM:-}" = "HARMONY" ]; then
934
+ validate_mobile_platform_args "$APP_APPLICATION_TYPE" || exit 1
935
+ else
936
+ validate_mobile_platform_args "$APP_APPLICATION_TYPE" >/dev/null 2>&1 \
937
+ || fail_application_combination CONTRACT_ERROR
938
+ fi
939
+ node "$CANDIDATE_RUNNER" application-candidates \
940
+ --application-type "$APP_APPLICATION_TYPE" \
941
+ --candidates-json "$PREVIOUS_CANDIDATES_JSON" >/dev/null 2>&1 \
942
+ || fail_application_combination SNAPSHOT_REFRESH_REQUIRED
943
+
944
+ command -v mktemp >/dev/null 2>&1 || fail_application_combination PREWRITE_FAILED
945
+ TMP_ROOT=$(umask 077; mktemp -d "${TMPDIR:-/tmp}/alipay-aipay-app-create.XXXXXX") \
946
+ || fail_application_combination PREWRITE_FAILED
947
+ CHILD_STDOUT="$TMP_ROOT/child.stdout"
948
+ CHILD_STDERR="$TMP_ROOT/child.stderr"
949
+ (umask 077; : > "$CHILD_STDOUT"; : > "$CHILD_STDERR") \
950
+ || { rm -rf "$TMP_ROOT"; fail_application_combination PREWRITE_FAILED; }
951
+ trap 'handle_create_from_snapshot_signal 130' INT
952
+ trap 'handle_create_from_snapshot_signal 143' TERM
953
+ trap 'rm -rf "$TMP_ROOT"' EXIT
954
+
955
+ COMBINATION_PHASE="LIST"
956
+ ALIPAY_AIPAY_SUPPRESS_AUTH_LOGOUT=1 bash "${SCRIPT_DIR}/app.sh" list \
957
+ --product-type "$PRODUCT_TYPE" \
958
+ --sales-code "$SALES_CODE" \
959
+ --application-type "$APP_APPLICATION_TYPE" \
960
+ >"$CHILD_STDOUT" 2>"$CHILD_STDERR"
961
+ CHILD_RC=$?
962
+ load_child_recovery_marker "$CHILD_STDERR" || fail_application_combination PREWRITE_FAILED
963
+ if [ -n "$CHILD_RECOVERY_MARKER" ]; then
964
+ [ "$CHILD_RC" -eq 0 ] || { emit_control "$CHILD_RECOVERY_MARKER"; exit "$CHILD_RC"; }
965
+ fail_application_combination PREWRITE_FAILED
966
+ fi
967
+ [ "$CHILD_RC" -eq 0 ] && [ ! -s "$CHILD_STDOUT" ] || fail_application_combination PREWRITE_FAILED
968
+ APP_CANDIDATES=$(extract_unique_control_field "$CHILD_STDERR" APP_CANDIDATES_JSON) \
969
+ || fail_application_combination PREWRITE_FAILED
970
+ CURRENT_APPLICATION_TYPE=$(extract_unique_control_field "$CHILD_STDERR" APPLICATION_TYPE) \
971
+ || fail_application_combination PREWRITE_FAILED
972
+ [ "$CURRENT_APPLICATION_TYPE" = "$APP_APPLICATION_TYPE" ] || fail_application_combination PREWRITE_FAILED
973
+
974
+ COMBINATION_PHASE="RECHECK"
975
+ : > "$CHILD_STDOUT"; : > "$CHILD_STDERR"
976
+ node "$CANDIDATE_RUNNER" application-recheck \
977
+ --application-type "$APP_APPLICATION_TYPE" \
978
+ --previous-candidates-json "$PREVIOUS_CANDIDATES_JSON" \
979
+ --current-candidates-json "$APP_CANDIDATES" \
980
+ >"$CHILD_STDOUT" 2>"$CHILD_STDERR"
981
+ CHILD_RC=$?
982
+ [ "$CHILD_RC" -eq 0 ] || fail_application_combination PREWRITE_FAILED
983
+ RECHECK_RESULT=$(extract_unique_control_field "$CHILD_STDERR" APPLICATION_RECHECK_RESULT) \
984
+ || fail_application_combination PREWRITE_FAILED
985
+ case "$RECHECK_RESULT" in
986
+ CHANGED_EMPTY|CHANGED_NONEMPTY)
987
+ RECHECK_MESSAGE=$(extract_unique_control_field "$CHILD_STDERR" APPLICATION_RECHECK_MESSAGE) \
988
+ || fail_application_combination PREWRITE_FAILED
989
+ [ "$RECHECK_MESSAGE" = READY ] || fail_application_combination PREWRITE_FAILED
990
+ emit_control_field "APP_CANDIDATES_JSON" "$APP_CANDIDATES" || exit 1
991
+ emit_control_field "APPLICATION_TYPE" "$APP_APPLICATION_TYPE" || exit 1
992
+ [ -s "$CHILD_STDOUT" ] || fail_application_combination PREWRITE_FAILED
993
+ copy_captured_output "$CHILD_STDOUT"
994
+ [ "$RECHECK_RESULT" != CHANGED_NONEMPTY ] || emit_control "CUSTOMER_MESSAGE_DELIVERY_REQUIRED=application.candidate.select"
995
+ emit_control "APPLICATION_RECHECK_RESULT=$RECHECK_RESULT"
996
+ exit 0
997
+ ;;
998
+ UNCHANGED) [ ! -s "$CHILD_STDOUT" ] || fail_application_combination PREWRITE_FAILED
999
+ emit_control "APPLICATION_RECHECK_RESULT=UNCHANGED" ;;
1000
+ *) fail_application_combination PREWRITE_FAILED ;;
1001
+ esac
1002
+
1003
+ COMBINATION_PHASE="CREATE"
1004
+ : > "$CHILD_STDOUT"; : > "$CHILD_STDERR"
1005
+ ALIPAY_AIPAY_SUPPRESS_AUTH_LOGOUT=1 ALIPAY_AIPAY_CAPTURE_CHILD_OUTPUT=1 bash "${SCRIPT_DIR}/app.sh" create "${CONTEXT_ARGS[@]}" \
1006
+ >"$CHILD_STDOUT" 2>"$CHILD_STDERR"
1007
+ CHILD_RC=$?
1008
+ forward_write_child_outcome create "$CHILD_STDOUT" "$CHILD_STDERR" "$CHILD_RC" "$APP_APPLICATION_TYPE"
1009
+ if [ "$?" -ne 0 ]; then
1010
+ [ "$CHILD_RC" -ne 0 ] && exit "$CHILD_RC"
1011
+ exit 1
1012
+ fi
1013
+ APP_ID="$FORWARDED_APP_ID"
1014
+ COMBINATION_PHASE="KEY_NOT_STARTED"
1015
+ : > "$CHILD_STDOUT"; : > "$CHILD_STDERR"
1016
+ COMBINATION_PHASE="KEY"
1017
+ ALIPAY_AIPAY_SUPPRESS_AUTH_LOGOUT=1 ALIPAY_AIPAY_CAPTURE_CHILD_OUTPUT=1 bash "${SCRIPT_DIR}/app.sh" key \
1018
+ --product-type "$PRODUCT_TYPE" --defer-open "$APP_ID" "$PUBLIC_KEY" >"$CHILD_STDOUT" 2>"$CHILD_STDERR"
1019
+ CHILD_RC=$?
1020
+ forward_write_child_outcome key "$CHILD_STDOUT" "$CHILD_STDERR" "$CHILD_RC"
1021
+ if [ "$?" -ne 0 ]; then
1022
+ [ "$CHILD_RC" -ne 0 ] && exit "$CHILD_RC"
1023
+ exit 1
1024
+ fi
1025
+ COMBINATION_PHASE="COMPLETE"
1026
+ exit 0
1027
+ }
1028
+
1029
+ app_key() {
1030
+ prepare_positional_app_cli_context "$@" || exit 1
1031
+ set -- "${APP_POSITIONAL_ARGS[@]}"
1032
+ if [ "${1:-}" = "--defer-open" ]; then
1033
+ shift
1034
+ else
1035
+ echo "❌ 参数格式错误:app key 必须使用 --defer-open,确保先展示公钥确认说明和链接,再自动打开页面"
1036
+ echo "用法: bash app.sh key --product-type <type> --defer-open <appId> <publicKey>"
1037
+ exit 1
1038
+ fi
1039
+
1040
+ if [ "$#" -ne 2 ] || [[ "${1:-}" == --* ]]; then
1041
+ echo "❌ 参数格式错误:key 使用位置参数 <appId> <publicKey>"
1042
+ echo "用法: bash app.sh key --product-type <type> --defer-open <appId> <publicKey>"
1043
+ exit 1
1044
+ fi
1045
+
1046
+ APP_ID="$1"
1047
+ PUBLIC_KEY="$2"
1048
+
1049
+ if [ -z "$APP_ID" ] || [ -z "$PUBLIC_KEY" ]; then
1050
+ echo "❌ 请提供 appId 和 publicKey"
1051
+ echo "用法: bash app.sh key --product-type <type> --defer-open <appId> <publicKey>"
1052
+ exit 1
1053
+ fi
1054
+ if ! validate_control_value "$APP_ID"; then
1055
+ echo "❌ appId 包含非法控制字符"
1056
+ exit 1
1057
+ fi
1058
+
1059
+ REQUEST_JSON=$(jq -n \
1060
+ --arg appId "$APP_ID" \
1061
+ --arg publicKey "$PUBLIC_KEY" \
1062
+ '{request:{appId:$appId,signType:"RSA2",publicKey:$publicKey}}')
1063
+
1064
+ require_alipay_cli_local_state_access_or_emit || exit "$?"
1065
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
1066
+ run_network_retry RESULT write application_key_confirm_page -- alipay-cli mcp call apprelease.createKeyConfirmPage \
1067
+ -d "$REQUEST_JSON" \
1068
+ --json
1069
+ RETRY_RC=$?
1070
+ if [ "$RETRY_RC" -eq 75 ]; then
1071
+ echo "❌ 公钥确认页生成响应不明,结果标记为 UNKNOWN;不得自动生成第二个页面"
1072
+ emit_control "KEY_PAGE_RESULT=UNKNOWN"
1073
+ exit 1
1074
+ elif [ "$RETRY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
1075
+ exit "$RETRY_RC"
1076
+ elif [ "$RETRY_RC" -ne 0 ]; then
1077
+ echo "❌ 公钥确认页因明确未发送的网络异常在自动重试两次后仍未恢复"
1078
+ emit_control "KEY_PAGE_RESULT=FAILED"
1079
+ exit 1
1080
+ fi
1081
+
1082
+ if ! handle_error "$RESULT" "MCP_AUTHENTICATED_CALL"; then
1083
+ emit_control "KEY_PAGE_RESULT=FAILED"
1084
+ exit 1
1085
+ fi
1086
+
1087
+ BUSINESS=$(unwrap_mcp "$RESULT")
1088
+ SUCCESS=$(echo "$BUSINESS" | jq -r '.success // false')
1089
+
1090
+ if [ "$SUCCESS" = "true" ]; then
1091
+ CONFIRM_PAGE_URL=$(echo "$BUSINESS" | jq -r '.resultObj.confirmPageUrl // empty')
1092
+
1093
+ if [ -n "$CONFIRM_PAGE_URL" ]; then
1094
+ RENDERER="${SCRIPT_DIR}/../../../normal/scripts/render_customer_message.mjs"
1095
+ if ! save_app_key_page_state; then
1096
+ emit_control "KEY_PAGE_STATE:SAVE_FAILED"
1097
+ fi
1098
+ MESSAGE_INPUT=$(printf '%s\n' "$APP_ID" "$CONFIRM_PAGE_URL" | jq -Rs '
1099
+ split("\n") as $v | {appId:$v[0],officialUrl:$v[1]}
1100
+ ')
1101
+ if ! printf '%s' "$MESSAGE_INPUT" | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=app.sh node "$RENDERER" application.key.page --variant READY; then
1102
+ echo "❌ 公钥确认页已生成,但标准消息无法安全输出;结果保持待核验,禁止自动生成第二个页面"
1103
+ emit_control "KEY_PAGE_RESULT=UNKNOWN"
1104
+ exit 1
1105
+ fi
1106
+ [ "${ALIPAY_AIPAY_CAPTURE_CHILD_OUTPUT:-0}" != "1" ] || emit_control "CHILD_CUSTOMER_MESSAGE_RENDERED"
1107
+ emit_control "KEY_PAGE_RESULT=READY_TO_OPEN"
1108
+ emit_control "KEY_PAGE:READY_TO_OPEN"
1109
+ else
1110
+ echo "❌ 公钥确认页响应缺少 confirmPageUrl,结果标记为 UNKNOWN;不得自动生成第二个页面"
1111
+ emit_control "KEY_PAGE_RESULT=UNKNOWN"
1112
+ exit 1
1113
+ fi
1114
+ else
1115
+ ERROR_MSG=$(echo "$BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
1116
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
1117
+ echo "❌ 公钥设置失败: $ERROR_MSG"
1118
+ emit_control "KEY_PAGE_RESULT=FAILED"
1119
+ exit 1
1120
+ fi
1121
+ }
1122
+
1123
+ app_reconcile_key() {
1124
+ local RECONCILE_STDERR QUERY_RC
1125
+ prepare_positional_app_cli_context "$@" >/dev/null 2>&1 || {
1126
+ emit_control "KEY_PAGE_RECONCILIATION=CONTRACT_ERROR"
1127
+ exit 1
1128
+ }
1129
+ set -- "${APP_POSITIONAL_ARGS[@]}"
1130
+ if [ "$#" -ne 2 ] || [[ "${1:-}" == --* ]] || ! validate_control_value "${1:-}" || [ -z "${2:-}" ]; then
1131
+ emit_control "KEY_PAGE_RECONCILIATION=CONTRACT_ERROR"
1132
+ exit 1
1133
+ fi
1134
+
1135
+ APP_ID="$1"
1136
+ PUBLIC_KEY="$2"
1137
+ RECONCILE_STDERR=$(mktemp "${TMPDIR:-/tmp}/alipay-aipay-key-reconcile.XXXXXX") || {
1138
+ emit_control "KEY_PAGE_RECONCILIATION=QUERY_FAILED"
1139
+ exit 1
1140
+ }
1141
+ chmod 600 "$RECONCILE_STDERR" 2>/dev/null || {
1142
+ rm -f "$RECONCILE_STDERR"
1143
+ emit_control "KEY_PAGE_RECONCILIATION=QUERY_FAILED"
1144
+ exit 1
1145
+ }
1146
+ ALIPAY_AIPAY_SUPPRESS_AUTH_LOGOUT=1 query_security_key "$APP_ID" "$PUBLIC_KEY" \
1147
+ >/dev/null 2>"$RECONCILE_STDERR"
1148
+ QUERY_RC=$?
1149
+ if [ "$QUERY_RC" -ne 0 ]; then
1150
+ if emit_unique_child_recovery_marker "$RECONCILE_STDERR"; then
1151
+ rm -f "$RECONCILE_STDERR"
1152
+ exit "$QUERY_RC"
1153
+ fi
1154
+ rm -f "$RECONCILE_STDERR"
1155
+ emit_control "KEY_PAGE_RECONCILIATION=QUERY_FAILED"
1156
+ exit 1
1157
+ fi
1158
+ rm -f "$RECONCILE_STDERR"
1159
+ RSA2_KEY_INFO=$(extract_rsa2_application_key_info)
1160
+ if is_rsa2_application_key_ready "$RSA2_KEY_INFO"; then
1161
+ emit_control_field "APP_ID" "$APP_ID" || exit 1
1162
+ emit_control "KEY_PAGE_RECONCILIATION=CONFIRMED"
1163
+ else
1164
+ emit_control "KEY_PAGE_RECONCILIATION=NOT_CONFIRMED"
1165
+ fi
1166
+ }
1167
+
1168
+ app_open_key_page() {
1169
+ local APP_ID="$1" SAVED_APP_ID CONFIRM_PAGE_URL MESSAGE_INPUT OPEN_HELPER OPEN_STATUS
1170
+
1171
+ if [ "$#" -ne 1 ] || [[ "${1:-}" == --* ]]; then
1172
+ echo "❌ 参数格式错误:open-key-page 使用位置参数 <appId>,不支持 --app-id 等未定义选项"
1173
+ echo "用法: bash app.sh open-key-page <appId>"
1174
+ exit 1
1175
+ fi
1176
+ if ! validate_control_value "$APP_ID"; then
1177
+ echo "❌ appId 包含非法控制字符"
1178
+ exit 1
1179
+ fi
1180
+ if ! validate_app_key_state_file; then
1181
+ emit_control "KEY_OPEN:LINK_ONLY"
1182
+ return 0
1183
+ fi
1184
+ if ! jq -e . "$APP_KEY_STATE_FILE" >/dev/null 2>&1; then
1185
+ echo "❌ 公钥确认页状态文件不是合法 JSON,已停止"
1186
+ emit_control "FLOW:ERROR"
1187
+ return 1
1188
+ fi
1189
+
1190
+ SAVED_APP_ID=$(jq -r '.appId // ""' "$APP_KEY_STATE_FILE")
1191
+ CONFIRM_PAGE_URL=$(jq -r '.confirmPageUrl // ""' "$APP_KEY_STATE_FILE")
1192
+ if [ "$SAVED_APP_ID" != "$APP_ID" ] || [ -z "$CONFIRM_PAGE_URL" ]; then
1193
+ echo "❌ 当前 appId 与已生成的公钥确认页上下文不一致,已停止打开"
1194
+ emit_control "FLOW:ERROR"
1195
+ return 1
1196
+ fi
1197
+
1198
+ MESSAGE_INPUT=$(printf '%s\n' "$APP_ID" "$CONFIRM_PAGE_URL" | jq -Rs '
1199
+ split("\n") as $v | {appId:$v[0],officialUrl:$v[1]}
1200
+ ')
1201
+ if ! printf '%s' "$MESSAGE_INPUT" | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=app.sh node "$RENDERER" application.key.page --variant READY >/dev/null; then
1202
+ echo "❌ 公钥确认页 URL 校验失败,已停止打开"
1203
+ emit_control "FLOW:ERROR"
1204
+ return 1
1205
+ fi
1206
+
1207
+ OPEN_HELPER="${SCRIPT_DIR}/../../../normal/scripts/open_official_url.sh"
1208
+ OPEN_STATUS=$(printf '%s\n' "$CONFIRM_PAGE_URL" | bash "$OPEN_HELPER" public-key-confirmation)
1209
+ case "$OPEN_STATUS" in OPENED|OPEN_FAILED|GUI_UNAVAILABLE|LINK_ONLY) ;; *) OPEN_STATUS="OPEN_FAILED" ;; esac
1210
+ emit_control "KEY_OPEN:${OPEN_STATUS}"
1211
+ }
1212
+
1213
+ submit_application_audit() {
1214
+ local APP_ID="$1"
1215
+ REQUEST_JSON=$(jq -n --arg appId "$APP_ID" '{request:{appId:$appId}}')
1216
+
1217
+ require_alipay_cli_local_state_access_or_emit || exit "$?"
1218
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
1219
+ run_network_retry RESULT write application_audit -- alipay-cli mcp call apprelease.submitApplicationAudit \
1220
+ -d "$REQUEST_JSON" \
1221
+ --json
1222
+ RETRY_RC=$?
1223
+ if [ "$RETRY_RC" -eq 75 ]; then
1224
+ if ALIPAY_AIPAY_SUPPRESS_LOCAL_FS_RETRY_MARKER=1 query_application_info "$APP_ID"; then
1225
+ ACTUAL_STATUS=$(extract_application_status)
1226
+ case "$ACTUAL_STATUS" in
1227
+ AUDITING|ON_LINE)
1228
+ emit_control "✅ 应用审核提交已由应用信息查询核验成功"
1229
+ emit_control_field "APP_ID" "$APP_ID" || return 1
1230
+ emit_control "APPLICATION_AUDIT_RESULT=SUBMITTED"
1231
+ emit_control "FLOW:AUDIT_SUBMITTED"
1232
+ return 0
1233
+ ;;
1234
+ esac
1235
+ fi
1236
+ echo "❌ 应用提审响应不明且现有状态无法排除传播延迟,结果标记为 UNKNOWN,禁止自动重复提审"
1237
+ emit_control "APPLICATION_AUDIT_RESULT=UNKNOWN"
1238
+ exit 1
1239
+ elif [ "$RETRY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
1240
+ exit "$RETRY_RC"
1241
+ elif [ "$RETRY_RC" -ne 0 ]; then
1242
+ echo "❌ 应用提审因明确未发送的网络异常在自动重试两次后仍未恢复"
1243
+ emit_control "APPLICATION_AUDIT_RESULT=FAILED"
1244
+ exit 1
1245
+ fi
1246
+
1247
+ if ! handle_error "$RESULT" "MCP_AUTHENTICATED_CALL"; then
1248
+ emit_control "APPLICATION_AUDIT_RESULT=FAILED"
1249
+ exit 1
1250
+ fi
1251
+
1252
+ BUSINESS=$(unwrap_mcp "$RESULT")
1253
+ SUCCESS=$(echo "$BUSINESS" | jq -r '.success // false')
1254
+
1255
+ if [ "$SUCCESS" = "true" ]; then
1256
+ emit_control "✅ 应用审核提交成功"
1257
+ emit_control_field "APP_ID" "$APP_ID" || return 1
1258
+ emit_control "APPLICATION_AUDIT_RESULT=SUBMITTED"
1259
+ emit_control "FLOW:AUDIT_SUBMITTED"
1260
+ else
1261
+ ERROR_MSG=$(echo "$BUSINESS" | jq -r '.error.message // .errorMessage // "未知错误"')
1262
+ ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
1263
+ echo "❌ 提交审核失败: $ERROR_MSG"
1264
+ emit_control "APPLICATION_AUDIT_RESULT=FAILED"
1265
+ exit 1
1266
+ fi
1267
+ }
1268
+
1269
+ app_audit() {
1270
+ prepare_positional_app_cli_context "$@" || exit 1
1271
+ set -- "${APP_POSITIONAL_ARGS[@]}"
1272
+ if [ "$#" -ne 1 ] || [[ "${1:-}" == --* ]]; then
1273
+ echo "❌ 参数格式错误:audit 使用位置参数 <appId>,不支持 --app-id 等未定义选项"
1274
+ echo "用法: bash app.sh audit --product-type <type> <appId>"
1275
+ exit 1
1276
+ fi
1277
+
1278
+ APP_ID="$1"
1279
+ [ -n "$APP_ID" ] || { echo "❌ 请提供 appId"; echo "用法: bash app.sh audit --product-type <type> <appId>"; exit 1; }
1280
+ validate_control_value "$APP_ID" || { echo "❌ appId 包含非法控制字符"; exit 1; }
1281
+ verify_key_and_export_alipay_public_key "$APP_ID"
1282
+ EXPORT_RC=$?
1283
+ [ "$EXPORT_RC" -eq 0 ] || exit "$EXPORT_RC"
1284
+ submit_application_audit "$APP_ID"
1285
+ }
1286
+
1287
+ app_verify_key() {
1288
+ prepare_positional_app_cli_context "$@" || exit 1
1289
+ set -- "${APP_POSITIONAL_ARGS[@]}"
1290
+ if [ "$#" -lt 1 ] || [ "$#" -gt 2 ] || [[ "${1:-}" == --* ]]; then
1291
+ echo "❌ 参数格式错误:verify-key 使用位置参数 <appId> [publicKey],不支持 --app-id 等未定义选项"
1292
+ echo "用法: bash app.sh verify-key --product-type <type> <appId> [publicKey]"
1293
+ exit 1
1294
+ fi
1295
+
1296
+ APP_ID="$1"
1297
+ PUBLIC_KEY="${2:-}"
1298
+
1299
+ if [ -z "$APP_ID" ]; then
1300
+ echo "❌ 请提供 appId"
1301
+ echo "用法: bash app.sh verify-key --product-type <type> <appId> [publicKey]"
1302
+ exit 1
1303
+ fi
1304
+ validate_control_value "$APP_ID" || { echo "❌ appId 包含非法控制字符"; exit 1; }
1305
+
1306
+ ensure_rsa2_application_key_ready "$APP_ID" "$PUBLIC_KEY"
1307
+ VERIFY_RC=$?
1308
+ if [ "$VERIFY_RC" -eq 0 ]; then
1309
+ export_alipay_public_key_from_security_business "$APP_ID"
1310
+ EXPORT_RC=$?
1311
+ [ "$EXPORT_RC" -eq 0 ] || exit "$EXPORT_RC"
1312
+ emit_control "✅ 应用公钥已确认"
1313
+ emit_control_field "APP_ID" "$APP_ID" || exit 1
1314
+ emit_control "FLOW:KEY_CONFIRMED"
1315
+ else
1316
+ if [ "$KEY_VERIFY_RESULT" = "LOCAL_FS_PERMISSION" ]; then
1317
+ exit "$VERIFY_RC"
1318
+ elif [ "$KEY_VERIFY_RESULT" = "PENDING" ]; then
1319
+ if ! render_key_pending; then
1320
+ emit_control "FLOW:ERROR"
1321
+ exit 1
1322
+ fi
1323
+ emit_control "FLOW:KEY_NOT_CONFIRMED"
1324
+ else
1325
+ emit_control "FLOW:ERROR"
1326
+ fi
1327
+ exit 1
1328
+ fi
1329
+ }
1330
+
1331
+ app_verify_key_and_audit() {
1332
+ prepare_positional_app_cli_context "$@" || exit 1
1333
+ set -- "${APP_POSITIONAL_ARGS[@]}"
1334
+ if [ "$#" -lt 1 ] || [ "$#" -gt 2 ] || [[ "${1:-}" == --* ]]; then
1335
+ echo "❌ 参数格式错误:verify-key-and-audit 使用位置参数 <appId> [publicKey],不支持 --app-id 等未定义选项"
1336
+ echo "用法: bash app.sh verify-key-and-audit --product-type <type> <appId> [publicKey]"
1337
+ exit 1
1338
+ fi
1339
+
1340
+ APP_ID="$1"
1341
+ PUBLIC_KEY="${2:-}"
1342
+ [ -n "$APP_ID" ] || { echo "❌ 请提供 appId"; exit 1; }
1343
+ validate_control_value "$APP_ID" || { echo "❌ appId 包含非法控制字符"; exit 1; }
1344
+
1345
+ ensure_rsa2_application_key_ready "$APP_ID" "$PUBLIC_KEY"
1346
+ VERIFY_RC=$?
1347
+ if [ "$VERIFY_RC" -ne 0 ]; then
1348
+ if [ "$KEY_VERIFY_RESULT" = "LOCAL_FS_PERMISSION" ]; then
1349
+ exit "$VERIFY_RC"
1350
+ elif [ "$KEY_VERIFY_RESULT" = "PENDING" ]; then
1351
+ if ! render_key_pending; then
1352
+ emit_control "FLOW:ERROR"
1353
+ exit 1
1354
+ fi
1355
+ emit_control "FLOW:KEY_NOT_CONFIRMED"
1356
+ else
1357
+ emit_control "FLOW:ERROR"
1358
+ fi
1359
+ exit 1
1360
+ fi
1361
+ export_alipay_public_key_from_security_business "$APP_ID"
1362
+ EXPORT_RC=$?
1363
+ [ "$EXPORT_RC" -eq 0 ] || exit "$EXPORT_RC"
1364
+ submit_application_audit "$APP_ID"
1365
+ }
1366
+
1367
+ app_reuse() {
1368
+ prepare_positional_app_cli_context "$@" || exit 1
1369
+ set -- "${APP_POSITIONAL_ARGS[@]}"
1370
+ if [ "$#" -ne 1 ] || [[ "${1:-}" == --* ]]; then
1371
+ echo "❌ 参数格式错误:reuse 使用位置参数 <appId>,不支持 --app-id 等未定义选项"
1372
+ echo "用法: bash app.sh reuse --product-type <type> <appId>"
1373
+ exit 1
1374
+ fi
1375
+
1376
+ APP_ID="$1"
1377
+
1378
+ if [ -z "$APP_ID" ]; then
1379
+ echo "❌ 请提供 appId"
1380
+ echo "用法: bash app.sh reuse --product-type <type> <appId>"
1381
+ exit 1
1382
+ fi
1383
+ validate_control_value "$APP_ID" || { echo "❌ appId 包含非法控制字符"; exit 1; }
1384
+
1385
+ query_application_info "$APP_ID"
1386
+ QUERY_RC=$?
1387
+ if [ "$QUERY_RC" -eq 0 ]; then
1388
+ APP_STATUS=$(extract_application_status)
1389
+ if [ "$APP_STATUS" != "ON_LINE" ]; then
1390
+ echo "❌ 仅允许复用 ON_LINE(已上线)应用,当前应用状态: ${APP_STATUS:-未知}"
1391
+ emit_control "FLOW:ERROR"
1392
+ exit 1
1393
+ fi
1394
+ else
1395
+ if [ "$QUERY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
1396
+ exit "$QUERY_RC"
1397
+ fi
1398
+ emit_control "FLOW:ERROR"
1399
+ exit 1
1400
+ fi
1401
+
1402
+ query_security_key "$APP_ID"
1403
+ QUERY_RC=$?
1404
+ if [ "$QUERY_RC" -eq 0 ]; then
1405
+ RSA2_KEY_INFO=$(extract_rsa2_application_key_info)
1406
+ RSA2_ALIPAY_KEY_INFO=$(extract_rsa2_alipay_key_info)
1407
+ ALIPAY_PUBLIC_KEY=$(extract_alipay_public_key "$RSA2_ALIPAY_KEY_INFO")
1408
+
1409
+ if ! is_rsa2_application_key_ready "$RSA2_KEY_INFO"; then
1410
+ emit_control "⚠️ 未确认到已生效的 RSA2 应用公钥"
1411
+ emit_control "FLOW:REUSE_NO_KEY"
1412
+ exit 1
1413
+ elif [ -n "$ALIPAY_PUBLIC_KEY" ]; then
1414
+ write_and_emit_alipay_public_key_export "$APP_ID" "$ALIPAY_PUBLIC_KEY"
1415
+ EXPORT_RC=$?
1416
+ [ "$EXPORT_RC" -eq 0 ] || exit "$EXPORT_RC"
1417
+ emit_control_field "APP_ID" "$APP_ID" || exit 1
1418
+ emit_control "FLOW:REUSE_SUCCESS"
1419
+ else
1420
+ echo "❌ 已确认应用公钥生效,但未获取到 RSA2 支付宝公钥"
1421
+ emit_control "FLOW:ERROR"
1422
+ exit 1
1423
+ fi
1424
+ else
1425
+ if [ "$QUERY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
1426
+ exit "$QUERY_RC"
1427
+ fi
1428
+ emit_control "FLOW:ERROR"
1429
+ exit 1
1430
+ fi
1431
+ }
1432
+
1433
+ APP_COMMAND="${1:-}"
1434
+ case "$APP_COMMAND" in
1435
+ list|create|create-from-snapshot|key|reconcile-key|verify-key|verify-key-and-audit|audit|reuse)
1436
+ require_command alipay-cli || exit 1
1437
+ ;;
1438
+ esac
1439
+
1440
+ case "$APP_COMMAND" in
1441
+ list)
1442
+ shift
1443
+ app_list "$@"
1444
+ ;;
1445
+ create)
1446
+ shift
1447
+ app_create "$@"
1448
+ ;;
1449
+ create-from-snapshot)
1450
+ shift
1451
+ app_create_from_snapshot "$@"
1452
+ ;;
1453
+ key)
1454
+ shift
1455
+ app_key "$@"
1456
+ ;;
1457
+ reconcile-key)
1458
+ shift
1459
+ app_reconcile_key "$@"
1460
+ ;;
1461
+ open-key-page)
1462
+ shift
1463
+ app_open_key_page "$@"
1464
+ ;;
1465
+ verify-key)
1466
+ shift
1467
+ app_verify_key "$@"
1468
+ ;;
1469
+ verify-key-and-audit)
1470
+ shift
1471
+ app_verify_key_and_audit "$@"
1472
+ ;;
1473
+ audit)
1474
+ shift
1475
+ app_audit "$@"
1476
+ ;;
1477
+ reuse)
1478
+ shift
1479
+ app_reuse "$@"
1480
+ ;;
1481
+ *)
1482
+ echo "用法: bash app.sh <list|create|create-from-snapshot|key|reconcile-key|open-key-page|verify-key|verify-key-and-audit|audit|reuse> [参数...]"
1483
+ exit 1
1484
+ ;;
1485
+ esac