@alipay/alipay-aipay 1.0.2 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +71 -22
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/prepare-tools.d.ts.map +1 -1
- package/dist/commands/prepare-tools.js +16 -4
- package/dist/commands/prepare-tools.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +9 -9
- package/dist/commands/update.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/alipay-cli.d.ts +6 -0
- package/dist/utils/alipay-cli.d.ts.map +1 -1
- package/dist/utils/alipay-cli.js +38 -22
- package/dist/utils/alipay-cli.js.map +1 -1
- package/dist/utils/file.d.ts +9 -2
- package/dist/utils/file.d.ts.map +1 -1
- package/dist/utils/file.js +33 -3
- package/dist/utils/file.js.map +1 -1
- package/dist/utils/gpg.d.ts.map +1 -1
- package/dist/utils/gpg.js +97 -53
- package/dist/utils/gpg.js.map +1 -1
- package/dist/utils/jq.d.ts.map +1 -1
- package/dist/utils/jq.js +6 -9
- package/dist/utils/jq.js.map +1 -1
- package/dist/utils/npm.d.ts.map +1 -1
- package/dist/utils/npm.js +2 -6
- package/dist/utils/npm.js.map +1 -1
- package/dist/utils/shell.d.ts +15 -2
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +216 -12
- package/dist/utils/shell.js.map +1 -1
- package/package.json +3 -4
- package/skill/SKILL.md +77 -11
- package/skill/VERSION +1 -0
- package/skill/references/integration/flow.md +154 -136
- package/skill/references/integration/modules/alipay-sdk-reminder.md +29 -13
- package/skill/references/integration/modules/checklist.md +49 -19
- 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 +3 -3
- package/skill/references/integration/modules/code-examples/csharp/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.cs +27 -13
- 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 +3 -3
- package/skill/references/integration/modules/code-examples/java/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemoController.java +31 -12
- 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 +5 -4
- package/skill/references/integration/modules/code-examples/nodejs/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.js +69 -37
- 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 +3 -3
- package/skill/references/integration/modules/code-examples/php/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.php +38 -17
- 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 +3 -3
- package/skill/references/integration/modules/code-examples/python/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.py +31 -18
- package/skill/references/integration/modules/interface-guide.md +7 -3
- package/skill/references/integration/modules/sandbox/a2m-sandbox-test.md +77 -48
- package/skill/references/integration/modules/sandbox/alipay-sandbox-tool.md +8 -5
- package/skill/references/integration/modules/sandbox/sandbox-setup-guide.md +157 -117
- package/skill/references/integration/modules/scripts/local_402_sandbox_pay.py +35 -4
- package/skill/references/normal/alipay-cli-env.md +13 -11
- package/skill/references/normal/self-update.md +59 -0
- package/skill/references/onboarding/flow.md +202 -282
- package/skill/references/onboarding/modules/app-release.md +91 -35
- package/skill/references/onboarding/modules/authorization.md +49 -22
- package/skill/references/onboarding/modules/error-handling.md +19 -14
- package/skill/references/onboarding/modules/product-sign.md +23 -20
- package/skill/references/onboarding/modules/scripts/app.sh +139 -40
- package/skill/references/onboarding/modules/scripts/auth.sh +183 -101
- package/skill/references/onboarding/modules/scripts/error_handler.sh +148 -18
- package/skill/references/onboarding/modules/scripts/query_sign_status.sh +17 -11
- package/skill/references/onboarding/modules/scripts/service.sh +208 -37
- package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +1 -1
- package/skill/references/onboarding/modules/service-registration.md +72 -29
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
# auth confirm [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>]
|
|
10
10
|
# auth mismatch [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>]
|
|
11
11
|
# 返回值:
|
|
12
|
-
# init: AUTH_FLOW:SKIP | AUTH_FLOW:READY
|
|
12
|
+
# init: AUTH_FLOW:SKIP | AUTH_FLOW:READY | AUTH_FLOW:SCOPE_MISMATCH | AUTH_FLOW:MCC_MISMATCH | AUTH_FLOW:FAILED
|
|
13
13
|
# confirm: AUTH_FLOW:AUTH_SUCCESS | AUTH_FLOW:PENDING | AUTH_FLOW:EXPIRED | AUTH_FLOW:SCOPE_MISMATCH | AUTH_FLOW:MCC_MISMATCH | AUTH_FLOW:FAILED
|
|
14
|
-
# mismatch:
|
|
14
|
+
# mismatch: 执行一次 logout 后调用 auth init 重新生成授权链接(不输出 FLOW 标记,由 auth init 输出 AUTH_FLOW:READY)
|
|
15
15
|
#=============================================================================
|
|
16
16
|
|
|
17
17
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
@@ -194,6 +194,123 @@ run_json_command() {
|
|
|
194
194
|
return 1
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
# 校验当前登录会话是否覆盖目标产品 scope 和 MCC。
|
|
198
|
+
# 可复用调用方已经取得的 whoami JSON,避免重复查询。
|
|
199
|
+
validate_current_authorization() {
|
|
200
|
+
local whoami_result="${1:-}"
|
|
201
|
+
local logged_in is_expired granted_scope required_scope
|
|
202
|
+
local scope_match mcc_verify_result mcc_verify_unwrapped mcc_match
|
|
203
|
+
local mcc_error_type mcc_error_code mcc_success
|
|
204
|
+
local granted_scope_item
|
|
205
|
+
local -a granted_scope_items
|
|
206
|
+
|
|
207
|
+
if [ -z "$SALES_CODE" ] || [ -z "$MCC_CODE" ]; then
|
|
208
|
+
echo "❌ 缺少授权校验参数"
|
|
209
|
+
echo "AUTH_FLOW:FAILED"
|
|
210
|
+
return 1
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
if [ -z "$whoami_result" ]; then
|
|
214
|
+
whoami_result=$(run_json_command "alipay-cli whoami --json" alipay-cli whoami --json) || {
|
|
215
|
+
echo "AUTH_FLOW:FAILED"
|
|
216
|
+
return 1
|
|
217
|
+
}
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
if ! handle_error "$whoami_result"; then
|
|
221
|
+
echo "AUTH_FLOW:FAILED"
|
|
222
|
+
return 1
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
logged_in=$(echo "$whoami_result" | jq -r '.data.logged_in // false')
|
|
226
|
+
is_expired=$(echo "$whoami_result" | jq -r '.data.is_expired // false')
|
|
227
|
+
if [ "$logged_in" != "true" ] || [ "$is_expired" = "true" ]; then
|
|
228
|
+
echo "❌ 当前登录状态无效或已过期"
|
|
229
|
+
echo "AUTH_FLOW:FAILED"
|
|
230
|
+
return 1
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
granted_scope=$(echo "$whoami_result" | jq -r '.data.scope // empty')
|
|
234
|
+
case "$SALES_CODE" in
|
|
235
|
+
"I1080300001000041203") required_scope="fast_instant_trade_pay:write" ;;
|
|
236
|
+
"I1080300001000041313") required_scope="auth_alipay_apppay:write" ;;
|
|
237
|
+
"I1080300001000160457") required_scope="machine_pay:write" ;;
|
|
238
|
+
*)
|
|
239
|
+
echo "❌ 未知产品码: $SALES_CODE"
|
|
240
|
+
echo "AUTH_FLOW:FAILED"
|
|
241
|
+
return 1
|
|
242
|
+
;;
|
|
243
|
+
esac
|
|
244
|
+
|
|
245
|
+
scope_match="false"
|
|
246
|
+
IFS=',' read -r -a granted_scope_items <<< "$granted_scope"
|
|
247
|
+
for granted_scope_item in "${granted_scope_items[@]}"; do
|
|
248
|
+
granted_scope_item="${granted_scope_item#"${granted_scope_item%%[![:space:]]*}"}"
|
|
249
|
+
granted_scope_item="${granted_scope_item%"${granted_scope_item##*[![:space:]]}"}"
|
|
250
|
+
if [ "$granted_scope_item" = "$required_scope" ]; then
|
|
251
|
+
scope_match="true"
|
|
252
|
+
break
|
|
253
|
+
fi
|
|
254
|
+
done
|
|
255
|
+
|
|
256
|
+
mcc_match="true"
|
|
257
|
+
if [ "$scope_match" = "true" ]; then
|
|
258
|
+
mcc_verify_result=$(run_json_command "alipay-cli mcp call ar-query.queryArInfosBySalesProd" \
|
|
259
|
+
alipay-cli mcp call ar-query.queryArInfosBySalesProd \
|
|
260
|
+
-d "{\"request\":{\"salesProductCodes\":[\"${SALES_CODE}\"]},\"ctx\":{}}" \
|
|
261
|
+
--json) || {
|
|
262
|
+
echo "AUTH_FLOW:FAILED"
|
|
263
|
+
return 1
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
mcc_verify_unwrapped=$(unwrap_mcp "$mcc_verify_result")
|
|
267
|
+
mcc_error_type=$(detect_error "$mcc_verify_result")
|
|
268
|
+
if [ "$mcc_error_type" = "AUTH_MISMATCH" ]; then
|
|
269
|
+
mcc_match="false"
|
|
270
|
+
elif [ "$mcc_error_type" != "SUCCESS" ]; then
|
|
271
|
+
echo "❌ MCC 校验失败"
|
|
272
|
+
echo "AUTH_FLOW:FAILED"
|
|
273
|
+
return 1
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
# 保留对非标准错误响应的兼容,只在后端明确返回 MCC 未授权时判定不匹配。
|
|
277
|
+
mcc_error_code=$(echo "$mcc_verify_unwrapped" | jq -r '.errorCode // .data.errorCode // ""' 2>/dev/null)
|
|
278
|
+
mcc_success=$(echo "$mcc_verify_unwrapped" | jq -r '.success // "null"' 2>/dev/null)
|
|
279
|
+
if [ "$mcc_success" = "false" ] && [ -n "$mcc_error_code" ] && \
|
|
280
|
+
echo "$mcc_verify_unwrapped" | jq -r '.errorMessage // .data.errorMessage // .error.message // ""' 2>/dev/null | grep -qi "mccCode.*is not auth"; then
|
|
281
|
+
mcc_match="false"
|
|
282
|
+
elif echo "$mcc_verify_unwrapped" | grep -qi "mccCode.*is not auth"; then
|
|
283
|
+
mcc_match="false"
|
|
284
|
+
fi
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
if [ "$scope_match" = "true" ] && [ "$mcc_match" = "true" ]; then
|
|
288
|
+
echo "✅ 授权范围校验通过(scope + MCC)"
|
|
289
|
+
return 0
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
echo ""
|
|
293
|
+
echo "⚠️ 授权范围不满足"
|
|
294
|
+
if [ "$scope_match" != "true" ]; then
|
|
295
|
+
echo "📋 原因:scope 权限不满足"
|
|
296
|
+
echo "🤖 需要权限: $required_scope"
|
|
297
|
+
echo "🤖 已授权 scope: $granted_scope"
|
|
298
|
+
fi
|
|
299
|
+
if [ "$mcc_match" != "true" ]; then
|
|
300
|
+
echo "📋 原因:经营类目未授权"
|
|
301
|
+
echo "🤖 当前类目: $MCC_CODE"
|
|
302
|
+
fi
|
|
303
|
+
|
|
304
|
+
echo ""
|
|
305
|
+
echo "📋 必须通过 auth.sh mismatch 退出当前登录并重新授权"
|
|
306
|
+
if [ "$scope_match" != "true" ]; then
|
|
307
|
+
echo "AUTH_FLOW:SCOPE_MISMATCH"
|
|
308
|
+
else
|
|
309
|
+
echo "AUTH_FLOW:MCC_MISMATCH"
|
|
310
|
+
fi
|
|
311
|
+
return 1
|
|
312
|
+
}
|
|
313
|
+
|
|
197
314
|
# ─── auth init: 检查登录状态 + 执行登录 + 构建授权链接 + 输出授权信息 ────────
|
|
198
315
|
auth_init() {
|
|
199
316
|
SCOPE=""
|
|
@@ -230,26 +347,42 @@ auth_init() {
|
|
|
230
347
|
fi
|
|
231
348
|
|
|
232
349
|
# Step 1: 检查登录状态
|
|
233
|
-
CHECK_RESULT=$(run_json_command "alipay-cli whoami --json" alipay-cli whoami --json) ||
|
|
350
|
+
CHECK_RESULT=$(run_json_command "alipay-cli whoami --json" alipay-cli whoami --json) || {
|
|
351
|
+
echo "AUTH_FLOW:FAILED"
|
|
352
|
+
return 1
|
|
353
|
+
}
|
|
354
|
+
if ! handle_error "$CHECK_RESULT"; then
|
|
355
|
+
echo "AUTH_FLOW:FAILED"
|
|
356
|
+
return 1
|
|
357
|
+
fi
|
|
234
358
|
LOGGED_IN=$(echo "$CHECK_RESULT" | jq -r '.data.logged_in // false')
|
|
235
359
|
IS_EXPIRED=$(echo "$CHECK_RESULT" | jq -r '.data.is_expired // false')
|
|
236
360
|
|
|
237
361
|
if [ "$LOGGED_IN" = "true" ] && [ "$IS_EXPIRED" = "false" ]; then
|
|
362
|
+
validate_current_authorization "$CHECK_RESULT" || return 1
|
|
238
363
|
DEVICE_CODE=""
|
|
239
364
|
save_auth_state
|
|
240
|
-
echo "✅
|
|
365
|
+
echo "✅ 当前登录及授权范围有效,可继续后续流程"
|
|
241
366
|
echo "AUTH_FLOW:SKIP"
|
|
242
367
|
return 0
|
|
243
368
|
fi
|
|
244
369
|
|
|
245
370
|
# Step 2: 执行登录
|
|
246
|
-
LOGIN_RESULT=$(run_json_command "alipay-cli login --non-interactive --scope" alipay-cli login --non-interactive --scope "$SCOPE" --json) ||
|
|
371
|
+
LOGIN_RESULT=$(run_json_command "alipay-cli login --non-interactive --scope" alipay-cli login --non-interactive --scope "$SCOPE" --json) || {
|
|
372
|
+
echo "AUTH_FLOW:FAILED"
|
|
373
|
+
return 1
|
|
374
|
+
}
|
|
375
|
+
if ! handle_error "$LOGIN_RESULT"; then
|
|
376
|
+
echo "AUTH_FLOW:FAILED"
|
|
377
|
+
return 1
|
|
378
|
+
fi
|
|
247
379
|
LOGIN_STATUS=$(echo "$LOGIN_RESULT" | jq -r '.data.status // ""')
|
|
248
380
|
|
|
249
381
|
if [ "$LOGIN_STATUS" = "already_logged_in" ]; then
|
|
382
|
+
validate_current_authorization || return 1
|
|
250
383
|
DEVICE_CODE=""
|
|
251
384
|
save_auth_state
|
|
252
|
-
echo "✅
|
|
385
|
+
echo "✅ 当前登录及授权范围有效,可继续后续流程"
|
|
253
386
|
echo "AUTH_FLOW:SKIP"
|
|
254
387
|
return 0
|
|
255
388
|
fi
|
|
@@ -269,8 +402,21 @@ auth_init() {
|
|
|
269
402
|
exit 1
|
|
270
403
|
fi
|
|
271
404
|
|
|
272
|
-
# Step 5:
|
|
273
|
-
BROWSER_URL="https://aipay.alipay.com/cli-auth?deviceCode=${DEVICE_CODE}&productCode=${SALES_CODE}&mccCode=${MCC_CODE}
|
|
405
|
+
# Step 5: 构建并校验授权链接(禁止使用 CLI 返回的 verification_url)
|
|
406
|
+
BROWSER_URL="https://aipay.alipay.com/cli-auth?deviceCode=${DEVICE_CODE}&productCode=${SALES_CODE}&mccCode=${MCC_CODE}"
|
|
407
|
+
if [ -n "${DEV_TOOL_NAME:-}" ] && [ "$DEV_TOOL_NAME" != "unknown" ]; then
|
|
408
|
+
AUTH_PLATFORM=$(jq -nr --arg value "$DEV_TOOL_NAME" '$value|@uri')
|
|
409
|
+
BROWSER_URL="${BROWSER_URL}&platform=${AUTH_PLATFORM}"
|
|
410
|
+
fi
|
|
411
|
+
if [[ "$BROWSER_URL" != https://aipay.alipay.com/cli-auth\?* ]] ||
|
|
412
|
+
[[ "$BROWSER_URL" != *"deviceCode=${DEVICE_CODE}"* ]] ||
|
|
413
|
+
[[ "$BROWSER_URL" != *"productCode=${SALES_CODE}"* ]] ||
|
|
414
|
+
[[ "$BROWSER_URL" != *"mccCode=${MCC_CODE}"* ]] ||
|
|
415
|
+
[[ "$BROWSER_URL" == *"opengw.alipay.com/oauth/device"* ]]; then
|
|
416
|
+
echo "❌ 授权链接构建失败,缺少必要参数或误用了 CLI verification_url,已停止。"
|
|
417
|
+
echo "📋 正确格式: https://aipay.alipay.com/cli-auth?deviceCode=...&productCode=...&mccCode=..."
|
|
418
|
+
exit 1
|
|
419
|
+
fi
|
|
274
420
|
|
|
275
421
|
# Step 6: 保存参数到状态文件(供 confirm/mismatch 使用)
|
|
276
422
|
save_auth_state
|
|
@@ -304,7 +450,8 @@ auth_init() {
|
|
|
304
450
|
echo ""
|
|
305
451
|
echo "⚠️ 安全提示:请核对授权页面显示的确认码是否与上方一致,如不一致,请勿授权,立即停止操作!"
|
|
306
452
|
echo ""
|
|
307
|
-
echo "
|
|
453
|
+
echo "提示:无法跳链时,请复制下方链接到网页浏览器打开。"
|
|
454
|
+
echo "🌐 授权链接:[点击跳转进行授权](${BROWSER_URL})"
|
|
308
455
|
echo ""
|
|
309
456
|
echo "请在完成授权后告诉我\"好了\"继续后续流程。"
|
|
310
457
|
echo ""
|
|
@@ -349,103 +496,29 @@ auth_confirm() {
|
|
|
349
496
|
echo "AUTH_FLOW:EXPIRED"
|
|
350
497
|
return 0
|
|
351
498
|
else
|
|
352
|
-
|
|
353
|
-
|
|
499
|
+
if handle_error "$LOGIN_COMPLETE_RESULT"; then
|
|
500
|
+
echo "❌ 授权确认失败:返回结果未包含可识别的完成状态"
|
|
501
|
+
fi
|
|
354
502
|
cleanup_auth_state
|
|
355
503
|
echo "AUTH_FLOW:FAILED"
|
|
356
504
|
return 1
|
|
357
505
|
fi
|
|
358
506
|
|
|
359
|
-
# Step 3:
|
|
507
|
+
# Step 3: 复用 init 的 scope + MCC 校验
|
|
360
508
|
WHOAMI_RESULT=$(run_json_command "alipay-cli whoami --json" alipay-cli whoami --json) || {
|
|
361
509
|
echo "AUTH_FLOW:FAILED"
|
|
362
510
|
return 1
|
|
363
511
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
"I1080300001000041313") REQUIRED_SCOPE="auth_alipay_apppay:write" ;;
|
|
369
|
-
"I1080300001000160457") REQUIRED_SCOPE="machine_pay:write" ;;
|
|
370
|
-
*) echo "❌ 未知产品码: $SALES_CODE"; return 1 ;;
|
|
371
|
-
esac
|
|
372
|
-
|
|
373
|
-
SCOPE_MATCH="false"
|
|
374
|
-
if echo "$GRANTED_SCOPE" | grep -Fq "$REQUIRED_SCOPE"; then
|
|
375
|
-
SCOPE_MATCH="true"
|
|
376
|
-
fi
|
|
377
|
-
|
|
378
|
-
# Step 4: MCC 一致性校验
|
|
379
|
-
MCC_VERIFY_RESULT=$(run_json_command "alipay-cli mcp call ar-query.queryArInfosBySalesProd" \
|
|
380
|
-
alipay-cli mcp call ar-query.queryArInfosBySalesProd \
|
|
381
|
-
-d "{\"request\":{\"salesProductCodes\":[\"${SALES_CODE}\"]},\"ctx\":{}}" \
|
|
382
|
-
--json) || {
|
|
383
|
-
echo "AUTH_FLOW:FAILED"
|
|
384
|
-
return 1
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
# 先解包 MCP 信封再精确解析
|
|
388
|
-
MCC_VERIFY_UNWRAPPED=$(unwrap_mcp "$MCC_VERIFY_RESULT")
|
|
389
|
-
|
|
390
|
-
MCC_MATCH="true"
|
|
391
|
-
MCC_ERROR_TYPE=$(detect_error "$MCC_VERIFY_RESULT")
|
|
392
|
-
if [ "$MCC_ERROR_TYPE" = "AUTH_MISMATCH" ]; then
|
|
393
|
-
MCC_MATCH="false"
|
|
394
|
-
elif [ "$MCC_ERROR_TYPE" != "SUCCESS" ]; then
|
|
395
|
-
echo "❌ MCC 校验失败"
|
|
396
|
-
echo "AUTH_FLOW:FAILED"
|
|
397
|
-
return 1
|
|
398
|
-
fi
|
|
399
|
-
|
|
400
|
-
# 精确解析 errorCode 字段判断 MCC 授权异常,避免 grep 全文匹配误判
|
|
401
|
-
MCC_ERROR_CODE=$(echo "$MCC_VERIFY_UNWRAPPED" | jq -r '.errorCode // .data.errorCode // ""' 2>/dev/null)
|
|
402
|
-
MCC_SUCCESS=$(echo "$MCC_VERIFY_UNWRAPPED" | jq -r '.success // "null"' 2>/dev/null)
|
|
403
|
-
# 优先路径:success=false 且 errorCode 存在,且 errorMessage 精确包含 mccCode is not auth
|
|
404
|
-
# 这条路径确保仅在后端明确返回授权类错误时才判定为 MCC 不匹配,避免正常数据误触发
|
|
405
|
-
if [ "$MCC_SUCCESS" = "false" ] && [ -n "$MCC_ERROR_CODE" ] && echo "$MCC_VERIFY_UNWRAPPED" | jq -r '.errorMessage // .data.errorMessage // .error.message // ""' 2>/dev/null | grep -qi "mccCode.*is not auth"; then
|
|
406
|
-
MCC_MATCH="false"
|
|
407
|
-
# 兜底路径:解包后的全文包含 "mccCode is not auth"(覆盖非 JSON 格式的错误响应)
|
|
408
|
-
elif echo "$MCC_VERIFY_UNWRAPPED" | grep -qi "mccCode.*is not auth"; then
|
|
409
|
-
MCC_MATCH="false"
|
|
410
|
-
fi
|
|
411
|
-
|
|
412
|
-
# Step 5: 判断校验结果
|
|
413
|
-
if [ "$SCOPE_MATCH" = "true" ] && [ "$MCC_MATCH" = "true" ]; then
|
|
414
|
-
echo "✅ 授权范围校验通过(scope + MCC)"
|
|
415
|
-
cleanup_auth_state
|
|
416
|
-
echo "AUTH_FLOW:AUTH_SUCCESS"
|
|
417
|
-
return 0
|
|
418
|
-
fi
|
|
419
|
-
|
|
420
|
-
# Step 6: 校验不通过,输出原因
|
|
421
|
-
echo ""
|
|
422
|
-
echo "⚠️ 授权范围不满足,正在退出登录..."
|
|
423
|
-
|
|
424
|
-
if [ "$SCOPE_MATCH" != "true" ]; then
|
|
425
|
-
echo "📋 原因:scope 权限不满足"
|
|
426
|
-
echo "🤖 需要权限: $REQUIRED_SCOPE"
|
|
427
|
-
echo "🤖 已授权 scope: $GRANTED_SCOPE"
|
|
428
|
-
fi
|
|
429
|
-
if [ "$MCC_MATCH" != "true" ]; then
|
|
430
|
-
echo "📋 原因:经营类目未授权"
|
|
431
|
-
echo "🤖 当前类目: $MCC_CODE"
|
|
432
|
-
fi
|
|
433
|
-
|
|
434
|
-
LOGOUT_RESULT=$(export PLATFORM=${DEV_TOOL_NAME} && alipay-cli logout --json 2>/dev/null)
|
|
435
|
-
echo "✅ 已退出当前登录"
|
|
436
|
-
echo ""
|
|
437
|
-
echo "📋 需要重新执行授权流程"
|
|
438
|
-
|
|
439
|
-
if [ "$SCOPE_MATCH" != "true" ]; then
|
|
440
|
-
echo "AUTH_FLOW:SCOPE_MISMATCH"
|
|
441
|
-
else
|
|
442
|
-
echo "AUTH_FLOW:MCC_MISMATCH"
|
|
443
|
-
fi
|
|
444
|
-
return 1
|
|
512
|
+
validate_current_authorization "$WHOAMI_RESULT" || return 1
|
|
513
|
+
cleanup_auth_state
|
|
514
|
+
echo "AUTH_FLOW:AUTH_SUCCESS"
|
|
515
|
+
return 0
|
|
445
516
|
}
|
|
446
517
|
|
|
447
|
-
# ─── auth mismatch: 授权范围不满足 → logout
|
|
518
|
+
# ─── auth mismatch: 授权范围不满足 → logout → 重新授权 ──────────────────────
|
|
448
519
|
auth_mismatch() {
|
|
520
|
+
local logout_result logout_error_type
|
|
521
|
+
|
|
449
522
|
# 优先使用显式传入的非敏感上下文;未传时再从状态文件兜底恢复。
|
|
450
523
|
restore_auth_context_or_state "$@" || exit 1
|
|
451
524
|
|
|
@@ -454,19 +527,28 @@ auth_mismatch() {
|
|
|
454
527
|
return 1
|
|
455
528
|
fi
|
|
456
529
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
# Step 1: 退出登录
|
|
460
|
-
LOGOUT_RESULT=$(export PLATFORM=${DEV_TOOL_NAME} && alipay-cli logout --json 2>/dev/null)
|
|
461
|
-
echo "📋 已退出当前登录"
|
|
462
|
-
|
|
463
|
-
# Step 2: 根据 salesCode 构造正确的 scope
|
|
530
|
+
# Step 1: 根据 salesCode 构造正确的 scope;目标无效时不退出当前登录。
|
|
464
531
|
SCOPE=$(get_scope "$SALES_CODE")
|
|
465
532
|
if [ -z "$SCOPE" ]; then
|
|
466
533
|
echo "❌ 未知产品码: $SALES_CODE"
|
|
467
534
|
return 1
|
|
468
535
|
fi
|
|
469
536
|
|
|
537
|
+
# Step 2: mismatch 是授权不匹配恢复路径中唯一执行 logout 的入口。
|
|
538
|
+
echo "⚠️ 授权范围不满足,正在退出当前登录并准备重新授权..."
|
|
539
|
+
logout_result=$(run_json_command "alipay-cli logout --json" alipay-cli logout --json) || {
|
|
540
|
+
echo "❌ 退出当前登录失败,已停止重新授权"
|
|
541
|
+
echo "AUTH_FLOW:FAILED"
|
|
542
|
+
return 1
|
|
543
|
+
}
|
|
544
|
+
logout_error_type=$(detect_error "$logout_result")
|
|
545
|
+
if [ "$logout_error_type" != "SUCCESS" ]; then
|
|
546
|
+
echo "❌ 退出当前登录失败,已停止重新授权"
|
|
547
|
+
echo "AUTH_FLOW:FAILED"
|
|
548
|
+
return 1
|
|
549
|
+
fi
|
|
550
|
+
echo "✅ 已退出当前登录"
|
|
551
|
+
|
|
470
552
|
# Step 3: 调用 auth init 执行登录 + 构建授权链接 + 输出授权信息
|
|
471
553
|
# DEV_TOOL_NAME 已 export,子进程可继承
|
|
472
554
|
echo "📋 请重新扫码授权,新的授权将包含正确的权限范围"
|
|
@@ -23,9 +23,87 @@ fi
|
|
|
23
23
|
# { "content": [ { "text": "<业务JSON>", "type": "text" } ] }
|
|
24
24
|
# 业务 JSON(含 success / resultObj / errorCode)被包在 content[0].text 里。
|
|
25
25
|
# 本函数负责解包:若检测到信封结构,提取 content[0].text;否则原样返回。
|
|
26
|
+
# 当 stdout 混有日志时,只接受唯一可确定的 MCP 信封或 JSON;候选不唯一时
|
|
27
|
+
# 保留原始文本,让后续错误检测安全阻断,禁止猜测业务响应。
|
|
26
28
|
#
|
|
27
29
|
# 对于非 mcp call 的命令(如 alipay-cli login / whoami),返回不带信封,
|
|
28
30
|
# 保证这些命令的原样透传。
|
|
31
|
+
extract_unique_json() {
|
|
32
|
+
local RAW="$1"
|
|
33
|
+
printf '%s' "$RAW" | jq -Rsc '
|
|
34
|
+
explode as $chars |
|
|
35
|
+
reduce range(0; ($chars | length)) as $index (
|
|
36
|
+
{
|
|
37
|
+
depth: 0,
|
|
38
|
+
start: null,
|
|
39
|
+
in_string: false,
|
|
40
|
+
escaped: false,
|
|
41
|
+
raw_candidates: []
|
|
42
|
+
};
|
|
43
|
+
($chars[$index]) as $char |
|
|
44
|
+
if .depth == 0 then
|
|
45
|
+
if $char == 123 or $char == 91 then
|
|
46
|
+
.depth = 1 |
|
|
47
|
+
.start = $index |
|
|
48
|
+
.in_string = false |
|
|
49
|
+
.escaped = false
|
|
50
|
+
else
|
|
51
|
+
.
|
|
52
|
+
end
|
|
53
|
+
elif .in_string then
|
|
54
|
+
if .escaped then
|
|
55
|
+
.escaped = false
|
|
56
|
+
elif $char == 92 then
|
|
57
|
+
.escaped = true
|
|
58
|
+
elif $char == 34 then
|
|
59
|
+
.in_string = false
|
|
60
|
+
else
|
|
61
|
+
.
|
|
62
|
+
end
|
|
63
|
+
elif $char == 34 then
|
|
64
|
+
.in_string = true
|
|
65
|
+
elif $char == 123 or $char == 91 then
|
|
66
|
+
.depth += 1
|
|
67
|
+
elif $char == 125 or $char == 93 then
|
|
68
|
+
.depth -= 1 |
|
|
69
|
+
if .depth == 0 then
|
|
70
|
+
($chars[.start:($index + 1)] | implode) as $candidate |
|
|
71
|
+
.raw_candidates += [$candidate] |
|
|
72
|
+
.start = null
|
|
73
|
+
else
|
|
74
|
+
.
|
|
75
|
+
end
|
|
76
|
+
else
|
|
77
|
+
.
|
|
78
|
+
end
|
|
79
|
+
) |
|
|
80
|
+
[
|
|
81
|
+
.raw_candidates[] |
|
|
82
|
+
fromjson? |
|
|
83
|
+
select(type == "object" or type == "array")
|
|
84
|
+
] |
|
|
85
|
+
unique as $candidates |
|
|
86
|
+
(
|
|
87
|
+
$candidates |
|
|
88
|
+
map(select(
|
|
89
|
+
type == "object" and
|
|
90
|
+
(.content | type) == "array" and
|
|
91
|
+
any(.content[]?; (.text? | type) == "string")
|
|
92
|
+
)) |
|
|
93
|
+
unique
|
|
94
|
+
) as $envelopes |
|
|
95
|
+
if ($envelopes | length) == 1 then
|
|
96
|
+
{status: "ok", value: $envelopes[0]}
|
|
97
|
+
elif ($envelopes | length) == 0 and ($candidates | length) == 1 then
|
|
98
|
+
{status: "ok", value: $candidates[0]}
|
|
99
|
+
elif ($candidates | length) == 0 then
|
|
100
|
+
{status: "none"}
|
|
101
|
+
else
|
|
102
|
+
{status: "ambiguous"}
|
|
103
|
+
end
|
|
104
|
+
' 2>/dev/null
|
|
105
|
+
}
|
|
106
|
+
|
|
29
107
|
unwrap_mcp() {
|
|
30
108
|
local RAW="$1"
|
|
31
109
|
if [ -z "$RAW" ]; then
|
|
@@ -33,17 +111,30 @@ unwrap_mcp() {
|
|
|
33
111
|
return
|
|
34
112
|
fi
|
|
35
113
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
114
|
+
local ANALYSIS STATUS JSON
|
|
115
|
+
ANALYSIS=$(extract_unique_json "$RAW")
|
|
116
|
+
STATUS=$(echo "$ANALYSIS" | jq -r '.status // "none"' 2>/dev/null)
|
|
117
|
+
case "$STATUS" in
|
|
118
|
+
ok)
|
|
119
|
+
JSON=$(echo "$ANALYSIS" | jq -c '.value' 2>/dev/null)
|
|
120
|
+
;;
|
|
121
|
+
ambiguous)
|
|
122
|
+
echo "CLI 输出包含多个 JSON 候选,无法唯一解析"
|
|
123
|
+
echo "$RAW"
|
|
124
|
+
return
|
|
125
|
+
;;
|
|
126
|
+
*)
|
|
127
|
+
echo "$RAW"
|
|
128
|
+
return
|
|
129
|
+
;;
|
|
130
|
+
esac
|
|
40
131
|
|
|
41
132
|
local TEXT
|
|
42
|
-
TEXT=$(echo "$
|
|
133
|
+
TEXT=$(echo "$JSON" | jq -r 'if (.content | type == "array") and (.content[0].text != null) then .content[0].text else empty end' 2>/dev/null)
|
|
43
134
|
if [ -n "$TEXT" ]; then
|
|
44
135
|
echo "$TEXT"
|
|
45
136
|
else
|
|
46
|
-
echo "$
|
|
137
|
+
echo "$JSON"
|
|
47
138
|
fi
|
|
48
139
|
}
|
|
49
140
|
|
|
@@ -103,11 +194,36 @@ extract_error_message() {
|
|
|
103
194
|
fi
|
|
104
195
|
|
|
105
196
|
echo "$JSON" | jq -r '
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
197
|
+
if .data.success? == false then
|
|
198
|
+
[
|
|
199
|
+
.data.subMsg?,
|
|
200
|
+
.data.msg?,
|
|
201
|
+
.subMsg?,
|
|
202
|
+
.errorMessage?,
|
|
203
|
+
.errorMsg?,
|
|
204
|
+
.message?,
|
|
205
|
+
(.error? | objects | .message?),
|
|
206
|
+
.msg?,
|
|
207
|
+
(
|
|
208
|
+
.. | objects |
|
|
209
|
+
(.errorMessage?, .errorMsg?, .message?, (.error? | objects | .message?))
|
|
210
|
+
)
|
|
211
|
+
] |
|
|
212
|
+
[
|
|
213
|
+
.[] |
|
|
214
|
+
select(
|
|
215
|
+
. != null and
|
|
216
|
+
(tostring != "") and
|
|
217
|
+
((tostring | ascii_downcase) != "success")
|
|
218
|
+
)
|
|
219
|
+
][0] // "未知错误"
|
|
220
|
+
else
|
|
221
|
+
[
|
|
222
|
+
.. | objects |
|
|
223
|
+
(.errorMessage?, .errorMsg?, .message?, (.error? | objects | .message?)) |
|
|
224
|
+
select(. != null and (tostring != ""))
|
|
225
|
+
][0] // "未知错误"
|
|
226
|
+
end
|
|
111
227
|
' 2>/dev/null
|
|
112
228
|
}
|
|
113
229
|
|
|
@@ -244,6 +360,8 @@ detect_error() {
|
|
|
244
360
|
|
|
245
361
|
# ─── MCP 认证错误处理 ──────────────────────────────────────────────────────
|
|
246
362
|
handle_mcp_auth_error() {
|
|
363
|
+
local LOGOUT_RESULT LOGOUT_EXIT_CODE LOGOUT_ERROR_TYPE LOGOUT_ERROR_MSG
|
|
364
|
+
|
|
247
365
|
echo ""
|
|
248
366
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
249
367
|
echo "🔐 未登录或授权已过期"
|
|
@@ -255,7 +373,22 @@ handle_mcp_auth_error() {
|
|
|
255
373
|
echo "📋 说明:Authorization is empty(认证信息为空)"
|
|
256
374
|
echo ""
|
|
257
375
|
echo "🔄 正在退出当前登录..."
|
|
258
|
-
|
|
376
|
+
LOGOUT_RESULT=$(PLATFORM=${DEV_TOOL_NAME:-unknown} alipay-cli logout --json 2>&1)
|
|
377
|
+
LOGOUT_EXIT_CODE=$?
|
|
378
|
+
LOGOUT_ERROR_TYPE=$(detect_error "$LOGOUT_RESULT")
|
|
379
|
+
|
|
380
|
+
if [ "$LOGOUT_EXIT_CODE" -ne 0 ] || [ "$LOGOUT_ERROR_TYPE" != "SUCCESS" ]; then
|
|
381
|
+
echo ""
|
|
382
|
+
echo "❌ 退出当前登录失败,已停止重新授权"
|
|
383
|
+
if echo "$LOGOUT_RESULT" | jq -e . >/dev/null 2>&1; then
|
|
384
|
+
LOGOUT_ERROR_MSG=$(extract_error_message "$LOGOUT_RESULT")
|
|
385
|
+
echo "📋 错误信息:$LOGOUT_ERROR_MSG"
|
|
386
|
+
elif [ -n "$LOGOUT_RESULT" ]; then
|
|
387
|
+
echo "📋 错误信息:$LOGOUT_RESULT"
|
|
388
|
+
fi
|
|
389
|
+
echo "📋 请先重试退出登录;确认退出成功后再重新授权。"
|
|
390
|
+
return 1
|
|
391
|
+
fi
|
|
259
392
|
|
|
260
393
|
echo ""
|
|
261
394
|
echo "✅ 已退出登录状态"
|
|
@@ -265,7 +398,7 @@ handle_mcp_auth_error() {
|
|
|
265
398
|
echo " 2. 您使用支付宝扫码授权"
|
|
266
399
|
echo " 3. 授权成功后继续当前操作"
|
|
267
400
|
echo ""
|
|
268
|
-
echo "
|
|
401
|
+
echo "📌 产品、MCC 和授权范围未变化时,沿用已有确认立即重新生成授权链接;发生变化时先重新确认受影响内容。"
|
|
269
402
|
}
|
|
270
403
|
|
|
271
404
|
# ─── MCP 服务不可用处理 ────────────────────────────────────────────────────
|
|
@@ -322,11 +455,8 @@ handle_auth_mismatch() {
|
|
|
322
455
|
|
|
323
456
|
echo ""
|
|
324
457
|
echo ""
|
|
325
|
-
|
|
326
|
-
echo "
|
|
327
|
-
export PLATFORM=${DEV_TOOL_NAME:-unknown} && alipay-cli logout --json 2>/dev/null
|
|
328
|
-
|
|
329
|
-
echo "✅ 已退出登录,请重新执行登录授权流程"
|
|
458
|
+
echo "⛔ 已停止当前操作和后续调用"
|
|
459
|
+
echo "📋 请调用 auth.sh mismatch;该命令会统一退出当前登录并使用正确范围重新授权"
|
|
330
460
|
}
|
|
331
461
|
|
|
332
462
|
# ─── 后端业务错误处理(完整透出错误信息) ──────────────────────────────────
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
#=============================================================================
|
|
3
3
|
# 脚本名称: query_sign_status.sh
|
|
4
4
|
# 功能描述: 查询签约状态并根据结果判断后续流程
|
|
5
|
-
# 调用位置: Step 3.1
|
|
5
|
+
# 调用位置: Step 3.1 状态与资源查询
|
|
6
6
|
# 调用前置: 脚本通过 error_handler.sh 间接初始化 DEV_TOOL_NAME;需传入 --sales-code,或兼容使用 SALES_CODE 环境变量
|
|
7
7
|
# 用法: bash query_sign_status.sh --sales-code <code> [--product-type aipay|webpay|apppay]
|
|
8
8
|
# 返回值: 输出签约状态判断结果和处理建议
|
|
@@ -73,15 +73,15 @@ emit_signed_flow() {
|
|
|
73
73
|
case "$SALES_CODE" in
|
|
74
74
|
"I1080300001000041203")
|
|
75
75
|
echo "FLOW:PC_WEB_SIGNED"
|
|
76
|
-
#
|
|
76
|
+
# 网站支付已生效或已提交:Step 4 只处理应用决策,Step 5 跳过签约提交
|
|
77
77
|
;;
|
|
78
78
|
"I1080300001000041313")
|
|
79
79
|
echo "FLOW:APP_SIGNED"
|
|
80
|
-
# APP
|
|
80
|
+
# APP支付已生效或已提交:Step 4 只处理应用决策,Step 5 跳过签约提交
|
|
81
81
|
;;
|
|
82
82
|
"I1080300001000160457")
|
|
83
83
|
echo "FLOW:AI_PAY_SIGNED"
|
|
84
|
-
#
|
|
84
|
+
# 按量付费已生效或已提交:Step 4 处理服务/应用决策,Step 5 跳过签约提交
|
|
85
85
|
;;
|
|
86
86
|
esac
|
|
87
87
|
}
|
|
@@ -99,8 +99,14 @@ fi
|
|
|
99
99
|
# 解包 MCP 信封后解析业务字段
|
|
100
100
|
BUSINESS=$(unwrap_mcp "$RESULT")
|
|
101
101
|
|
|
102
|
-
#
|
|
103
|
-
AR_LIST=$(echo "$BUSINESS" | jq -c '
|
|
102
|
+
# 只有明确返回数组才可判断签约状态;缺少字段必须阻断,避免误判为未签约
|
|
103
|
+
AR_LIST=$(echo "$BUSINESS" | jq -c '
|
|
104
|
+
if (.resultObj | type) == "object" and (.resultObj.arInfoList | type) == "array" then
|
|
105
|
+
.resultObj.arInfoList
|
|
106
|
+
else
|
|
107
|
+
null
|
|
108
|
+
end
|
|
109
|
+
' 2>/dev/null)
|
|
104
110
|
if [ -z "$AR_LIST" ] || ! echo "$AR_LIST" | jq -e 'type == "array"' >/dev/null 2>&1; then
|
|
105
111
|
echo "❌ 签约状态返回结构异常,无法解析 arInfoList"
|
|
106
112
|
exit 1
|
|
@@ -108,7 +114,7 @@ fi
|
|
|
108
114
|
AR_COUNT=$(echo "$AR_LIST" | jq 'length')
|
|
109
115
|
|
|
110
116
|
if [ "$AR_COUNT" -eq 0 ]; then
|
|
111
|
-
echo "📋 未签约 (NOT_SIGNED)
|
|
117
|
+
echo "📋 签约状态: 未签约 (NOT_SIGNED);继续 Step 4 一次性资料与资源决策"
|
|
112
118
|
echo "SIGN_STATUS=NOT_SIGNED"
|
|
113
119
|
|
|
114
120
|
case "$SALES_CODE" in
|
|
@@ -122,16 +128,16 @@ if [ "$AR_COUNT" -eq 0 ]; then
|
|
|
122
128
|
;;
|
|
123
129
|
"I1080300001000160457")
|
|
124
130
|
echo "FLOW:AI_PAY_NOT_SIGNED"
|
|
125
|
-
#
|
|
131
|
+
# 按量付费未签约:Step 4 无需页面图片,但仍完成服务/应用决策
|
|
126
132
|
;;
|
|
127
133
|
esac
|
|
128
134
|
else
|
|
129
|
-
#
|
|
135
|
+
# AR_LIST 已在上方确认为数组
|
|
130
136
|
HAS_EFFECTIVE=$(echo "$AR_LIST" | jq -r '[.[] | select(.arStatus == "02")] | length')
|
|
131
137
|
HAS_SUBMITTED=$(echo "$AR_LIST" | jq -r '[.[] | select(.arStatus == "01")] | length')
|
|
132
138
|
|
|
133
139
|
if [ "$HAS_EFFECTIVE" -gt 0 ]; then
|
|
134
|
-
echo "✅
|
|
140
|
+
echo "✅ 签约已生效;无需签约材料,继续 Step 4 资源决策"
|
|
135
141
|
echo "SIGN_STATUS=SIGNED_EFFECTIVE"
|
|
136
142
|
emit_signed_flow
|
|
137
143
|
elif [ "$HAS_SUBMITTED" -gt 0 ]; then
|
|
@@ -139,7 +145,7 @@ else
|
|
|
139
145
|
echo "SIGN_STATUS=SIGN_SUBMITTED"
|
|
140
146
|
emit_signed_flow
|
|
141
147
|
else
|
|
142
|
-
echo "📋
|
|
148
|
+
echo "📋 其他签约状态;保留已取得的只读结果并将签约分支标记为待核验"
|
|
143
149
|
echo "SIGN_STATUS=OTHER_STATUS"
|
|
144
150
|
echo "FLOW:OTHER_STATUS"
|
|
145
151
|
fi
|