@alipay/alipay-aipay 1.2.2 → 1.5.0

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 (104) hide show
  1. package/README.md +5 -0
  2. package/dist/commands/install.d.ts.map +1 -1
  3. package/dist/commands/install.js +13 -12
  4. package/dist/commands/install.js.map +1 -1
  5. package/dist/commands/uninstall.d.ts.map +1 -1
  6. package/dist/commands/uninstall.js +17 -9
  7. package/dist/commands/uninstall.js.map +1 -1
  8. package/dist/commands/update.d.ts.map +1 -1
  9. package/dist/commands/update.js +5 -5
  10. package/dist/commands/update.js.map +1 -1
  11. package/dist/index.js +4 -4
  12. package/dist/index.js.map +1 -1
  13. package/dist/utils/config.d.ts +5 -3
  14. package/dist/utils/config.d.ts.map +1 -1
  15. package/dist/utils/config.js +34 -12
  16. package/dist/utils/config.js.map +1 -1
  17. package/dist/utils/file.d.ts +8 -0
  18. package/dist/utils/file.d.ts.map +1 -1
  19. package/dist/utils/file.js +161 -9
  20. package/dist/utils/file.js.map +1 -1
  21. package/dist/utils/jq.d.ts.map +1 -1
  22. package/dist/utils/jq.js +8 -0
  23. package/dist/utils/jq.js.map +1 -1
  24. package/package.json +9 -5
  25. package/skill/SKILL.md +15 -169
  26. package/skill/VERSION +1 -1
  27. package/skill/references/integration/flow.md +388 -291
  28. package/skill/references/integration/modules/alipay-sdk-reminder.md +5 -3
  29. package/skill/references/integration/modules/checklist.md +54 -32
  30. package/skill/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
  31. package/skill/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 +8 -4
  32. package/skill/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 +6 -2
  33. package/skill/references/integration/modules/code-examples/csharp/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.cs +3 -1
  34. package/skill/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
  35. package/skill/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 +4 -1
  36. package/skill/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 +5 -2
  37. package/skill/references/integration/modules/code-examples/java/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemoController.java +3 -1
  38. package/skill/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
  39. package/skill/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 +16 -10
  40. package/skill/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 +10 -4
  41. package/skill/references/integration/modules/code-examples/nodejs/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.js +2 -1
  42. package/skill/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
  43. package/skill/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 +4 -1
  44. package/skill/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 +5 -2
  45. package/skill/references/integration/modules/code-examples/php/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.php +2 -1
  46. package/skill/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
  47. package/skill/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 +4 -1
  48. package/skill/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 +5 -1
  49. package/skill/references/integration/modules/code-examples/python/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.py +4 -2
  50. package/skill/references/integration/modules/interface-contract/aipay-interface-contract.md +52 -0
  51. package/skill/references/integration/modules/interface-contract/apppay-interface-contract.md +45 -0
  52. package/skill/references/integration/modules/interface-contract/common-trade-contract.md +42 -0
  53. package/skill/references/integration/modules/interface-contract/notify-url-contract.md +68 -0
  54. package/skill/references/integration/modules/interface-contract/webpay-interface-contract.md +44 -0
  55. package/skill/references/integration/modules/interface-guide.md +38 -71
  56. package/skill/references/integration/modules/product-decision.md +18 -35
  57. package/skill/references/integration/modules/sandbox/a2m-sandbox-test.md +62 -94
  58. package/skill/references/integration/modules/sandbox/alipay-sandbox-tool.md +43 -40
  59. package/skill/references/integration/modules/sandbox/sandbox-setup-guide.md +93 -114
  60. package/skill/references/integration/modules/scripts/local_402_sandbox_pay.py +358 -148
  61. package/skill/references/integration/modules/scripts/sandbox_config.sh +299 -0
  62. package/skill/references/integration/modules/troubleshooting-flow.md +28 -0
  63. package/skill/references/normal/alipay-cli-env.md +15 -5
  64. package/skill/references/normal/full-process-routing.md +82 -0
  65. package/skill/references/normal/json/customer-messages.json +942 -0
  66. package/skill/references/normal/json/policy-rules.json +179 -0
  67. package/skill/references/normal/rejection-guide.md +12 -7
  68. package/skill/references/normal/scripts/alipay_cli_env.mjs +33 -0
  69. package/skill/references/normal/scripts/alipay_cli_env.sh +55 -0
  70. package/skill/references/normal/scripts/common.sh +151 -0
  71. package/skill/references/normal/scripts/detect_dev_tool.sh +5 -1
  72. package/skill/references/normal/scripts/flow_loader.mjs +241 -0
  73. package/skill/references/normal/scripts/integration_context_runner.mjs +166 -0
  74. package/skill/references/normal/scripts/integration_message_runner.mjs +1023 -0
  75. package/skill/references/normal/scripts/interaction_reply.mjs +133 -0
  76. package/skill/references/normal/scripts/onboarding_discovery_runner.mjs +224 -0
  77. package/skill/references/normal/scripts/onboarding_message_runner.mjs +451 -0
  78. package/skill/references/normal/scripts/onboarding_recovery_runner.mjs +96 -0
  79. package/skill/references/normal/scripts/open_official_url.sh +82 -0
  80. package/skill/references/normal/scripts/platform_compat.mjs +39 -0
  81. package/skill/references/normal/scripts/project_route_inspector.sh +132 -0
  82. package/skill/references/normal/scripts/python_script_runner.mjs +56 -0
  83. package/skill/references/normal/scripts/render_customer_message.mjs +1288 -0
  84. package/skill/references/normal/scripts/self_update.mjs +160 -0
  85. package/skill/references/normal/scripts/telemetry.mjs +1327 -0
  86. package/skill/references/normal/self-update.md +18 -51
  87. package/skill/references/normal/telemetry.md +26 -0
  88. package/skill/references/onboarding/flow.md +421 -309
  89. package/skill/references/onboarding/modules/app-release.md +44 -438
  90. package/skill/references/onboarding/modules/authorization.md +25 -281
  91. package/skill/references/onboarding/modules/error-handling.md +31 -237
  92. package/skill/references/onboarding/modules/product-sign.md +25 -294
  93. package/skill/references/onboarding/modules/scripts/app.sh +445 -169
  94. package/skill/references/onboarding/modules/scripts/ar_sign_apply.sh +47 -28
  95. package/skill/references/onboarding/modules/scripts/auth.sh +484 -167
  96. package/skill/references/onboarding/modules/scripts/download_key_tool.sh +157 -0
  97. package/skill/references/onboarding/modules/scripts/error_handler.sh +101 -130
  98. package/skill/references/onboarding/modules/scripts/network_retry.sh +96 -0
  99. package/skill/references/onboarding/modules/scripts/query_sign_status.sh +20 -71
  100. package/skill/references/onboarding/modules/scripts/service.sh +154 -89
  101. package/skill/references/onboarding/modules/scripts/sign_status_parser.sh +58 -0
  102. package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +34 -20
  103. package/skill/references/onboarding/modules/service-registration.md +20 -317
  104. package/skill/references/runtime-entry.md +63 -0
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import process from 'node:process';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const internalPrefix = 'ALIPAY_AIPAY_INTERNAL:';
9
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
10
+ const catalogPath = path.resolve(scriptDir, '../json/customer-messages.json');
11
+ const questionPattern = /[??]|(?:吗|么|嘛|呢)$|是否|能否|可否|要不要|是不是|好了吗|完成了吗|为什么|为何|怎么|怎样|如何|什么意思|什么(?:方式|时候)?|哪个|哪种|何时|什么时候|多少|可不可以|能不能|行不行/u;
12
+ const englishQuestionPattern = /\b(?:can|could|would|should|why|how|what|when|whether)\b/iu;
13
+ const uncertaintyPattern = /可能|大概|也许|或许|不确定|说不好|看情况|再看看|考虑一下|好像|似乎|应该|有风险|有问题|担心|顾虑/u;
14
+ const englishUncertaintyPattern = /\b(?:maybe|perhaps|probably|possibly|unsure)\b/iu;
15
+ const rejectionPattern = /(?:不(?:是|需要|要|用|必|能|可|再)?|别|无(?:需|须|法))(?:再)?(?:继续|推进|执行|处理|开始|提交|确认|同意|允许|批准|创建|操作|往下|下一步|通过|完成)|不可以|不行|不同意|不确认|不允许|不批准|不通过|未确认|未完成|未通过|没(?:有)?完成|还没(?:有)?(?:好|完成|确认|准备)|拒绝|取消|停止|暂停|稍后|晚点|待会|等一下|等等|算了|否定/u;
16
+ const englishRejectionPattern = /\b(?:no|nope|not|don't|do\s+not|stop|cancel|wait|change)\b/iu;
17
+ const conditionalOrChangePattern = /如果|假如|要是|除非|只要|之后再|以后再|但是|不过|只是|前提|再说|(?:修改|调整|改一下|改成|换成|重选|重新选择)|先.{0,20}(?:再|然后)/u;
18
+ const affirmativeIntentPattern = /继续|推进|执行|开始|确认(?:无误)?|确定|同意|允许|批准|可以|没(?:有)?问题|无异议|通过|(?:已经|已)(?:完成|好了)|完成(?:了)?|搞定(?:了)?|弄好了|处理好了|就这么办|往下(?:走|做)|下一步|按(?:此|这个|该|当前)(?:方案)?(?:做|办|执行|推进|处理)|照(?:此|这个|该|当前)(?:方案)?(?:做|办|执行|推进|处理)/u;
19
+ const englishAffirmativeIntentPattern = /\b(?:ok(?:ay)?|yes|confirm(?:ed)?|continue|proceed|approve(?:d)?|go\s+ahead)\b/iu;
20
+ const simpleAffirmativePattern = /^(?:行|行的|行吧)$/u;
21
+ const englishAffirmativeReplies = new Set(['ok', 'okay', 'yes', 'y']);
22
+ const safePrefixes = ['那么就', '那就', '麻烦', '烦请', '嗯嗯', '好的', '我们', '请', '那', '就', '嗯', '我'];
23
+ const safeSuffixes = ['谢谢', '就行', '即可', '吧', '呀', '啊', '哈', '了'];
24
+
25
+ function affirmativeReplies() {
26
+ const catalog = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
27
+ const replies = catalog.interactionReplySets?.affirmative;
28
+ if (!Array.isArray(replies) || replies.length === 0 || replies.some((reply) => typeof reply !== 'string' || !reply)) {
29
+ throw new Error('customer-messages affirmative 回复集合无效');
30
+ }
31
+ return new Set(replies.map(normalizeAffirmativeReply));
32
+ }
33
+
34
+ export function normalizeAffirmativeReply(value) {
35
+ if (typeof value !== 'string') return '';
36
+ return value
37
+ .normalize('NFKC')
38
+ .trim()
39
+ .replace(/[。!!]+$/gu, '')
40
+ .trim()
41
+ .toLowerCase();
42
+ }
43
+
44
+ export function isAffirmativeReply(value) {
45
+ const normalized = normalizeAffirmativeReply(value);
46
+ if (normalized === '') return false;
47
+ const replies = affirmativeReplies();
48
+ let compact = normalized.replace(/[\s,,.。!!、;;::]+/gu, '');
49
+ const intentText = compact
50
+ .replace(/(?:不需要|无需|不用)(?:再)?(?:修改|调整|改动|改|确认)/gu, '无异议')
51
+ .replace(/没(?:有)?问题/gu, '无异议');
52
+ if (
53
+ questionPattern.test(intentText)
54
+ || englishQuestionPattern.test(normalized)
55
+ || uncertaintyPattern.test(intentText)
56
+ || englishUncertaintyPattern.test(normalized)
57
+ || rejectionPattern.test(intentText)
58
+ || englishRejectionPattern.test(normalized)
59
+ || conditionalOrChangePattern.test(intentText)
60
+ ) return false;
61
+ if (
62
+ replies.has(normalized)
63
+ || replies.has(compact)
64
+ || simpleAffirmativePattern.test(compact)
65
+ || affirmativeIntentPattern.test(compact)
66
+ || englishAffirmativeIntentPattern.test(normalized)
67
+ ) return true;
68
+
69
+ let changed = true;
70
+ while (changed && compact) {
71
+ changed = false;
72
+ for (const prefix of safePrefixes) {
73
+ if (compact.startsWith(prefix)) {
74
+ compact = compact.slice(prefix.length);
75
+ changed = true;
76
+ break;
77
+ }
78
+ }
79
+ for (const suffix of safeSuffixes) {
80
+ if (compact.endsWith(suffix)) {
81
+ compact = compact.slice(0, -suffix.length);
82
+ changed = true;
83
+ break;
84
+ }
85
+ }
86
+ }
87
+ if (compact === '') return false;
88
+ if (
89
+ replies.has(compact)
90
+ || simpleAffirmativePattern.test(compact)
91
+ || affirmativeIntentPattern.test(compact)
92
+ || englishAffirmativeIntentPattern.test(compact)
93
+ ) return true;
94
+
95
+ // Only collapse repeated Latin keystrokes after all negative/ambiguous guards.
96
+ const deduplicatedLatin = compact.replace(/([a-z])\1+/gu, '$1');
97
+ return englishAffirmativeReplies.has(deduplicatedLatin);
98
+ }
99
+
100
+ function parseArgs(argv) {
101
+ const command = argv[2];
102
+ const args = {};
103
+ for (let index = 3; index < argv.length; index += 1) {
104
+ const token = argv[index];
105
+ if (!token.startsWith('--')) throw new Error(`未知参数 ${token}`);
106
+ const value = argv[index + 1];
107
+ if (value === undefined || value.startsWith('--')) throw new Error(`参数 ${token} 缺少取值`);
108
+ const name = token.slice(2);
109
+ if (Object.prototype.hasOwnProperty.call(args, name)) throw new Error(`参数 ${token} 重复`);
110
+ args[name] = value;
111
+ index += 1;
112
+ }
113
+ return { command, args };
114
+ }
115
+
116
+ function main() {
117
+ const { command, args } = parseArgs(process.argv);
118
+ if (command !== 'confirm' || typeof args.reply !== 'string' || !args.reply) {
119
+ throw new Error('用法: interaction_reply.mjs confirm --reply <用户原始回复>');
120
+ }
121
+ const decision = isAffirmativeReply(args.reply) ? 'CONFIRMED' : 'REJECTED';
122
+ process.stderr.write(`${internalPrefix}REPLY_DECISION=${decision}\n`);
123
+ if (decision === 'REJECTED') process.exitCode = 2;
124
+ }
125
+
126
+ if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
127
+ try {
128
+ main();
129
+ } catch (error) {
130
+ process.stderr.write(`${internalPrefix}REPLY_VALIDATION_ERROR=${String(error?.message || error).replace(/[\r\n]/gu, ' ')}\n`);
131
+ process.exitCode = 1;
132
+ }
133
+ }
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from 'node:child_process';
4
+ import path from 'node:path';
5
+ import process from 'node:process';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ import { renderDiscoverySummary } from './onboarding_message_runner.mjs';
9
+ import { emitStageMetricAsync } from './telemetry.mjs';
10
+
11
+ const internalPrefix = 'ALIPAY_AIPAY_INTERNAL:';
12
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
13
+ const onboardingScriptsDir = path.resolve(scriptDir, '../../onboarding/modules/scripts');
14
+ const scriptPaths = Object.freeze({
15
+ signing: path.join(onboardingScriptsDir, 'query_sign_status.sh'),
16
+ service: path.join(onboardingScriptsDir, 'service.sh'),
17
+ application: path.join(onboardingScriptsDir, 'app.sh')
18
+ });
19
+ const productSalesCodes = Object.freeze({
20
+ aipay: 'I1080300001000160457',
21
+ webpay: 'I1080300001000041203',
22
+ apppay: 'I1080300001000041313'
23
+ });
24
+ const signStatuses = new Set(['NOT_SIGNED', 'SIGN_SUBMITTED', 'SIGNED_EFFECTIVE', 'OTHER_STATUS']);
25
+ const signFlows = Object.freeze({
26
+ aipay: new Set(['FLOW:AI_PAY_NOT_SIGNED', 'FLOW:AI_PAY_SIGNED']),
27
+ webpay: new Set(['FLOW:PC_WEB_NOT_SIGNED', 'FLOW:PC_WEB_SIGNED']),
28
+ apppay: new Set(['FLOW:APP_NOT_SIGNED', 'FLOW:APP_SIGNED'])
29
+ });
30
+ const serviceFlows = new Set(['FLOW:CREATE_NEW', 'FLOW:SELECT']);
31
+ const applicationFlows = new Set(['FLOW:CREATE_NEW', 'FLOW:SELECT', 'FLOW:PENDING_APPLICATIONS']);
32
+
33
+ function parseArgs(argv) {
34
+ const command = argv[2];
35
+ const args = {};
36
+ for (let index = 3; index < argv.length; index += 1) {
37
+ const token = argv[index];
38
+ if (!token.startsWith('--')) throw new Error(`未知参数 ${token}`);
39
+ const value = argv[index + 1];
40
+ if (value === undefined || value.startsWith('--')) throw new Error(`参数 ${token} 缺少取值`);
41
+ const name = token.slice(2);
42
+ if (Object.prototype.hasOwnProperty.call(args, name)) throw new Error(`参数 ${token} 重复`);
43
+ args[name] = value;
44
+ index += 1;
45
+ }
46
+ return { command, args };
47
+ }
48
+
49
+ function required(args, name) {
50
+ const value = args[name];
51
+ if (typeof value !== 'string' || !value) throw new Error(`缺少参数 --${name}`);
52
+ return value;
53
+ }
54
+
55
+ function internalLines(stderr) {
56
+ return String(stderr || '')
57
+ .split(/\r?\n/u)
58
+ .filter((line) => line.startsWith(internalPrefix))
59
+ .map((line) => line.slice(internalPrefix.length));
60
+ }
61
+
62
+ function uniqueValue(lines, prefix) {
63
+ const values = lines.filter((line) => line.startsWith(prefix)).map((line) => line.slice(prefix.length));
64
+ return values.length === 1 ? values[0] : '';
65
+ }
66
+
67
+ function uniqueFlow(lines, allowed) {
68
+ const values = lines.filter((line) => line.startsWith('FLOW:') && allowed.has(line));
69
+ return values.length === 1 ? values[0] : '';
70
+ }
71
+
72
+ function parseArray(value) {
73
+ if (!value) return null;
74
+ try {
75
+ const parsed = JSON.parse(value);
76
+ return Array.isArray(parsed) ? parsed : null;
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+
82
+ function runScript(script, args) {
83
+ return new Promise((resolve) => {
84
+ const child = spawn('bash', [script, ...args], {
85
+ cwd: onboardingScriptsDir,
86
+ env: process.env,
87
+ stdio: ['ignore', 'pipe', 'pipe']
88
+ });
89
+ let stdout = '';
90
+ let stderr = '';
91
+ child.stdout.setEncoding('utf8');
92
+ child.stderr.setEncoding('utf8');
93
+ child.stdout.on('data', (chunk) => { stdout += chunk; });
94
+ child.stderr.on('data', (chunk) => { stderr += chunk; });
95
+ child.on('error', (error) => resolve({ status: null, stdout, stderr, spawnError: error }));
96
+ child.on('close', (status) => resolve({ status, stdout, stderr, spawnError: null }));
97
+ });
98
+ }
99
+
100
+ function failedBranch(result, extra = {}) {
101
+ return {
102
+ query_status: 'QUERY_FAILED',
103
+ ...extra,
104
+ customer_output: result.stdout.trim() ? true : false
105
+ };
106
+ }
107
+
108
+ function signingBranch(result, productType) {
109
+ const lines = internalLines(result.stderr);
110
+ const signStatus = uniqueValue(lines, 'SIGN_STATUS=');
111
+ const flow = uniqueFlow(lines, signFlows[productType]);
112
+ if (result.status !== 0 || !signStatuses.has(signStatus) || !flow) return failedBranch(result);
113
+ return { query_status: 'SUCCESS', source: 'QUERY', sign_status: signStatus, flow };
114
+ }
115
+
116
+ function reusedSigningBranch(args, productType) {
117
+ const signStatus = args['reuse-sign-status'];
118
+ const flow = args['reuse-sign-flow'];
119
+ if (signStatus === undefined && flow === undefined) return null;
120
+ if (!signStatuses.has(signStatus) || !signFlows[productType].has(flow)) {
121
+ throw new Error('授权签约复用标记不完整或与当前产品不匹配');
122
+ }
123
+ return { query_status: 'SUCCESS', source: 'AUTH_REUSE', sign_status: signStatus, flow };
124
+ }
125
+
126
+ function serviceBranch(result) {
127
+ const lines = internalLines(result.stderr);
128
+ const candidates = parseArray(uniqueValue(lines, 'SERVICE_CANDIDATES_JSON='));
129
+ const flow = uniqueFlow(lines, serviceFlows);
130
+ if (result.status !== 0 || !candidates || !flow) return failedBranch(result, { applicable: true });
131
+ return { query_status: 'SUCCESS', applicable: true, flow, candidates };
132
+ }
133
+
134
+ function applicationBranch(result) {
135
+ const lines = internalLines(result.stderr);
136
+ const candidates = parseArray(uniqueValue(lines, 'APP_CANDIDATES_JSON='));
137
+ const applicationType = uniqueValue(lines, 'APPLICATION_TYPE=');
138
+ const flow = uniqueFlow(lines, applicationFlows);
139
+ if (result.status !== 0 || !candidates || !['WEBAPP', 'MOBILEAPP'].includes(applicationType) || !flow) {
140
+ return failedBranch(result);
141
+ }
142
+ return { query_status: 'SUCCESS', application_type: applicationType, flow, candidates };
143
+ }
144
+
145
+ function hasAuthBlocker(result) {
146
+ return internalLines(result.stderr).some((line) => [
147
+ 'AUTH_FLOW:AUTH_REQUIRED',
148
+ 'AUTH_FLOW:SCOPE_MISMATCH',
149
+ 'AUTH_FLOW:MCC_MISMATCH'
150
+ ].includes(line));
151
+ }
152
+
153
+ function emitEnvelope(envelope) {
154
+ process.stderr.write(`${internalPrefix}DISCOVERY_RESULT=${JSON.stringify(envelope)}\n`);
155
+ }
156
+
157
+ async function runDiscovery(args) {
158
+ const productType = required(args, 'product-type');
159
+ if (!productSalesCodes[productType]) throw new Error('product-type 非法,仅支持 aipay、webpay、apppay');
160
+ const salesCode = required(args, 'sales-code');
161
+ if (salesCode !== productSalesCodes[productType]) throw new Error('sales-code 与 product-type 不匹配');
162
+ const reusedSigning = reusedSigningBranch(args, productType);
163
+ emitStageMetricAsync('onboarding.discovery.start', { productType });
164
+
165
+ const signingPromise = reusedSigning
166
+ ? Promise.resolve(null)
167
+ : runScript(scriptPaths.signing, ['--sales-code', salesCode, '--product-type', productType]);
168
+ const servicePromise = productType === 'aipay'
169
+ ? runScript(scriptPaths.service, ['list'])
170
+ : Promise.resolve(null);
171
+ const applicationPromise = runScript(scriptPaths.application, [
172
+ 'list', '--product-type', productType, '--sales-code', salesCode
173
+ ]);
174
+ const [signingResult, serviceResult, applicationResult] = await Promise.all([
175
+ signingPromise,
176
+ servicePromise,
177
+ applicationPromise
178
+ ]);
179
+
180
+ const executedResults = [signingResult, serviceResult, applicationResult].filter(Boolean);
181
+ const globalBlocker = executedResults.some(hasAuthBlocker) ? 'AUTHORIZATION_INVALID' : '';
182
+ const envelope = {
183
+ schema_ver: 1,
184
+ product_type: productType,
185
+ signing: reusedSigning || signingBranch(signingResult, productType),
186
+ service: productType === 'aipay'
187
+ ? serviceBranch(serviceResult)
188
+ : { query_status: 'NOT_APPLICABLE', applicable: false },
189
+ application: applicationBranch(applicationResult)
190
+ };
191
+ if (globalBlocker) envelope.global_blocker = globalBlocker;
192
+ emitEnvelope(envelope);
193
+
194
+ let customerOutputs = executedResults
195
+ .map((result) => result.stdout.trim())
196
+ .filter(Boolean);
197
+ customerOutputs = [...new Set(customerOutputs)];
198
+ if (globalBlocker) {
199
+ customerOutputs = customerOutputs.slice(0, 1);
200
+ } else {
201
+ customerOutputs.push(renderDiscoverySummary(productType, {
202
+ signStatus: envelope.signing.query_status === 'SUCCESS' ? envelope.signing.sign_status : 'QUERY_FAILED',
203
+ serviceFlow: envelope.service.query_status === 'SUCCESS' ? envelope.service.flow : 'QUERY_FAILED',
204
+ appFlow: envelope.application.query_status === 'SUCCESS' ? envelope.application.flow : 'QUERY_FAILED'
205
+ }));
206
+ }
207
+ if (customerOutputs.length > 0) process.stdout.write(`${customerOutputs.join('\n\n')}\n`);
208
+ return globalBlocker ? 2 : 0;
209
+ }
210
+
211
+ async function main() {
212
+ const { command, args } = parseArgs(process.argv);
213
+ if (command !== 'run') {
214
+ throw new Error('用法: onboarding_discovery_runner.mjs run --product-type <aipay|webpay|apppay> --sales-code <code> [--reuse-sign-status <status> --reuse-sign-flow <flow>]');
215
+ }
216
+ process.exitCode = await runDiscovery(args);
217
+ }
218
+
219
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
220
+ main().catch((error) => {
221
+ process.stderr.write(`${internalPrefix}DISCOVERY_RUNNER_ERROR=${String(error?.message || error).replace(/[\r\n]/gu, ' ')}\n`);
222
+ process.exitCode = 1;
223
+ });
224
+ }