@alipay/alipay-aipay 1.5.0 → 1.5.1-beta
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 +1 -1
- package/skill/VERSION +1 -1
- package/skill/references/integration/flow.md +7 -14
- package/skill/references/integration/modules/checklist.md +2 -7
- package/skill/references/integration/modules/code-examples/php/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.php +330 -30
- package/skill/references/integration/modules/sandbox/a2m-sandbox-test.md +1 -1
- package/skill/references/integration/modules/sandbox/sandbox-setup-guide.md +1 -1
- package/skill/references/normal/rejection-guide.md +1 -2
- package/skill/references/normal/scripts/integration_message_runner.mjs +19 -1
- package/skill/references/normal/scripts/onboarding_message_runner.mjs +204 -12
- package/skill/references/normal/scripts/render_customer_message.mjs +68 -8
- package/skill/references/normal/scripts/telemetry.mjs +5 -3
- package/skill/references/onboarding/flow.md +28 -91
- package/skill/references/onboarding/modules/app-release.md +4 -4
- package/skill/references/onboarding/modules/service-registration.md +2 -2
- package/skill/references/runtime-entry.md +4 -4
package/package.json
CHANGED
package/skill/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.5.
|
|
1
|
+
1.5.1-beta
|
|
@@ -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
|
-
|
|
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
|
|
548
|
+
先在内部汇总 checklist 证据。发现本轮范围内可直接修复的代码或配置缺口时,先修复并重新执行受影响检查;在仍可继续自动修复期间不得渲染中间 checklist 结果。只有已通过、只剩人工待验证,或本轮无法继续修复时,才生成最终对客结论参数并渲染一次。
|
|
549
549
|
|
|
550
|
-
对客结论统一使用 `integration.checklist.result`,并严格按当前产品选择 `AIPAY`、`WEBPAY` 或 `APPPAY` 变体;整体结论、失败/部分通过项和人工待验证项必须来自最终一轮 checklist 实际证据。必须从本 flow
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
$
|
|
48
|
-
$
|
|
49
|
-
$
|
|
50
|
-
$
|
|
51
|
-
$
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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`
|
|
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
|
-
|
|
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
|
-
|
|
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)) {
|