@alipay/alipay-aipay 1.7.0 → 1.7.1

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 +926 -0
  112. package/skill-pre/references/onboarding/flow.md +595 -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 +97 -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,1040 @@
1
+ #!/bin/bash
2
+ #=============================================================================
3
+ # 脚本名称: auth.sh
4
+ # 功能描述: 登录授权全流程 - 检查状态 + 执行登录 + 授权确认 + scope/MCC 校验 + 重新授权
5
+ # 调用位置: Step 3 登录授权
6
+ # 调用前置: 脚本通过 error_handler.sh 间接初始化 DEV_TOOL_NAME/PLATFORM_ID;必要时可用 DEV_TOOL_NAME 环境变量覆盖
7
+ # 用法:
8
+ # auth init --defer-open --scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name> --mcc-context-receipt <receipt>
9
+ # auth open [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name> --mcc-context-receipt <receipt>]
10
+ # auth manual-open
11
+ # auth confirm [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name> --mcc-context-receipt <receipt>]
12
+ # auth wait [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name> --mcc-context-receipt <receipt>]
13
+ # auth mismatch --defer-open [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name> --mcc-context-receipt <receipt>]
14
+ # 返回值:
15
+ # init: AUTH_FLOW:SKIP | AUTH_FLOW:READY_TO_OPEN | AUTH_FLOW:SCOPE_MISMATCH | AUTH_FLOW:MCC_MISMATCH | AUTH_FLOW:RETRY_WITH_NETWORK | AUTH_FLOW:FAILED
16
+ # wait: AUTH_FLOW:AUTH_SUCCESS | AUTH_FLOW:PENDING | AUTH_FLOW:EXPIRED | AUTH_FLOW:SCOPE_MISMATCH | AUTH_FLOW:MCC_MISMATCH | AUTH_FLOW:CONTEXT_MISMATCH | AUTH_FLOW:RETRY_WITH_NETWORK | AUTH_FLOW:FAILED
17
+ # confirm: AUTH_FLOW:AUTH_SUCCESS | AUTH_FLOW:PENDING | AUTH_FLOW:EXPIRED | AUTH_FLOW:SCOPE_MISMATCH | AUTH_FLOW:MCC_MISMATCH | AUTH_FLOW:CONTEXT_MISMATCH | AUTH_FLOW:RETRY_WITH_NETWORK | AUTH_FLOW:FAILED
18
+ # local fs: init/wait/confirm/mismatch 可返回 AUTH_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION
19
+ # mismatch: logout 后置条件可确认后调用 auth init 重新生成授权链接;上下文冲突时输出 AUTH_FLOW:CONTEXT_MISMATCH 且不 logout;无法确认 CLI 结果时输出 AUTH_FLOW:RETRY_WITH_NETWORK;仍处于登录态时输出 AUTH_FLOW:LOGOUT_STILL_LOGGED_IN
20
+ # stdout: 仅登记对客正文;AUTH_FLOW/状态/查询复用标记写入 stderr
21
+ #=============================================================================
22
+
23
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24
+
25
+ # 一次性 source error_handler(含 unwrap_mcp)
26
+ if [ -f "${SCRIPT_DIR}/error_handler.sh" ]; then
27
+ source "${SCRIPT_DIR}/error_handler.sh"
28
+ else
29
+ echo "❌ 缺少错误处理脚本: ${SCRIPT_DIR}/error_handler.sh"
30
+ exit 1
31
+ fi
32
+ source "${SCRIPT_DIR}/network_retry.sh"
33
+ source "${SCRIPT_DIR}/sign_status_parser.sh"
34
+ RENDERER="${SCRIPT_DIR}/../../../normal/scripts/render_customer_message.mjs"
35
+ AUTH_LOCAL_STATE_PERMISSION_EXIT_CODE="${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}"
36
+
37
+ require_command jq || exit 1
38
+ require_command alipay-cli || exit 1
39
+ require_command node || exit 1
40
+ require_command mktemp || exit 1
41
+ umask 077
42
+
43
+ auth_product_type_from_sales_code() {
44
+ case "$1" in
45
+ "I1080300001000160457") printf 'aipay' ;;
46
+ "I1080300001000041203") printf 'webpay' ;;
47
+ "I1080300001000041313") printf 'apppay' ;;
48
+ *) return 1 ;;
49
+ esac
50
+ }
51
+
52
+ create_auth_sign_reuse_receipt() {
53
+ local sign_status="$1" sign_flow="$2" product_type payload encoded
54
+ product_type=$(auth_product_type_from_sales_code "$SALES_CODE") || return 1
55
+ payload=$(jq -cn \
56
+ --arg productType "$product_type" \
57
+ --arg salesCode "$SALES_CODE" \
58
+ --arg signStatus "$sign_status" \
59
+ --arg flow "$sign_flow" \
60
+ '{schema_ver:1,product_type:$productType,sales_code:$salesCode,sign_status:$signStatus,flow:$flow}') || return 1
61
+ encoded=$(printf '%s' "$payload" | jq -rR '@base64' | tr '+/' '-_' | tr -d '=\n') || return 1
62
+ printf 'AIPAY_SIGN_REUSE_V1.%s' "$encoded"
63
+ }
64
+
65
+ # ─── 授权状态文件(用于独立执行 confirm/mismatch 时兜底恢复非敏感参数)────
66
+ AUTH_STATE_OWNER="$(id -u 2>/dev/null || printf '%s' "${USER:-unknown}" | tr -c 'A-Za-z0-9_-' '_')"
67
+ AUTH_STATE_BASE="${TMPDIR:-/tmp}"
68
+ AUTH_STATE_BASE="${AUTH_STATE_BASE%/}"
69
+ if [ -n "${ALIPAY_AUTH_STATE_FILE:-}" ]; then
70
+ AUTH_STATE_FILE="$ALIPAY_AUTH_STATE_FILE"
71
+ AUTH_STATE_DIR="$(dirname "$AUTH_STATE_FILE")"
72
+ AUTH_STATE_DIR_MANAGED=false
73
+ else
74
+ AUTH_STATE_DIR="${AUTH_STATE_BASE}/alipay-aipay-auth-${AUTH_STATE_OWNER}"
75
+ AUTH_STATE_FILE="${AUTH_STATE_DIR}/state.json"
76
+ AUTH_STATE_DIR_MANAGED=true
77
+ fi
78
+
79
+ secure_mode() {
80
+ local target="$1" expected="$2" mode
81
+ if mode=$(stat -f '%Lp' "$target" 2>/dev/null); then
82
+ :
83
+ elif mode=$(stat -c '%a' "$target" 2>/dev/null); then
84
+ :
85
+ else
86
+ return 1
87
+ fi
88
+ [ "$mode" = "$expected" ]
89
+ }
90
+
91
+ prepare_auth_state_dir() {
92
+ if [ "$AUTH_STATE_DIR_MANAGED" = true ]; then
93
+ if [ -L "$AUTH_STATE_DIR" ] || { [ -e "$AUTH_STATE_DIR" ] && [ ! -d "$AUTH_STATE_DIR" ]; }; then
94
+ echo "❌ 授权临时目录不安全,已停止" >&2
95
+ return 1
96
+ fi
97
+ mkdir -p "$AUTH_STATE_DIR" || return 1
98
+ chmod 700 "$AUTH_STATE_DIR" 2>/dev/null || return 1
99
+ secure_mode "$AUTH_STATE_DIR" 700 || return 1
100
+ else
101
+ [ -d "$AUTH_STATE_DIR" ] && [ ! -L "$AUTH_STATE_DIR" ] || {
102
+ echo "❌ 指定的授权状态目录不可用或不安全,已停止" >&2
103
+ return 1
104
+ }
105
+ fi
106
+ }
107
+
108
+ validate_auth_state_file() {
109
+ [ -f "$AUTH_STATE_FILE" ] && [ ! -L "$AUTH_STATE_FILE" ] || return 1
110
+ chmod 600 "$AUTH_STATE_FILE" 2>/dev/null || return 1
111
+ secure_mode "$AUTH_STATE_FILE" 600
112
+ }
113
+
114
+ cleanup_auth_state() {
115
+ rm -f "$AUTH_STATE_FILE"
116
+ }
117
+
118
+ reset_auth_state_for_init() {
119
+ local field current saved
120
+ AUTH_INIT_CONTEXT_MISMATCH_TYPE=""
121
+ [ -e "$AUTH_STATE_FILE" ] || [ -L "$AUTH_STATE_FILE" ] || return 0
122
+ if ! prepare_auth_state_dir || ! validate_auth_state_file; then
123
+ echo "❌ 授权状态文件不可用或不安全,已停止"
124
+ emit_control "AUTH_FLOW:FAILED"
125
+ return 1
126
+ fi
127
+
128
+ # 新上下文通过机器校验后才读取旧状态;只记录差异类型,不复用旧 deviceCode。
129
+ if jq -e 'type == "object"' "$AUTH_STATE_FILE" >/dev/null 2>&1; then
130
+ for field in salesCode scope productName mccCode mccName mccContextReceipt; do
131
+ case "$field" in
132
+ salesCode) current="$SALES_CODE" ;;
133
+ scope) current="$SCOPE" ;;
134
+ productName) current="$PRODUCT_NAME" ;;
135
+ mccCode) current="$MCC_CODE" ;;
136
+ mccName) current="$MCC_NAME" ;;
137
+ mccContextReceipt) current="${MCC_CONTEXT_RECEIPT:-}" ;;
138
+ esac
139
+ saved=$(jq -r --arg field "$field" '.[$field] // ""' "$AUTH_STATE_FILE")
140
+ if [ "$current" != "$saved" ]; then
141
+ case "$field" in
142
+ salesCode|scope|productName) AUTH_INIT_CONTEXT_MISMATCH_TYPE="SCOPE_MISMATCH" ;;
143
+ *) [ -n "$AUTH_INIT_CONTEXT_MISMATCH_TYPE" ] || AUTH_INIT_CONTEXT_MISMATCH_TYPE="MCC_MISMATCH" ;;
144
+ esac
145
+ fi
146
+ done
147
+ else
148
+ # 无法证明旧状态属于当前产品上下文时,已有登录也必须重新授权。
149
+ AUTH_INIT_CONTEXT_MISMATCH_TYPE="SCOPE_MISMATCH"
150
+ fi
151
+ cleanup_auth_state
152
+ }
153
+
154
+ save_auth_state() {
155
+ local temp_state
156
+ prepare_auth_state_dir || return 1
157
+ if [ -L "$AUTH_STATE_FILE" ] || { [ -e "$AUTH_STATE_FILE" ] && [ ! -f "$AUTH_STATE_FILE" ]; }; then
158
+ echo "❌ 授权状态路径不是安全的普通文件,已停止" >&2
159
+ return 1
160
+ fi
161
+ temp_state=$(mktemp "${AUTH_STATE_DIR}/.auth-state.XXXXXX") || return 1
162
+ chmod 600 "$temp_state" 2>/dev/null || { rm -f "$temp_state"; return 1; }
163
+ jq -n \
164
+ --arg salesCode "$SALES_CODE" \
165
+ --arg mccCode "$MCC_CODE" \
166
+ --arg scope "$SCOPE" \
167
+ --arg productName "${PRODUCT_NAME:-}" \
168
+ --arg mccName "${MCC_NAME:-}" \
169
+ --arg mccContextReceipt "${MCC_CONTEXT_RECEIPT:-}" \
170
+ --arg deviceCode "${DEVICE_CODE:-}" \
171
+ --arg authorizationUrl "${BROWSER_URL:-}" \
172
+ '{
173
+ salesCode: $salesCode,
174
+ mccCode: $mccCode,
175
+ scope: $scope,
176
+ productName: $productName,
177
+ mccName: $mccName,
178
+ mccContextReceipt: $mccContextReceipt,
179
+ deviceCode: $deviceCode,
180
+ authorizationUrl: $authorizationUrl
181
+ }' > "$temp_state" || { rm -f "$temp_state"; return 1; }
182
+ mv "$temp_state" "$AUTH_STATE_FILE" || { rm -f "$temp_state"; return 1; }
183
+ validate_auth_state_file || {
184
+ cleanup_auth_state
185
+ echo "❌ 无法确认授权状态文件权限为 0600,已停止" >&2
186
+ return 1
187
+ }
188
+ }
189
+
190
+ save_auth_state_after_page_delivery() {
191
+ if save_auth_state; then
192
+ return 0
193
+ fi
194
+ emit_control "AUTH_STATE:SAVE_FAILED"
195
+ return 0
196
+ }
197
+
198
+ load_auth_state() {
199
+ if ! validate_auth_state_file; then
200
+ echo "❌ 缺少授权上下文(请传入 --sales-code/--mcc-code,或先执行 auth.sh init)"
201
+ return 1
202
+ fi
203
+ if ! jq -e . "$AUTH_STATE_FILE" >/dev/null 2>&1; then
204
+ echo "❌ 授权状态文件不是合法 JSON: $AUTH_STATE_FILE"
205
+ echo "📋 请重新执行 auth.sh init 生成授权状态。"
206
+ return 1
207
+ fi
208
+
209
+ [ -n "${SALES_CODE:-}" ] || SALES_CODE=$(jq -r '.salesCode // ""' "$AUTH_STATE_FILE")
210
+ [ -n "${MCC_CODE:-}" ] || MCC_CODE=$(jq -r '.mccCode // ""' "$AUTH_STATE_FILE")
211
+ [ -n "${SCOPE:-}" ] || SCOPE=$(jq -r '.scope // ""' "$AUTH_STATE_FILE")
212
+ [ -n "${PRODUCT_NAME:-}" ] || PRODUCT_NAME=$(jq -r '.productName // ""' "$AUTH_STATE_FILE")
213
+ [ -n "${MCC_NAME:-}" ] || MCC_NAME=$(jq -r '.mccName // ""' "$AUTH_STATE_FILE")
214
+ [ -n "${MCC_CONTEXT_RECEIPT:-}" ] || MCC_CONTEXT_RECEIPT=$(jq -r '.mccContextReceipt // ""' "$AUTH_STATE_FILE")
215
+ }
216
+
217
+ verify_context_against_auth_state() {
218
+ local field current saved
219
+ [ -e "$AUTH_STATE_FILE" ] || [ -L "$AUTH_STATE_FILE" ] || return 0
220
+ if ! validate_auth_state_file; then
221
+ echo "❌ 授权状态文件不可用或不安全,已停止"
222
+ return 1
223
+ fi
224
+ if ! jq -e . "$AUTH_STATE_FILE" >/dev/null 2>&1; then
225
+ echo "❌ 授权状态文件不是合法 JSON: $AUTH_STATE_FILE"
226
+ return 1
227
+ fi
228
+ for field in salesCode mccCode scope productName mccName mccContextReceipt; do
229
+ case "$field" in
230
+ salesCode) current="$SALES_CODE" ;;
231
+ mccCode) current="$MCC_CODE" ;;
232
+ scope) current="$SCOPE" ;;
233
+ productName) current="$PRODUCT_NAME" ;;
234
+ mccName) current="$MCC_NAME" ;;
235
+ mccContextReceipt) current="${MCC_CONTEXT_RECEIPT:-}" ;;
236
+ esac
237
+ saved=$(jq -r --arg field "$field" '.[$field] // ""' "$AUTH_STATE_FILE")
238
+ if [ -n "$current" ] && [ -n "$saved" ] && [ "$current" != "$saved" ]; then
239
+ emit_control "AUTH_FLOW:CONTEXT_MISMATCH"
240
+ return 1
241
+ fi
242
+ done
243
+ }
244
+
245
+ parse_auth_context_args() {
246
+ while [[ $# -gt 0 ]]; do
247
+ case $1 in
248
+ --scope)
249
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; return 1; }
250
+ SCOPE="$2"; shift 2 ;;
251
+ --sales-code)
252
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; return 1; }
253
+ SALES_CODE="$2"; shift 2 ;;
254
+ --mcc-code)
255
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; return 1; }
256
+ MCC_CODE="$2"; shift 2 ;;
257
+ --product-name)
258
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; return 1; }
259
+ PRODUCT_NAME="$2"; shift 2 ;;
260
+ --mcc-name)
261
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; return 1; }
262
+ MCC_NAME="$2"; shift 2 ;;
263
+ --mcc-context-receipt)
264
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; return 1; }
265
+ MCC_CONTEXT_RECEIPT="$2"; shift 2 ;;
266
+ *) echo "❌ 未知参数: $1"; return 1 ;;
267
+ esac
268
+ done
269
+ }
270
+
271
+ restore_auth_context_or_state() {
272
+ SCOPE="${SCOPE:-}"
273
+ SALES_CODE="${SALES_CODE:-}"
274
+ MCC_CODE="${MCC_CODE:-}"
275
+ PRODUCT_NAME="${PRODUCT_NAME:-}"
276
+ MCC_NAME="${MCC_NAME:-}"
277
+ MCC_CONTEXT_RECEIPT="${MCC_CONTEXT_RECEIPT:-}"
278
+
279
+ if [ "$#" -gt 0 ]; then
280
+ parse_auth_context_args "$@" || return 1
281
+ fi
282
+
283
+ verify_context_against_auth_state || return 1
284
+ if [ -e "$AUTH_STATE_FILE" ] || [ -L "$AUTH_STATE_FILE" ]; then
285
+ load_auth_state || return 1
286
+ elif [ -z "$SALES_CODE" ] || [ -z "$MCC_CODE" ] || [ -z "$PRODUCT_NAME" ] || [ -z "$MCC_NAME" ]; then
287
+ load_auth_state || return 1
288
+ fi
289
+
290
+ if [ -z "$SCOPE" ]; then
291
+ SCOPE=$(get_scope "$SALES_CODE")
292
+ fi
293
+ validate_auth_context || return 1
294
+ }
295
+
296
+ # ─── 共用:根据 salesCode 获取 scope ─────────────────────────────────────────
297
+ get_scope() {
298
+ case "$1" in
299
+ "I1080300001000041203") echo "app:all,fast_instant_trade_pay:write" ;;
300
+ "I1080300001000041313") echo "app:all,auth_alipay_apppay:write" ;;
301
+ "I1080300001000160457") echo "app:all,machine_pay:write,agmnt:write" ;;
302
+ *) echo "" ;;
303
+ esac
304
+ }
305
+
306
+ validate_auth_context() {
307
+ local context_json
308
+ context_json=$(jq -n \
309
+ --arg productName "$PRODUCT_NAME" \
310
+ --arg salesCode "$SALES_CODE" \
311
+ --arg scope "$SCOPE" \
312
+ --arg mccCode "$MCC_CODE" \
313
+ --arg mccName "$MCC_NAME" \
314
+ --arg mccContextReceipt "${MCC_CONTEXT_RECEIPT:-}" \
315
+ '{productName:$productName,salesCode:$salesCode,scope:$scope,mccCode:$mccCode,mccName:$mccName,mccContextReceipt:$mccContextReceipt}') || return 1
316
+ if ! printf '%s' "$context_json" | node "$RENDERER" --validate-authorization-context >/dev/null; then
317
+ echo "❌ 授权上下文校验失败:产品、产品码、scope 或 MCC 不匹配当前固定规则"
318
+ emit_control "AUTH_FLOW:FAILED"
319
+ return 1
320
+ fi
321
+ init_alipay_cli_product_from_sales_code "$SALES_CODE" || {
322
+ emit_control "AUTH_FLOW:FAILED"
323
+ return 1
324
+ }
325
+ }
326
+
327
+ build_authorization_url() {
328
+ local input_json platform_value
329
+ platform_value="${DEV_TOOL_NAME:-}"
330
+ [ "$platform_value" = "unknown" ] && platform_value=""
331
+ input_json=$(jq -n \
332
+ --arg productName "$PRODUCT_NAME" \
333
+ --arg salesCode "$SALES_CODE" \
334
+ --arg scope "$SCOPE" \
335
+ --arg mccCode "$MCC_CODE" \
336
+ --arg mccName "$MCC_NAME" \
337
+ --arg deviceCode "$DEVICE_CODE" \
338
+ --arg platform "$platform_value" \
339
+ --arg mccContextReceipt "${MCC_CONTEXT_RECEIPT:-}" \
340
+ '{productName:$productName,salesCode:$salesCode,scope:$scope,mccCode:$mccCode,mccName:$mccName,mccContextReceipt:$mccContextReceipt,deviceCode:$deviceCode,platform:$platform}') || return 1
341
+ printf '%s' "$input_json" | node "$RENDERER" --build-authorization-url
342
+ }
343
+
344
+ extract_json_payload() {
345
+ local raw="$1"
346
+ local analysis status
347
+ analysis=$(extract_unique_json "$raw") || return 1
348
+ status=$(printf '%s' "$analysis" | jq -r '.status // "none"' 2>/dev/null)
349
+ [ "$status" = "ok" ] || return 1
350
+ printf '%s' "$analysis" | jq -ce '.value | select(type == "object")' 2>/dev/null
351
+ }
352
+
353
+ auth_has_local_state_permission_error() {
354
+ alipay_cli_has_local_state_permission_error "$1"
355
+ }
356
+
357
+ auth_local_state_permission_failure() {
358
+ emit_control "AUTH_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION"
359
+ }
360
+
361
+ auth_require_local_state_access() {
362
+ require_alipay_cli_local_state_access || {
363
+ auth_local_state_permission_failure
364
+ return 1
365
+ }
366
+ }
367
+
368
+ auth_cli_environment_failure() {
369
+ local action="$1" failure_code="${2:-1}"
370
+ if [ "$failure_code" -eq "$AUTH_LOCAL_STATE_PERMISSION_EXIT_CODE" ]; then
371
+ auth_local_state_permission_failure
372
+ return
373
+ fi
374
+ echo "❌ ${action} 未能在当前 Agent 执行环境取得可确认结果,已停止。"
375
+ echo "📋 这通常表示当前执行环境缺少联网权限、CLI wrapper 混入输出,或 stdout/stderr 无法唯一解析。"
376
+ echo "📋 这不代表支付宝业务失败,也不能据此判断用户本机 logout/login 失败。"
377
+ echo "📋 Agent 应申请可联网权限后重试同一条 auth.sh 命令;不要要求用户手动 logout/login 代替本轮事实。"
378
+ emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
379
+ }
380
+
381
+ handle_auth_cli_result() {
382
+ local cli_result="$1" error_type
383
+ error_type=$(detect_error "$cli_result")
384
+ if handle_error "$cli_result"; then
385
+ return 0
386
+ fi
387
+ case "$error_type" in
388
+ MCP_AUTH_ERROR|AUTH_MISMATCH|CLI_LOCAL_FS_PERMISSION)
389
+ # error_handler.sh already emitted the unique recovery marker.
390
+ ;;
391
+ MCP_SERVICE_ERROR|SERVICE_UNSTABLE)
392
+ emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
393
+ ;;
394
+ *)
395
+ emit_control "AUTH_FLOW:FAILED"
396
+ ;;
397
+ esac
398
+ return 1
399
+ }
400
+
401
+ wait_for_effective_whoami() {
402
+ local action="$1"
403
+ local max_attempts=3
404
+ local interval_seconds=2
405
+ local attempt=1
406
+ local logged_in is_expired
407
+
408
+ AUTH_WHOAMI_RESULT=""
409
+ if [ "${ALIPAY_AIPAY_TEST_MODE:-0}" = "1" ] && [[ "${AUTH_WHOAMI_RECHECK_INTERVAL_SECONDS:-}" =~ ^[0-9]+$ ]]; then
410
+ interval_seconds="$AUTH_WHOAMI_RECHECK_INTERVAL_SECONDS"
411
+ fi
412
+
413
+ while [ "$attempt" -le "$max_attempts" ]; do
414
+ AUTH_WHOAMI_RESULT=$(run_json_command "alipay-cli whoami --json" alipay-cli whoami --json) || {
415
+ auth_cli_environment_failure "$action" "$?"
416
+ return 1
417
+ }
418
+ handle_auth_cli_result "$AUTH_WHOAMI_RESULT" || return 1
419
+
420
+ logged_in=$(echo "$AUTH_WHOAMI_RESULT" | jq -r '.data.logged_in // false')
421
+ is_expired=$(echo "$AUTH_WHOAMI_RESULT" | jq -r '.data.is_expired // false')
422
+ if [ "$logged_in" = "true" ] && [ "$is_expired" != "true" ]; then
423
+ return 0
424
+ fi
425
+
426
+ emit_control "AUTH_WHOAMI_RECHECK=${attempt}/${max_attempts}"
427
+ if [ "$attempt" -lt "$max_attempts" ]; then
428
+ sleep "$interval_seconds"
429
+ fi
430
+ attempt=$((attempt + 1))
431
+ done
432
+
433
+ emit_control "AUTH_WHOAMI_NOT_EFFECTIVE"
434
+ return 2
435
+ }
436
+
437
+ run_json_command() {
438
+ local label="$1"
439
+ shift
440
+ local stderr_file stdout stderr_output combined_output exit_code json_payload
441
+
442
+ stderr_file="$(mktemp "${TMPDIR:-/tmp}/alipay_auth_stderr.XXXXXX")" || return 1
443
+ stdout=$(PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}" ALIPAYCLI_BIZ_INFO="$ALIPAYCLI_BIZ_INFO" PRODUCT="${PRODUCT:-}" "$@" 2>"$stderr_file")
444
+ exit_code=$?
445
+ stderr_output=$(cat "$stderr_file")
446
+
447
+ combined_output=$(printf '%s\n%s' "$stdout" "$stderr_output")
448
+ if auth_has_local_state_permission_error "$combined_output"; then
449
+ rm -f "$stderr_file"
450
+ return "$AUTH_LOCAL_STATE_PERMISSION_EXIT_CODE"
451
+ fi
452
+ if json_payload=$(extract_json_payload "$combined_output"); then
453
+ rm -f "$stderr_file"
454
+ printf '%s' "$json_payload"
455
+ return 0
456
+ fi
457
+
458
+ echo "❌ ${label} 未返回可唯一解析的合法 JSON(退出码: ${exit_code});原始输出已隐藏,请申请联网权限后重试同一条 auth.sh 命令" >&2
459
+ rm -f "$stderr_file"
460
+ return 1
461
+ }
462
+
463
+ # 校验当前登录会话是否覆盖目标产品 scope 和 MCC。
464
+ # 可复用调用方已经取得的 whoami JSON,避免重复查询。
465
+ validate_current_authorization() {
466
+ local whoami_result="${1:-}"
467
+ local logged_in is_expired granted_scope required_scope
468
+ local scope_match mcc_verify_result mcc_verify_unwrapped mcc_match mcc_raw retry_rc
469
+ local mcc_error_type mcc_error_code mcc_success
470
+ local mismatch_type sign_status_output sign_status_marker sign_flow_marker sign_reuse_receipt
471
+ local granted_scope_item required_scope_item required_scope_found
472
+ local -a granted_scope_items required_scope_items
473
+
474
+ if [ -z "$SALES_CODE" ] || [ -z "$MCC_CODE" ]; then
475
+ echo "❌ 缺少授权校验参数"
476
+ emit_control "AUTH_FLOW:FAILED"
477
+ return 1
478
+ fi
479
+
480
+ if [ -z "$whoami_result" ]; then
481
+ whoami_result=$(run_json_command "alipay-cli whoami --json" alipay-cli whoami --json) || {
482
+ auth_cli_environment_failure "登录状态复核" "$?"
483
+ return 1
484
+ }
485
+ fi
486
+
487
+ handle_auth_cli_result "$whoami_result" || return 1
488
+
489
+ logged_in=$(echo "$whoami_result" | jq -r '.data.logged_in // false')
490
+ is_expired=$(echo "$whoami_result" | jq -r '.data.is_expired // false')
491
+ if [ "$logged_in" != "true" ] || [ "$is_expired" = "true" ]; then
492
+ echo "❌ 当前登录状态无效或已过期"
493
+ emit_control "AUTH_FLOW:FAILED"
494
+ return 1
495
+ fi
496
+
497
+ granted_scope=$(echo "$whoami_result" | jq -r '.data.scope // empty')
498
+ required_scope=$(get_scope "$SALES_CODE")
499
+ if [ -z "$required_scope" ] || [ "$SCOPE" != "$required_scope" ]; then
500
+ echo "❌ 当前产品的固定 scope 上下文无效"
501
+ emit_control "AUTH_FLOW:FAILED"
502
+ return 1
503
+ fi
504
+
505
+ scope_match="true"
506
+ IFS=',' read -r -a granted_scope_items <<< "$granted_scope"
507
+ IFS=',' read -r -a required_scope_items <<< "$required_scope"
508
+ for required_scope_item in "${required_scope_items[@]}"; do
509
+ required_scope_found="false"
510
+ for granted_scope_item in "${granted_scope_items[@]}"; do
511
+ granted_scope_item="${granted_scope_item#"${granted_scope_item%%[![:space:]]*}"}"
512
+ granted_scope_item="${granted_scope_item%"${granted_scope_item##*[![:space:]]}"}"
513
+ if [ "$granted_scope_item" = "$required_scope_item" ]; then
514
+ required_scope_found="true"
515
+ break
516
+ fi
517
+ done
518
+ if [ "$required_scope_found" != "true" ]; then
519
+ scope_match="false"
520
+ break
521
+ fi
522
+ done
523
+
524
+ mcc_match="true"
525
+ if [ "$scope_match" = "true" ]; then
526
+ export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
527
+ ALIPAY_AIPAY_SUPPRESS_LOCAL_FS_RETRY_MARKER=1 run_network_retry mcc_raw read authorization_mcc_check -- alipay-cli mcp call ar-query.queryArInfosBySalesProd \
528
+ -d "{\"request\":{\"salesProductCodes\":[\"${SALES_CODE}\"]},\"ctx\":{}}" \
529
+ --json
530
+ retry_rc=$?
531
+ if [ "$retry_rc" -eq "$AUTH_LOCAL_STATE_PERMISSION_EXIT_CODE" ]; then
532
+ auth_local_state_permission_failure
533
+ return 1
534
+ fi
535
+ if [ "$retry_rc" -ne 0 ]; then
536
+ echo "❌ 授权 MCC 校验因网络或服务异常在自动重试两次后仍未恢复"
537
+ emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
538
+ return 1
539
+ fi
540
+ mcc_verify_result=$(extract_json_payload "$mcc_raw") || {
541
+ echo "❌ alipay-cli mcp call ar-query.queryArInfosBySalesProd 未返回合法 JSON"
542
+ emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
543
+ return 1
544
+ }
545
+
546
+ mcc_verify_unwrapped=$(unwrap_mcp "$mcc_verify_result")
547
+ mcc_error_type=$(detect_error "$mcc_verify_result" "MCP_AUTHENTICATED_CALL")
548
+ if [ "$mcc_error_type" = "AUTH_MISMATCH" ]; then
549
+ mcc_match="false"
550
+ elif [ "$mcc_error_type" = "MCP_AUTH_ERROR" ]; then
551
+ handle_error "$mcc_verify_result" "MCP_AUTHENTICATED_CALL" || true
552
+ return 1
553
+ elif [ "$mcc_error_type" != "SUCCESS" ]; then
554
+ echo "❌ MCC 校验失败"
555
+ emit_control "AUTH_FLOW:FAILED"
556
+ return 1
557
+ fi
558
+
559
+ # 保留对非标准错误响应的兼容,只在后端明确返回 MCC 未授权时判定不匹配。
560
+ mcc_error_code=$(echo "$mcc_verify_unwrapped" | jq -r '.errorCode // .data.errorCode // ""' 2>/dev/null)
561
+ mcc_success=$(echo "$mcc_verify_unwrapped" | jq -r '.success // "null"' 2>/dev/null)
562
+ if [ "$mcc_success" = "false" ] && [ -n "$mcc_error_code" ] && \
563
+ echo "$mcc_verify_unwrapped" | jq -r '.errorMessage // .data.errorMessage // .error.message // ""' 2>/dev/null | grep -qi "mccCode.*is not auth"; then
564
+ mcc_match="false"
565
+ elif echo "$mcc_verify_unwrapped" | grep -qi "mccCode.*is not auth"; then
566
+ mcc_match="false"
567
+ fi
568
+
569
+ # 同一请求也是 Step 3.1 的签约查询;仅在完整结构可由共享解析器证明时输出可复用事实。
570
+ if [ "$mcc_match" = "true" ] && sign_status_output=$(emit_sign_status_from_business "$mcc_verify_unwrapped" "$SALES_CODE" 2>/dev/null); then
571
+ printf '%s\n' "$sign_status_output" \
572
+ | while IFS= read -r marker; do emit_control "$marker"; done
573
+ sign_status_marker=$(printf '%s\n' "$sign_status_output" | awk -F= '/^SIGN_STATUS=/{print $2; exit}')
574
+ sign_flow_marker=$(printf '%s\n' "$sign_status_output" | awk '/^FLOW:/{print; exit}')
575
+ if [ -n "$sign_status_marker" ] && [ -n "$sign_flow_marker" ] && sign_reuse_receipt=$(create_auth_sign_reuse_receipt "$sign_status_marker" "$sign_flow_marker"); then
576
+ emit_control "AUTH_SIGN_REUSE_RECEIPT=${sign_reuse_receipt}"
577
+ fi
578
+ emit_control "AUTH_SIGN_QUERY_REUSABLE=true"
579
+ fi
580
+ fi
581
+
582
+ if [ "$scope_match" = "true" ] && [ "$mcc_match" = "true" ]; then
583
+ emit_control "✅ 授权范围校验通过(scope + MCC 授权有效性)"
584
+ return 0
585
+ fi
586
+
587
+ if [ "$scope_match" != "true" ]; then
588
+ mismatch_type="SCOPE_MISMATCH"
589
+ else
590
+ mismatch_type="MCC_MISMATCH"
591
+ fi
592
+ emit_control "AUTH_FLOW:${mismatch_type}"
593
+ return 1
594
+ }
595
+
596
+ # ─── auth init: 检查登录状态 + 执行登录 + 构建授权链接 + 输出授权信息 ────────
597
+ auth_init() {
598
+ local DEFER_OPEN=false
599
+ SCOPE=""
600
+ SALES_CODE=""
601
+ MCC_CODE=""
602
+ PRODUCT_NAME=""
603
+ MCC_NAME=""
604
+ MCC_CONTEXT_RECEIPT=""
605
+
606
+ while [[ $# -gt 0 ]]; do
607
+ case $1 in
608
+ --defer-open)
609
+ DEFER_OPEN=true; shift ;;
610
+ --scope)
611
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
612
+ SCOPE="$2"; shift 2 ;;
613
+ --sales-code)
614
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
615
+ SALES_CODE="$2"; shift 2 ;;
616
+ --mcc-code)
617
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
618
+ MCC_CODE="$2"; shift 2 ;;
619
+ --product-name)
620
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
621
+ PRODUCT_NAME="$2"; shift 2 ;;
622
+ --mcc-name)
623
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
624
+ MCC_NAME="$2"; shift 2 ;;
625
+ --mcc-context-receipt)
626
+ [ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
627
+ MCC_CONTEXT_RECEIPT="$2"; shift 2 ;;
628
+ *) echo "❌ 未知参数: $1"; exit 1 ;;
629
+ esac
630
+ done
631
+
632
+ if [ -z "$SCOPE" ] || [ -z "$SALES_CODE" ] || [ -z "$MCC_CODE" ] || [ -z "$PRODUCT_NAME" ] || [ -z "$MCC_NAME" ] || [ -z "$MCC_CONTEXT_RECEIPT" ]; then
633
+ echo "❌ 缺少必填参数"
634
+ echo "用法: bash auth.sh init --defer-open --scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name> --mcc-context-receipt <receipt>"
635
+ exit 1
636
+ fi
637
+ if [ "$DEFER_OPEN" != true ]; then
638
+ echo "❌ 参数格式错误:auth init 必须使用 --defer-open,确保先展示授权说明和链接,再自动打开页面"
639
+ echo "用法: bash auth.sh init --defer-open --scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name> --mcc-context-receipt <receipt>"
640
+ exit 1
641
+ fi
642
+ validate_auth_context || return 1
643
+ # 新 init 表示按已确认上下文重建授权链路;只在机器校验通过后替换旧短时状态。
644
+ reset_auth_state_for_init || return 1
645
+
646
+ # Step 1: 检查登录状态
647
+ CHECK_RESULT=$(run_json_command "alipay-cli whoami --json" alipay-cli whoami --json) || {
648
+ auth_cli_environment_failure "登录状态检查" "$?"
649
+ return 1
650
+ }
651
+ handle_auth_cli_result "$CHECK_RESULT" || return 1
652
+ LOGGED_IN=$(echo "$CHECK_RESULT" | jq -r '.data.logged_in // false')
653
+ IS_EXPIRED=$(echo "$CHECK_RESULT" | jq -r '.data.is_expired // false')
654
+
655
+ if [ "$LOGGED_IN" = "true" ] && [ "$IS_EXPIRED" = "false" ]; then
656
+ if [ -n "$AUTH_INIT_CONTEXT_MISMATCH_TYPE" ]; then
657
+ # 后端查询没有权威 MCC 字段,旧状态变化时不能把现有登录误判为当前上下文已授权。
658
+ DEVICE_CODE=""
659
+ BROWSER_URL=""
660
+ if ! save_auth_state; then
661
+ emit_control "AUTH_STATE:SAVE_FAILED"
662
+ fi
663
+ emit_control "AUTH_FLOW:${AUTH_INIT_CONTEXT_MISMATCH_TYPE}"
664
+ return 1
665
+ fi
666
+ validate_current_authorization "$CHECK_RESULT" || return 1
667
+ DEVICE_CODE=""
668
+ if ! save_auth_state; then
669
+ emit_control "AUTH_STATE:SAVE_FAILED"
670
+ fi
671
+ emit_stage_metric "onboarding.login"
672
+ emit_control "✅ 当前登录及授权范围有效,可继续后续流程"
673
+ emit_control "AUTH_FLOW:SKIP"
674
+ return 0
675
+ fi
676
+
677
+ # Step 2: 执行登录
678
+ auth_require_local_state_access || return 1
679
+ LOGIN_RESULT=$(run_json_command "alipay-cli login --non-interactive --scope" alipay-cli login --non-interactive --scope "$SCOPE" --json) || {
680
+ auth_cli_environment_failure "授权登录发起" "$?"
681
+ return 1
682
+ }
683
+ handle_auth_cli_result "$LOGIN_RESULT" || return 1
684
+ LOGIN_STATUS=$(echo "$LOGIN_RESULT" | jq -r '.data.status // ""')
685
+
686
+ if [ "$LOGIN_STATUS" = "already_logged_in" ]; then
687
+ validate_current_authorization || return 1
688
+ DEVICE_CODE=""
689
+ if ! save_auth_state; then
690
+ emit_control "AUTH_STATE:SAVE_FAILED"
691
+ fi
692
+ emit_stage_metric "onboarding.login"
693
+ emit_control "✅ 当前登录及授权范围有效,可继续后续流程"
694
+ emit_control "AUTH_FLOW:SKIP"
695
+ return 0
696
+ fi
697
+
698
+ # Step 3: 解析返回结果
699
+ # alipay-cli login 返回结构为 .data.device_code(不是 .data.data.device_code)
700
+ DEVICE_CODE=$(echo "$LOGIN_RESULT" | jq -r '.data.device_code // empty')
701
+ VERIFICATION_CODE=$(echo "$LOGIN_RESULT" | jq -r '.data.verification_code // empty')
702
+ EXPIRES_IN=$(echo "$LOGIN_RESULT" | jq -r '.data.expires_in // 600')
703
+
704
+ # Step 4: 参数校验
705
+ if [ -z "$DEVICE_CODE" ] || [ -z "$SALES_CODE" ] || [ -z "$MCC_CODE" ]; then
706
+ echo "❌ 授权链接参数不完整,无法生成授权链接"
707
+ [ -n "$DEVICE_CODE" ] || echo "📋 缺少字段: deviceCode"
708
+ [ -n "$SALES_CODE" ] || echo "📋 缺少字段: productCode"
709
+ [ -n "$MCC_CODE" ] || echo "📋 缺少字段: mccCode"
710
+ exit 1
711
+ fi
712
+
713
+ # Step 5: 由统一校验器编码、构建并逐字段校验授权链接。
714
+ BROWSER_URL=$(build_authorization_url) || {
715
+ echo "❌ 授权链接构建失败,缺少必要参数或误用了 CLI verification_url,已停止。"
716
+ echo "📋 正确格式: https://aipay.alipay.com/cli-auth?deviceCode=...&productCode=...&mccCode=..."
717
+ return 1
718
+ }
719
+
720
+ # Step 6: 有效期转换(对非数字做防护)
721
+ if [[ "$EXPIRES_IN" =~ ^[0-9]+$ ]] && [ "$EXPIRES_IN" -ge 60 ]; then
722
+ EXPIRES_MIN=$((EXPIRES_IN / 60))
723
+ EXPIRES_DISPLAY="${EXPIRES_MIN} 分钟"
724
+ elif [[ "$EXPIRES_IN" =~ ^[0-9]+$ ]]; then
725
+ EXPIRES_DISPLAY="${EXPIRES_IN} 秒"
726
+ else
727
+ EXPIRES_DISPLAY="10 分钟"
728
+ fi
729
+
730
+ # Step 7: 由统一目录渲染授权信息;独立 open 动作在消息发出后打开。
731
+ DISPLAY_CODE="${VERIFICATION_CODE:-(请查看支付宝授权页面)}"
732
+ MESSAGE_INPUT=$(printf '%s\n' "$PRODUCT_NAME" "$DEVICE_CODE" "$MCC_CODE" "$MCC_NAME" "$MCC_CONTEXT_RECEIPT" "$DISPLAY_CODE" "$EXPIRES_DISPLAY" "$BROWSER_URL" | jq -Rs '
733
+ split("\n") as $v | {productName:$v[0],deviceCode:$v[1],mccCode:$v[2],mccName:$v[3],mccContextReceipt:$v[4],verificationCode:$v[5],expiresDisplay:$v[6],officialUrl:$v[7]}
734
+ ')
735
+ if ! printf '%s' "$MESSAGE_INPUT" | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=auth.sh node "$RENDERER" auth.page --variant READY; then
736
+ cleanup_auth_state
737
+ emit_control "AUTH_FLOW:FAILED"
738
+ exit 1
739
+ fi
740
+ # Step 8: 页面 stdout 已经安全送达时,状态文件只用于 opener 便利;保存失败不得生成第二个授权页或截断 wait。
741
+ save_auth_state_after_page_delivery
742
+ echo ""
743
+ emit_control "AUTH_FLOW:READY_TO_OPEN"
744
+ }
745
+
746
+ render_auth_open_manual() {
747
+ printf '%s' '{}' | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=auth.sh node "$RENDERER" auth.open.manual --variant DEFAULT
748
+ }
749
+
750
+ auth_manual_open() {
751
+ if [ "$#" -ne 0 ]; then
752
+ echo "用法: bash auth.sh manual-open"
753
+ return 1
754
+ fi
755
+ render_auth_open_manual || { emit_control "AUTH_FLOW:FAILED"; return 1; }
756
+ }
757
+
758
+ auth_open() {
759
+ local saved_device saved_url validate_input open_status open_helper
760
+
761
+ restore_auth_context_or_state "$@" || exit 1
762
+ saved_device=$(jq -r '.deviceCode // ""' "$AUTH_STATE_FILE" 2>/dev/null || printf '')
763
+ saved_url=$(jq -r '.authorizationUrl // ""' "$AUTH_STATE_FILE" 2>/dev/null || printf '')
764
+ if [ -z "$saved_device" ] || [ -z "$saved_url" ]; then
765
+ emit_control "AUTH_OPEN:LINK_ONLY"
766
+ return 0
767
+ fi
768
+
769
+ validate_input=$(jq -n \
770
+ --arg productName "$PRODUCT_NAME" \
771
+ --arg salesCode "$SALES_CODE" \
772
+ --arg scope "$SCOPE" \
773
+ --arg mccCode "$MCC_CODE" \
774
+ --arg mccName "$MCC_NAME" \
775
+ --arg mccContextReceipt "${MCC_CONTEXT_RECEIPT:-}" \
776
+ --arg deviceCode "$saved_device" \
777
+ --arg officialUrl "$saved_url" \
778
+ '{productName:$productName,salesCode:$salesCode,scope:$scope,mccCode:$mccCode,mccName:$mccName,mccContextReceipt:$mccContextReceipt,deviceCode:$deviceCode,officialUrl:$officialUrl}') || return 1
779
+ if ! printf '%s' "$validate_input" | node "$RENDERER" --validate-authorization-context >/dev/null; then
780
+ echo "❌ 授权页面上下文校验失败,已停止打开"
781
+ emit_control "AUTH_FLOW:FAILED"
782
+ return 1
783
+ fi
784
+
785
+ open_helper="${SCRIPT_DIR}/../../../normal/scripts/open_official_url.sh"
786
+ open_status=$(printf '%s\n' "$saved_url" | bash "$open_helper" authorization)
787
+ case "$open_status" in OPENED|OPEN_FAILED|GUI_UNAVAILABLE|LINK_ONLY) ;; *) open_status="OPEN_FAILED" ;; esac
788
+ emit_control "AUTH_OPEN:${open_status}"
789
+ }
790
+
791
+ # ─── auth confirm: 授权确认 + scope 校验 + MCC 授权有效性校验 ────────────────
792
+ auth_confirm() {
793
+ AUTH_CONFIRM_RESULT=""
794
+ # 优先使用显式传入的非敏感上下文;未传时再从状态文件兜底恢复。
795
+ restore_auth_context_or_state "$@" || exit 1
796
+
797
+ if [ -z "$SALES_CODE" ]; then
798
+ echo "❌ 缺少 SALES_CODE(请传入 --sales-code,或先执行 auth.sh init)"
799
+ exit 1
800
+ fi
801
+ if [ -z "$MCC_CODE" ]; then
802
+ echo "❌ 缺少 MCC_CODE(请传入 --mcc-code,或先执行 auth.sh init)"
803
+ exit 1
804
+ fi
805
+
806
+ # Step 1: 执行授权确认
807
+ auth_require_local_state_access || return 1
808
+ LOGIN_COMPLETE_RESULT=$(run_json_command "alipay-cli login --complete --json" alipay-cli login --complete --json) || {
809
+ auth_cli_environment_failure "授权完成确认" "$?"
810
+ return 1
811
+ }
812
+
813
+ # Step 2: 判断授权结果
814
+ # alipay-cli login --complete: success=true/logged_in 表示可进入 whoami 门禁;
815
+ # authorization_pending/auth_expired 可能出现在 .error.code、.data.error.code 或 status 中。
816
+ OUTER_SUCCESS=$(echo "$LOGIN_COMPLETE_RESULT" | jq -r '.success // false')
817
+ DATA_STATUS=$(echo "$LOGIN_COMPLETE_RESULT" | jq -r '.data.status // ""')
818
+ ERROR_CODE=$(echo "$LOGIN_COMPLETE_RESULT" | jq -r '.error.code // .data.error.code // ""')
819
+
820
+ if [ "$ERROR_CODE" = "authorization_pending" ] || [ "$DATA_STATUS" = "authorization_pending" ] || [ "$DATA_STATUS" = "pending" ]; then
821
+ AUTH_CONFIRM_RESULT="PENDING"
822
+ if [ "${AUTH_POLLING:-false}" != "true" ]; then
823
+ if ! printf '%s' '{}' | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=auth.sh node "$RENDERER" auth.pending --variant DEFAULT; then
824
+ emit_control "AUTH_FLOW:FAILED"
825
+ return 1
826
+ fi
827
+ emit_control "AUTH_FLOW:PENDING"
828
+ fi
829
+ return 0
830
+ elif [ "$ERROR_CODE" = "auth_expired" ] || [ "$DATA_STATUS" = "auth_expired" ] || [ "$DATA_STATUS" = "expired" ]; then
831
+ AUTH_CONFIRM_RESULT="EXPIRED"
832
+ cleanup_auth_state
833
+ if ! printf '%s' '{}' | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=auth.sh node "$RENDERER" auth.expired --variant DEFAULT; then
834
+ emit_control "AUTH_FLOW:FAILED"
835
+ return 1
836
+ fi
837
+ emit_control "AUTH_FLOW:EXPIRED"
838
+ return 0
839
+ elif [ "$OUTER_SUCCESS" = "true" ] || [ "$DATA_STATUS" = "logged_in" ] || [ "$DATA_STATUS" = "already_logged_in" ] || [ "$DATA_STATUS" = "completed" ]; then
840
+ :
841
+ else
842
+ if ! handle_auth_cli_result "$LOGIN_COMPLETE_RESULT"; then
843
+ cleanup_auth_state
844
+ return 1
845
+ fi
846
+ wait_for_effective_whoami "授权完成确认状态复核"
847
+ whoami_rc=$?
848
+ if [ "$whoami_rc" -eq 2 ]; then
849
+ AUTH_CONFIRM_RESULT="PENDING"
850
+ if [ "${AUTH_POLLING:-false}" != "true" ]; then
851
+ if ! printf '%s' '{}' | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=auth.sh node "$RENDERER" auth.pending --variant DEFAULT; then
852
+ emit_control "AUTH_FLOW:FAILED"
853
+ return 1
854
+ fi
855
+ emit_control "AUTH_FLOW:PENDING"
856
+ fi
857
+ return 0
858
+ elif [ "$whoami_rc" -ne 0 ]; then
859
+ return 1
860
+ fi
861
+ validate_current_authorization "$AUTH_WHOAMI_RESULT" || return 1
862
+ cleanup_auth_state
863
+ AUTH_CONFIRM_RESULT="AUTH_SUCCESS"
864
+ emit_stage_metric "onboarding.login"
865
+ emit_control "✅ 授权确认成功(本机 CLI 登录态、scope 与 MCC 授权有效性均已通过)"
866
+ emit_control "AUTH_FLOW:AUTH_SUCCESS"
867
+ return 0
868
+ fi
869
+
870
+ # Step 3: 复用 init 的 scope + MCC 授权有效性校验
871
+ wait_for_effective_whoami "授权后登录状态复核"
872
+ whoami_rc=$?
873
+ if [ "$whoami_rc" -eq 2 ]; then
874
+ AUTH_CONFIRM_RESULT="PENDING"
875
+ if [ "${AUTH_POLLING:-false}" != "true" ]; then
876
+ if ! printf '%s' '{}' | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=auth.sh node "$RENDERER" auth.pending --variant DEFAULT; then
877
+ emit_control "AUTH_FLOW:FAILED"
878
+ return 1
879
+ fi
880
+ emit_control "AUTH_FLOW:PENDING"
881
+ fi
882
+ return 0
883
+ elif [ "$whoami_rc" -ne 0 ]; then
884
+ return 1
885
+ fi
886
+ validate_current_authorization "$AUTH_WHOAMI_RESULT" || return 1
887
+ cleanup_auth_state
888
+ AUTH_CONFIRM_RESULT="AUTH_SUCCESS"
889
+ emit_stage_metric "onboarding.login"
890
+ emit_control "✅ 授权确认成功(本机 CLI 登录态、scope 与 MCC 授权有效性均已通过)"
891
+ emit_control "AUTH_FLOW:AUTH_SUCCESS"
892
+ return 0
893
+ }
894
+
895
+ # ─── auth wait: 授权页打开后有限轮询确认状态 ────────────────────────────────
896
+ auth_wait() {
897
+ local attempt=1
898
+ local max_attempts=12
899
+ local interval_seconds=5
900
+
901
+ if [ "${ALIPAY_AIPAY_TEST_MODE:-0}" = "1" ] && [[ "${AUTH_CONFIRM_POLL_INTERVAL_SECONDS:-}" =~ ^[0-9]+$ ]]; then
902
+ interval_seconds="$AUTH_CONFIRM_POLL_INTERVAL_SECONDS"
903
+ fi
904
+
905
+ while [ "$attempt" -le "$max_attempts" ]; do
906
+ AUTH_POLLING=true
907
+ if ! auth_confirm "$@"; then
908
+ AUTH_POLLING=false
909
+ return 1
910
+ fi
911
+ AUTH_POLLING=false
912
+
913
+ case "$AUTH_CONFIRM_RESULT" in
914
+ AUTH_SUCCESS|EXPIRED)
915
+ return 0
916
+ ;;
917
+ PENDING)
918
+ emit_control "AUTH_POLL_PENDING=${attempt}/${max_attempts}"
919
+ if [ "$attempt" -eq "$max_attempts" ]; then
920
+ if ! printf '%s' '{}' | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=auth.sh node "$RENDERER" auth.pending --variant DEFAULT; then
921
+ emit_control "AUTH_FLOW:FAILED"
922
+ return 1
923
+ fi
924
+ emit_control "AUTH_FLOW:PENDING"
925
+ return 0
926
+ fi
927
+ sleep "$interval_seconds"
928
+ ;;
929
+ *)
930
+ emit_control "AUTH_FLOW:FAILED"
931
+ return 1
932
+ ;;
933
+ esac
934
+ attempt=$((attempt + 1))
935
+ done
936
+ }
937
+
938
+ # ─── auth mismatch: 授权范围不满足 → logout → 重新授权 ──────────────────────
939
+ auth_mismatch() {
940
+ local mismatch_message=""
941
+ if [ "${1:-}" = "--defer-open" ]; then
942
+ shift
943
+ else
944
+ echo "❌ 参数格式错误:auth mismatch 必须使用 --defer-open,确保重新授权时先展示说明和链接,再自动打开页面"
945
+ echo "用法: bash auth.sh mismatch --defer-open [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name> --mcc-context-receipt <receipt>]"
946
+ return 1
947
+ fi
948
+
949
+ # 优先使用显式传入的非敏感上下文;未传时再从状态文件兜底恢复。
950
+ restore_auth_context_or_state "$@" || exit 1
951
+
952
+ if [ -z "$SALES_CODE" ]; then
953
+ echo "❌ 缺少 SALES_CODE(请传入 --sales-code,或先执行 auth.sh init)"
954
+ return 1
955
+ fi
956
+
957
+ # Step 1: 根据 salesCode 构造正确的 scope;目标无效时不退出当前登录。
958
+ SCOPE=$(get_scope "$SALES_CODE")
959
+ if [ -z "$SCOPE" ]; then
960
+ echo "❌ 未知产品码: $SALES_CODE"
961
+ return 1
962
+ fi
963
+
964
+ # Step 2: mismatch 是授权不匹配恢复路径中唯一执行 logout 的入口。
965
+ auth_require_local_state_access || return 1
966
+ if ! mismatch_message=$(printf '%s' '{}' | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=auth.sh node "$RENDERER" auth.mismatch --variant DEFAULT); then
967
+ emit_control "AUTH_FLOW:FAILED"
968
+ return 1
969
+ fi
970
+ confirm_logout_postcondition
971
+ case "$ALIPAY_LOGOUT_POSTCONDITION" in
972
+ COMMAND_CONFIRMED_SUCCESS|POSTCONDITION_LOGGED_OUT)
973
+ ;;
974
+ STILL_LOGGED_IN)
975
+ render_logout_still_logged_in || return 1
976
+ emit_control "AUTH_FLOW:LOGOUT_STILL_LOGGED_IN"
977
+ return 1
978
+ ;;
979
+ LOCAL_FS_PERMISSION)
980
+ auth_local_state_permission_failure
981
+ return 1
982
+ ;;
983
+ *)
984
+ render_logout_unconfirmed || return 1
985
+ emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
986
+ return 1
987
+ ;;
988
+ esac
989
+ printf '%s\n' "$mismatch_message"
990
+
991
+ # Step 3: 调用 auth init 执行登录 + 构建授权链接 + 输出唯一授权消息。
992
+ # DEV_TOOL_NAME 已 export,子进程可继承。
993
+ auth_init \
994
+ --defer-open \
995
+ --scope "$SCOPE" \
996
+ --sales-code "$SALES_CODE" \
997
+ --mcc-code "$MCC_CODE" \
998
+ --product-name "${PRODUCT_NAME:-}" \
999
+ --mcc-name "${MCC_NAME:-}" \
1000
+ --mcc-context-receipt "${MCC_CONTEXT_RECEIPT:-}"
1001
+ }
1002
+
1003
+ # ─── 入口分发 ────────────────────────────────────────────────────────────────
1004
+ case "${1:-}" in
1005
+ init)
1006
+ shift
1007
+ auth_init "$@"
1008
+ ;;
1009
+ confirm)
1010
+ shift
1011
+ auth_confirm "$@"
1012
+ ;;
1013
+ open)
1014
+ shift
1015
+ auth_open "$@"
1016
+ ;;
1017
+ manual-open)
1018
+ shift
1019
+ auth_manual_open "$@"
1020
+ ;;
1021
+ wait)
1022
+ shift
1023
+ auth_wait "$@"
1024
+ ;;
1025
+ mismatch)
1026
+ shift
1027
+ auth_mismatch "$@"
1028
+ ;;
1029
+ *)
1030
+ echo "用法: bash auth.sh <init|open|manual-open|wait|confirm|mismatch> [参数...]"
1031
+ echo ""
1032
+ echo " auth init --defer-open --scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name> --mcc-context-receipt <receipt>"
1033
+ echo " auth open [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name> --mcc-context-receipt <receipt>]"
1034
+ echo " auth manual-open - 输出手动访问提示"
1035
+ echo " auth wait [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name> --mcc-context-receipt <receipt>]"
1036
+ echo " auth confirm [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name> --mcc-context-receipt <receipt>]"
1037
+ echo " auth mismatch --defer-open [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name> --mcc-context-receipt <receipt>]"
1038
+ exit 1
1039
+ ;;
1040
+ esac