@alipay/alipay-aipay 1.2.2 → 1.4.9
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/README.md +5 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +13 -12
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +17 -9
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +5 -5
- package/dist/commands/update.js.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/utils/config.d.ts +5 -3
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +34 -12
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/file.d.ts +8 -0
- package/dist/utils/file.d.ts.map +1 -1
- package/dist/utils/file.js +161 -9
- package/dist/utils/file.js.map +1 -1
- package/dist/utils/jq.d.ts.map +1 -1
- package/dist/utils/jq.js +8 -0
- package/dist/utils/jq.js.map +1 -1
- package/package.json +6 -5
- package/skill/SKILL.md +15 -169
- package/skill/VERSION +1 -1
- package/skill/references/integration/flow.md +321 -292
- package/skill/references/integration/modules/alipay-sdk-reminder.md +5 -3
- package/skill/references/integration/modules/checklist.md +41 -27
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/skill/references/integration/modules/interface-contract/aipay-interface-contract.md +53 -0
- package/skill/references/integration/modules/interface-contract/apppay-interface-contract.md +45 -0
- package/skill/references/integration/modules/interface-contract/common-trade-contract.md +42 -0
- package/skill/references/integration/modules/interface-contract/notify-url-contract.md +68 -0
- package/skill/references/integration/modules/interface-contract/webpay-interface-contract.md +44 -0
- package/skill/references/integration/modules/interface-guide.md +37 -72
- package/skill/references/integration/modules/product-decision.md +10 -27
- package/skill/references/integration/modules/sandbox/a2m-sandbox-test.md +62 -94
- package/skill/references/integration/modules/sandbox/alipay-sandbox-tool.md +43 -40
- package/skill/references/integration/modules/sandbox/sandbox-setup-guide.md +93 -114
- package/skill/references/integration/modules/scripts/local_402_sandbox_pay.py +357 -147
- package/skill/references/integration/modules/scripts/sandbox_config.sh +299 -0
- package/skill/references/integration/modules/troubleshooting-flow.md +28 -0
- package/skill/references/normal/alipay-cli-env.md +15 -5
- package/skill/references/normal/full-process-routing.md +80 -0
- package/skill/references/normal/json/customer-messages.json +937 -0
- package/skill/references/normal/json/policy-rules.json +179 -0
- package/skill/references/normal/rejection-guide.md +11 -6
- package/skill/references/normal/scripts/alipay_cli_env.mjs +29 -0
- package/skill/references/normal/scripts/alipay_cli_env.sh +21 -0
- package/skill/references/normal/scripts/common.sh +87 -0
- package/skill/references/normal/scripts/detect_dev_tool.sh +5 -1
- package/skill/references/normal/scripts/integration_message_runner.mjs +801 -0
- package/skill/references/normal/scripts/onboarding_message_runner.mjs +436 -0
- package/skill/references/normal/scripts/open_official_url.sh +82 -0
- package/skill/references/normal/scripts/platform_compat.mjs +39 -0
- package/skill/references/normal/scripts/project_route_inspector.sh +132 -0
- package/skill/references/normal/scripts/python_script_runner.mjs +56 -0
- package/skill/references/normal/scripts/render_customer_message.mjs +1263 -0
- package/skill/references/normal/scripts/self_update.mjs +160 -0
- package/skill/references/normal/scripts/telemetry.mjs +1278 -0
- package/skill/references/normal/self-update.md +18 -51
- package/skill/references/onboarding/flow.md +381 -283
- package/skill/references/onboarding/modules/app-release.md +132 -179
- package/skill/references/onboarding/modules/authorization.md +53 -64
- package/skill/references/onboarding/modules/error-handling.md +31 -34
- package/skill/references/onboarding/modules/product-sign.md +62 -66
- package/skill/references/onboarding/modules/scripts/app.sh +397 -158
- package/skill/references/onboarding/modules/scripts/ar_sign_apply.sh +38 -22
- package/skill/references/onboarding/modules/scripts/auth.sh +479 -167
- package/skill/references/onboarding/modules/scripts/download_key_tool.sh +157 -0
- package/skill/references/onboarding/modules/scripts/error_handler.sh +100 -130
- package/skill/references/onboarding/modules/scripts/network_retry.sh +96 -0
- package/skill/references/onboarding/modules/scripts/query_sign_status.sh +17 -70
- package/skill/references/onboarding/modules/scripts/service.sh +153 -89
- package/skill/references/onboarding/modules/scripts/sign_status_parser.sh +58 -0
- package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +16 -14
- package/skill/references/onboarding/modules/service-registration.md +67 -72
- package/skill/references/runtime-entry.md +62 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { isWindowsPlatform } from './platform_compat.mjs';
|
|
7
|
+
import { loadMccRecords, renderMessage } from './render_customer_message.mjs';
|
|
8
|
+
import { emitStageMetric, emitTerminalMetric } from './telemetry.mjs';
|
|
9
|
+
|
|
10
|
+
const productTypes = {
|
|
11
|
+
aipay: { variant: 'AIPAY', productName: '按量付费' },
|
|
12
|
+
webpay: { variant: 'WEBPAY', productName: '网站支付' },
|
|
13
|
+
apppay: { variant: 'APPPAY', productName: 'APP 支付' }
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const signingStatusMap = {
|
|
17
|
+
NOT_SIGNED: '未签约',
|
|
18
|
+
SIGN_SUBMITTED: '已提交待生效',
|
|
19
|
+
SIGNED_EFFECTIVE: '已生效',
|
|
20
|
+
OTHER_STATUS: '其他状态待核验',
|
|
21
|
+
QUERY_FAILED: '查询失败',
|
|
22
|
+
FAILED: '查询失败',
|
|
23
|
+
ERROR: '查询失败'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const serviceFlowMap = {
|
|
27
|
+
'FLOW:CREATE_NEW': '无已有服务',
|
|
28
|
+
'FLOW:SELECT': '已有服务候选',
|
|
29
|
+
QUERY_FAILED: '查询失败',
|
|
30
|
+
'FLOW:ERROR': '查询失败',
|
|
31
|
+
ERROR: '查询失败'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const applicationFlowMap = {
|
|
35
|
+
'FLOW:CREATE_NEW': '无同类型应用',
|
|
36
|
+
'FLOW:SELECT': '有上线应用可复用',
|
|
37
|
+
'FLOW:PENDING_APPLICATIONS': '仅有未上线应用',
|
|
38
|
+
QUERY_FAILED: '查询失败',
|
|
39
|
+
'FLOW:ERROR': '查询失败',
|
|
40
|
+
ERROR: '查询失败'
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const categoryNames = {
|
|
44
|
+
signing: '签约材料',
|
|
45
|
+
service: '服务资料',
|
|
46
|
+
application: '应用资料'
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const materialStates = {
|
|
50
|
+
INITIAL: { variant: 'INITIAL', currentStatus: '待补充' },
|
|
51
|
+
PARTIAL: { variant: 'PARTIAL', currentStatus: '部分已提供,待补充' },
|
|
52
|
+
INVALID: { variant: 'INVALID', currentStatus: '校验失败,需更正' },
|
|
53
|
+
APP_MOBILE_INITIAL: { variant: 'APP_MOBILE_INITIAL', currentStatus: '待补充' }
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const directFieldAliases = new Map([
|
|
57
|
+
['APP名称', ['APP名称']],
|
|
58
|
+
['appName', ['APP名称']],
|
|
59
|
+
['首页截图', ['首页截图']],
|
|
60
|
+
['商品页截图', ['商品页截图']],
|
|
61
|
+
['支付页截图', ['支付页截图']],
|
|
62
|
+
['服务名称', ['服务名称']],
|
|
63
|
+
['serviceName', ['服务名称']],
|
|
64
|
+
['服务描述', ['服务描述']],
|
|
65
|
+
['serviceDesc', ['服务描述']],
|
|
66
|
+
['服务地址', ['服务地址']],
|
|
67
|
+
['resourceUrl', ['服务地址']],
|
|
68
|
+
['服务单价', ['服务单价']],
|
|
69
|
+
['pricing', ['服务单价']],
|
|
70
|
+
['请求示例JSON', ['请求示例JSON']],
|
|
71
|
+
['请求示例 JSON', ['请求示例JSON']],
|
|
72
|
+
['schemaUrl', ['请求示例JSON']],
|
|
73
|
+
['应用平台和对应资料', ['应用平台和对应资料']],
|
|
74
|
+
['mobilePlatform', ['应用平台和对应资料']],
|
|
75
|
+
['iOS Bundle ID(bundleId)', ['iOS Bundle ID(bundleId)']],
|
|
76
|
+
['iOS Bundle ID', ['iOS Bundle ID(bundleId)']],
|
|
77
|
+
['bundleId', ['iOS Bundle ID(bundleId)']],
|
|
78
|
+
['Android 应用包名(appPackage)', ['Android 应用包名(appPackage)']],
|
|
79
|
+
['Android 应用包名', ['Android 应用包名(appPackage)']],
|
|
80
|
+
['appPackage', ['Android 应用包名(appPackage)']],
|
|
81
|
+
['Android 应用签名摘要(appSign)', ['Android 应用签名摘要(appSign)']],
|
|
82
|
+
['Android 应用签名摘要', ['Android 应用签名摘要(appSign)']],
|
|
83
|
+
['appSign', ['Android 应用签名摘要(appSign)']],
|
|
84
|
+
['应用公钥', ['应用公钥']],
|
|
85
|
+
['publicKey', ['应用公钥']]
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
function parseArgs(argv) {
|
|
89
|
+
const command = argv[2];
|
|
90
|
+
const args = {};
|
|
91
|
+
for (let index = 3; index < argv.length; index += 1) {
|
|
92
|
+
const key = argv[index];
|
|
93
|
+
if (!key.startsWith('--')) throw new Error(`未知参数 ${key}`);
|
|
94
|
+
const value = argv[index + 1];
|
|
95
|
+
if (value === undefined || value.startsWith('--')) throw new Error(`参数 ${key} 缺少取值`);
|
|
96
|
+
args[key.slice(2)] = value;
|
|
97
|
+
index += 1;
|
|
98
|
+
}
|
|
99
|
+
return { command, args };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function required(args, name) {
|
|
103
|
+
const value = args[name];
|
|
104
|
+
if (typeof value !== 'string' || value.length === 0) throw new Error(`缺少参数 --${name}`);
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function productFor(args) {
|
|
109
|
+
const productType = required(args, 'product-type');
|
|
110
|
+
const mapping = productTypes[productType];
|
|
111
|
+
if (!mapping) throw new Error('product-type 非法,仅支持 aipay、webpay、apppay');
|
|
112
|
+
return mapping;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalizeEnum(value, map, label) {
|
|
116
|
+
if (typeof value !== 'string' || value.length === 0) throw new Error(`缺少 ${label}`);
|
|
117
|
+
const normalized = map[value];
|
|
118
|
+
if (!normalized) throw new Error(`${label} 不在允许脚本标记中;允许值:${Object.keys(map).join('、')}`);
|
|
119
|
+
return normalized;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function readStdin() {
|
|
123
|
+
let input = '';
|
|
124
|
+
for await (const chunk of process.stdin) input += chunk;
|
|
125
|
+
return input.trim() ? JSON.parse(input) : {};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function runPlatformEntry() {
|
|
129
|
+
return isWindowsPlatform()
|
|
130
|
+
? renderMessage('onboarding.windows.redirect', 'DEFAULT', {})
|
|
131
|
+
: '';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function runMccClarify(args) {
|
|
135
|
+
const productType = required(args, 'product-type');
|
|
136
|
+
const product = productFor({ ...args, 'product-type': productType });
|
|
137
|
+
emitStageMetric('onboarding.mcc_prepare.start', { productType });
|
|
138
|
+
return renderMessage('onboarding.mcc.clarify', 'DEFAULT', {
|
|
139
|
+
productName: product.productName
|
|
140
|
+
}, { productType });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function normalizeSearch(value) {
|
|
144
|
+
return value.toLocaleLowerCase('zh-CN').replace(/[\s,,。;;、/()()\-_]/gu, '');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function assertMccQuery(query) {
|
|
148
|
+
if (query.length > 500 || /[\u0000-\u001f\u007f]/u.test(query)) throw new Error('query 包含控制字符或过长');
|
|
149
|
+
const normalizedQuery = normalizeSearch(query);
|
|
150
|
+
if (normalizedQuery.length < 2) throw new Error('query 过短,无法执行确定性 MCC 检索');
|
|
151
|
+
return normalizedQuery;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const mccAliasRules = [
|
|
155
|
+
{
|
|
156
|
+
queryTerms: ['电商', '电子商务', '网上商城', '线上商城', '网络购物'],
|
|
157
|
+
targetTerms: ['电商', '网上商城', '线上商城']
|
|
158
|
+
}
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
function mccSearchResult(query) {
|
|
162
|
+
const normalizedQuery = assertMccQuery(query);
|
|
163
|
+
const scored = [];
|
|
164
|
+
for (const record of loadMccRecords().values()) {
|
|
165
|
+
const name = normalizeSearch(record.name);
|
|
166
|
+
const businesses = normalizeSearch(record.applicableBusinesses);
|
|
167
|
+
const code = normalizeSearch(record.code);
|
|
168
|
+
const haystack = `${name}${businesses}`;
|
|
169
|
+
let score = 0;
|
|
170
|
+
if (code === normalizedQuery || normalizedQuery.includes(code) || name === normalizedQuery) score = Math.max(score, 100);
|
|
171
|
+
if (normalizedQuery.includes(name) || name.includes(normalizedQuery)) score = Math.max(score, 80);
|
|
172
|
+
if (businesses.includes(normalizedQuery)) score = Math.max(score, 70);
|
|
173
|
+
for (const rule of mccAliasRules) {
|
|
174
|
+
if (rule.queryTerms.some((term) => normalizedQuery.includes(term)) && rule.targetTerms.some((term) => haystack.includes(term))) {
|
|
175
|
+
score = Math.max(score, 50);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (score > 0) scored.push({ record, score });
|
|
179
|
+
}
|
|
180
|
+
const strongMatches = scored.filter(({ score }) => score >= 70);
|
|
181
|
+
const ranked = strongMatches.length > 0 ? strongMatches : scored;
|
|
182
|
+
const candidates = ranked
|
|
183
|
+
.sort((left, right) => right.score - left.score || left.record.code.localeCompare(right.record.code))
|
|
184
|
+
.slice(0, 8)
|
|
185
|
+
.map(({ record }) => ({
|
|
186
|
+
mccCode: record.code,
|
|
187
|
+
mccName: record.name,
|
|
188
|
+
applicableBusinesses: record.applicableBusinesses,
|
|
189
|
+
qualification: record.qualification
|
|
190
|
+
}));
|
|
191
|
+
return {
|
|
192
|
+
matchType: candidates.length === 1 ? 'UNIQUE' : candidates.length === 0 ? 'NONE' : 'MULTIPLE',
|
|
193
|
+
candidates
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function runMccSearch(args) {
|
|
198
|
+
const productType = args['product-type'];
|
|
199
|
+
if (productType !== undefined) productFor({ ...args, 'product-type': productType });
|
|
200
|
+
emitStageMetric('onboarding.mcc_prepare.start', { productType });
|
|
201
|
+
const query = required(args, 'query');
|
|
202
|
+
return JSON.stringify(mccSearchResult(query));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function runMccCandidates(args) {
|
|
206
|
+
const productType = required(args, 'product-type');
|
|
207
|
+
productFor({ ...args, 'product-type': productType });
|
|
208
|
+
const result = mccSearchResult(required(args, 'query'));
|
|
209
|
+
if (result.candidates.length === 0) return renderMessage('onboarding.mcc.no_match', 'DEFAULT', {}, { productType });
|
|
210
|
+
return renderMessage('onboarding.mcc.candidates', 'DEFAULT', {
|
|
211
|
+
mccCandidates: result.candidates
|
|
212
|
+
}, { productType });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function runDiscoverySummary(args) {
|
|
216
|
+
const product = productFor(args);
|
|
217
|
+
const input = await readStdin();
|
|
218
|
+
const variables = {
|
|
219
|
+
signingStatus: normalizeEnum(input.signStatus, signingStatusMap, 'signStatus'),
|
|
220
|
+
applicationStatus: normalizeEnum(input.appFlow, applicationFlowMap, 'appFlow')
|
|
221
|
+
};
|
|
222
|
+
if (product.variant === 'AIPAY') {
|
|
223
|
+
variables.serviceStatus = normalizeEnum(input.serviceFlow, serviceFlowMap, 'serviceFlow');
|
|
224
|
+
}
|
|
225
|
+
return renderMessage('onboarding.discovery.summary', product.variant, variables);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function splitFieldInput(value) {
|
|
229
|
+
if (Array.isArray(value)) return value.flatMap((item) => splitFieldInput(item));
|
|
230
|
+
if (typeof value !== 'string') throw new Error('missingFields 必须是字符串或字符串数组');
|
|
231
|
+
return value
|
|
232
|
+
.split(/[、,,\n]/u)
|
|
233
|
+
.map((item) => item.trim())
|
|
234
|
+
.filter(Boolean);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function normalizeMaterialField(category, raw) {
|
|
238
|
+
if (directFieldAliases.has(raw)) return directFieldAliases.get(raw);
|
|
239
|
+
if (category === 'signing' && /签约.*截图/.test(raw)) return ['首页截图', '商品页截图', '支付页截图'];
|
|
240
|
+
if (/首页.*截图/.test(raw)) return ['首页截图'];
|
|
241
|
+
if (/商品.*截图/.test(raw)) return ['商品页截图'];
|
|
242
|
+
if (/支付.*截图/.test(raw)) return ['支付页截图'];
|
|
243
|
+
if (category === 'signing' && /^(?:截图|三张截图|3张截图)$/.test(raw)) return ['首页截图', '商品页截图', '支付页截图'];
|
|
244
|
+
if (/服务.*价格|服务.*单价/.test(raw)) return ['服务单价'];
|
|
245
|
+
if (/请求.*示例.*JSON/i.test(raw)) return ['请求示例JSON'];
|
|
246
|
+
if (/iOS.*Bundle/i.test(raw)) return ['iOS Bundle ID(bundleId)'];
|
|
247
|
+
if (/Android.*包名/i.test(raw)) return ['Android 应用包名(appPackage)'];
|
|
248
|
+
if (/Android.*签名/i.test(raw)) return ['Android 应用签名摘要(appSign)'];
|
|
249
|
+
throw new Error(`材料字段不在允许范围:${raw}`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function normalizeMissingFields(category, missingFields) {
|
|
253
|
+
const normalized = [];
|
|
254
|
+
for (const raw of splitFieldInput(missingFields)) {
|
|
255
|
+
for (const field of normalizeMaterialField(category, raw)) {
|
|
256
|
+
if (!normalized.includes(field)) normalized.push(field);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (normalized.length === 0) throw new Error('缺少 missingFields');
|
|
260
|
+
return normalized.join('、');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function runMaterialCollect(args) {
|
|
264
|
+
const productType = required(args, 'product-type');
|
|
265
|
+
productFor({ ...args, 'product-type': productType });
|
|
266
|
+
const category = required(args, 'category');
|
|
267
|
+
const categoryName = categoryNames[category];
|
|
268
|
+
if (!categoryName) throw new Error('category 非法,仅支持 signing、service、application');
|
|
269
|
+
|
|
270
|
+
const state = required(args, 'state').toUpperCase().replace(/-/g, '_');
|
|
271
|
+
const stateRule = materialStates[state];
|
|
272
|
+
if (!stateRule) throw new Error('state 非法,仅支持 INITIAL、PARTIAL、INVALID、APP_MOBILE_INITIAL');
|
|
273
|
+
|
|
274
|
+
if (state === 'APP_MOBILE_INITIAL') {
|
|
275
|
+
if (category !== 'application') throw new Error('APP_MOBILE_INITIAL 只适用于 application 类别');
|
|
276
|
+
return renderMessage('materials.category.collect', stateRule.variant, {
|
|
277
|
+
categoryName: '应用资料',
|
|
278
|
+
currentStatus: stateRule.currentStatus,
|
|
279
|
+
missingFields: '应用平台和对应资料'
|
|
280
|
+
}, { productType });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const input = await readStdin();
|
|
284
|
+
return renderMessage('materials.category.collect', stateRule.variant, {
|
|
285
|
+
categoryName,
|
|
286
|
+
currentStatus: stateRule.currentStatus,
|
|
287
|
+
missingFields: normalizeMissingFields(category, input.missingFields)
|
|
288
|
+
}, { productType });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function runApplicationCandidates(args) {
|
|
292
|
+
const applicationType = required(args, 'application-type');
|
|
293
|
+
if (!['WEBAPP', 'MOBILEAPP'].includes(applicationType)) throw new Error('application-type 非法,仅支持 WEBAPP、MOBILEAPP');
|
|
294
|
+
const applicationCandidates = await readStdin();
|
|
295
|
+
if (!Array.isArray(applicationCandidates)) throw new Error('应用候选输入必须是 JSON 数组');
|
|
296
|
+
const onlineCount = applicationCandidates.filter((candidate) => candidate?.status === 'ON_LINE').length;
|
|
297
|
+
const variant = applicationCandidates.length === 0
|
|
298
|
+
? 'EMPTY'
|
|
299
|
+
: onlineCount === 0
|
|
300
|
+
? 'PENDING_ONLY'
|
|
301
|
+
: 'ONLINE_WITH_OPTIONAL_PENDING';
|
|
302
|
+
const facts = renderMessage('application.candidates.facts', variant, { applicationType, applicationCandidates });
|
|
303
|
+
if (applicationCandidates.length === 0) return facts;
|
|
304
|
+
const selectionVariant = onlineCount === 0 ? 'PENDING_OR_NEW' : 'ONLINE_OR_NEW';
|
|
305
|
+
const selection = renderMessage('application.candidate.select', selectionVariant, {});
|
|
306
|
+
return `${facts}\n\n${selection}`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function runServiceCandidates() {
|
|
310
|
+
const serviceCandidates = await readStdin();
|
|
311
|
+
if (!Array.isArray(serviceCandidates)) throw new Error('服务候选输入必须是 JSON 数组');
|
|
312
|
+
const variant = serviceCandidates.length === 0 ? 'EMPTY' : 'DEFAULT';
|
|
313
|
+
const facts = renderMessage('service.candidates.facts', variant, { serviceCandidates });
|
|
314
|
+
if (serviceCandidates.length === 0) return facts;
|
|
315
|
+
return `${facts}\n\n${renderMessage('service.candidate.select', 'DEFAULT', {})}`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function canonicalCandidates(candidates) {
|
|
319
|
+
if (!Array.isArray(candidates)) throw new Error('应用候选快照必须是 JSON 数组');
|
|
320
|
+
return JSON.stringify([...candidates].sort((left, right) => {
|
|
321
|
+
const leftId = typeof left?.appId === 'string' ? left.appId : '';
|
|
322
|
+
const rightId = typeof right?.appId === 'string' ? right.appId : '';
|
|
323
|
+
return leftId.localeCompare(rightId);
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function runApplicationRecheck(args) {
|
|
328
|
+
const applicationType = required(args, 'application-type');
|
|
329
|
+
if (!['WEBAPP', 'MOBILEAPP'].includes(applicationType)) throw new Error('application-type 非法,仅支持 WEBAPP、MOBILEAPP');
|
|
330
|
+
const input = await readStdin();
|
|
331
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('应用候选重查输入必须是 JSON 对象');
|
|
332
|
+
if (canonicalCandidates(input.previousCandidates) === canonicalCandidates(input.currentCandidates)) return '';
|
|
333
|
+
const applicationCandidates = input.currentCandidates;
|
|
334
|
+
const onlineCount = applicationCandidates.filter((candidate) => candidate?.status === 'ON_LINE').length;
|
|
335
|
+
const variant = applicationCandidates.length === 0
|
|
336
|
+
? 'EMPTY'
|
|
337
|
+
: onlineCount === 0
|
|
338
|
+
? 'PENDING_ONLY'
|
|
339
|
+
: 'ONLINE_WITH_OPTIONAL_PENDING';
|
|
340
|
+
const facts = renderMessage('application.candidates.facts', variant, { applicationType, applicationCandidates });
|
|
341
|
+
if (applicationCandidates.length === 0) return facts;
|
|
342
|
+
const selectionVariant = onlineCount === 0 ? 'PENDING_OR_NEW' : 'ONLINE_OR_NEW';
|
|
343
|
+
return `${facts}\n\n${renderMessage('application.candidate.select', selectionVariant, {})}`;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function normalizeCloseoutInput(input) {
|
|
347
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
348
|
+
throw new Error('closeout 输入必须是 JSON 对象');
|
|
349
|
+
}
|
|
350
|
+
const variables = { ...input };
|
|
351
|
+
if (variables.applicationOperation !== undefined) {
|
|
352
|
+
const operation = variables.applicationOperation;
|
|
353
|
+
if (!operation || typeof operation !== 'object' || Array.isArray(operation)) {
|
|
354
|
+
throw new Error('applicationOperation 必须是 JSON 对象');
|
|
355
|
+
}
|
|
356
|
+
const allowedOperationKeys = new Set(['appId', 'actualStatus', 'nextAction']);
|
|
357
|
+
for (const key of Object.keys(operation)) {
|
|
358
|
+
if (!allowedOperationKeys.has(key)) throw new Error(`applicationOperation 包含未知字段 ${key}`);
|
|
359
|
+
}
|
|
360
|
+
variables.applicationOperationAppId = operation.appId;
|
|
361
|
+
variables.applicationOperationActualStatus = operation.actualStatus;
|
|
362
|
+
variables.applicationOperationNextAction = operation.nextAction;
|
|
363
|
+
delete variables.applicationOperation;
|
|
364
|
+
}
|
|
365
|
+
for (const key of [
|
|
366
|
+
'sandboxConfigState',
|
|
367
|
+
'manualVerificationItems',
|
|
368
|
+
'applicationOperationAppId',
|
|
369
|
+
'applicationOperationActualStatus',
|
|
370
|
+
'applicationOperationNextAction'
|
|
371
|
+
]) {
|
|
372
|
+
if (variables[key] === '') delete variables[key];
|
|
373
|
+
}
|
|
374
|
+
return variables;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async function runCloseout() {
|
|
378
|
+
const variables = normalizeCloseoutInput(await readStdin());
|
|
379
|
+
const rendered = renderMessage('process.partial_result', 'DEFAULT', variables);
|
|
380
|
+
emitTerminalMetric({ productName: variables.productName });
|
|
381
|
+
return rendered;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function main() {
|
|
385
|
+
const { command, args } = parseArgs(process.argv);
|
|
386
|
+
if (command === 'platform-entry') {
|
|
387
|
+
const message = runPlatformEntry();
|
|
388
|
+
if (message) console.log(message);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (command === 'mcc-clarify') {
|
|
392
|
+
console.log(await runMccClarify(args));
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (command === 'mcc-search') {
|
|
396
|
+
console.log(await runMccSearch(args));
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (command === 'mcc-candidates') {
|
|
400
|
+
console.log(await runMccCandidates(args));
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (command === 'discovery-summary') {
|
|
404
|
+
console.log(await runDiscoverySummary(args));
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (command === 'material-collect') {
|
|
408
|
+
console.log(await runMaterialCollect(args));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (command === 'application-candidates') {
|
|
412
|
+
console.log(await runApplicationCandidates(args));
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (command === 'service-candidates') {
|
|
416
|
+
console.log(await runServiceCandidates());
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (command === 'application-recheck') {
|
|
420
|
+
const message = await runApplicationRecheck(args);
|
|
421
|
+
if (message) console.log(message);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (command === 'closeout') {
|
|
425
|
+
console.log(await runCloseout());
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
throw new Error('用法: onboarding_message_runner.mjs <platform-entry|mcc-search|mcc-clarify|mcc-candidates|discovery-summary|material-collect|application-candidates|service-candidates|application-recheck|closeout> ...');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
432
|
+
main().catch((error) => {
|
|
433
|
+
console.error(`ONBOARDING_MESSAGE_ERROR:${error.message}`);
|
|
434
|
+
process.exitCode = 1;
|
|
435
|
+
});
|
|
436
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -u
|
|
4
|
+
|
|
5
|
+
KIND="${1:-}"
|
|
6
|
+
IFS= read -r URL || URL=""
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
POLICY_FILE="${SCRIPT_DIR}/../json/policy-rules.json"
|
|
9
|
+
|
|
10
|
+
case "$KIND" in authorization|public-key-confirmation) ;; *) echo "OPEN_FAILED"; exit 1 ;; esac
|
|
11
|
+
command -v node >/dev/null 2>&1 || { echo "OPEN_FAILED"; exit 1; }
|
|
12
|
+
[ -f "$POLICY_FILE" ] || { echo "OPEN_FAILED"; exit 1; }
|
|
13
|
+
|
|
14
|
+
if ! node -e '
|
|
15
|
+
const fs = require("node:fs");
|
|
16
|
+
const [kind, raw, policyFile] = process.argv.slice(1);
|
|
17
|
+
const policy = JSON.parse(fs.readFileSync(policyFile, "utf8"));
|
|
18
|
+
if (/[\u0000-\u0020\u007f`()\[\]<>|]/.test(raw)) process.exit(1);
|
|
19
|
+
let url;
|
|
20
|
+
try { url = new URL(raw); } catch { process.exit(1); }
|
|
21
|
+
const allowed = policy.temporaryUrlAllowlist[kind === "authorization" ? "authorization" : "publicKeyConfirmation"];
|
|
22
|
+
if (!allowed || url.protocol !== "https:" || url.hostname !== allowed.host || url.pathname !== allowed.path || url.username || url.password || url.port || url.hash) process.exit(1);
|
|
23
|
+
if (kind === "authorization") {
|
|
24
|
+
const required = allowed.requiredQueryParams;
|
|
25
|
+
const optional = allowed.optionalQueryParams;
|
|
26
|
+
const allowedKeys = new Set([...required, ...optional]);
|
|
27
|
+
if ([...url.searchParams.keys()].some((key) => !allowedKeys.has(key))) process.exit(1);
|
|
28
|
+
for (const key of required) {
|
|
29
|
+
if (url.searchParams.getAll(key).length !== 1 || !url.searchParams.get(key)) process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
for (const key of optional) {
|
|
32
|
+
if (url.searchParams.getAll(key).length > 1) process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
if (!policy.authorizationProducts.some((product) => product.salesCode === url.searchParams.get("productCode"))) process.exit(1);
|
|
35
|
+
if (!/^A\d{4}_B\d{4}$/.test(url.searchParams.get("mccCode"))) process.exit(1);
|
|
36
|
+
if (/[\r\n]/.test(url.searchParams.get("deviceCode"))) process.exit(1);
|
|
37
|
+
if (url.searchParams.has("platform") && (!url.searchParams.get("platform") || /[\r\n]/.test(url.searchParams.get("platform")))) process.exit(1);
|
|
38
|
+
} else {
|
|
39
|
+
const required = allowed.requiredQueryParams || ["keyConfirmToken"];
|
|
40
|
+
const optional = allowed.optionalQueryParams || [];
|
|
41
|
+
const allowedKeys = new Set([...required, ...optional]);
|
|
42
|
+
if ([...url.searchParams.keys()].some((key) => !allowedKeys.has(key))) process.exit(1);
|
|
43
|
+
for (const key of required) {
|
|
44
|
+
if (url.searchParams.getAll(key).length !== 1 || !url.searchParams.get(key)) process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
for (const key of optional) {
|
|
47
|
+
if (url.searchParams.getAll(key).length > 1) process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
if (/[\r\n]/.test(url.searchParams.get("keyConfirmToken"))) process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
' "$KIND" "$URL" "$POLICY_FILE"; then
|
|
52
|
+
echo "OPEN_FAILED"
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
OS_NAME="$(uname -s 2>/dev/null || true)"
|
|
57
|
+
WINDOWS_OPENER=false
|
|
58
|
+
if [ "$OS_NAME" = "Darwin" ] && command -v open >/dev/null 2>&1; then
|
|
59
|
+
OPENER=(open)
|
|
60
|
+
elif [ "$OS_NAME" = "Linux" ] && command -v xdg-open >/dev/null 2>&1; then
|
|
61
|
+
OPENER=(xdg-open)
|
|
62
|
+
elif [[ "$OS_NAME" = MINGW* || "$OS_NAME" = MSYS* || "$OS_NAME" = CYGWIN* ]] && command -v powershell.exe >/dev/null 2>&1; then
|
|
63
|
+
WINDOWS_OPENER=true
|
|
64
|
+
OPENER=(powershell.exe -NoProfile -NonInteractive -Command 'Start-Process -FilePath $env:ALIPAY_AIPAY_OPEN_URL')
|
|
65
|
+
else
|
|
66
|
+
echo "GUI_UNAVAILABLE"
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
if [ "$WINDOWS_OPENER" = true ]; then
|
|
71
|
+
env "ALIPAY_AIPAY_OPEN_URL=$URL" "${OPENER[@]}" >/dev/null 2>&1
|
|
72
|
+
OPEN_RC=$?
|
|
73
|
+
else
|
|
74
|
+
"${OPENER[@]}" "$URL" >/dev/null 2>&1
|
|
75
|
+
OPEN_RC=$?
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
if [ "$OPEN_RC" -eq 0 ]; then
|
|
79
|
+
echo "OPENED"
|
|
80
|
+
else
|
|
81
|
+
echo "OPEN_FAILED"
|
|
82
|
+
fi
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
|
|
5
|
+
export function runtimePlatform(env = process.env) {
|
|
6
|
+
const testPlatform = env.ALIPAY_AIPAY_TEST_MODE === '1' ? env.ALIPAY_AIPAY_TEST_PLATFORM : '';
|
|
7
|
+
return ['darwin', 'linux', 'win32'].includes(testPlatform) ? testPlatform : process.platform;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isWindowsPlatform(env = process.env) {
|
|
11
|
+
return runtimePlatform(env) === 'win32';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function commandRuns(command, args = [], options = {}) {
|
|
15
|
+
const result = spawnSync(command, args, {
|
|
16
|
+
encoding: 'utf8',
|
|
17
|
+
stdio: 'ignore',
|
|
18
|
+
shell: false,
|
|
19
|
+
env: options.env || process.env
|
|
20
|
+
});
|
|
21
|
+
return result.status === 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function currentNodeAvailable(env = process.env) {
|
|
25
|
+
return Boolean(process.execPath && fs.existsSync(process.execPath));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function checkCliEnvironment(options = {}) {
|
|
29
|
+
const env = options.env || process.env;
|
|
30
|
+
const requireJq = options.requireJq ?? !isWindowsPlatform(env);
|
|
31
|
+
const requireAlipayCli = options.requireAlipayCli ?? true;
|
|
32
|
+
const missing = [];
|
|
33
|
+
|
|
34
|
+
if (!currentNodeAvailable(env)) missing.push('node');
|
|
35
|
+
if (requireJq && !commandRuns('jq', ['--version'], { env })) missing.push('jq');
|
|
36
|
+
if (requireAlipayCli && !commandRuns('alipay-cli', ['version'], { env })) missing.push('alipay-cli');
|
|
37
|
+
|
|
38
|
+
return missing;
|
|
39
|
+
}
|