@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
|
@@ -3,15 +3,19 @@
|
|
|
3
3
|
# 脚本名称: auth.sh
|
|
4
4
|
# 功能描述: 登录授权全流程 - 检查状态 + 执行登录 + 授权确认 + scope/MCC 校验 + 重新授权
|
|
5
5
|
# 调用位置: Step 3 登录授权
|
|
6
|
-
# 调用前置: 脚本通过 error_handler.sh 间接初始化 DEV_TOOL_NAME;必要时可用 DEV_TOOL_NAME 环境变量覆盖
|
|
6
|
+
# 调用前置: 脚本通过 error_handler.sh 间接初始化 DEV_TOOL_NAME/PLATFORM_ID;必要时可用 DEV_TOOL_NAME 环境变量覆盖
|
|
7
7
|
# 用法:
|
|
8
|
-
# auth init --scope <scope> --sales-code <code> --mcc-code <code>
|
|
8
|
+
# auth init --defer-open --scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>
|
|
9
|
+
# auth open [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>]
|
|
9
10
|
# auth confirm [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>]
|
|
10
|
-
# auth
|
|
11
|
+
# auth wait [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>]
|
|
12
|
+
# auth mismatch --defer-open [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>]
|
|
11
13
|
# 返回值:
|
|
12
|
-
# init: AUTH_FLOW:SKIP | AUTH_FLOW:
|
|
13
|
-
#
|
|
14
|
-
#
|
|
14
|
+
# init: AUTH_FLOW:SKIP | AUTH_FLOW:READY_TO_OPEN | AUTH_FLOW:SCOPE_MISMATCH | AUTH_FLOW:MCC_MISMATCH | AUTH_FLOW:RETRY_WITH_NETWORK | AUTH_FLOW:FAILED
|
|
15
|
+
# wait: AUTH_FLOW:AUTH_SUCCESS | AUTH_FLOW:PENDING | AUTH_FLOW:EXPIRED | AUTH_FLOW:SCOPE_MISMATCH | AUTH_FLOW:MCC_MISMATCH | AUTH_FLOW:CONTEXT_MISMATCH | AUTH_FLOW:RETRY_WITH_NETWORK | AUTH_FLOW:FAILED
|
|
16
|
+
# confirm: AUTH_FLOW:AUTH_SUCCESS | AUTH_FLOW:PENDING | AUTH_FLOW:EXPIRED | AUTH_FLOW:SCOPE_MISMATCH | AUTH_FLOW:MCC_MISMATCH | AUTH_FLOW:CONTEXT_MISMATCH | AUTH_FLOW:RETRY_WITH_NETWORK | AUTH_FLOW:FAILED
|
|
17
|
+
# mismatch: logout 可确认成功后调用 auth init 重新生成授权链接;上下文冲突时输出 AUTH_FLOW:CONTEXT_MISMATCH 且不 logout;无法确认 CLI 结果时输出 AUTH_FLOW:RETRY_WITH_NETWORK
|
|
18
|
+
# stdout: 仅登记对客正文;AUTH_FLOW/状态/查询复用标记写入 stderr
|
|
15
19
|
#=============================================================================
|
|
16
20
|
|
|
17
21
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
@@ -23,22 +27,113 @@ else
|
|
|
23
27
|
echo "❌ 缺少错误处理脚本: ${SCRIPT_DIR}/error_handler.sh"
|
|
24
28
|
exit 1
|
|
25
29
|
fi
|
|
30
|
+
source "${SCRIPT_DIR}/network_retry.sh"
|
|
31
|
+
source "${SCRIPT_DIR}/sign_status_parser.sh"
|
|
32
|
+
RENDERER="${SCRIPT_DIR}/../../../normal/scripts/render_customer_message.mjs"
|
|
26
33
|
|
|
27
34
|
require_command jq || exit 1
|
|
28
35
|
require_command alipay-cli || exit 1
|
|
36
|
+
require_command node || exit 1
|
|
29
37
|
require_command mktemp || exit 1
|
|
38
|
+
umask 077
|
|
30
39
|
|
|
31
40
|
# ─── 授权状态文件(用于独立执行 confirm/mismatch 时兜底恢复非敏感参数)────
|
|
32
41
|
AUTH_STATE_OWNER="$(id -u 2>/dev/null || printf '%s' "${USER:-unknown}" | tr -c 'A-Za-z0-9_-' '_')"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
AUTH_STATE_BASE="${TMPDIR:-/tmp}"
|
|
43
|
+
AUTH_STATE_BASE="${AUTH_STATE_BASE%/}"
|
|
44
|
+
if [ -n "${ALIPAY_AUTH_STATE_FILE:-}" ]; then
|
|
45
|
+
AUTH_STATE_FILE="$ALIPAY_AUTH_STATE_FILE"
|
|
46
|
+
AUTH_STATE_DIR="$(dirname "$AUTH_STATE_FILE")"
|
|
47
|
+
AUTH_STATE_DIR_MANAGED=false
|
|
48
|
+
else
|
|
49
|
+
AUTH_STATE_DIR="${AUTH_STATE_BASE}/alipay-aipay-auth-${AUTH_STATE_OWNER}"
|
|
50
|
+
AUTH_STATE_FILE="${AUTH_STATE_DIR}/state.json"
|
|
51
|
+
AUTH_STATE_DIR_MANAGED=true
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
secure_mode() {
|
|
55
|
+
local target="$1" expected="$2" mode
|
|
56
|
+
if mode=$(stat -f '%Lp' "$target" 2>/dev/null); then
|
|
57
|
+
:
|
|
58
|
+
elif mode=$(stat -c '%a' "$target" 2>/dev/null); then
|
|
59
|
+
:
|
|
60
|
+
else
|
|
61
|
+
return 1
|
|
62
|
+
fi
|
|
63
|
+
[ "$mode" = "$expected" ]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
prepare_auth_state_dir() {
|
|
67
|
+
if [ "$AUTH_STATE_DIR_MANAGED" = true ]; then
|
|
68
|
+
if [ -L "$AUTH_STATE_DIR" ] || { [ -e "$AUTH_STATE_DIR" ] && [ ! -d "$AUTH_STATE_DIR" ]; }; then
|
|
69
|
+
echo "❌ 授权临时目录不安全,已停止" >&2
|
|
70
|
+
return 1
|
|
71
|
+
fi
|
|
72
|
+
mkdir -p "$AUTH_STATE_DIR" || return 1
|
|
73
|
+
chmod 700 "$AUTH_STATE_DIR" 2>/dev/null || return 1
|
|
74
|
+
secure_mode "$AUTH_STATE_DIR" 700 || return 1
|
|
75
|
+
else
|
|
76
|
+
[ -d "$AUTH_STATE_DIR" ] && [ ! -L "$AUTH_STATE_DIR" ] || {
|
|
77
|
+
echo "❌ 指定的授权状态目录不可用或不安全,已停止" >&2
|
|
78
|
+
return 1
|
|
79
|
+
}
|
|
80
|
+
fi
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
validate_auth_state_file() {
|
|
84
|
+
[ -f "$AUTH_STATE_FILE" ] && [ ! -L "$AUTH_STATE_FILE" ] || return 1
|
|
85
|
+
chmod 600 "$AUTH_STATE_FILE" 2>/dev/null || return 1
|
|
86
|
+
secure_mode "$AUTH_STATE_FILE" 600
|
|
87
|
+
}
|
|
36
88
|
|
|
37
89
|
cleanup_auth_state() {
|
|
38
90
|
rm -f "$AUTH_STATE_FILE"
|
|
39
91
|
}
|
|
40
92
|
|
|
93
|
+
reset_auth_state_for_init() {
|
|
94
|
+
local field current saved
|
|
95
|
+
AUTH_INIT_CONTEXT_MISMATCH_TYPE=""
|
|
96
|
+
[ -e "$AUTH_STATE_FILE" ] || [ -L "$AUTH_STATE_FILE" ] || return 0
|
|
97
|
+
if ! prepare_auth_state_dir || ! validate_auth_state_file; then
|
|
98
|
+
echo "❌ 授权状态文件不可用或不安全,已停止"
|
|
99
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
100
|
+
return 1
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# 新上下文通过机器校验后才读取旧状态;只记录差异类型,不复用旧 deviceCode。
|
|
104
|
+
if jq -e 'type == "object"' "$AUTH_STATE_FILE" >/dev/null 2>&1; then
|
|
105
|
+
for field in salesCode scope productName mccCode mccName; do
|
|
106
|
+
case "$field" in
|
|
107
|
+
salesCode) current="$SALES_CODE" ;;
|
|
108
|
+
scope) current="$SCOPE" ;;
|
|
109
|
+
productName) current="$PRODUCT_NAME" ;;
|
|
110
|
+
mccCode) current="$MCC_CODE" ;;
|
|
111
|
+
mccName) current="$MCC_NAME" ;;
|
|
112
|
+
esac
|
|
113
|
+
saved=$(jq -r --arg field "$field" '.[$field] // ""' "$AUTH_STATE_FILE")
|
|
114
|
+
if [ "$current" != "$saved" ]; then
|
|
115
|
+
case "$field" in
|
|
116
|
+
salesCode|scope|productName) AUTH_INIT_CONTEXT_MISMATCH_TYPE="SCOPE_MISMATCH" ;;
|
|
117
|
+
*) [ -n "$AUTH_INIT_CONTEXT_MISMATCH_TYPE" ] || AUTH_INIT_CONTEXT_MISMATCH_TYPE="MCC_MISMATCH" ;;
|
|
118
|
+
esac
|
|
119
|
+
fi
|
|
120
|
+
done
|
|
121
|
+
else
|
|
122
|
+
# 无法证明旧状态属于当前产品上下文时,已有登录也必须重新授权。
|
|
123
|
+
AUTH_INIT_CONTEXT_MISMATCH_TYPE="SCOPE_MISMATCH"
|
|
124
|
+
fi
|
|
125
|
+
cleanup_auth_state
|
|
126
|
+
}
|
|
127
|
+
|
|
41
128
|
save_auth_state() {
|
|
129
|
+
local temp_state
|
|
130
|
+
prepare_auth_state_dir || return 1
|
|
131
|
+
if [ -L "$AUTH_STATE_FILE" ] || { [ -e "$AUTH_STATE_FILE" ] && [ ! -f "$AUTH_STATE_FILE" ]; }; then
|
|
132
|
+
echo "❌ 授权状态路径不是安全的普通文件,已停止" >&2
|
|
133
|
+
return 1
|
|
134
|
+
fi
|
|
135
|
+
temp_state=$(mktemp "${AUTH_STATE_DIR}/.auth-state.XXXXXX") || return 1
|
|
136
|
+
chmod 600 "$temp_state" 2>/dev/null || { rm -f "$temp_state"; return 1; }
|
|
42
137
|
jq -n \
|
|
43
138
|
--arg salesCode "$SALES_CODE" \
|
|
44
139
|
--arg mccCode "$MCC_CODE" \
|
|
@@ -46,19 +141,26 @@ save_auth_state() {
|
|
|
46
141
|
--arg productName "${PRODUCT_NAME:-}" \
|
|
47
142
|
--arg mccName "${MCC_NAME:-}" \
|
|
48
143
|
--arg deviceCode "${DEVICE_CODE:-}" \
|
|
144
|
+
--arg authorizationUrl "${BROWSER_URL:-}" \
|
|
49
145
|
'{
|
|
50
146
|
salesCode: $salesCode,
|
|
51
147
|
mccCode: $mccCode,
|
|
52
148
|
scope: $scope,
|
|
53
149
|
productName: $productName,
|
|
54
150
|
mccName: $mccName,
|
|
55
|
-
deviceCode: $deviceCode
|
|
56
|
-
|
|
57
|
-
|
|
151
|
+
deviceCode: $deviceCode,
|
|
152
|
+
authorizationUrl: $authorizationUrl
|
|
153
|
+
}' > "$temp_state" || { rm -f "$temp_state"; return 1; }
|
|
154
|
+
mv "$temp_state" "$AUTH_STATE_FILE" || { rm -f "$temp_state"; return 1; }
|
|
155
|
+
validate_auth_state_file || {
|
|
156
|
+
cleanup_auth_state
|
|
157
|
+
echo "❌ 无法确认授权状态文件权限为 0600,已停止" >&2
|
|
158
|
+
return 1
|
|
159
|
+
}
|
|
58
160
|
}
|
|
59
161
|
|
|
60
162
|
load_auth_state() {
|
|
61
|
-
if
|
|
163
|
+
if ! validate_auth_state_file; then
|
|
62
164
|
echo "❌ 缺少授权上下文(请传入 --sales-code/--mcc-code,或先执行 auth.sh init)"
|
|
63
165
|
return 1
|
|
64
166
|
fi
|
|
@@ -75,6 +177,33 @@ load_auth_state() {
|
|
|
75
177
|
[ -n "${MCC_NAME:-}" ] || MCC_NAME=$(jq -r '.mccName // ""' "$AUTH_STATE_FILE")
|
|
76
178
|
}
|
|
77
179
|
|
|
180
|
+
verify_context_against_auth_state() {
|
|
181
|
+
local field current saved
|
|
182
|
+
[ -e "$AUTH_STATE_FILE" ] || [ -L "$AUTH_STATE_FILE" ] || return 0
|
|
183
|
+
if ! validate_auth_state_file; then
|
|
184
|
+
echo "❌ 授权状态文件不可用或不安全,已停止"
|
|
185
|
+
return 1
|
|
186
|
+
fi
|
|
187
|
+
if ! jq -e . "$AUTH_STATE_FILE" >/dev/null 2>&1; then
|
|
188
|
+
echo "❌ 授权状态文件不是合法 JSON: $AUTH_STATE_FILE"
|
|
189
|
+
return 1
|
|
190
|
+
fi
|
|
191
|
+
for field in salesCode mccCode scope productName mccName; do
|
|
192
|
+
case "$field" in
|
|
193
|
+
salesCode) current="$SALES_CODE" ;;
|
|
194
|
+
mccCode) current="$MCC_CODE" ;;
|
|
195
|
+
scope) current="$SCOPE" ;;
|
|
196
|
+
productName) current="$PRODUCT_NAME" ;;
|
|
197
|
+
mccName) current="$MCC_NAME" ;;
|
|
198
|
+
esac
|
|
199
|
+
saved=$(jq -r --arg field "$field" '.[$field] // ""' "$AUTH_STATE_FILE")
|
|
200
|
+
if [ -n "$current" ] && [ -n "$saved" ] && [ "$current" != "$saved" ]; then
|
|
201
|
+
emit_control "AUTH_FLOW:CONTEXT_MISMATCH"
|
|
202
|
+
return 1
|
|
203
|
+
fi
|
|
204
|
+
done
|
|
205
|
+
}
|
|
206
|
+
|
|
78
207
|
parse_auth_context_args() {
|
|
79
208
|
while [[ $# -gt 0 ]]; do
|
|
80
209
|
case $1 in
|
|
@@ -109,13 +238,17 @@ restore_auth_context_or_state() {
|
|
|
109
238
|
parse_auth_context_args "$@" || return 1
|
|
110
239
|
fi
|
|
111
240
|
|
|
112
|
-
|
|
241
|
+
verify_context_against_auth_state || return 1
|
|
242
|
+
if [ -e "$AUTH_STATE_FILE" ] || [ -L "$AUTH_STATE_FILE" ]; then
|
|
243
|
+
load_auth_state || return 1
|
|
244
|
+
elif [ -z "$SALES_CODE" ] || [ -z "$MCC_CODE" ] || [ -z "$PRODUCT_NAME" ] || [ -z "$MCC_NAME" ]; then
|
|
113
245
|
load_auth_state || return 1
|
|
114
246
|
fi
|
|
115
247
|
|
|
116
248
|
if [ -z "$SCOPE" ]; then
|
|
117
249
|
SCOPE=$(get_scope "$SALES_CODE")
|
|
118
250
|
fi
|
|
251
|
+
validate_auth_context || return 1
|
|
119
252
|
}
|
|
120
253
|
|
|
121
254
|
# ─── 共用:根据 salesCode 获取 scope ─────────────────────────────────────────
|
|
@@ -128,27 +261,93 @@ get_scope() {
|
|
|
128
261
|
esac
|
|
129
262
|
}
|
|
130
263
|
|
|
264
|
+
validate_auth_context() {
|
|
265
|
+
local context_json
|
|
266
|
+
context_json=$(jq -n \
|
|
267
|
+
--arg productName "$PRODUCT_NAME" \
|
|
268
|
+
--arg salesCode "$SALES_CODE" \
|
|
269
|
+
--arg scope "$SCOPE" \
|
|
270
|
+
--arg mccCode "$MCC_CODE" \
|
|
271
|
+
--arg mccName "$MCC_NAME" \
|
|
272
|
+
'{productName:$productName,salesCode:$salesCode,scope:$scope,mccCode:$mccCode,mccName:$mccName}') || return 1
|
|
273
|
+
if ! printf '%s' "$context_json" | node "$RENDERER" --validate-authorization-context >/dev/null; then
|
|
274
|
+
echo "❌ 授权上下文校验失败:产品、产品码、scope 或 MCC 不匹配当前固定规则"
|
|
275
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
276
|
+
return 1
|
|
277
|
+
fi
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
build_authorization_url() {
|
|
281
|
+
local input_json platform_value
|
|
282
|
+
platform_value="${DEV_TOOL_NAME:-}"
|
|
283
|
+
[ "$platform_value" = "unknown" ] && platform_value=""
|
|
284
|
+
input_json=$(jq -n \
|
|
285
|
+
--arg productName "$PRODUCT_NAME" \
|
|
286
|
+
--arg salesCode "$SALES_CODE" \
|
|
287
|
+
--arg scope "$SCOPE" \
|
|
288
|
+
--arg mccCode "$MCC_CODE" \
|
|
289
|
+
--arg mccName "$MCC_NAME" \
|
|
290
|
+
--arg deviceCode "$DEVICE_CODE" \
|
|
291
|
+
--arg platform "$platform_value" \
|
|
292
|
+
'{productName:$productName,salesCode:$salesCode,scope:$scope,mccCode:$mccCode,mccName:$mccName,deviceCode:$deviceCode,platform:$platform}') || return 1
|
|
293
|
+
printf '%s' "$input_json" | node "$RENDERER" --build-authorization-url
|
|
294
|
+
}
|
|
295
|
+
|
|
131
296
|
extract_json_payload() {
|
|
132
297
|
local raw="$1"
|
|
133
|
-
local
|
|
298
|
+
local analysis status
|
|
299
|
+
analysis=$(extract_unique_json "$raw") || return 1
|
|
300
|
+
status=$(printf '%s' "$analysis" | jq -r '.status // "none"' 2>/dev/null)
|
|
301
|
+
[ "$status" = "ok" ] || return 1
|
|
302
|
+
printf '%s' "$analysis" | jq -ce '.value | select(type == "object")' 2>/dev/null
|
|
303
|
+
}
|
|
134
304
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
305
|
+
auth_cli_environment_failure() {
|
|
306
|
+
local action="$1"
|
|
307
|
+
echo "❌ ${action} 未能在当前 Agent 执行环境取得可确认结果,已停止。"
|
|
308
|
+
echo "📋 这通常表示当前执行环境缺少联网权限、CLI wrapper 混入输出,或 stdout/stderr 无法唯一解析。"
|
|
309
|
+
echo "📋 这不代表支付宝业务失败,也不能据此判断用户本机 logout/login 失败。"
|
|
310
|
+
echo "📋 Agent 应申请可联网权限后重试同一条 auth.sh 命令;不要要求用户手动 logout/login 代替本轮事实。"
|
|
311
|
+
emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
wait_for_effective_whoami() {
|
|
315
|
+
local action="$1"
|
|
316
|
+
local max_attempts=3
|
|
317
|
+
local interval_seconds=2
|
|
318
|
+
local attempt=1
|
|
319
|
+
local logged_in is_expired
|
|
320
|
+
|
|
321
|
+
AUTH_WHOAMI_RESULT=""
|
|
322
|
+
if [ "${ALIPAY_AIPAY_TEST_MODE:-0}" = "1" ] && [[ "${AUTH_WHOAMI_RECHECK_INTERVAL_SECONDS:-}" =~ ^[0-9]+$ ]]; then
|
|
323
|
+
interval_seconds="$AUTH_WHOAMI_RECHECK_INTERVAL_SECONDS"
|
|
138
324
|
fi
|
|
139
325
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
326
|
+
while [ "$attempt" -le "$max_attempts" ]; do
|
|
327
|
+
AUTH_WHOAMI_RESULT=$(run_json_command "alipay-cli whoami --json" alipay-cli whoami --json) || {
|
|
328
|
+
auth_cli_environment_failure "$action"
|
|
329
|
+
return 1
|
|
330
|
+
}
|
|
331
|
+
if ! handle_error "$AUTH_WHOAMI_RESULT"; then
|
|
332
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
333
|
+
return 1
|
|
334
|
+
fi
|
|
335
|
+
|
|
336
|
+
logged_in=$(echo "$AUTH_WHOAMI_RESULT" | jq -r '.data.logged_in // false')
|
|
337
|
+
is_expired=$(echo "$AUTH_WHOAMI_RESULT" | jq -r '.data.is_expired // false')
|
|
338
|
+
if [ "$logged_in" = "true" ] && [ "$is_expired" != "true" ]; then
|
|
146
339
|
return 0
|
|
147
340
|
fi
|
|
148
|
-
|
|
341
|
+
|
|
342
|
+
emit_control "AUTH_WHOAMI_RECHECK=${attempt}/${max_attempts}"
|
|
343
|
+
if [ "$attempt" -lt "$max_attempts" ]; then
|
|
344
|
+
sleep "$interval_seconds"
|
|
345
|
+
fi
|
|
346
|
+
attempt=$((attempt + 1))
|
|
149
347
|
done
|
|
150
348
|
|
|
151
|
-
|
|
349
|
+
emit_control "AUTH_WHOAMI_NOT_EFFECTIVE"
|
|
350
|
+
return 2
|
|
152
351
|
}
|
|
153
352
|
|
|
154
353
|
run_json_command() {
|
|
@@ -157,7 +356,7 @@ run_json_command() {
|
|
|
157
356
|
local stderr_file stdout stderr_output combined_output exit_code json_payload
|
|
158
357
|
|
|
159
358
|
stderr_file="$(mktemp "${TMPDIR:-/tmp}/alipay_auth_stderr.XXXXXX")" || return 1
|
|
160
|
-
stdout=$(PLATFORM="${DEV_TOOL_NAME}" "$@" 2>"$stderr_file")
|
|
359
|
+
stdout=$(PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}" "$@" 2>"$stderr_file")
|
|
161
360
|
exit_code=$?
|
|
162
361
|
stderr_output=$(cat "$stderr_file")
|
|
163
362
|
|
|
@@ -180,16 +379,7 @@ run_json_command() {
|
|
|
180
379
|
return 0
|
|
181
380
|
fi
|
|
182
381
|
|
|
183
|
-
echo "❌ ${label}
|
|
184
|
-
echo "📋 退出码: ${exit_code}" >&2
|
|
185
|
-
if [ -n "$stderr_output" ]; then
|
|
186
|
-
echo "📋 错误输出:" >&2
|
|
187
|
-
printf '%s\n' "$stderr_output" >&2
|
|
188
|
-
fi
|
|
189
|
-
if [ -n "$stdout" ]; then
|
|
190
|
-
echo "📋 标准输出:" >&2
|
|
191
|
-
printf '%s\n' "$stdout" >&2
|
|
192
|
-
fi
|
|
382
|
+
echo "❌ ${label} 未返回可唯一解析的合法 JSON(退出码: ${exit_code});原始输出已隐藏,请申请联网权限后重试同一条 auth.sh 命令" >&2
|
|
193
383
|
rm -f "$stderr_file"
|
|
194
384
|
return 1
|
|
195
385
|
}
|
|
@@ -199,26 +389,27 @@ run_json_command() {
|
|
|
199
389
|
validate_current_authorization() {
|
|
200
390
|
local whoami_result="${1:-}"
|
|
201
391
|
local logged_in is_expired granted_scope required_scope
|
|
202
|
-
local scope_match mcc_verify_result mcc_verify_unwrapped mcc_match
|
|
392
|
+
local scope_match mcc_verify_result mcc_verify_unwrapped mcc_match mcc_raw retry_rc
|
|
203
393
|
local mcc_error_type mcc_error_code mcc_success
|
|
204
|
-
local
|
|
205
|
-
local
|
|
394
|
+
local mismatch_type sign_status_output
|
|
395
|
+
local granted_scope_item required_scope_item required_scope_found
|
|
396
|
+
local -a granted_scope_items required_scope_items
|
|
206
397
|
|
|
207
398
|
if [ -z "$SALES_CODE" ] || [ -z "$MCC_CODE" ]; then
|
|
208
399
|
echo "❌ 缺少授权校验参数"
|
|
209
|
-
|
|
400
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
210
401
|
return 1
|
|
211
402
|
fi
|
|
212
403
|
|
|
213
404
|
if [ -z "$whoami_result" ]; then
|
|
214
405
|
whoami_result=$(run_json_command "alipay-cli whoami --json" alipay-cli whoami --json) || {
|
|
215
|
-
|
|
406
|
+
auth_cli_environment_failure "登录状态复核"
|
|
216
407
|
return 1
|
|
217
408
|
}
|
|
218
409
|
fi
|
|
219
410
|
|
|
220
411
|
if ! handle_error "$whoami_result"; then
|
|
221
|
-
|
|
412
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
222
413
|
return 1
|
|
223
414
|
fi
|
|
224
415
|
|
|
@@ -226,40 +417,52 @@ validate_current_authorization() {
|
|
|
226
417
|
is_expired=$(echo "$whoami_result" | jq -r '.data.is_expired // false')
|
|
227
418
|
if [ "$logged_in" != "true" ] || [ "$is_expired" = "true" ]; then
|
|
228
419
|
echo "❌ 当前登录状态无效或已过期"
|
|
229
|
-
|
|
420
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
230
421
|
return 1
|
|
231
422
|
fi
|
|
232
423
|
|
|
233
424
|
granted_scope=$(echo "$whoami_result" | jq -r '.data.scope // empty')
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
"
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
echo "AUTH_FLOW:FAILED"
|
|
241
|
-
return 1
|
|
242
|
-
;;
|
|
243
|
-
esac
|
|
425
|
+
required_scope=$(get_scope "$SALES_CODE")
|
|
426
|
+
if [ -z "$required_scope" ] || [ "$SCOPE" != "$required_scope" ]; then
|
|
427
|
+
echo "❌ 当前产品的固定 scope 上下文无效"
|
|
428
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
429
|
+
return 1
|
|
430
|
+
fi
|
|
244
431
|
|
|
245
|
-
scope_match="
|
|
432
|
+
scope_match="true"
|
|
246
433
|
IFS=',' read -r -a granted_scope_items <<< "$granted_scope"
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
434
|
+
IFS=',' read -r -a required_scope_items <<< "$required_scope"
|
|
435
|
+
for required_scope_item in "${required_scope_items[@]}"; do
|
|
436
|
+
required_scope_found="false"
|
|
437
|
+
for granted_scope_item in "${granted_scope_items[@]}"; do
|
|
438
|
+
granted_scope_item="${granted_scope_item#"${granted_scope_item%%[![:space:]]*}"}"
|
|
439
|
+
granted_scope_item="${granted_scope_item%"${granted_scope_item##*[![:space:]]}"}"
|
|
440
|
+
if [ "$granted_scope_item" = "$required_scope_item" ]; then
|
|
441
|
+
required_scope_found="true"
|
|
442
|
+
break
|
|
443
|
+
fi
|
|
444
|
+
done
|
|
445
|
+
if [ "$required_scope_found" != "true" ]; then
|
|
446
|
+
scope_match="false"
|
|
252
447
|
break
|
|
253
448
|
fi
|
|
254
449
|
done
|
|
255
450
|
|
|
256
451
|
mcc_match="true"
|
|
257
452
|
if [ "$scope_match" = "true" ]; then
|
|
258
|
-
|
|
259
|
-
|
|
453
|
+
export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
|
|
454
|
+
run_network_retry mcc_raw read authorization_mcc_check -- alipay-cli mcp call ar-query.queryArInfosBySalesProd \
|
|
260
455
|
-d "{\"request\":{\"salesProductCodes\":[\"${SALES_CODE}\"]},\"ctx\":{}}" \
|
|
261
|
-
--json
|
|
262
|
-
|
|
456
|
+
--json
|
|
457
|
+
retry_rc=$?
|
|
458
|
+
if [ "$retry_rc" -ne 0 ]; then
|
|
459
|
+
echo "❌ 授权 MCC 校验因网络或服务异常在自动重试两次后仍未恢复"
|
|
460
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
461
|
+
return 1
|
|
462
|
+
fi
|
|
463
|
+
mcc_verify_result=$(extract_json_payload "$mcc_raw") || {
|
|
464
|
+
echo "❌ alipay-cli mcp call ar-query.queryArInfosBySalesProd 未返回合法 JSON"
|
|
465
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
263
466
|
return 1
|
|
264
467
|
}
|
|
265
468
|
|
|
@@ -269,7 +472,7 @@ validate_current_authorization() {
|
|
|
269
472
|
mcc_match="false"
|
|
270
473
|
elif [ "$mcc_error_type" != "SUCCESS" ]; then
|
|
271
474
|
echo "❌ MCC 校验失败"
|
|
272
|
-
|
|
475
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
273
476
|
return 1
|
|
274
477
|
fi
|
|
275
478
|
|
|
@@ -282,37 +485,32 @@ validate_current_authorization() {
|
|
|
282
485
|
elif echo "$mcc_verify_unwrapped" | grep -qi "mccCode.*is not auth"; then
|
|
283
486
|
mcc_match="false"
|
|
284
487
|
fi
|
|
488
|
+
|
|
489
|
+
# 同一请求也是 Step 3.1 的签约查询;仅在完整结构可由共享解析器证明时输出可复用事实。
|
|
490
|
+
if [ "$mcc_match" = "true" ] && sign_status_output=$(emit_sign_status_from_business "$mcc_verify_unwrapped" "$SALES_CODE" 2>/dev/null); then
|
|
491
|
+
printf '%s\n' "$sign_status_output" \
|
|
492
|
+
| while IFS= read -r marker; do emit_control "$marker"; done
|
|
493
|
+
emit_control "AUTH_SIGN_QUERY_REUSABLE=true"
|
|
494
|
+
fi
|
|
285
495
|
fi
|
|
286
496
|
|
|
287
497
|
if [ "$scope_match" = "true" ] && [ "$mcc_match" = "true" ]; then
|
|
288
|
-
|
|
498
|
+
emit_control "✅ 授权范围校验通过(scope + MCC 授权有效性)"
|
|
289
499
|
return 0
|
|
290
500
|
fi
|
|
291
501
|
|
|
292
|
-
echo ""
|
|
293
|
-
echo "⚠️ 授权范围不满足"
|
|
294
502
|
if [ "$scope_match" != "true" ]; then
|
|
295
|
-
|
|
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"
|
|
503
|
+
mismatch_type="SCOPE_MISMATCH"
|
|
308
504
|
else
|
|
309
|
-
|
|
505
|
+
mismatch_type="MCC_MISMATCH"
|
|
310
506
|
fi
|
|
507
|
+
emit_control "AUTH_FLOW:${mismatch_type}"
|
|
311
508
|
return 1
|
|
312
509
|
}
|
|
313
510
|
|
|
314
511
|
# ─── auth init: 检查登录状态 + 执行登录 + 构建授权链接 + 输出授权信息 ────────
|
|
315
512
|
auth_init() {
|
|
513
|
+
local DEFER_OPEN=false
|
|
316
514
|
SCOPE=""
|
|
317
515
|
SALES_CODE=""
|
|
318
516
|
MCC_CODE=""
|
|
@@ -321,6 +519,8 @@ auth_init() {
|
|
|
321
519
|
|
|
322
520
|
while [[ $# -gt 0 ]]; do
|
|
323
521
|
case $1 in
|
|
522
|
+
--defer-open)
|
|
523
|
+
DEFER_OPEN=true; shift ;;
|
|
324
524
|
--scope)
|
|
325
525
|
[ $# -ge 2 ] || { echo "❌ 缺少参数值: $1"; exit 1; }
|
|
326
526
|
SCOPE="$2"; shift 2 ;;
|
|
@@ -340,40 +540,56 @@ auth_init() {
|
|
|
340
540
|
esac
|
|
341
541
|
done
|
|
342
542
|
|
|
343
|
-
if [ -z "$SCOPE" ] || [ -z "$SALES_CODE" ] || [ -z "$MCC_CODE" ]; then
|
|
543
|
+
if [ -z "$SCOPE" ] || [ -z "$SALES_CODE" ] || [ -z "$MCC_CODE" ] || [ -z "$PRODUCT_NAME" ] || [ -z "$MCC_NAME" ]; then
|
|
344
544
|
echo "❌ 缺少必填参数"
|
|
345
|
-
echo "用法: bash auth.sh init --scope <scope> --sales-code <code> --mcc-code <code>
|
|
545
|
+
echo "用法: bash auth.sh init --defer-open --scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>"
|
|
546
|
+
exit 1
|
|
547
|
+
fi
|
|
548
|
+
if [ "$DEFER_OPEN" != true ]; then
|
|
549
|
+
echo "❌ 参数格式错误:auth init 必须使用 --defer-open,确保先展示授权说明和链接,再自动打开页面"
|
|
550
|
+
echo "用法: bash auth.sh init --defer-open --scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>"
|
|
346
551
|
exit 1
|
|
347
552
|
fi
|
|
553
|
+
validate_auth_context || return 1
|
|
554
|
+
# 新 init 表示按已确认上下文重建授权链路;只在机器校验通过后替换旧短时状态。
|
|
555
|
+
reset_auth_state_for_init || return 1
|
|
348
556
|
|
|
349
557
|
# Step 1: 检查登录状态
|
|
350
558
|
CHECK_RESULT=$(run_json_command "alipay-cli whoami --json" alipay-cli whoami --json) || {
|
|
351
|
-
|
|
559
|
+
auth_cli_environment_failure "登录状态检查"
|
|
352
560
|
return 1
|
|
353
561
|
}
|
|
354
562
|
if ! handle_error "$CHECK_RESULT"; then
|
|
355
|
-
|
|
563
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
356
564
|
return 1
|
|
357
565
|
fi
|
|
358
566
|
LOGGED_IN=$(echo "$CHECK_RESULT" | jq -r '.data.logged_in // false')
|
|
359
567
|
IS_EXPIRED=$(echo "$CHECK_RESULT" | jq -r '.data.is_expired // false')
|
|
360
568
|
|
|
361
569
|
if [ "$LOGGED_IN" = "true" ] && [ "$IS_EXPIRED" = "false" ]; then
|
|
570
|
+
if [ -n "$AUTH_INIT_CONTEXT_MISMATCH_TYPE" ]; then
|
|
571
|
+
# 后端查询没有权威 MCC 字段,旧状态变化时不能把现有登录误判为当前上下文已授权。
|
|
572
|
+
DEVICE_CODE=""
|
|
573
|
+
BROWSER_URL=""
|
|
574
|
+
save_auth_state || return 1
|
|
575
|
+
emit_control "AUTH_FLOW:${AUTH_INIT_CONTEXT_MISMATCH_TYPE}"
|
|
576
|
+
return 1
|
|
577
|
+
fi
|
|
362
578
|
validate_current_authorization "$CHECK_RESULT" || return 1
|
|
363
579
|
DEVICE_CODE=""
|
|
364
|
-
save_auth_state
|
|
365
|
-
|
|
366
|
-
|
|
580
|
+
save_auth_state || return 1
|
|
581
|
+
emit_control "✅ 当前登录及授权范围有效,可继续后续流程"
|
|
582
|
+
emit_control "AUTH_FLOW:SKIP"
|
|
367
583
|
return 0
|
|
368
584
|
fi
|
|
369
585
|
|
|
370
586
|
# Step 2: 执行登录
|
|
371
587
|
LOGIN_RESULT=$(run_json_command "alipay-cli login --non-interactive --scope" alipay-cli login --non-interactive --scope "$SCOPE" --json) || {
|
|
372
|
-
|
|
588
|
+
auth_cli_environment_failure "授权登录发起"
|
|
373
589
|
return 1
|
|
374
590
|
}
|
|
375
591
|
if ! handle_error "$LOGIN_RESULT"; then
|
|
376
|
-
|
|
592
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
377
593
|
return 1
|
|
378
594
|
fi
|
|
379
595
|
LOGIN_STATUS=$(echo "$LOGIN_RESULT" | jq -r '.data.status // ""')
|
|
@@ -381,9 +597,9 @@ auth_init() {
|
|
|
381
597
|
if [ "$LOGIN_STATUS" = "already_logged_in" ]; then
|
|
382
598
|
validate_current_authorization || return 1
|
|
383
599
|
DEVICE_CODE=""
|
|
384
|
-
save_auth_state
|
|
385
|
-
|
|
386
|
-
|
|
600
|
+
save_auth_state || return 1
|
|
601
|
+
emit_control "✅ 当前登录及授权范围有效,可继续后续流程"
|
|
602
|
+
emit_control "AUTH_FLOW:SKIP"
|
|
387
603
|
return 0
|
|
388
604
|
fi
|
|
389
605
|
|
|
@@ -396,30 +612,21 @@ auth_init() {
|
|
|
396
612
|
# Step 4: 参数校验
|
|
397
613
|
if [ -z "$DEVICE_CODE" ] || [ -z "$SALES_CODE" ] || [ -z "$MCC_CODE" ]; then
|
|
398
614
|
echo "❌ 授权链接参数不完整,无法生成授权链接"
|
|
399
|
-
echo "📋 deviceCode
|
|
400
|
-
echo "📋 productCode
|
|
401
|
-
echo "📋 mccCode
|
|
615
|
+
[ -n "$DEVICE_CODE" ] || echo "📋 缺少字段: deviceCode"
|
|
616
|
+
[ -n "$SALES_CODE" ] || echo "📋 缺少字段: productCode"
|
|
617
|
+
[ -n "$MCC_CODE" ] || echo "📋 缺少字段: mccCode"
|
|
402
618
|
exit 1
|
|
403
619
|
fi
|
|
404
620
|
|
|
405
|
-
# Step 5:
|
|
406
|
-
BROWSER_URL
|
|
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
|
|
621
|
+
# Step 5: 由统一校验器编码、构建并逐字段校验授权链接。
|
|
622
|
+
BROWSER_URL=$(build_authorization_url) || {
|
|
416
623
|
echo "❌ 授权链接构建失败,缺少必要参数或误用了 CLI verification_url,已停止。"
|
|
417
624
|
echo "📋 正确格式: https://aipay.alipay.com/cli-auth?deviceCode=...&productCode=...&mccCode=..."
|
|
418
|
-
|
|
419
|
-
|
|
625
|
+
return 1
|
|
626
|
+
}
|
|
420
627
|
|
|
421
628
|
# Step 6: 保存参数到状态文件(供 confirm/mismatch 使用)
|
|
422
|
-
save_auth_state
|
|
629
|
+
save_auth_state || return 1
|
|
423
630
|
|
|
424
631
|
# Step 7: 有效期转换(对非数字做防护)
|
|
425
632
|
if [[ "$EXPIRES_IN" =~ ^[0-9]+$ ]] && [ "$EXPIRES_IN" -ge 60 ]; then
|
|
@@ -431,35 +638,56 @@ auth_init() {
|
|
|
431
638
|
EXPIRES_DISPLAY="10 分钟"
|
|
432
639
|
fi
|
|
433
640
|
|
|
434
|
-
# Step 8:
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
echo "| 经营类目 | ${MCC_NAME:-${MCC_CODE}} (${MCC_CODE}) |"
|
|
444
|
-
if [ -n "$VERIFICATION_CODE" ]; then
|
|
445
|
-
echo "| 确认码 | ${VERIFICATION_CODE} |"
|
|
446
|
-
else
|
|
447
|
-
echo "| 确认码 | (请查看支付宝授权页面) |"
|
|
641
|
+
# Step 8: 由统一目录渲染授权信息;独立 open 动作在消息发出后打开。
|
|
642
|
+
DISPLAY_CODE="${VERIFICATION_CODE:-(请查看支付宝授权页面)}"
|
|
643
|
+
MESSAGE_INPUT=$(printf '%s\n' "$PRODUCT_NAME" "$DEVICE_CODE" "$MCC_CODE" "$MCC_NAME" "$DISPLAY_CODE" "$EXPIRES_DISPLAY" "$BROWSER_URL" | jq -Rs '
|
|
644
|
+
split("\n") as $v | {productName:$v[0],deviceCode:$v[1],mccCode:$v[2],mccName:$v[3],verificationCode:$v[4],expiresDisplay:$v[5],officialUrl:$v[6]}
|
|
645
|
+
')
|
|
646
|
+
if ! printf '%s' "$MESSAGE_INPUT" | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=auth.sh node "$RENDERER" auth.page --variant READY; then
|
|
647
|
+
cleanup_auth_state
|
|
648
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
649
|
+
exit 1
|
|
448
650
|
fi
|
|
449
|
-
echo "| 授权链接有效期 | ${EXPIRES_DISPLAY} |"
|
|
450
|
-
echo ""
|
|
451
|
-
echo "⚠️ 安全提示:请核对授权页面显示的确认码是否与上方一致,如不一致,请勿授权,立即停止操作!"
|
|
452
|
-
echo ""
|
|
453
|
-
echo "提示:无法跳链时,请复制下方链接到网页浏览器打开。"
|
|
454
|
-
echo "🌐 授权链接:[点击跳转进行授权](${BROWSER_URL})"
|
|
455
651
|
echo ""
|
|
456
|
-
|
|
457
|
-
echo ""
|
|
458
|
-
echo "AUTH_FLOW:READY"
|
|
652
|
+
emit_control "AUTH_FLOW:READY_TO_OPEN"
|
|
459
653
|
}
|
|
460
654
|
|
|
461
|
-
|
|
655
|
+
auth_open() {
|
|
656
|
+
local saved_device saved_url validate_input open_status open_helper
|
|
657
|
+
|
|
658
|
+
restore_auth_context_or_state "$@" || exit 1
|
|
659
|
+
saved_device=$(jq -r '.deviceCode // ""' "$AUTH_STATE_FILE")
|
|
660
|
+
saved_url=$(jq -r '.authorizationUrl // ""' "$AUTH_STATE_FILE")
|
|
661
|
+
if [ -z "$saved_device" ] || [ -z "$saved_url" ]; then
|
|
662
|
+
echo "❌ 缺少待打开的授权页面上下文,请重新执行 auth.sh init --defer-open"
|
|
663
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
664
|
+
return 1
|
|
665
|
+
fi
|
|
666
|
+
|
|
667
|
+
validate_input=$(jq -n \
|
|
668
|
+
--arg productName "$PRODUCT_NAME" \
|
|
669
|
+
--arg salesCode "$SALES_CODE" \
|
|
670
|
+
--arg scope "$SCOPE" \
|
|
671
|
+
--arg mccCode "$MCC_CODE" \
|
|
672
|
+
--arg mccName "$MCC_NAME" \
|
|
673
|
+
--arg deviceCode "$saved_device" \
|
|
674
|
+
--arg officialUrl "$saved_url" \
|
|
675
|
+
'{productName:$productName,salesCode:$salesCode,scope:$scope,mccCode:$mccCode,mccName:$mccName,deviceCode:$deviceCode,officialUrl:$officialUrl}') || return 1
|
|
676
|
+
if ! printf '%s' "$validate_input" | node "$RENDERER" --validate-authorization-context >/dev/null; then
|
|
677
|
+
echo "❌ 授权页面上下文校验失败,已停止打开"
|
|
678
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
679
|
+
return 1
|
|
680
|
+
fi
|
|
681
|
+
|
|
682
|
+
open_helper="${SCRIPT_DIR}/../../../normal/scripts/open_official_url.sh"
|
|
683
|
+
open_status=$(printf '%s\n' "$saved_url" | bash "$open_helper" authorization)
|
|
684
|
+
case "$open_status" in OPENED|OPEN_FAILED|GUI_UNAVAILABLE|LINK_ONLY) ;; *) open_status="OPEN_FAILED" ;; esac
|
|
685
|
+
emit_control "AUTH_OPEN:${open_status}"
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
# ─── auth confirm: 授权确认 + scope 校验 + MCC 授权有效性校验 ────────────────
|
|
462
689
|
auth_confirm() {
|
|
690
|
+
AUTH_CONFIRM_RESULT=""
|
|
463
691
|
# 优先使用显式传入的非敏感上下文;未传时再从状态文件兜底恢复。
|
|
464
692
|
restore_auth_context_or_state "$@" || exit 1
|
|
465
693
|
|
|
@@ -474,51 +702,126 @@ auth_confirm() {
|
|
|
474
702
|
|
|
475
703
|
# Step 1: 执行授权确认
|
|
476
704
|
LOGIN_COMPLETE_RESULT=$(run_json_command "alipay-cli login --complete --json" alipay-cli login --complete --json) || {
|
|
477
|
-
|
|
705
|
+
auth_cli_environment_failure "授权完成确认"
|
|
478
706
|
return 1
|
|
479
707
|
}
|
|
480
708
|
|
|
481
709
|
# Step 2: 判断授权结果
|
|
482
|
-
# alipay-cli login --complete
|
|
710
|
+
# alipay-cli login --complete: success=true/logged_in 表示可进入 whoami 门禁;
|
|
711
|
+
# authorization_pending/auth_expired 可能出现在 .error.code、.data.error.code 或 status 中。
|
|
483
712
|
OUTER_SUCCESS=$(echo "$LOGIN_COMPLETE_RESULT" | jq -r '.success // false')
|
|
484
713
|
DATA_STATUS=$(echo "$LOGIN_COMPLETE_RESULT" | jq -r '.data.status // ""')
|
|
485
|
-
ERROR_CODE=$(echo "$LOGIN_COMPLETE_RESULT" | jq -r '.data.error.code // ""')
|
|
486
|
-
|
|
487
|
-
if [ "$
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
714
|
+
ERROR_CODE=$(echo "$LOGIN_COMPLETE_RESULT" | jq -r '.error.code // .data.error.code // ""')
|
|
715
|
+
|
|
716
|
+
if [ "$ERROR_CODE" = "authorization_pending" ] || [ "$DATA_STATUS" = "authorization_pending" ] || [ "$DATA_STATUS" = "pending" ]; then
|
|
717
|
+
AUTH_CONFIRM_RESULT="PENDING"
|
|
718
|
+
if [ "${AUTH_POLLING:-false}" != "true" ]; then
|
|
719
|
+
if ! printf '%s' '{}' | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=auth.sh node "$RENDERER" auth.pending --variant DEFAULT; then
|
|
720
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
721
|
+
return 1
|
|
722
|
+
fi
|
|
723
|
+
emit_control "AUTH_FLOW:PENDING"
|
|
724
|
+
fi
|
|
492
725
|
return 0
|
|
493
|
-
elif [ "$ERROR_CODE" = "auth_expired" ]; then
|
|
494
|
-
|
|
726
|
+
elif [ "$ERROR_CODE" = "auth_expired" ] || [ "$DATA_STATUS" = "auth_expired" ] || [ "$DATA_STATUS" = "expired" ]; then
|
|
727
|
+
AUTH_CONFIRM_RESULT="EXPIRED"
|
|
495
728
|
cleanup_auth_state
|
|
496
|
-
|
|
729
|
+
if ! printf '%s' '{}' | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=auth.sh node "$RENDERER" auth.expired --variant DEFAULT; then
|
|
730
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
731
|
+
return 1
|
|
732
|
+
fi
|
|
733
|
+
emit_control "AUTH_FLOW:EXPIRED"
|
|
497
734
|
return 0
|
|
735
|
+
elif [ "$OUTER_SUCCESS" = "true" ] || [ "$DATA_STATUS" = "logged_in" ] || [ "$DATA_STATUS" = "already_logged_in" ] || [ "$DATA_STATUS" = "completed" ]; then
|
|
736
|
+
:
|
|
498
737
|
else
|
|
499
738
|
if handle_error "$LOGIN_COMPLETE_RESULT"; then
|
|
500
739
|
echo "❌ 授权确认失败:返回结果未包含可识别的完成状态"
|
|
501
740
|
fi
|
|
502
741
|
cleanup_auth_state
|
|
503
|
-
|
|
742
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
504
743
|
return 1
|
|
505
744
|
fi
|
|
506
745
|
|
|
507
|
-
# Step 3: 复用 init 的 scope + MCC
|
|
508
|
-
|
|
509
|
-
|
|
746
|
+
# Step 3: 复用 init 的 scope + MCC 授权有效性校验
|
|
747
|
+
wait_for_effective_whoami "授权后登录状态复核"
|
|
748
|
+
whoami_rc=$?
|
|
749
|
+
if [ "$whoami_rc" -eq 2 ]; then
|
|
750
|
+
AUTH_CONFIRM_RESULT="PENDING"
|
|
751
|
+
if [ "${AUTH_POLLING:-false}" != "true" ]; then
|
|
752
|
+
if ! printf '%s' '{}' | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=auth.sh node "$RENDERER" auth.pending --variant DEFAULT; then
|
|
753
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
754
|
+
return 1
|
|
755
|
+
fi
|
|
756
|
+
emit_control "AUTH_FLOW:PENDING"
|
|
757
|
+
fi
|
|
758
|
+
return 0
|
|
759
|
+
elif [ "$whoami_rc" -ne 0 ]; then
|
|
510
760
|
return 1
|
|
511
|
-
|
|
512
|
-
validate_current_authorization "$
|
|
761
|
+
fi
|
|
762
|
+
validate_current_authorization "$AUTH_WHOAMI_RESULT" || return 1
|
|
513
763
|
cleanup_auth_state
|
|
514
|
-
|
|
764
|
+
AUTH_CONFIRM_RESULT="AUTH_SUCCESS"
|
|
765
|
+
emit_control "✅ 授权确认成功(本机 CLI 登录态、scope 与 MCC 授权有效性均已通过)"
|
|
766
|
+
emit_control "AUTH_FLOW:AUTH_SUCCESS"
|
|
515
767
|
return 0
|
|
516
768
|
}
|
|
517
769
|
|
|
770
|
+
# ─── auth wait: 授权页打开后有限轮询确认状态 ────────────────────────────────
|
|
771
|
+
auth_wait() {
|
|
772
|
+
local attempt=1
|
|
773
|
+
local max_attempts=12
|
|
774
|
+
local interval_seconds=5
|
|
775
|
+
|
|
776
|
+
if [ "${ALIPAY_AIPAY_TEST_MODE:-0}" = "1" ] && [[ "${AUTH_CONFIRM_POLL_INTERVAL_SECONDS:-}" =~ ^[0-9]+$ ]]; then
|
|
777
|
+
interval_seconds="$AUTH_CONFIRM_POLL_INTERVAL_SECONDS"
|
|
778
|
+
fi
|
|
779
|
+
|
|
780
|
+
while [ "$attempt" -le "$max_attempts" ]; do
|
|
781
|
+
AUTH_POLLING=true
|
|
782
|
+
if ! auth_confirm "$@"; then
|
|
783
|
+
AUTH_POLLING=false
|
|
784
|
+
return 1
|
|
785
|
+
fi
|
|
786
|
+
AUTH_POLLING=false
|
|
787
|
+
|
|
788
|
+
case "$AUTH_CONFIRM_RESULT" in
|
|
789
|
+
AUTH_SUCCESS|EXPIRED)
|
|
790
|
+
return 0
|
|
791
|
+
;;
|
|
792
|
+
PENDING)
|
|
793
|
+
emit_control "AUTH_POLL_PENDING=${attempt}/${max_attempts}"
|
|
794
|
+
if [ "$attempt" -eq "$max_attempts" ]; then
|
|
795
|
+
if ! printf '%s' '{}' | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=auth.sh node "$RENDERER" auth.pending --variant DEFAULT; then
|
|
796
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
797
|
+
return 1
|
|
798
|
+
fi
|
|
799
|
+
emit_control "AUTH_FLOW:PENDING"
|
|
800
|
+
return 0
|
|
801
|
+
fi
|
|
802
|
+
sleep "$interval_seconds"
|
|
803
|
+
;;
|
|
804
|
+
*)
|
|
805
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
806
|
+
return 1
|
|
807
|
+
;;
|
|
808
|
+
esac
|
|
809
|
+
attempt=$((attempt + 1))
|
|
810
|
+
done
|
|
811
|
+
}
|
|
812
|
+
|
|
518
813
|
# ─── auth mismatch: 授权范围不满足 → logout → 重新授权 ──────────────────────
|
|
519
814
|
auth_mismatch() {
|
|
520
815
|
local logout_result logout_error_type
|
|
521
816
|
|
|
817
|
+
if [ "${1:-}" = "--defer-open" ]; then
|
|
818
|
+
shift
|
|
819
|
+
else
|
|
820
|
+
echo "❌ 参数格式错误:auth mismatch 必须使用 --defer-open,确保重新授权时先展示说明和链接,再自动打开页面"
|
|
821
|
+
echo "用法: bash auth.sh mismatch --defer-open [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>]"
|
|
822
|
+
return 1
|
|
823
|
+
fi
|
|
824
|
+
|
|
522
825
|
# 优先使用显式传入的非敏感上下文;未传时再从状态文件兜底恢复。
|
|
523
826
|
restore_auth_context_or_state "$@" || exit 1
|
|
524
827
|
|
|
@@ -535,26 +838,25 @@ auth_mismatch() {
|
|
|
535
838
|
fi
|
|
536
839
|
|
|
537
840
|
# Step 2: mismatch 是授权不匹配恢复路径中唯一执行 logout 的入口。
|
|
538
|
-
|
|
841
|
+
if ! printf '%s' '{}' | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=auth.sh node "$RENDERER" auth.mismatch --variant DEFAULT; then
|
|
842
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
843
|
+
return 1
|
|
844
|
+
fi
|
|
539
845
|
logout_result=$(run_json_command "alipay-cli logout --json" alipay-cli logout --json) || {
|
|
540
|
-
|
|
541
|
-
echo "AUTH_FLOW:FAILED"
|
|
846
|
+
auth_cli_environment_failure "退出当前登录"
|
|
542
847
|
return 1
|
|
543
848
|
}
|
|
544
849
|
logout_error_type=$(detect_error "$logout_result")
|
|
545
850
|
if [ "$logout_error_type" != "SUCCESS" ]; then
|
|
546
851
|
echo "❌ 退出当前登录失败,已停止重新授权"
|
|
547
|
-
|
|
852
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
548
853
|
return 1
|
|
549
854
|
fi
|
|
550
|
-
echo "✅ 已退出当前登录"
|
|
551
|
-
|
|
552
|
-
# Step 3: 调用 auth init 执行登录 + 构建授权链接 + 输出授权信息
|
|
553
|
-
# DEV_TOOL_NAME 已 export,子进程可继承
|
|
554
|
-
echo "📋 请重新扫码授权,新的授权将包含正确的权限范围"
|
|
555
|
-
echo ""
|
|
556
855
|
|
|
856
|
+
# Step 3: 调用 auth init 执行登录 + 构建授权链接 + 输出唯一授权消息。
|
|
857
|
+
# DEV_TOOL_NAME 已 export,子进程可继承。
|
|
557
858
|
auth_init \
|
|
859
|
+
--defer-open \
|
|
558
860
|
--scope "$SCOPE" \
|
|
559
861
|
--sales-code "$SALES_CODE" \
|
|
560
862
|
--mcc-code "$MCC_CODE" \
|
|
@@ -572,16 +874,26 @@ case "${1:-}" in
|
|
|
572
874
|
shift
|
|
573
875
|
auth_confirm "$@"
|
|
574
876
|
;;
|
|
877
|
+
open)
|
|
878
|
+
shift
|
|
879
|
+
auth_open "$@"
|
|
880
|
+
;;
|
|
881
|
+
wait)
|
|
882
|
+
shift
|
|
883
|
+
auth_wait "$@"
|
|
884
|
+
;;
|
|
575
885
|
mismatch)
|
|
576
886
|
shift
|
|
577
887
|
auth_mismatch "$@"
|
|
578
888
|
;;
|
|
579
889
|
*)
|
|
580
|
-
echo "用法: bash auth.sh <init|confirm|mismatch> [参数...]"
|
|
890
|
+
echo "用法: bash auth.sh <init|open|wait|confirm|mismatch> [参数...]"
|
|
581
891
|
echo ""
|
|
582
|
-
echo " auth init --scope <scope> --sales-code <code> --mcc-code <code>
|
|
892
|
+
echo " auth init --defer-open --scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>"
|
|
893
|
+
echo " auth open [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>]"
|
|
894
|
+
echo " auth wait [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>]"
|
|
583
895
|
echo " auth confirm [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>]"
|
|
584
|
-
echo " auth mismatch [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>]"
|
|
896
|
+
echo " auth mismatch --defer-open [--scope <scope> --sales-code <code> --mcc-code <code> --product-name <name> --mcc-name <name>]"
|
|
585
897
|
exit 1
|
|
586
898
|
;;
|
|
587
899
|
esac
|