@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,806 @@
1
+ # AI 按量付费完整接入示例 - PHP
2
+
3
+ 以下代码展示严格验付和幂等履约的完整控制流。生成项目代码时,请按当前项目结构拆分并绑定真实持久化实现。
4
+
5
+ ```php
6
+ <?php
7
+ /**
8
+ * A2M 智能收产品接入示例 - PHP 版本
9
+ *
10
+ * 本示例展示 A2M 核心协议、严格验付和幂等履约流程。
11
+ * 必须通过 configureOrderRepository 绑定项目真实持久化实现;
12
+ * 端口未绑定时明确失败,不使用内存订单、固定资源或验付字段缺失兜底。
13
+ * 订单快照将支付状态映射为 PENDING_PAYMENT/PAID,
14
+ * 将履约状态映射为 UNFULFILLED/PENDING_CONFIRM/FULFILLED。
15
+ *
16
+ * 本文件演示 A2M 核心协议调用流程:
17
+ * 1. 返回 402 Payment-Needed Header
18
+ * 2. 验证 Payment-Proof 支付凭证
19
+ * 3. 发送履约回执确认
20
+ * 4. 返回资源内容
21
+ *
22
+ * 依赖安装:
23
+ * composer require alipaysdk/openapi
24
+ */
25
+
26
+ define('RESOURCE_CONFIG', [
27
+ 'path' => '/demo/a2m/resource',
28
+ 'goodsName' => 'AI 生成内容服务'
29
+ ]);
30
+
31
+ $ORDER_REPOSITORY = null;
32
+
33
+ function configureOrderRepository($repository) {
34
+ $requiredMethods = ['createPending', 'findByOutTradeNo', 'prepareFulfillment', 'markFulfilled'];
35
+ foreach ($requiredMethods as $method) {
36
+ if (!is_object($repository) || !is_callable([$repository, $method])) {
37
+ throw new RuntimeException('orderRepository 必须实现 ' . implode(', ', $requiredMethods));
38
+ }
39
+ }
40
+ $GLOBALS['ORDER_REPOSITORY'] = $repository;
41
+ }
42
+
43
+ function requireOrderRepository() {
44
+ $repository = $GLOBALS['ORDER_REPOSITORY'] ?? null;
45
+ if ($repository === null) {
46
+ throw new RuntimeException('尚未绑定项目真实 orderRepository,禁止回退到内存订单');
47
+ }
48
+ return $repository;
49
+ }
50
+
51
+ // ==================== 配置信息(实际使用时请从配置中心读取)====================
52
+
53
+ /**
54
+ * 返回第一个非空配置值。
55
+ */
56
+ function firstNonBlank(...$values) {
57
+ foreach ($values as $value) {
58
+ if ($value !== false && $value !== null && $value !== '') {
59
+ return (string)$value;
60
+ }
61
+ }
62
+ return '';
63
+ }
64
+
65
+ /**
66
+ * 只从已确认的项目根目录读取 Skill 快速沙箱配置,不依赖进程启动目录。
67
+ */
68
+ function loadSandboxConfig() {
69
+ $projectRoot = getenv('ALIPAY_AIPAY_PROJECT_ROOT');
70
+ if ($projectRoot === false || $projectRoot === '') {
71
+ return [];
72
+ }
73
+ $canonicalRoot = realpath($projectRoot);
74
+ if ($canonicalRoot === false || !is_dir($canonicalRoot)) {
75
+ throw new RuntimeException('ALIPAY_AIPAY_PROJECT_ROOT 必须是已确认的项目根目录绝对路径');
76
+ }
77
+ $path = $canonicalRoot . '/.alipay-sandbox.json';
78
+ if (!is_file($path)) {
79
+ return [];
80
+ }
81
+ $config = json_decode((string)file_get_contents($path), true);
82
+ if (!is_array($config)) {
83
+ throw new RuntimeException('.alipay-sandbox.json 不是合法 JSON');
84
+ }
85
+ return $config;
86
+ }
87
+
88
+ /**
89
+ * 加载支付宝配置。
90
+ *
91
+ * Unix/macOS/Linux 快速沙箱生成的真实商家 PID 位于 .alipay-sandbox.json 的 appIds[0].pid。
92
+ * 生产环境请通过环境变量或配置中心显式设置同一生产应用的 appId、应用私钥、支付宝公钥、
93
+ * 商户 PID 和服务市场真实 serviceId。
94
+ */
95
+ function loadAlipayConfig() {
96
+ $sandbox = loadSandboxConfig();
97
+ $sandboxApp = [];
98
+ if (isset($sandbox['appIds']) && is_array($sandbox['appIds']) && isset($sandbox['appIds'][0]) && is_array($sandbox['appIds'][0])) {
99
+ $sandboxApp = $sandbox['appIds'][0];
100
+ }
101
+
102
+ $privateKey = firstNonBlank(
103
+ getenv('ALIPAY_AIPAY_PRIVATE_KEY'),
104
+ $sandboxApp['appPrivatePkcsKey'] ?? null,
105
+ $sandbox['appPrivatePkcsKey'] ?? null
106
+ );
107
+
108
+ $gateway = firstNonBlank(
109
+ getenv('ALIPAY_AIPAY_GATEWAY'),
110
+ getenv('ALIPAY_GATEWAY'),
111
+ $sandbox['gateway'] ?? null,
112
+ 'https://openapi-sandbox.dl.alipaydev.com/gateway.do'
113
+ );
114
+ $serviceId = firstNonBlank(
115
+ getenv('ALIPAY_AIPAY_SERVICE_ID'),
116
+ $gateway === 'https://openapi-sandbox.dl.alipaydev.com/gateway.do' ? 'api_mock_service_id' : null
117
+ );
118
+
119
+ $config = [
120
+ 'appId' => firstNonBlank(
121
+ getenv('ALIPAY_AIPAY_APP_ID'),
122
+ $sandboxApp['appId'] ?? null,
123
+ $sandbox['appId'] ?? null
124
+ ),
125
+ 'privateKey' => $privateKey, // PHP 使用 PKCS#1 格式应用私钥
126
+ 'alipayPublicKey' => firstNonBlank(
127
+ getenv('ALIPAY_AIPAY_PUBLIC_KEY'),
128
+ $sandboxApp['alipayPublicKey'] ?? null,
129
+ $sandbox['alipayPublicKey'] ?? null
130
+ ),
131
+ // 默认用于本 Skill 的快速沙箱联调;生产部署时设置 ALIPAY_AIPAY_GATEWAY=https://openapi.alipay.com/gateway.do
132
+ 'gateway' => $gateway,
133
+ 'sellerId' => firstNonBlank(
134
+ getenv('ALIPAY_AIPAY_SELLER_ID'),
135
+ $sandboxApp['pid'] ?? null,
136
+ $sandboxApp['uid'] ?? null,
137
+ $sandbox['sellerId'] ?? null
138
+ ), // 商户 PID(2088 格式)
139
+ 'sellerName' => firstNonBlank(getenv('ALIPAY_AIPAY_SELLER_NAME'), '测试商户'),
140
+ 'serviceId' => $serviceId, // 仅固定沙箱网关可默认 api_mock_service_id;上线前替换为真实 serviceId
141
+ 'merchantPrivateKey' => $privateKey // 用于商家账单签名
142
+ ];
143
+
144
+ foreach (['appId', 'privateKey', 'alipayPublicKey', 'sellerId', 'serviceId'] as $requiredField) {
145
+ if ($config[$requiredField] === '') {
146
+ throw new RuntimeException("支付宝配置缺失:{$requiredField}");
147
+ }
148
+ }
149
+ return $config;
150
+ }
151
+
152
+ define('ALIPAY_CONFIG', loadAlipayConfig());
153
+
154
+ /**
155
+ * 初始化 SDK
156
+ */
157
+ function initAlipayClient() {
158
+ loadAopSdk();
159
+
160
+ if (!class_exists('\AopClient')) {
161
+ throw new RuntimeException('未找到支付宝 PHP SDK,请先执行:composer require alipaysdk/openapi');
162
+ }
163
+
164
+ $client = new \AopClient();
165
+ $client->gatewayUrl = ALIPAY_CONFIG['gateway'];
166
+ $client->appId = ALIPAY_CONFIG['appId'];
167
+ $client->rsaPrivateKey = ALIPAY_CONFIG['privateKey'];
168
+ $client->alipayrsaPublicKey = ALIPAY_CONFIG['alipayPublicKey'];
169
+ $client->format = 'json';
170
+ $client->postCharset = 'UTF-8';
171
+ $client->signType = 'RSA2';
172
+
173
+ return $client;
174
+ }
175
+
176
+ /**
177
+ * 加载官方 alipaysdk/openapi v2 AOP 客户端。
178
+ */
179
+ function loadAopSdk() {
180
+ $configuredRoot = getenv('ALIPAY_AIPAY_PROJECT_ROOT');
181
+ $candidateRoots = $configuredRoot ? [$configuredRoot] : [__DIR__, dirname(__DIR__)];
182
+
183
+ $visited = [];
184
+ foreach ($candidateRoots as $root) {
185
+ if (isset($visited[$root])) {
186
+ continue;
187
+ }
188
+ $visited[$root] = true;
189
+
190
+ $autoload = $root . '/vendor/autoload.php';
191
+ if (is_file($autoload)) {
192
+ require_once $autoload;
193
+ }
194
+
195
+ $aopClient = $root . '/vendor/alipaysdk/openapi/v2/aop/AopClient.php';
196
+ if (is_file($aopClient)) {
197
+ require_once $aopClient;
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * 格式化支付宝时间戳:yyyy-MM-dd HH:mm:ss
204
+ */
205
+ function formatAlipayTimestamp($timestamp = null) {
206
+ if ($timestamp === null) {
207
+ $timestamp = time();
208
+ }
209
+ return date('Y-m-d H:i:s', $timestamp);
210
+ }
211
+
212
+ /**
213
+ * 生成商家签名(seller_signature)
214
+ */
215
+ function generateSellerSignature($params, $privateKey) {
216
+ // 1. 按 key 字典序排序
217
+ ksort($params);
218
+
219
+ // 2. 拼接签名内容
220
+ $signContent = [];
221
+ foreach ($params as $key => $value) {
222
+ if ($value !== null && $value !== '') {
223
+ $signContent[] = "{$key}={$value}";
224
+ }
225
+ }
226
+ $signString = implode('&', $signContent);
227
+
228
+ // 3. OpenSSL 需要 PEM;仅为本次签名临时包装裸 PKCS#1 Base64,不修改原始配置。
229
+ $privateKeyPem = "-----BEGIN RSA PRIVATE KEY-----\n"
230
+ . chunk_split($privateKey, 64, "\n")
231
+ . "-----END RSA PRIVATE KEY-----";
232
+ $privateKeyResource = openssl_pkey_get_private($privateKeyPem);
233
+ if ($privateKeyResource === false) {
234
+ throw new RuntimeException('无法解析 PKCS#1 应用私钥');
235
+ }
236
+ if (!openssl_sign($signString, $signature, $privateKeyResource, OPENSSL_ALGO_SHA256)) {
237
+ throw new RuntimeException('seller_signature 生成失败');
238
+ }
239
+
240
+ return base64_encode($signature);
241
+ }
242
+
243
+ /**
244
+ * Base64URL 编码
245
+ */
246
+ function base64UrlEncode($data) {
247
+ return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
248
+ }
249
+
250
+ /**
251
+ * Base64URL 解码
252
+ */
253
+ function base64UrlDecode($data) {
254
+ $padding = strlen($data) % 4;
255
+ if ($padding) {
256
+ $data .= str_repeat('=', 4 - $padding);
257
+ }
258
+ return base64_decode(strtr($data, '-_', '+/'));
259
+ }
260
+
261
+ function normalizeAmount($value) {
262
+ $text = trim((string)$value);
263
+ if (!preg_match('/^\d+(?:\.\d{1,2})?$/', $text)) {
264
+ return null;
265
+ }
266
+ [$integer, $fraction] = array_pad(explode('.', $text, 2), 2, '');
267
+ $integer = ltrim($integer, '0');
268
+ return ($integer === '' ? '0' : $integer) . '.' . str_pad($fraction, 2, '0');
269
+ }
270
+
271
+ function amountsEqual($left, $right) {
272
+ $leftAmount = normalizeAmount($left);
273
+ $rightAmount = normalizeAmount($right);
274
+ return $leftAmount !== null && $rightAmount !== null && $leftAmount === $rightAmount;
275
+ }
276
+
277
+ function isExactSandboxMode() {
278
+ return ALIPAY_CONFIG['gateway'] === 'https://openapi-sandbox.dl.alipaydev.com/gateway.do'
279
+ && ALIPAY_CONFIG['serviceId'] === 'api_mock_service_id';
280
+ }
281
+
282
+ /**
283
+ * 发送 JSON 响应
284
+ */
285
+ function jsonResponse($data, $statusCode = 200, $headers = []) {
286
+ http_response_code($statusCode);
287
+ header('Content-Type: application/json; charset=utf-8');
288
+
289
+ foreach ($headers as $key => $value) {
290
+ header("{$key}: {$value}");
291
+ }
292
+
293
+ echo json_encode($data, JSON_UNESCAPED_UNICODE);
294
+ exit;
295
+ }
296
+
297
+ // ==================== 智能收产品接入示例接口 ====================
298
+
299
+ /**
300
+ * 智能收产品统一接口
301
+ *
302
+ * 核心协议流程演示:
303
+ * 1. 不带 Payment-Proof Header:返回 HTTP 402 + Payment-Needed Header
304
+ * 2. 带 Payment-Proof Header:验证支付 → 自动履约 → 返回资源
305
+ */
306
+ function handleResourceRequest() {
307
+ // 获取 Payment-Proof Header
308
+ $paymentProof = isset($_SERVER['HTTP_PAYMENT_PROOF']) ? $_SERVER['HTTP_PAYMENT_PROOF'] : null;
309
+
310
+ // 场景 1:用户未支付,返回 402 + Payment-Needed Header
311
+ if (empty($paymentProof) || trim($paymentProof) === '') {
312
+ createPaymentRequiredResponse();
313
+ return;
314
+ }
315
+
316
+ // 场景 2:用户已支付,验证 Payment-Proof 并返回资源
317
+ verifyPaymentAndDeliverResource($paymentProof);
318
+ }
319
+
320
+ /**
321
+ * 创建 402 支付请求响应
322
+ */
323
+ function createPaymentRequiredResponse() {
324
+ try {
325
+ // 1. 构造订单信息
326
+ $outTradeNo = 'ORDER_' . time() . mt_rand(1000, 9999);
327
+ $amount = '0.01'; // 单位:元
328
+ $currency = 'CNY';
329
+ $resourceId = RESOURCE_CONFIG['path'];
330
+ $goodsName = RESOURCE_CONFIG['goodsName'];
331
+
332
+ // 2. 计算支付截止时间(30 分钟后)
333
+ $payBefore = date('c', time() + 30 * 60); // ISO 8601 格式
334
+
335
+ // 3. 生成商家签名
336
+ $sellerSignature = generateSellerSignature([
337
+ 'amount' => $amount,
338
+ 'currency' => $currency,
339
+ 'goods_name' => $goodsName,
340
+ 'out_trade_no' => $outTradeNo,
341
+ 'pay_before' => $payBefore,
342
+ 'resource_id' => $resourceId,
343
+ 'seller_id' => ALIPAY_CONFIG['sellerId'],
344
+ 'service_id' => ALIPAY_CONFIG['serviceId']
345
+ ], ALIPAY_CONFIG['merchantPrivateKey']);
346
+
347
+ requireOrderRepository()->createPending([
348
+ 'outTradeNo' => $outTradeNo,
349
+ 'amount' => normalizeAmount($amount),
350
+ 'currency' => $currency,
351
+ 'resourceId' => $resourceId,
352
+ 'goodsName' => $goodsName,
353
+ 'payBefore' => $payBefore,
354
+ 'orderStatus' => 'PENDING_PAYMENT',
355
+ 'fulfillStatus' => 'UNFULFILLED'
356
+ ]);
357
+
358
+ // 4. 持久化成功后构造 Payment-Needed Header 内容
359
+ $paymentNeeded = [
360
+ 'protocol' => [
361
+ 'out_trade_no' => $outTradeNo,
362
+ 'amount' => $amount,
363
+ 'currency' => $currency,
364
+ 'resource_id' => $resourceId,
365
+ 'pay_before' => $payBefore,
366
+ 'seller_signature' => $sellerSignature,
367
+ 'seller_sign_type' => 'RSA2',
368
+ 'seller_unique_id' => ALIPAY_CONFIG['sellerId']
369
+ ],
370
+ 'method' => [
371
+ 'seller_name' => ALIPAY_CONFIG['sellerName'],
372
+ 'seller_id' => ALIPAY_CONFIG['sellerId'],
373
+ 'seller_app_id' => ALIPAY_CONFIG['appId'],
374
+ 'goods_name' => $goodsName,
375
+ 'seller_unique_id_key' => 'seller_id',
376
+ 'service_id' => ALIPAY_CONFIG['serviceId']
377
+ ]
378
+ ];
379
+
380
+ // 5. Base64URL 编码
381
+ $paymentNeededEncoded = base64UrlEncode(json_encode($paymentNeeded, JSON_UNESCAPED_UNICODE));
382
+
383
+ // 6. 构造 402 响应
384
+ $response = [
385
+ 'code' => 'Payment-Needed',
386
+ 'message' => '需要支付',
387
+ 'out_trade_no' => $outTradeNo,
388
+ 'amount' => $amount,
389
+ 'currency' => $currency,
390
+ 'goods_name' => $goodsName
391
+ ];
392
+
393
+ error_log("创建支付订单成功:outTradeNo={$outTradeNo}, amount={$amount}");
394
+
395
+ jsonResponse($response, 402, ['Payment-Needed' => $paymentNeededEncoded]);
396
+
397
+ } catch (Exception $e) {
398
+ error_log('创建订单失败:' . $e->getMessage());
399
+ jsonResponse([
400
+ 'code' => 'CREATE_ORDER_ERROR',
401
+ 'message' => '创建订单失败:' . $e->getMessage()
402
+ ], 500);
403
+ }
404
+ }
405
+
406
+ /**
407
+ * 验证支付凭证并交付资源
408
+ */
409
+ function verifyPaymentAndDeliverResource($paymentProof) {
410
+ try {
411
+ // 1. 从 Payment-Proof 中解析订单信息
412
+ $decodedProof = base64UrlDecode($paymentProof);
413
+ $proofJson = json_decode($decodedProof, true);
414
+
415
+ if (json_last_error() !== JSON_ERROR_NONE) {
416
+ error_log('Payment-Proof 解析失败:' . json_last_error_msg());
417
+ createPaymentRequiredResponse();
418
+ }
419
+
420
+ // 从 protocol 层获取 payment_proof 和 trade_no
421
+ $paymentProofValue = null;
422
+ $tradeNo = null;
423
+ $clientSession = null;
424
+
425
+ if (isset($proofJson['protocol'])) {
426
+ $paymentProofValue = $proofJson['protocol']['payment_proof'] ?? null;
427
+ $tradeNo = $proofJson['protocol']['trade_no'] ?? null;
428
+ }
429
+
430
+ // 从 method 层获取 client_session
431
+ if (isset($proofJson['method'])) {
432
+ $clientSession = $proofJson['method']['client_session'] ?? null;
433
+ }
434
+
435
+ // 校验必要字段
436
+ if (empty($paymentProofValue)) {
437
+ createPaymentRequiredResponse();
438
+ }
439
+
440
+ if (empty($tradeNo)) {
441
+ createPaymentRequiredResponse();
442
+ }
443
+
444
+ // 2. 调用支付宝 API 验证支付凭证
445
+ $alipayClient = initAlipayClient();
446
+ $request = new \AlipayAipayAgentPaymentVerifyRequest();
447
+
448
+ $model = [
449
+ 'payment_proof' => $paymentProofValue,
450
+ 'trade_no' => $tradeNo
451
+ ];
452
+ if ($clientSession !== null && trim((string)$clientSession) !== '') {
453
+ $model['client_session'] = $clientSession;
454
+ }
455
+
456
+ $request->setBizContent(json_encode($model, JSON_UNESCAPED_UNICODE));
457
+ $responseResult = $alipayClient->execute($request);
458
+ $responseApiName = str_replace('.', '_', $request->getApiMethodName()) . '_response';
459
+ $verifyResponse = $responseResult->$responseApiName ?? (object)[];
460
+
461
+ // 3. 验证失败,返回错误
462
+ if (empty($verifyResponse->code) || $verifyResponse->code !== '10000') {
463
+ error_log('支付凭证验证失败:' . ($verifyResponse->sub_msg ?? 'Unknown error'));
464
+ createPaymentRequiredResponse();
465
+ }
466
+
467
+ // 4. 验证成功,获取订单信息
468
+ $returnedTradeNo = $verifyResponse->trade_no ?? $verifyResponse->tradeNo ?? '';
469
+ $verifyOutTradeNo = $verifyResponse->out_trade_no ?? $verifyResponse->outTradeNo ?? '';
470
+ $returnedAmount = $verifyResponse->amount ?? '';
471
+ $returnedResourceId = $verifyResponse->resource_id ?? $verifyResponse->resourceId ?? '';
472
+ $active = $verifyResponse->active ?? null;
473
+
474
+ $repository = requireOrderRepository();
475
+ $order = $verifyOutTradeNo !== '' ? $repository->findByOutTradeNo($verifyOutTradeNo) : null;
476
+ $sandboxMode = isExactSandboxMode();
477
+ $verifyTradeNo = $returnedTradeNo !== '' ? $returnedTradeNo : ($sandboxMode ? $tradeNo : '');
478
+ $verifyAmount = $returnedAmount !== '' ? $returnedAmount : ($sandboxMode && $order ? $order['amount'] : '');
479
+ $resourceIdVerified = $returnedResourceId !== '' ? $returnedResourceId : ($sandboxMode && $order ? $order['resourceId'] : '');
480
+
481
+ error_log("支付凭证验证成功:tradeNo={$verifyTradeNo}, outTradeNo={$verifyOutTradeNo}");
482
+
483
+ // 5. 校验凭证有效性(active=true 表示凭证有效)
484
+ $isActive = $active === true || $active === 'true' || $active === '1' || $active === 1;
485
+ if (!$isActive || $verifyTradeNo === '' || $verifyTradeNo !== $tradeNo || $verifyOutTradeNo === '' || $resourceIdVerified === '') {
486
+ error_log("支付凭证无效或已过期:outTradeNo={$verifyOutTradeNo}");
487
+ createPaymentRequiredResponse();
488
+ }
489
+
490
+ $amountMatches = $order && amountsEqual($order['amount'], $verifyAmount);
491
+ $resourceMatches = $order
492
+ && $order['resourceId'] === $resourceIdVerified
493
+ && $resourceIdVerified === RESOURCE_CONFIG['path'];
494
+ $fulfillmentInProgress = $order
495
+ && in_array($order['fulfillStatus'], ['PENDING_CONFIRM', 'FULFILLED'], true);
496
+ $orderUsable = $order
497
+ && $order['currency'] === 'CNY'
498
+ && in_array($order['orderStatus'], ['PENDING_PAYMENT', 'PAID'], true)
499
+ && in_array($order['fulfillStatus'], ['UNFULFILLED', 'PENDING_CONFIRM', 'FULFILLED'], true)
500
+ && ($fulfillmentInProgress || strtotime($order['payBefore']) > time());
501
+ if (!$amountMatches || !$resourceMatches || !$orderUsable) {
502
+ createPaymentRequiredResponse();
503
+ }
504
+
505
+ $fulfillment = $repository->prepareFulfillment([
506
+ 'outTradeNo' => $verifyOutTradeNo,
507
+ 'tradeNo' => $verifyTradeNo,
508
+ 'expectedAmount' => normalizeAmount($verifyAmount),
509
+ 'expectedResourceId' => $resourceIdVerified,
510
+ 'createResource' => function () use ($resourceIdVerified) {
511
+ return generateServiceResource($resourceIdVerified);
512
+ }
513
+ ]);
514
+ if (!is_array($fulfillment)
515
+ || !in_array($fulfillment['state'] ?? '', ['PENDING_CONFIRM', 'FULFILLED'], true)
516
+ || empty($fulfillment['serviceResult'])) {
517
+ throw new RuntimeException('orderRepository::prepareFulfillment 未返回已持久化的履约结果');
518
+ }
519
+ $serviceResult = $fulfillment['serviceResult'];
520
+ if ($fulfillment['state'] === 'FULFILLED') {
521
+ sendSuccessfulResource($verifyTradeNo, $verifyOutTradeNo, $resourceIdVerified, $serviceResult, true);
522
+ }
523
+
524
+ $fulfillmentTradeNo = $verifyTradeNo;
525
+ error_log("资源已生成,准备发送履约确认:outTradeNo={$verifyOutTradeNo}, tradeNo={$fulfillmentTradeNo}");
526
+
527
+ // 12. 发送履约确认到支付宝,确认成功后才返回成功交付
528
+ if (!sendFulfillmentConfirm($fulfillmentTradeNo)) {
529
+ jsonResponse([
530
+ 'code' => 'FULFILLMENT_CONFIRM_FAILED',
531
+ 'message' => '资源已生成但履约确认失败,请稍后使用同一 Payment-Proof 重试'
532
+ ], 502);
533
+ }
534
+
535
+ $repository->markFulfilled($verifyOutTradeNo, $fulfillmentTradeNo);
536
+
537
+ error_log("履约确认成功:outTradeNo={$verifyOutTradeNo}, tradeNo={$fulfillmentTradeNo}");
538
+
539
+ sendSuccessfulResource($fulfillmentTradeNo, $verifyOutTradeNo, $resourceIdVerified, $serviceResult, false);
540
+
541
+ } catch (Exception $e) {
542
+ error_log('支付凭证验证异常:' . $e->getMessage());
543
+ jsonResponse([
544
+ 'code' => 'VERIFY_FAILED',
545
+ 'message' => '支付凭证验证失败:' . $e->getMessage()
546
+ ], 500);
547
+ }
548
+ }
549
+
550
+ function sendSuccessfulResource($tradeNo, $outTradeNo, $resourceId, $serviceResult, $alreadyFulfilled) {
551
+ $paymentValidation = base64UrlEncode(json_encode([
552
+ 'trade_no' => $tradeNo,
553
+ 'out_trade_no' => $outTradeNo,
554
+ 'validated' => true,
555
+ 'resource_id' => $resourceId
556
+ ], JSON_UNESCAPED_UNICODE));
557
+ jsonResponse([
558
+ 'resource_id' => $resourceId,
559
+ 'content' => $serviceResult,
560
+ 'trade_no' => $tradeNo,
561
+ 'out_trade_no' => $outTradeNo,
562
+ 'already_fulfilled' => $alreadyFulfilled,
563
+ 'fulfillment_confirmed' => true
564
+ ], 200, ['Payment-Validation' => $paymentValidation]);
565
+ }
566
+
567
+ /**
568
+ * 生成服务资源内容
569
+ */
570
+ function generateServiceResource($resourceId) {
571
+ return json_encode([
572
+ 'status' => 'success',
573
+ 'service_type' => 'AI_CONTENT_GENERATION',
574
+ 'resource_id' => $resourceId,
575
+ 'content' => '这是 AI 生成的内容示例,可根据实际业务替换为任意数字服务内容',
576
+ 'generated_at' => date('c')
577
+ ], JSON_UNESCAPED_UNICODE);
578
+ }
579
+
580
+ /**
581
+ * 发送履约确认
582
+ */
583
+ function sendFulfillmentConfirm($tradeNo) {
584
+ if (empty($tradeNo)) {
585
+ error_log('履约确认失败:tradeNo 为空');
586
+ return false;
587
+ }
588
+
589
+ try {
590
+ error_log("开始发送履约确认:tradeNo={$tradeNo}");
591
+
592
+ $alipayClient = initAlipayClient();
593
+ $request = new \AlipayAipayAgentFulfillmentConfirmRequest();
594
+
595
+ $request->setBizContent(json_encode([
596
+ 'trade_no' => $tradeNo
597
+ ], JSON_UNESCAPED_UNICODE));
598
+
599
+ $responseResult = $alipayClient->execute($request);
600
+ $responseApiName = str_replace('.', '_', $request->getApiMethodName()) . '_response';
601
+ $response = $responseResult->$responseApiName ?? (object)[];
602
+
603
+ if (!empty($response->code) && $response->code === '10000') {
604
+ error_log("履约确认成功:tradeNo={$tradeNo}");
605
+ return true;
606
+ } else {
607
+ error_log("履约确认失败:tradeNo={$tradeNo}, errorCode=" .
608
+ ($response->sub_code ?? 'Unknown') . ", errorMsg=" .
609
+ ($response->sub_msg ?? 'Unknown'));
610
+ return false;
611
+ }
612
+
613
+ } catch (Exception $e) {
614
+ error_log("履约确认异常:tradeNo={$tradeNo}, error=" . $e->getMessage());
615
+ return false;
616
+ }
617
+ }
618
+
619
+ // 官方 alipaysdk/openapi 当前未内置以下两个 A2M Request 类,示例在本地补齐 AOP 客户端会调用的方法。
620
+ if (!class_exists('AlipayAipayAgentPaymentVerifyRequest')) {
621
+ final class AlipayAipayAgentPaymentVerifyRequest
622
+ {
623
+ private $bizContent = '';
624
+ private $apiParas = [];
625
+ private $apiVersion = '1.0';
626
+ private $needEncrypt = false;
627
+ private $terminalType = null;
628
+ private $terminalInfo = null;
629
+ private $prodCode = null;
630
+ private $notifyUrl = null;
631
+ private $returnUrl = null;
632
+
633
+ public function setBizContent($bizContent) {
634
+ $this->bizContent = $bizContent;
635
+ $this->apiParas['biz_content'] = $bizContent;
636
+ }
637
+
638
+ public function getBizContent() {
639
+ return $this->bizContent;
640
+ }
641
+
642
+ public function getApiMethodName() {
643
+ return 'alipay.aipay.agent.payment.verify';
644
+ }
645
+
646
+ public function getApiParas() {
647
+ return $this->apiParas;
648
+ }
649
+
650
+ public function getApiVersion() {
651
+ return $this->apiVersion;
652
+ }
653
+
654
+ public function setApiVersion($apiVersion) {
655
+ $this->apiVersion = $apiVersion;
656
+ }
657
+
658
+ public function getNeedEncrypt() {
659
+ return $this->needEncrypt;
660
+ }
661
+
662
+ public function setNeedEncrypt($needEncrypt) {
663
+ $this->needEncrypt = $needEncrypt;
664
+ }
665
+
666
+ public function getTerminalType() {
667
+ return $this->terminalType;
668
+ }
669
+
670
+ public function setTerminalType($terminalType) {
671
+ $this->terminalType = $terminalType;
672
+ }
673
+
674
+ public function getTerminalInfo() {
675
+ return $this->terminalInfo;
676
+ }
677
+
678
+ public function setTerminalInfo($terminalInfo) {
679
+ $this->terminalInfo = $terminalInfo;
680
+ }
681
+
682
+ public function getProdCode() {
683
+ return $this->prodCode;
684
+ }
685
+
686
+ public function setProdCode($prodCode) {
687
+ $this->prodCode = $prodCode;
688
+ }
689
+
690
+ public function getNotifyUrl() {
691
+ return $this->notifyUrl;
692
+ }
693
+
694
+ public function setNotifyUrl($notifyUrl) {
695
+ $this->notifyUrl = $notifyUrl;
696
+ }
697
+
698
+ public function getReturnUrl() {
699
+ return $this->returnUrl;
700
+ }
701
+
702
+ public function setReturnUrl($returnUrl) {
703
+ $this->returnUrl = $returnUrl;
704
+ }
705
+ }
706
+ }
707
+
708
+ if (!class_exists('AlipayAipayAgentFulfillmentConfirmRequest')) {
709
+ final class AlipayAipayAgentFulfillmentConfirmRequest
710
+ {
711
+ private $bizContent = '';
712
+ private $apiParas = [];
713
+ private $apiVersion = '1.0';
714
+ private $needEncrypt = false;
715
+ private $terminalType = null;
716
+ private $terminalInfo = null;
717
+ private $prodCode = null;
718
+ private $notifyUrl = null;
719
+ private $returnUrl = null;
720
+
721
+ public function setBizContent($bizContent) {
722
+ $this->bizContent = $bizContent;
723
+ $this->apiParas['biz_content'] = $bizContent;
724
+ }
725
+
726
+ public function getBizContent() {
727
+ return $this->bizContent;
728
+ }
729
+
730
+ public function getApiMethodName() {
731
+ return 'alipay.aipay.agent.fulfillment.confirm';
732
+ }
733
+
734
+ public function getApiParas() {
735
+ return $this->apiParas;
736
+ }
737
+
738
+ public function getApiVersion() {
739
+ return $this->apiVersion;
740
+ }
741
+
742
+ public function setApiVersion($apiVersion) {
743
+ $this->apiVersion = $apiVersion;
744
+ }
745
+
746
+ public function getNeedEncrypt() {
747
+ return $this->needEncrypt;
748
+ }
749
+
750
+ public function setNeedEncrypt($needEncrypt) {
751
+ $this->needEncrypt = $needEncrypt;
752
+ }
753
+
754
+ public function getTerminalType() {
755
+ return $this->terminalType;
756
+ }
757
+
758
+ public function setTerminalType($terminalType) {
759
+ $this->terminalType = $terminalType;
760
+ }
761
+
762
+ public function getTerminalInfo() {
763
+ return $this->terminalInfo;
764
+ }
765
+
766
+ public function setTerminalInfo($terminalInfo) {
767
+ $this->terminalInfo = $terminalInfo;
768
+ }
769
+
770
+ public function getProdCode() {
771
+ return $this->prodCode;
772
+ }
773
+
774
+ public function setProdCode($prodCode) {
775
+ $this->prodCode = $prodCode;
776
+ }
777
+
778
+ public function getNotifyUrl() {
779
+ return $this->notifyUrl;
780
+ }
781
+
782
+ public function setNotifyUrl($notifyUrl) {
783
+ $this->notifyUrl = $notifyUrl;
784
+ }
785
+
786
+ public function getReturnUrl() {
787
+ return $this->returnUrl;
788
+ }
789
+
790
+ public function setReturnUrl($returnUrl) {
791
+ $this->returnUrl = $returnUrl;
792
+ }
793
+ }
794
+ }
795
+
796
+ // ==================== 路由处理 ====================
797
+
798
+ // 简单路由
799
+ $requestUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
800
+ if ($requestUri === RESOURCE_CONFIG['path'] && $_SERVER['REQUEST_METHOD'] === 'GET') {
801
+ handleResourceRequest();
802
+ } else {
803
+ http_response_code(404);
804
+ echo json_encode(['code' => 'NOT_FOUND', 'message' => '接口不存在'], JSON_UNESCAPED_UNICODE);
805
+ }
806
+ ```