@alipay/alipay-aipay 1.5.0 → 1.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alipay/alipay-aipay",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "支付宝 AI 付 Skill 安装包 - 安装 Agent Skill,并检测/后台准备 alipay-cli;jq 可通过 --with-jq 尝试准备",
5
5
  "type": "module",
6
6
  "bin": {
package/skill/VERSION CHANGED
@@ -1 +1 @@
1
- 1.5.0
1
+ 1.5.1
@@ -21,11 +21,11 @@
21
21
 
22
22
  ### 标准消息执行规则
23
23
 
24
- 对客消息遵守 `SKILL.md` 唯一目录和 stdout 规则。无托管入口时,动态值只来自本轮实际项目、脚本输出、校验结果或用户确认,并只用动作处受控命令构造;无变量传 `{}`:
24
+ 对客消息遵守 `SKILL.md` 唯一目录和 stdout 规则。展示命令禁止追加 `2>&1`、管道、`grep/sed/awk/tee` 或临时包装;托管消息禁止拆回 `jq/printf/renderer`。无托管入口时,动态值只来自本轮实际项目、脚本输出、校验结果或用户确认,并使用 renderer `--input-json`;无变量传 `{}`:
25
25
 
26
26
  ```bash
27
27
  node ../normal/scripts/render_customer_message.mjs --schema <messageId> --variant <VARIANT>
28
- printf '%s' "$MESSAGE_INPUT_JSON" | node ../normal/scripts/render_customer_message.mjs <messageId> --variant <VARIANT>
28
+ node ../normal/scripts/render_customer_message.mjs <messageId> --variant <VARIANT> --input-json "$MESSAGE_INPUT_JSON"
29
29
  ```
30
30
 
31
31
  直接调用 renderer 前先查同一 `messageId + variant` schema;托管消息必须用 `integration_message_runner.mjs` 或登记脚本。失败停步,不手写兜底或猜枚举;成功时 stdout 是唯一正文。脚本终态满足后立即转移,不追加命令复核。
@@ -545,29 +545,22 @@ node ../normal/scripts/integration_message_runner.mjs safety-closeout --variant
545
545
 
546
546
  **校验完整性要求**:内部必须覆盖 `modules/checklist.md` 的密钥与安全、异步通知、适用接口覆盖、支付结果处理、按量付费专项、退款和上线前七类校验,不得只检查上方三个摘要项。通过时默认对客只输出通过结论和下一步;部分通过或未通过时只输出实际未完成项、人工待验证项和下一步。步骤 5/6 已展示的沙箱配置、联调或付款体验结论不在 checklist 结果中重复。通过项与不适用项无需逐行展开,用户要求明细时再输出完整逐项结果。精简输出不得导致任何适用校验被跳过。
547
547
 
548
- 先在内部汇总 checklist 证据。发现本轮范围内可直接修复的代码或配置缺口时,先修复并重新执行受影响检查;在仍可继续自动修复期间不得渲染中间 checklist 结果。只有已通过、只剩人工待验证,或本轮无法继续修复时,才生成最终 `CHECKLIST_RESULT_JSON` 并渲染一次对客结论。
548
+ 先在内部汇总 checklist 证据。发现本轮范围内可直接修复的代码或配置缺口时,先修复并重新执行受影响检查;在仍可继续自动修复期间不得渲染中间 checklist 结果。只有已通过、只剩人工待验证,或本轮无法继续修复时,才生成最终对客结论参数并渲染一次。
549
549
 
550
- 对客结论统一使用 `integration.checklist.result`,并严格按当前产品选择 `AIPAY`、`WEBPAY` 或 `APPPAY` 变体;整体结论、失败/部分通过项和人工待验证项必须来自最终一轮 checklist 实际证据。必须从本 flow 所在目录执行下列 runner;runner stdout 是唯一对客正文,必须把 stdout 原文作为当前回复正文发给用户:
551
-
552
- ```bash
553
- printf '%s' "$CHECKLIST_RESULT_JSON" \
554
- | node ../normal/scripts/integration_message_runner.mjs checklist-result --variant <AIPAY|WEBPAY|APPPAY> --project-path "$PROJECT_PATH" --scope-receipt "$CHECKLIST_SCOPE_RECEIPT"
555
- ```
556
-
557
- 上方 stdin 命令保持用于 Unix/macOS/Linux。Windows 不使用 shell 管道或续行符,按当前产品在 PowerShell 中执行对应单行命令;参数值仍来自同一最终 checklist 事实:
550
+ 对客结论统一使用 `integration.checklist.result`,并严格按当前产品选择 `AIPAY`、`WEBPAY` 或 `APPPAY` 变体;整体结论、失败/部分通过项和人工待验证项必须来自最终一轮 checklist 实际证据。必须从本 flow 所在目录执行当前产品对应单行 runner;runner stdout 是唯一对客正文,必须把 stdout 原文作为当前回复正文发给用户:
558
551
 
559
552
  <!-- ALIPAY_AIPAY_PRODUCT:aipay:START -->
560
- ```powershell
553
+ ```bash
561
554
  node ../normal/scripts/integration_message_runner.mjs checklist-result --variant AIPAY --project-path "$PROJECT_PATH" --scope-receipt "$CHECKLIST_SCOPE_RECEIPT" --overall-result "$OVERALL_RESULT" --failed-items "$FAILED_ITEMS" --manual-items "$MANUAL_ITEMS" --sandbox-result "$SANDBOX_RESULT" --sandbox-config-state "$SANDBOX_CONFIG_STATE" --blocking-defect-state "$BLOCKING_DEFECT_STATE" --execution-mode "$EXECUTION_MODE" --next-flow-reminder "$NEXT_FLOW_REMINDER"
562
555
  ```
563
556
  <!-- ALIPAY_AIPAY_PRODUCT:END -->
564
557
  <!-- ALIPAY_AIPAY_PRODUCT:webpay:START -->
565
- ```powershell
558
+ ```bash
566
559
  node ../normal/scripts/integration_message_runner.mjs checklist-result --variant WEBPAY --project-path "$PROJECT_PATH" --scope-receipt "$CHECKLIST_SCOPE_RECEIPT" --overall-result "$OVERALL_RESULT" --failed-items "$FAILED_ITEMS" --manual-items "$MANUAL_ITEMS" --webpay-experience-result "$WEBPAY_EXPERIENCE_RESULT" --sandbox-config-state "$SANDBOX_CONFIG_STATE" --blocking-defect-state "$BLOCKING_DEFECT_STATE" --execution-mode "$EXECUTION_MODE" --next-flow-reminder "$NEXT_FLOW_REMINDER"
567
560
  ```
568
561
  <!-- ALIPAY_AIPAY_PRODUCT:END -->
569
562
  <!-- ALIPAY_AIPAY_PRODUCT:apppay:START -->
570
- ```powershell
563
+ ```bash
571
564
  node ../normal/scripts/integration_message_runner.mjs checklist-result --variant APPPAY --project-path "$PROJECT_PATH" --scope-receipt "$CHECKLIST_SCOPE_RECEIPT" --overall-result "$OVERALL_RESULT" --failed-items "$FAILED_ITEMS" --manual-items "$MANUAL_ITEMS" --sandbox-config-state "$SANDBOX_CONFIG_STATE" --blocking-defect-state "$BLOCKING_DEFECT_STATE" --execution-mode "$EXECUTION_MODE" --next-flow-reminder "$NEXT_FLOW_REMINDER"
572
565
  ```
573
566
  <!-- ALIPAY_AIPAY_PRODUCT:END -->
@@ -132,14 +132,9 @@
132
132
 
133
133
  Unix/macOS/Linux 的 `sandboxConfigState` 必须复用 integration flow 的固定脚本终态。Windows 必须复用步骤 1/3 记录的 `READY|VERIFY_PENDING`。待配置时不在本步骤重跑创建或复核,不读取当前配置,把配置后置校验、摘要和适用沙箱测试列入未完成;若代码实现与其他安全检查均通过,整体结果为“部分通过”,否则按其他缺口判为“未通过”。
134
134
 
135
- ```bash
136
- printf '%s' "$CHECKLIST_RESULT_JSON" \
137
- | node ../../normal/scripts/integration_message_runner.mjs checklist-result --variant <AIPAY|WEBPAY|APPPAY> --project-path "$PROJECT_PATH" --scope-receipt "$CHECKLIST_SCOPE_RECEIPT"
138
- ```
135
+ 按当前产品执行对应单行 runner,禁止管道、临时 JSON 包装或二次渲染:
139
136
 
140
- 上方 stdin 命令保持用于 Unix/macOS/Linux。Windows 不使用管道或续行符,按当前产品执行对应 PowerShell 单行命令:
141
-
142
- ```powershell
137
+ ```bash
143
138
  node ../../normal/scripts/integration_message_runner.mjs checklist-result --variant AIPAY --project-path "$PROJECT_PATH" --scope-receipt "$CHECKLIST_SCOPE_RECEIPT" --overall-result "$OVERALL_RESULT" --failed-items "$FAILED_ITEMS" --manual-items "$MANUAL_ITEMS" --sandbox-result "$SANDBOX_RESULT" --sandbox-config-state "$SANDBOX_CONFIG_STATE" --blocking-defect-state "$BLOCKING_DEFECT_STATE" --execution-mode "$EXECUTION_MODE" --next-flow-reminder "$NEXT_FLOW_REMINDER"
144
139
  node ../../normal/scripts/integration_message_runner.mjs checklist-result --variant WEBPAY --project-path "$PROJECT_PATH" --scope-receipt "$CHECKLIST_SCOPE_RECEIPT" --overall-result "$OVERALL_RESULT" --failed-items "$FAILED_ITEMS" --manual-items "$MANUAL_ITEMS" --webpay-experience-result "$WEBPAY_EXPERIENCE_RESULT" --sandbox-config-state "$SANDBOX_CONFIG_STATE" --blocking-defect-state "$BLOCKING_DEFECT_STATE" --execution-mode "$EXECUTION_MODE" --next-flow-reminder "$NEXT_FLOW_REMINDER"
145
140
  node ../../normal/scripts/integration_message_runner.mjs checklist-result --variant APPPAY --project-path "$PROJECT_PATH" --scope-receipt "$CHECKLIST_SCOPE_RECEIPT" --overall-result "$OVERALL_RESULT" --failed-items "$FAILED_ITEMS" --manual-items "$MANUAL_ITEMS" --sandbox-config-state "$SANDBOX_CONFIG_STATE" --blocking-defect-state "$BLOCKING_DEFECT_STATE" --execution-mode "$EXECUTION_MODE" --next-flow-reminder "$NEXT_FLOW_REMINDER"
@@ -14,43 +14,164 @@
14
14
  * 4. 返回资源内容
15
15
  *
16
16
  * 依赖安装:
17
- * composer require alipay/alipay-sdk
17
+ * composer require alipaysdk/openapi
18
18
  */
19
19
 
20
- // ==================== 配置信息(实际使用时请从配置中心读取)====================
21
- define('ALIPAY_CONFIG', [
22
- 'appId' => '<APP_ID>',
23
- 'privateKey' => '<APP_PRIVATE_KEY>', // 请填写您的应用私钥(PKCS#1 格式)
24
- 'alipayPublicKey' => '<ALIPAY_PUBLIC_KEY>', // 请填写您的支付宝公钥
25
- // 默认用于本 Skill 的快速沙箱联调;生产部署时设置 ALIPAY_GATEWAY=https://openapi.alipay.com/gateway.do
26
- 'gateway' => getenv('ALIPAY_GATEWAY') ?: 'https://openapi-sandbox.dl.alipaydev.com/gateway.do',
27
- 'sellerId' => '<SELLER_ID_2088>', // 商户 ID(2088 格式)
28
- 'serviceId' => 'api_mock_service_id', // 仅用于沙箱联调;上线前替换为服务市场真实 serviceId
29
- 'merchantPrivateKey' => '<APP_PRIVATE_KEY>' // 请填写您的应用私钥(用于商家签名)
30
- ]);
31
-
32
20
  define('RESOURCE_CONFIG', [
33
21
  'path' => '/demo/a2m/resource',
34
22
  'goodsName' => 'AI 生成内容服务'
35
23
  ]);
36
24
 
25
+ // ==================== 配置信息(实际使用时请从配置中心读取)====================
26
+
27
+ /**
28
+ * 返回第一个非空配置值。
29
+ */
30
+ function firstNonBlank(...$values) {
31
+ foreach ($values as $value) {
32
+ if ($value !== false && $value !== null && $value !== '') {
33
+ return (string)$value;
34
+ }
35
+ }
36
+ return '';
37
+ }
38
+
39
+ /**
40
+ * 读取 Skill 快速沙箱配置。示例支持放在项目根目录或 src/ 等子目录下运行。
41
+ */
42
+ function loadSandboxConfig() {
43
+ $candidatePaths = [
44
+ getcwd() . '/.alipay-sandbox.json',
45
+ __DIR__ . '/.alipay-sandbox.json',
46
+ dirname(__DIR__) . '/.alipay-sandbox.json',
47
+ ];
48
+
49
+ $visited = [];
50
+ foreach ($candidatePaths as $path) {
51
+ if (isset($visited[$path])) {
52
+ continue;
53
+ }
54
+ $visited[$path] = true;
55
+
56
+ if (!is_file($path)) {
57
+ continue;
58
+ }
59
+
60
+ $config = json_decode((string)file_get_contents($path), true);
61
+ if (is_array($config)) {
62
+ return $config;
63
+ }
64
+ }
65
+
66
+ return [];
67
+ }
68
+
69
+ /**
70
+ * 加载支付宝配置。
71
+ *
72
+ * Unix/macOS/Linux 快速沙箱生成的真实商家 PID 位于 .alipay-sandbox.json 的 appIds[0].pid。
73
+ * 生产环境请通过环境变量或配置中心显式设置同一生产应用的 appId、应用私钥、支付宝公钥、
74
+ * 商户 PID 和服务市场真实 serviceId。
75
+ */
76
+ function loadAlipayConfig() {
77
+ $sandbox = loadSandboxConfig();
78
+ $sandboxApp = [];
79
+ if (isset($sandbox['appIds']) && is_array($sandbox['appIds']) && isset($sandbox['appIds'][0]) && is_array($sandbox['appIds'][0])) {
80
+ $sandboxApp = $sandbox['appIds'][0];
81
+ }
82
+
83
+ $privateKey = firstNonBlank(
84
+ getenv('ALIPAY_AIPAY_PRIVATE_KEY'),
85
+ $sandboxApp['appPrivatePkcsKey'] ?? null,
86
+ $sandbox['appPrivatePkcsKey'] ?? null,
87
+ '<APP_PRIVATE_KEY>'
88
+ );
89
+
90
+ return [
91
+ 'appId' => firstNonBlank(
92
+ getenv('ALIPAY_AIPAY_APP_ID'),
93
+ $sandboxApp['appId'] ?? null,
94
+ $sandbox['appId'] ?? null,
95
+ '<APP_ID>'
96
+ ),
97
+ 'privateKey' => $privateKey, // PHP 使用 PKCS#1 格式应用私钥
98
+ 'alipayPublicKey' => firstNonBlank(
99
+ getenv('ALIPAY_AIPAY_PUBLIC_KEY'),
100
+ $sandboxApp['alipayPublicKey'] ?? null,
101
+ $sandbox['alipayPublicKey'] ?? null,
102
+ '<ALIPAY_PUBLIC_KEY>'
103
+ ),
104
+ // 默认用于本 Skill 的快速沙箱联调;生产部署时设置 ALIPAY_AIPAY_GATEWAY=https://openapi.alipay.com/gateway.do
105
+ 'gateway' => firstNonBlank(
106
+ getenv('ALIPAY_AIPAY_GATEWAY'),
107
+ getenv('ALIPAY_GATEWAY'),
108
+ $sandbox['gateway'] ?? null,
109
+ 'https://openapi-sandbox.dl.alipaydev.com/gateway.do'
110
+ ),
111
+ 'sellerId' => firstNonBlank(
112
+ getenv('ALIPAY_AIPAY_SELLER_ID'),
113
+ $sandboxApp['pid'] ?? null,
114
+ $sandboxApp['uid'] ?? null,
115
+ $sandbox['sellerId'] ?? null,
116
+ '<SELLER_ID_2088>'
117
+ ), // 商户 PID(2088 格式)
118
+ 'sellerName' => firstNonBlank(getenv('ALIPAY_AIPAY_SELLER_NAME'), '测试商户'),
119
+ 'serviceId' => firstNonBlank(getenv('ALIPAY_AIPAY_SERVICE_ID'), 'api_mock_service_id'), // 仅用于沙箱联调;上线前替换为服务市场真实 serviceId
120
+ 'merchantPrivateKey' => $privateKey // 用于商家账单签名
121
+ ];
122
+ }
123
+
124
+ define('ALIPAY_CONFIG', loadAlipayConfig());
125
+
37
126
  /**
38
127
  * 初始化 SDK
39
128
  */
40
129
  function initAlipayClient() {
41
- require_once 'aop/AopClient.php';
42
- require_once 'aop/AlipayConfig.php';
43
-
44
- $config = new AlipayConfig();
45
- $config->setServerUrl(ALIPAY_CONFIG['gateway']);
46
- $config->setAppId(ALIPAY_CONFIG['appId']);
47
- $config->setPrivateKey(ALIPAY_CONFIG['privateKey']);
48
- $config->setFormat('json');
49
- $config->setAlipayPublicKey(ALIPAY_CONFIG['alipayPublicKey']);
50
- $config->setCharset('UTF-8');
51
- $config->setSignType('RSA2');
52
-
53
- return new AopClient($config);
130
+ loadAopSdk();
131
+
132
+ if (!class_exists('\AopClient')) {
133
+ throw new RuntimeException('未找到支付宝 PHP SDK,请先执行:composer require alipaysdk/openapi');
134
+ }
135
+
136
+ $client = new \AopClient();
137
+ $client->gatewayUrl = ALIPAY_CONFIG['gateway'];
138
+ $client->appId = ALIPAY_CONFIG['appId'];
139
+ $client->rsaPrivateKey = ALIPAY_CONFIG['privateKey'];
140
+ $client->alipayrsaPublicKey = ALIPAY_CONFIG['alipayPublicKey'];
141
+ $client->format = 'json';
142
+ $client->postCharset = 'UTF-8';
143
+ $client->signType = 'RSA2';
144
+
145
+ return $client;
146
+ }
147
+
148
+ /**
149
+ * 加载官方 alipaysdk/openapi v2 AOP 客户端。
150
+ */
151
+ function loadAopSdk() {
152
+ $candidateRoots = [
153
+ getcwd(),
154
+ __DIR__,
155
+ dirname(__DIR__),
156
+ ];
157
+
158
+ $visited = [];
159
+ foreach ($candidateRoots as $root) {
160
+ if (isset($visited[$root])) {
161
+ continue;
162
+ }
163
+ $visited[$root] = true;
164
+
165
+ $autoload = $root . '/vendor/autoload.php';
166
+ if (is_file($autoload)) {
167
+ require_once $autoload;
168
+ }
169
+
170
+ $aopClient = $root . '/vendor/alipaysdk/openapi/v2/aop/AopClient.php';
171
+ if (is_file($aopClient)) {
172
+ require_once $aopClient;
173
+ }
174
+ }
54
175
  }
55
176
 
56
177
  /**
@@ -190,7 +311,7 @@ function createPaymentRequiredResponse() {
190
311
  'seller_unique_id' => ALIPAY_CONFIG['sellerId']
191
312
  ],
192
313
  'method' => [
193
- 'seller_name' => '测试商户',
314
+ 'seller_name' => ALIPAY_CONFIG['sellerName'],
194
315
  'seller_id' => ALIPAY_CONFIG['sellerId'],
195
316
  'seller_app_id' => ALIPAY_CONFIG['appId'],
196
317
  'goods_name' => $goodsName,
@@ -281,7 +402,7 @@ function verifyPaymentAndDeliverResource($paymentProof) {
281
402
  $request->setBizContent(json_encode($model, JSON_UNESCAPED_UNICODE));
282
403
  $responseResult = $alipayClient->execute($request);
283
404
  $responseApiName = str_replace('.', '_', $request->getApiMethodName()) . '_response';
284
- $verifyResponse = $responseResult->$responseApiName;
405
+ $verifyResponse = $responseResult->$responseApiName ?? (object)[];
285
406
 
286
407
  // 3. 验证失败,返回错误
287
408
  if (empty($verifyResponse->code) || $verifyResponse->code !== '10000') {
@@ -303,7 +424,8 @@ function verifyPaymentAndDeliverResource($paymentProof) {
303
424
  error_log("支付凭证验证成功:tradeNo={$verifyTradeNo}, outTradeNo={$verifyOutTradeNo}");
304
425
 
305
426
  // 5. 校验凭证有效性(active=true 表示凭证有效)
306
- if ($active !== true) {
427
+ $isActive = $active === true || $active === 'true' || $active === '1' || $active === 1;
428
+ if (!$isActive) {
307
429
  error_log("支付凭证无效或已过期:outTradeNo={$verifyOutTradeNo}");
308
430
  jsonResponse([
309
431
  'code' => 'INVALID_PAYMENT_PROOF',
@@ -354,6 +476,7 @@ function verifyPaymentAndDeliverResource($paymentProof) {
354
476
  // 'serviceResult' => $serviceResult
355
477
  // ]);
356
478
 
479
+ // 优先使用 alipay.aipay.agent.payment.verify 返回的真实 trade_no;沙箱字段为空时回退到 Payment-Proof 中的 trade_no。
357
480
  $fulfillmentTradeNo = $verifyTradeNo ?: $tradeNo;
358
481
  error_log("资源已生成,准备发送履约确认:outTradeNo={$verifyOutTradeNo}, tradeNo={$fulfillmentTradeNo}");
359
482
 
@@ -436,7 +559,7 @@ function sendFulfillmentConfirm($tradeNo) {
436
559
 
437
560
  $responseResult = $alipayClient->execute($request);
438
561
  $responseApiName = str_replace('.', '_', $request->getApiMethodName()) . '_response';
439
- $response = $responseResult->$responseApiName;
562
+ $response = $responseResult->$responseApiName ?? (object)[];
440
563
 
441
564
  if (!empty($response->code) && $response->code === '10000') {
442
565
  error_log("履约确认成功:tradeNo={$tradeNo}");
@@ -454,6 +577,183 @@ function sendFulfillmentConfirm($tradeNo) {
454
577
  }
455
578
  }
456
579
 
580
+ // 官方 alipaysdk/openapi 当前未内置以下两个 A2M Request 类,示例在本地补齐 AOP 客户端会调用的方法。
581
+ if (!class_exists('AlipayAipayAgentPaymentVerifyRequest')) {
582
+ final class AlipayAipayAgentPaymentVerifyRequest
583
+ {
584
+ private $bizContent = '';
585
+ private $apiParas = [];
586
+ private $apiVersion = '1.0';
587
+ private $needEncrypt = false;
588
+ private $terminalType = null;
589
+ private $terminalInfo = null;
590
+ private $prodCode = null;
591
+ private $notifyUrl = null;
592
+ private $returnUrl = null;
593
+
594
+ public function setBizContent($bizContent) {
595
+ $this->bizContent = $bizContent;
596
+ $this->apiParas['biz_content'] = $bizContent;
597
+ }
598
+
599
+ public function getBizContent() {
600
+ return $this->bizContent;
601
+ }
602
+
603
+ public function getApiMethodName() {
604
+ return 'alipay.aipay.agent.payment.verify';
605
+ }
606
+
607
+ public function getApiParas() {
608
+ return $this->apiParas;
609
+ }
610
+
611
+ public function getApiVersion() {
612
+ return $this->apiVersion;
613
+ }
614
+
615
+ public function setApiVersion($apiVersion) {
616
+ $this->apiVersion = $apiVersion;
617
+ }
618
+
619
+ public function getNeedEncrypt() {
620
+ return $this->needEncrypt;
621
+ }
622
+
623
+ public function setNeedEncrypt($needEncrypt) {
624
+ $this->needEncrypt = $needEncrypt;
625
+ }
626
+
627
+ public function getTerminalType() {
628
+ return $this->terminalType;
629
+ }
630
+
631
+ public function setTerminalType($terminalType) {
632
+ $this->terminalType = $terminalType;
633
+ }
634
+
635
+ public function getTerminalInfo() {
636
+ return $this->terminalInfo;
637
+ }
638
+
639
+ public function setTerminalInfo($terminalInfo) {
640
+ $this->terminalInfo = $terminalInfo;
641
+ }
642
+
643
+ public function getProdCode() {
644
+ return $this->prodCode;
645
+ }
646
+
647
+ public function setProdCode($prodCode) {
648
+ $this->prodCode = $prodCode;
649
+ }
650
+
651
+ public function getNotifyUrl() {
652
+ return $this->notifyUrl;
653
+ }
654
+
655
+ public function setNotifyUrl($notifyUrl) {
656
+ $this->notifyUrl = $notifyUrl;
657
+ }
658
+
659
+ public function getReturnUrl() {
660
+ return $this->returnUrl;
661
+ }
662
+
663
+ public function setReturnUrl($returnUrl) {
664
+ $this->returnUrl = $returnUrl;
665
+ }
666
+ }
667
+ }
668
+
669
+ if (!class_exists('AlipayAipayAgentFulfillmentConfirmRequest')) {
670
+ final class AlipayAipayAgentFulfillmentConfirmRequest
671
+ {
672
+ private $bizContent = '';
673
+ private $apiParas = [];
674
+ private $apiVersion = '1.0';
675
+ private $needEncrypt = false;
676
+ private $terminalType = null;
677
+ private $terminalInfo = null;
678
+ private $prodCode = null;
679
+ private $notifyUrl = null;
680
+ private $returnUrl = null;
681
+
682
+ public function setBizContent($bizContent) {
683
+ $this->bizContent = $bizContent;
684
+ $this->apiParas['biz_content'] = $bizContent;
685
+ }
686
+
687
+ public function getBizContent() {
688
+ return $this->bizContent;
689
+ }
690
+
691
+ public function getApiMethodName() {
692
+ return 'alipay.aipay.agent.fulfillment.confirm';
693
+ }
694
+
695
+ public function getApiParas() {
696
+ return $this->apiParas;
697
+ }
698
+
699
+ public function getApiVersion() {
700
+ return $this->apiVersion;
701
+ }
702
+
703
+ public function setApiVersion($apiVersion) {
704
+ $this->apiVersion = $apiVersion;
705
+ }
706
+
707
+ public function getNeedEncrypt() {
708
+ return $this->needEncrypt;
709
+ }
710
+
711
+ public function setNeedEncrypt($needEncrypt) {
712
+ $this->needEncrypt = $needEncrypt;
713
+ }
714
+
715
+ public function getTerminalType() {
716
+ return $this->terminalType;
717
+ }
718
+
719
+ public function setTerminalType($terminalType) {
720
+ $this->terminalType = $terminalType;
721
+ }
722
+
723
+ public function getTerminalInfo() {
724
+ return $this->terminalInfo;
725
+ }
726
+
727
+ public function setTerminalInfo($terminalInfo) {
728
+ $this->terminalInfo = $terminalInfo;
729
+ }
730
+
731
+ public function getProdCode() {
732
+ return $this->prodCode;
733
+ }
734
+
735
+ public function setProdCode($prodCode) {
736
+ $this->prodCode = $prodCode;
737
+ }
738
+
739
+ public function getNotifyUrl() {
740
+ return $this->notifyUrl;
741
+ }
742
+
743
+ public function setNotifyUrl($notifyUrl) {
744
+ $this->notifyUrl = $notifyUrl;
745
+ }
746
+
747
+ public function getReturnUrl() {
748
+ return $this->returnUrl;
749
+ }
750
+
751
+ public function setReturnUrl($returnUrl) {
752
+ $this->returnUrl = $returnUrl;
753
+ }
754
+ }
755
+ }
756
+
457
757
  // ==================== 路由处理 ====================
458
758
 
459
759
  // 简单路由
@@ -218,7 +218,7 @@ Payment-Proof 参考结构:
218
218
 
219
219
  测试结束后使用 `a2m.test.result`:只有脚本退出码和本模块要求的组合证据都成立时选择 `PASSED`;其他情况选择 `FAILED` 并传入实际证据与确定恢复动作。不得把用户付款体验、沙箱配置落盘或任意 2xx 响应替代通过证据。
220
220
 
221
- 输出测试结论后,使用 `a2m.payment.experience/FULL` 展示付款链接和已经通过快速沙箱字段校验的实际买家账号。缺少任一账号字段时,当前沙箱配置必须已经在前置校验进入待配置状态,不得执行本联调或输出付款体验,也不得编造缺失凭据。随后使用 `sandbox.android.client` 统一输出 Android-only 官方下载与失败兜底。两条消息都不增加阻塞点,也不反向改变服务端联调结论。
221
+ 输出测试结论后,使用 `a2m.payment.experience/FULL` 展示付款链接和已经通过快速沙箱字段校验的实际买家账号。缺少任一账号字段时,当前沙箱配置必须已经在前置校验进入待配置状态,不得执行本联调或输出付款体验,也不得编造缺失凭据。随后执行 `node ../../../normal/scripts/integration_message_runner.mjs sandbox-android-client` 统一输出 Android-only 官方下载与失败兜底。两条消息都不增加阻塞点,也不反向改变服务端联调结论。
222
222
 
223
223
  最后必须提醒用户:
224
224
 
@@ -174,7 +174,7 @@ renderer 固定输出唯一摘要表、密钥字段已落盘提示和“配置
174
174
 
175
175
  `curl` 调用下单接口并取得 HTML 支付表单只属于接口冒烟检查,不能替代用户付款指引,也不能据此标记步骤 6 完成。必须提供用户可在浏览器中打开的支付页面地址;页面打开沙箱收银台后,以下两种体验方式同时告知用户,由用户自行二选一。
176
176
 
177
- 紧接当前系统对应的网站支付付款指引渲染 `sandbox.android.client`,统一输出 Android-only、官方直链、直链/页面二维码失败后的开放平台沙箱工具页,以及禁止 iOS、应用市场、第三方站点和自编 URL 的口径。
177
+ 紧接当前系统对应的网站支付付款指引执行 `node ../../../normal/scripts/integration_message_runner.mjs sandbox-android-client`,统一输出 Android-only、官方直链、直链/页面二维码失败后的开放平台沙箱工具页,以及禁止 iOS、应用市场、第三方站点和自编 URL 的口径。
178
178
 
179
179
  `resultPageInstruction` 必须按实际回跳分支二选一,禁止同时输出两项:
180
180
 
@@ -10,8 +10,7 @@
10
10
  当用户提及以下不支持的产品时,使用 `json/customer-messages.json` 的 `product.unsupported`,传入用户实际产品名。必须执行:
11
11
 
12
12
  ```bash
13
- MESSAGE_INPUT_JSON=$(jq -cn --arg productName "$PRODUCT_NAME" '{productName:$productName}')
14
- printf '%s' "$MESSAGE_INPUT_JSON" | node scripts/render_customer_message.mjs product.unsupported --variant DEFAULT
13
+ node scripts/integration_message_runner.mjs product-unsupported --product-name "$PRODUCT_NAME"
15
14
  ```
16
15
 
17
16
  支持范围、开放平台文档、传统支付集成 Skill 和商家平台链接只在消息目录维护;本文件不复制标准正文。
@@ -856,6 +856,12 @@ function runProductClarify() {
856
856
  return renderMessage('product.clarify', 'DEFAULT', {});
857
857
  }
858
858
 
859
+ function runProductUnsupported(args) {
860
+ return renderMessage('product.unsupported', 'DEFAULT', {
861
+ productName: required(args, 'product-name')
862
+ });
863
+ }
864
+
859
865
  function runWindowsSandboxSetup() {
860
866
  return renderMessage('sandbox.windows.manual_setup', 'DEFAULT', {});
861
867
  }
@@ -868,6 +874,10 @@ function runA2mTestStart() {
868
874
  return renderMessage('a2m.test.start', 'DEFAULT', {});
869
875
  }
870
876
 
877
+ function runSandboxAndroidClient() {
878
+ return renderMessage('sandbox.android.client', 'DEFAULT', {});
879
+ }
880
+
871
881
  function runServiceNotReady(args) {
872
882
  return renderMessage('integration.service.not_ready', 'DEFAULT', {
873
883
  recoveryAction: required(args, 'recovery-action')
@@ -984,6 +994,10 @@ async function main() {
984
994
  console.log(runProductClarify());
985
995
  return;
986
996
  }
997
+ if (command === 'product-unsupported') {
998
+ console.log(runProductUnsupported(args));
999
+ return;
1000
+ }
987
1001
  if (command === 'windows-sandbox-setup') {
988
1002
  console.log(runWindowsSandboxSetup());
989
1003
  return;
@@ -996,6 +1010,10 @@ async function main() {
996
1010
  console.log(runA2mTestStart());
997
1011
  return;
998
1012
  }
1013
+ if (command === 'sandbox-android-client') {
1014
+ console.log(runSandboxAndroidClient());
1015
+ return;
1016
+ }
999
1017
  if (command === 'service-not-ready') {
1000
1018
  console.log(runServiceNotReady(args));
1001
1019
  return;
@@ -1012,7 +1030,7 @@ async function main() {
1012
1030
  console.log(await runChecklistResult(args));
1013
1031
  return;
1014
1032
  }
1015
- throw new Error('用法: integration_message_runner.mjs <resolve-project|locate-projects|start-confirm|checklist-scope-receipt|a2m-execution-guard|a2m-execution-result|context-required|product-clarify|windows-sandbox-setup|local-acceptance-notice|a2m-test-start|service-not-ready|safety-closeout|windows-sandbox-summary|checklist-result> ...');
1033
+ throw new Error('用法: integration_message_runner.mjs <resolve-project|locate-projects|start-confirm|checklist-scope-receipt|a2m-execution-guard|a2m-execution-result|context-required|product-clarify|product-unsupported|windows-sandbox-setup|local-acceptance-notice|a2m-test-start|sandbox-android-client|service-not-ready|safety-closeout|windows-sandbox-summary|checklist-result> ...');
1016
1034
  }
1017
1035
 
1018
1036
  if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {