@alipay/alipay-aipay 1.5.2 → 1.5.3
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 +1 -1
- package/package.json +1 -1
- package/skill/VERSION +1 -1
- package/skill/references/integration/flow.md +2 -2
- package/skill/references/normal/scripts/onboarding_discovery_runner.mjs +128 -13
- package/skill/references/normal/scripts/onboarding_message_runner.mjs +132 -47
- package/skill/references/normal/scripts/render_customer_message.mjs +3 -3
- package/skill/references/normal/scripts/runtime.mjs +69 -0
- package/skill/references/onboarding/flow.md +33 -21
- package/skill/references/onboarding/modules/app-release.md +3 -1
- package/skill/references/onboarding/modules/authorization.md +3 -2
- package/skill/references/onboarding/modules/error-handling.md +6 -2
- package/skill/references/onboarding/modules/product-sign.md +5 -1
- package/skill/references/onboarding/modules/scripts/app.sh +27 -10
- package/skill/references/onboarding/modules/scripts/ar_sign_apply.sh +6 -0
- package/skill/references/onboarding/modules/scripts/auth.sh +127 -37
- package/skill/references/onboarding/modules/scripts/error_handler.sh +108 -20
- package/skill/references/onboarding/modules/scripts/service.sh +9 -0
- package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +10 -1
- package/skill/references/onboarding/modules/service-registration.md +1 -1
- package/skill/references/runtime-entry.md +5 -5
|
@@ -14,7 +14,7 @@
|
|
|
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
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
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
|
|
17
|
+
# mismatch: logout 后置条件可确认后调用 auth init 重新生成授权链接;上下文冲突时输出 AUTH_FLOW:CONTEXT_MISMATCH 且不 logout;无法确认 CLI 结果时输出 AUTH_FLOW:RETRY_WITH_NETWORK;仍处于登录态时输出 AUTH_FLOW:LOGOUT_STILL_LOGGED_IN
|
|
18
18
|
# stdout: 仅登记对客正文;AUTH_FLOW/状态/查询复用标记写入 stderr
|
|
19
19
|
#=============================================================================
|
|
20
20
|
|
|
@@ -37,6 +37,28 @@ require_command node || exit 1
|
|
|
37
37
|
require_command mktemp || exit 1
|
|
38
38
|
umask 077
|
|
39
39
|
|
|
40
|
+
auth_product_type_from_sales_code() {
|
|
41
|
+
case "$1" in
|
|
42
|
+
"I1080300001000160457") printf 'aipay' ;;
|
|
43
|
+
"I1080300001000041203") printf 'webpay' ;;
|
|
44
|
+
"I1080300001000041313") printf 'apppay' ;;
|
|
45
|
+
*) return 1 ;;
|
|
46
|
+
esac
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
create_auth_sign_reuse_receipt() {
|
|
50
|
+
local sign_status="$1" sign_flow="$2" product_type payload encoded
|
|
51
|
+
product_type=$(auth_product_type_from_sales_code "$SALES_CODE") || return 1
|
|
52
|
+
payload=$(jq -cn \
|
|
53
|
+
--arg productType "$product_type" \
|
|
54
|
+
--arg salesCode "$SALES_CODE" \
|
|
55
|
+
--arg signStatus "$sign_status" \
|
|
56
|
+
--arg flow "$sign_flow" \
|
|
57
|
+
'{schema_ver:1,product_type:$productType,sales_code:$salesCode,sign_status:$signStatus,flow:$flow}') || return 1
|
|
58
|
+
encoded=$(printf '%s' "$payload" | jq -rR '@base64' | tr '+/' '-_' | tr -d '=\n') || return 1
|
|
59
|
+
printf 'AIPAY_SIGN_REUSE_V1.%s' "$encoded"
|
|
60
|
+
}
|
|
61
|
+
|
|
40
62
|
# ─── 授权状态文件(用于独立执行 confirm/mismatch 时兜底恢复非敏感参数)────
|
|
41
63
|
AUTH_STATE_OWNER="$(id -u 2>/dev/null || printf '%s' "${USER:-unknown}" | tr -c 'A-Za-z0-9_-' '_')"
|
|
42
64
|
AUTH_STATE_BASE="${TMPDIR:-/tmp}"
|
|
@@ -159,6 +181,14 @@ save_auth_state() {
|
|
|
159
181
|
}
|
|
160
182
|
}
|
|
161
183
|
|
|
184
|
+
save_auth_state_after_page_delivery() {
|
|
185
|
+
if save_auth_state; then
|
|
186
|
+
return 0
|
|
187
|
+
fi
|
|
188
|
+
emit_control "AUTH_STATE:SAVE_FAILED"
|
|
189
|
+
return 0
|
|
190
|
+
}
|
|
191
|
+
|
|
162
192
|
load_auth_state() {
|
|
163
193
|
if ! validate_auth_state_file; then
|
|
164
194
|
echo "❌ 缺少授权上下文(请传入 --sales-code/--mcc-code,或先执行 auth.sh init)"
|
|
@@ -320,7 +350,7 @@ wait_for_effective_whoami() {
|
|
|
320
350
|
local max_attempts=3
|
|
321
351
|
local interval_seconds=2
|
|
322
352
|
local attempt=1
|
|
323
|
-
local logged_in is_expired
|
|
353
|
+
local logged_in is_expired whoami_error_type
|
|
324
354
|
|
|
325
355
|
AUTH_WHOAMI_RESULT=""
|
|
326
356
|
if [ "${ALIPAY_AIPAY_TEST_MODE:-0}" = "1" ] && [[ "${AUTH_WHOAMI_RECHECK_INTERVAL_SECONDS:-}" =~ ^[0-9]+$ ]]; then
|
|
@@ -332,8 +362,17 @@ wait_for_effective_whoami() {
|
|
|
332
362
|
auth_cli_environment_failure "$action"
|
|
333
363
|
return 1
|
|
334
364
|
}
|
|
365
|
+
whoami_error_type=$(detect_error "$AUTH_WHOAMI_RESULT")
|
|
335
366
|
if ! handle_error "$AUTH_WHOAMI_RESULT"; then
|
|
336
|
-
|
|
367
|
+
case "$whoami_error_type" in
|
|
368
|
+
MCP_AUTH_ERROR|AUTH_MISMATCH) ;;
|
|
369
|
+
MCP_SERVICE_ERROR|SERVICE_UNSTABLE)
|
|
370
|
+
emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
|
|
371
|
+
;;
|
|
372
|
+
*)
|
|
373
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
374
|
+
;;
|
|
375
|
+
esac
|
|
337
376
|
return 1
|
|
338
377
|
fi
|
|
339
378
|
|
|
@@ -394,8 +433,8 @@ validate_current_authorization() {
|
|
|
394
433
|
local whoami_result="${1:-}"
|
|
395
434
|
local logged_in is_expired granted_scope required_scope
|
|
396
435
|
local scope_match mcc_verify_result mcc_verify_unwrapped mcc_match mcc_raw retry_rc
|
|
397
|
-
local mcc_error_type mcc_error_code mcc_success
|
|
398
|
-
local mismatch_type sign_status_output
|
|
436
|
+
local mcc_error_type mcc_error_code mcc_success whoami_error_type
|
|
437
|
+
local mismatch_type sign_status_output sign_status_marker sign_flow_marker sign_reuse_receipt
|
|
399
438
|
local granted_scope_item required_scope_item required_scope_found
|
|
400
439
|
local -a granted_scope_items required_scope_items
|
|
401
440
|
|
|
@@ -412,8 +451,17 @@ validate_current_authorization() {
|
|
|
412
451
|
}
|
|
413
452
|
fi
|
|
414
453
|
|
|
454
|
+
whoami_error_type=$(detect_error "$whoami_result")
|
|
415
455
|
if ! handle_error "$whoami_result"; then
|
|
416
|
-
|
|
456
|
+
case "$whoami_error_type" in
|
|
457
|
+
MCP_AUTH_ERROR|AUTH_MISMATCH) ;;
|
|
458
|
+
MCP_SERVICE_ERROR|SERVICE_UNSTABLE)
|
|
459
|
+
emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
|
|
460
|
+
;;
|
|
461
|
+
*)
|
|
462
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
463
|
+
;;
|
|
464
|
+
esac
|
|
417
465
|
return 1
|
|
418
466
|
fi
|
|
419
467
|
|
|
@@ -461,12 +509,12 @@ validate_current_authorization() {
|
|
|
461
509
|
retry_rc=$?
|
|
462
510
|
if [ "$retry_rc" -ne 0 ]; then
|
|
463
511
|
echo "❌ 授权 MCC 校验因网络或服务异常在自动重试两次后仍未恢复"
|
|
464
|
-
emit_control "AUTH_FLOW:
|
|
512
|
+
emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
|
|
465
513
|
return 1
|
|
466
514
|
fi
|
|
467
515
|
mcc_verify_result=$(extract_json_payload "$mcc_raw") || {
|
|
468
516
|
echo "❌ alipay-cli mcp call ar-query.queryArInfosBySalesProd 未返回合法 JSON"
|
|
469
|
-
emit_control "AUTH_FLOW:
|
|
517
|
+
emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
|
|
470
518
|
return 1
|
|
471
519
|
}
|
|
472
520
|
|
|
@@ -494,6 +542,11 @@ validate_current_authorization() {
|
|
|
494
542
|
if [ "$mcc_match" = "true" ] && sign_status_output=$(emit_sign_status_from_business "$mcc_verify_unwrapped" "$SALES_CODE" 2>/dev/null); then
|
|
495
543
|
printf '%s\n' "$sign_status_output" \
|
|
496
544
|
| while IFS= read -r marker; do emit_control "$marker"; done
|
|
545
|
+
sign_status_marker=$(printf '%s\n' "$sign_status_output" | awk -F= '/^SIGN_STATUS=/{print $2; exit}')
|
|
546
|
+
sign_flow_marker=$(printf '%s\n' "$sign_status_output" | awk '/^FLOW:/{print; exit}')
|
|
547
|
+
if [ -n "$sign_status_marker" ] && [ -n "$sign_flow_marker" ] && sign_reuse_receipt=$(create_auth_sign_reuse_receipt "$sign_status_marker" "$sign_flow_marker"); then
|
|
548
|
+
emit_control "AUTH_SIGN_REUSE_RECEIPT=${sign_reuse_receipt}"
|
|
549
|
+
fi
|
|
497
550
|
emit_control "AUTH_SIGN_QUERY_REUSABLE=true"
|
|
498
551
|
fi
|
|
499
552
|
fi
|
|
@@ -576,13 +629,17 @@ auth_init() {
|
|
|
576
629
|
# 后端查询没有权威 MCC 字段,旧状态变化时不能把现有登录误判为当前上下文已授权。
|
|
577
630
|
DEVICE_CODE=""
|
|
578
631
|
BROWSER_URL=""
|
|
579
|
-
|
|
632
|
+
if ! save_auth_state; then
|
|
633
|
+
emit_control "AUTH_STATE:SAVE_FAILED"
|
|
634
|
+
fi
|
|
580
635
|
emit_control "AUTH_FLOW:${AUTH_INIT_CONTEXT_MISMATCH_TYPE}"
|
|
581
636
|
return 1
|
|
582
637
|
fi
|
|
583
638
|
validate_current_authorization "$CHECK_RESULT" || return 1
|
|
584
639
|
DEVICE_CODE=""
|
|
585
|
-
|
|
640
|
+
if ! save_auth_state; then
|
|
641
|
+
emit_control "AUTH_STATE:SAVE_FAILED"
|
|
642
|
+
fi
|
|
586
643
|
emit_control "✅ 当前登录及授权范围有效,可继续后续流程"
|
|
587
644
|
emit_control "AUTH_FLOW:SKIP"
|
|
588
645
|
return 0
|
|
@@ -602,7 +659,9 @@ auth_init() {
|
|
|
602
659
|
if [ "$LOGIN_STATUS" = "already_logged_in" ]; then
|
|
603
660
|
validate_current_authorization || return 1
|
|
604
661
|
DEVICE_CODE=""
|
|
605
|
-
|
|
662
|
+
if ! save_auth_state; then
|
|
663
|
+
emit_control "AUTH_STATE:SAVE_FAILED"
|
|
664
|
+
fi
|
|
606
665
|
emit_control "✅ 当前登录及授权范围有效,可继续后续流程"
|
|
607
666
|
emit_control "AUTH_FLOW:SKIP"
|
|
608
667
|
return 0
|
|
@@ -630,10 +689,7 @@ auth_init() {
|
|
|
630
689
|
return 1
|
|
631
690
|
}
|
|
632
691
|
|
|
633
|
-
# Step 6:
|
|
634
|
-
save_auth_state || return 1
|
|
635
|
-
|
|
636
|
-
# Step 7: 有效期转换(对非数字做防护)
|
|
692
|
+
# Step 6: 有效期转换(对非数字做防护)
|
|
637
693
|
if [[ "$EXPIRES_IN" =~ ^[0-9]+$ ]] && [ "$EXPIRES_IN" -ge 60 ]; then
|
|
638
694
|
EXPIRES_MIN=$((EXPIRES_IN / 60))
|
|
639
695
|
EXPIRES_DISPLAY="${EXPIRES_MIN} 分钟"
|
|
@@ -643,7 +699,7 @@ auth_init() {
|
|
|
643
699
|
EXPIRES_DISPLAY="10 分钟"
|
|
644
700
|
fi
|
|
645
701
|
|
|
646
|
-
# Step
|
|
702
|
+
# Step 7: 由统一目录渲染授权信息;独立 open 动作在消息发出后打开。
|
|
647
703
|
DISPLAY_CODE="${VERIFICATION_CODE:-(请查看支付宝授权页面)}"
|
|
648
704
|
MESSAGE_INPUT=$(printf '%s\n' "$PRODUCT_NAME" "$DEVICE_CODE" "$MCC_CODE" "$MCC_NAME" "$DISPLAY_CODE" "$EXPIRES_DISPLAY" "$BROWSER_URL" | jq -Rs '
|
|
649
705
|
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]}
|
|
@@ -653,6 +709,8 @@ auth_init() {
|
|
|
653
709
|
emit_control "AUTH_FLOW:FAILED"
|
|
654
710
|
exit 1
|
|
655
711
|
fi
|
|
712
|
+
# Step 8: 页面 stdout 已经安全送达时,状态文件只用于 opener 便利;保存失败不得生成第二个授权页或截断 wait。
|
|
713
|
+
save_auth_state_after_page_delivery
|
|
656
714
|
echo ""
|
|
657
715
|
emit_control "AUTH_FLOW:READY_TO_OPEN"
|
|
658
716
|
}
|
|
@@ -661,12 +719,11 @@ auth_open() {
|
|
|
661
719
|
local saved_device saved_url validate_input open_status open_helper
|
|
662
720
|
|
|
663
721
|
restore_auth_context_or_state "$@" || exit 1
|
|
664
|
-
saved_device=$(jq -r '.deviceCode // ""' "$AUTH_STATE_FILE")
|
|
665
|
-
saved_url=$(jq -r '.authorizationUrl // ""' "$AUTH_STATE_FILE")
|
|
722
|
+
saved_device=$(jq -r '.deviceCode // ""' "$AUTH_STATE_FILE" 2>/dev/null || printf '')
|
|
723
|
+
saved_url=$(jq -r '.authorizationUrl // ""' "$AUTH_STATE_FILE" 2>/dev/null || printf '')
|
|
666
724
|
if [ -z "$saved_device" ] || [ -z "$saved_url" ]; then
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
return 1
|
|
725
|
+
emit_control "AUTH_OPEN:LINK_ONLY"
|
|
726
|
+
return 0
|
|
670
727
|
fi
|
|
671
728
|
|
|
672
729
|
validate_input=$(jq -n \
|
|
@@ -693,6 +750,7 @@ auth_open() {
|
|
|
693
750
|
# ─── auth confirm: 授权确认 + scope 校验 + MCC 授权有效性校验 ────────────────
|
|
694
751
|
auth_confirm() {
|
|
695
752
|
AUTH_CONFIRM_RESULT=""
|
|
753
|
+
local complete_error_type
|
|
696
754
|
# 优先使用显式传入的非敏感上下文;未传时再从状态文件兜底恢复。
|
|
697
755
|
restore_auth_context_or_state "$@" || exit 1
|
|
698
756
|
|
|
@@ -740,12 +798,41 @@ auth_confirm() {
|
|
|
740
798
|
elif [ "$OUTER_SUCCESS" = "true" ] || [ "$DATA_STATUS" = "logged_in" ] || [ "$DATA_STATUS" = "already_logged_in" ] || [ "$DATA_STATUS" = "completed" ]; then
|
|
741
799
|
:
|
|
742
800
|
else
|
|
743
|
-
|
|
744
|
-
|
|
801
|
+
complete_error_type=$(detect_error "$LOGIN_COMPLETE_RESULT")
|
|
802
|
+
if ! handle_error "$LOGIN_COMPLETE_RESULT"; then
|
|
803
|
+
cleanup_auth_state
|
|
804
|
+
case "$complete_error_type" in
|
|
805
|
+
MCP_AUTH_ERROR|AUTH_MISMATCH) ;;
|
|
806
|
+
MCP_SERVICE_ERROR|SERVICE_UNSTABLE)
|
|
807
|
+
emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
|
|
808
|
+
;;
|
|
809
|
+
*)
|
|
810
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
811
|
+
;;
|
|
812
|
+
esac
|
|
813
|
+
return 1
|
|
814
|
+
fi
|
|
815
|
+
wait_for_effective_whoami "授权完成确认状态复核"
|
|
816
|
+
whoami_rc=$?
|
|
817
|
+
if [ "$whoami_rc" -eq 2 ]; then
|
|
818
|
+
AUTH_CONFIRM_RESULT="PENDING"
|
|
819
|
+
if [ "${AUTH_POLLING:-false}" != "true" ]; then
|
|
820
|
+
if ! printf '%s' '{}' | ALIPAY_AIPAY_RENDERER_MANAGED_CALLER=auth.sh node "$RENDERER" auth.pending --variant DEFAULT; then
|
|
821
|
+
emit_control "AUTH_FLOW:FAILED"
|
|
822
|
+
return 1
|
|
823
|
+
fi
|
|
824
|
+
emit_control "AUTH_FLOW:PENDING"
|
|
825
|
+
fi
|
|
826
|
+
return 0
|
|
827
|
+
elif [ "$whoami_rc" -ne 0 ]; then
|
|
828
|
+
return 1
|
|
745
829
|
fi
|
|
830
|
+
validate_current_authorization "$AUTH_WHOAMI_RESULT" || return 1
|
|
746
831
|
cleanup_auth_state
|
|
747
|
-
|
|
748
|
-
|
|
832
|
+
AUTH_CONFIRM_RESULT="AUTH_SUCCESS"
|
|
833
|
+
emit_control "✅ 授权确认成功(本机 CLI 登录态、scope 与 MCC 授权有效性均已通过)"
|
|
834
|
+
emit_control "AUTH_FLOW:AUTH_SUCCESS"
|
|
835
|
+
return 0
|
|
749
836
|
fi
|
|
750
837
|
|
|
751
838
|
# Step 3: 复用 init 的 scope + MCC 授权有效性校验
|
|
@@ -817,8 +904,6 @@ auth_wait() {
|
|
|
817
904
|
|
|
818
905
|
# ─── auth mismatch: 授权范围不满足 → logout → 重新授权 ──────────────────────
|
|
819
906
|
auth_mismatch() {
|
|
820
|
-
local logout_result logout_error_type
|
|
821
|
-
|
|
822
907
|
if [ "${1:-}" = "--defer-open" ]; then
|
|
823
908
|
shift
|
|
824
909
|
else
|
|
@@ -847,16 +932,21 @@ auth_mismatch() {
|
|
|
847
932
|
emit_control "AUTH_FLOW:FAILED"
|
|
848
933
|
return 1
|
|
849
934
|
fi
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
935
|
+
confirm_logout_postcondition
|
|
936
|
+
case "$ALIPAY_LOGOUT_POSTCONDITION" in
|
|
937
|
+
COMMAND_CONFIRMED_SUCCESS|POSTCONDITION_LOGGED_OUT)
|
|
938
|
+
;;
|
|
939
|
+
STILL_LOGGED_IN)
|
|
940
|
+
render_logout_still_logged_in || return 1
|
|
941
|
+
emit_control "AUTH_FLOW:LOGOUT_STILL_LOGGED_IN"
|
|
942
|
+
return 1
|
|
943
|
+
;;
|
|
944
|
+
*)
|
|
945
|
+
render_logout_unconfirmed || return 1
|
|
946
|
+
emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
|
|
947
|
+
return 1
|
|
948
|
+
;;
|
|
949
|
+
esac
|
|
860
950
|
|
|
861
951
|
# Step 3: 调用 auth init 执行登录 + 构建授权链接 + 输出唯一授权消息。
|
|
862
952
|
# DEV_TOOL_NAME 已 export,子进程可继承。
|
|
@@ -383,31 +383,119 @@ detect_error() {
|
|
|
383
383
|
echo "SUCCESS"
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
local
|
|
389
|
-
|
|
386
|
+
extract_captured_json() {
|
|
387
|
+
local RAW="$1"
|
|
388
|
+
local ANALYSIS STATUS
|
|
389
|
+
ANALYSIS=$(extract_unique_json "$RAW" 2>/dev/null || echo '{"status":"none"}')
|
|
390
|
+
STATUS=$(echo "$ANALYSIS" | jq -r '.status // "none"' 2>/dev/null)
|
|
391
|
+
[ "$STATUS" = "ok" ] || return 1
|
|
392
|
+
echo "$ANALYSIS" | jq -ce '.value | select(type == "object")' 2>/dev/null
|
|
393
|
+
}
|
|
390
394
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
395
|
+
run_alipay_cli_json_capture() {
|
|
396
|
+
local stderr_file stdout stderr_output combined_output
|
|
397
|
+
|
|
398
|
+
ALIPAY_CLI_JSON_CAPTURE=""
|
|
399
|
+
ALIPAY_CLI_JSON_EXIT_CODE=0
|
|
400
|
+
stderr_file="$(mktemp "${TMPDIR:-/tmp}/alipay_cli_capture_stderr.XXXXXX")" || return 1
|
|
401
|
+
stdout=$(PLATFORM="${DEV_TOOL_NAME:-unknown}" PLATFORM_ID="${PLATFORM_ID:-}" PRODUCT="${PRODUCT:-}" "$@" 2>"$stderr_file")
|
|
402
|
+
ALIPAY_CLI_JSON_EXIT_CODE=$?
|
|
403
|
+
stderr_output=$(cat "$stderr_file")
|
|
404
|
+
rm -f "$stderr_file"
|
|
405
|
+
|
|
406
|
+
if ALIPAY_CLI_JSON_CAPTURE=$(extract_captured_json "$stdout"); then
|
|
407
|
+
return 0
|
|
408
|
+
fi
|
|
409
|
+
if ALIPAY_CLI_JSON_CAPTURE=$(extract_captured_json "$stderr_output"); then
|
|
410
|
+
return 0
|
|
411
|
+
fi
|
|
412
|
+
combined_output=$(printf '%s\n%s' "$stdout" "$stderr_output")
|
|
413
|
+
if ALIPAY_CLI_JSON_CAPTURE=$(extract_captured_json "$combined_output"); then
|
|
414
|
+
return 0
|
|
415
|
+
fi
|
|
416
|
+
|
|
417
|
+
ALIPAY_CLI_JSON_CAPTURE=""
|
|
418
|
+
return 1
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
confirm_logout_postcondition() {
|
|
422
|
+
local logout_error_type whoami_error_type whoami_has_state logged_in is_expired
|
|
423
|
+
|
|
424
|
+
ALIPAY_LOGOUT_POSTCONDITION=""
|
|
425
|
+
ALIPAY_LOGOUT_ERROR_MESSAGE=""
|
|
426
|
+
|
|
427
|
+
if run_alipay_cli_json_capture alipay-cli logout --json; then
|
|
428
|
+
logout_error_type=$(detect_error "$ALIPAY_CLI_JSON_CAPTURE")
|
|
429
|
+
if [ "$ALIPAY_CLI_JSON_EXIT_CODE" -eq 0 ] && [ "$logout_error_type" = "SUCCESS" ]; then
|
|
430
|
+
ALIPAY_LOGOUT_POSTCONDITION="COMMAND_CONFIRMED_SUCCESS"
|
|
431
|
+
return 0
|
|
406
432
|
fi
|
|
433
|
+
ALIPAY_LOGOUT_ERROR_MESSAGE=$(sanitize_customer_error_text "$(extract_error_message "$ALIPAY_CLI_JSON_CAPTURE")")
|
|
434
|
+
else
|
|
435
|
+
ALIPAY_LOGOUT_ERROR_MESSAGE="退出命令未返回可唯一解析结果"
|
|
436
|
+
fi
|
|
437
|
+
|
|
438
|
+
if ! run_alipay_cli_json_capture alipay-cli whoami --json; then
|
|
439
|
+
ALIPAY_LOGOUT_POSTCONDITION="UNCONFIRMED"
|
|
407
440
|
return 1
|
|
408
441
|
fi
|
|
409
442
|
|
|
410
|
-
|
|
443
|
+
whoami_has_state=$(echo "$ALIPAY_CLI_JSON_CAPTURE" | jq -r '
|
|
444
|
+
if (.data | type) == "object" and ((.data | has("logged_in")) or (.data | has("is_expired"))) then "true" else "false" end
|
|
445
|
+
' 2>/dev/null)
|
|
446
|
+
if [ "$whoami_has_state" != "true" ]; then
|
|
447
|
+
ALIPAY_LOGOUT_POSTCONDITION="UNCONFIRMED"
|
|
448
|
+
return 1
|
|
449
|
+
fi
|
|
450
|
+
logged_in=$(echo "$ALIPAY_CLI_JSON_CAPTURE" | jq -r '.data.logged_in // false' 2>/dev/null)
|
|
451
|
+
is_expired=$(echo "$ALIPAY_CLI_JSON_CAPTURE" | jq -r '.data.is_expired // false' 2>/dev/null)
|
|
452
|
+
if [ "$logged_in" = "true" ] && [ "$is_expired" != "true" ]; then
|
|
453
|
+
ALIPAY_LOGOUT_POSTCONDITION="STILL_LOGGED_IN"
|
|
454
|
+
return 1
|
|
455
|
+
fi
|
|
456
|
+
whoami_error_type=$(detect_error "$ALIPAY_CLI_JSON_CAPTURE")
|
|
457
|
+
if [ "$ALIPAY_CLI_JSON_EXIT_CODE" -ne 0 ] || { [ "$whoami_error_type" != "SUCCESS" ] && [ "$logged_in" = "true" ] && [ "$is_expired" != "true" ]; }; then
|
|
458
|
+
ALIPAY_LOGOUT_POSTCONDITION="UNCONFIRMED"
|
|
459
|
+
return 1
|
|
460
|
+
fi
|
|
461
|
+
|
|
462
|
+
ALIPAY_LOGOUT_POSTCONDITION="POSTCONDITION_LOGGED_OUT"
|
|
463
|
+
return 0
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
render_logout_unconfirmed() {
|
|
467
|
+
render_error_message error.mcp.auth LOGOUT_UNCONFIRMED '{}'
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
render_logout_still_logged_in() {
|
|
471
|
+
local message input
|
|
472
|
+
message="${ALIPAY_LOGOUT_ERROR_MESSAGE:-退出命令未能使当前登录态失效}"
|
|
473
|
+
input=$(jq -cn --arg errorMessage "$message" '{errorMessage:$errorMessage}')
|
|
474
|
+
render_error_message error.mcp.auth LOGOUT_FAILED "$input"
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
# ─── MCP 认证错误处理 ──────────────────────────────────────────────────────
|
|
478
|
+
handle_mcp_auth_error() {
|
|
479
|
+
render_error_message error.mcp.auth START '{}' || return 1
|
|
480
|
+
if [ "${ALIPAY_AIPAY_SUPPRESS_AUTH_LOGOUT:-}" = "1" ]; then
|
|
481
|
+
emit_control "AUTH_FLOW:AUTH_REQUIRED"
|
|
482
|
+
return 1
|
|
483
|
+
fi
|
|
484
|
+
confirm_logout_postcondition
|
|
485
|
+
case "$ALIPAY_LOGOUT_POSTCONDITION" in
|
|
486
|
+
COMMAND_CONFIRMED_SUCCESS|POSTCONDITION_LOGGED_OUT)
|
|
487
|
+
render_error_message error.mcp.auth LOGOUT_SUCCESS '{}' || return 1
|
|
488
|
+
emit_control "AUTH_FLOW:AUTH_REQUIRED"
|
|
489
|
+
;;
|
|
490
|
+
STILL_LOGGED_IN)
|
|
491
|
+
render_logout_still_logged_in || return 1
|
|
492
|
+
emit_control "AUTH_FLOW:LOGOUT_STILL_LOGGED_IN"
|
|
493
|
+
;;
|
|
494
|
+
*)
|
|
495
|
+
render_logout_unconfirmed || return 1
|
|
496
|
+
emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
|
|
497
|
+
;;
|
|
498
|
+
esac
|
|
411
499
|
}
|
|
412
500
|
|
|
413
501
|
# ─── MCP 服务不可用处理 ────────────────────────────────────────────────────
|
|
@@ -351,6 +351,7 @@ service_save() {
|
|
|
351
351
|
if [ "$RETRY_RC" -eq 75 ]; then
|
|
352
352
|
if [ -z "$SERVICE_ID" ]; then
|
|
353
353
|
echo "❌ 服务创建响应不明;现有查询无法把同名候选唯一归属于本次创建,结果标记为 UNKNOWN,禁止自动重复创建"
|
|
354
|
+
emit_control "SERVICE_SAVE_RESULT=UNKNOWN"
|
|
354
355
|
exit 1
|
|
355
356
|
fi
|
|
356
357
|
if fetch_service_list; then
|
|
@@ -365,11 +366,13 @@ service_save() {
|
|
|
365
366
|
if [ "$MATCH_COUNT" -eq 1 ]; then
|
|
366
367
|
emit_control "✅ 服务修改已由列表查询核验成功"
|
|
367
368
|
emit_control "📋 服务ID: $SERVICE_ID"
|
|
369
|
+
emit_control "SERVICE_SAVE_RESULT=SUCCESS"
|
|
368
370
|
emit_control_field "SERVICE_ID" "$SERVICE_ID" || return 1
|
|
369
371
|
return 0
|
|
370
372
|
fi
|
|
371
373
|
fi
|
|
372
374
|
echo "❌ 服务修改响应不明且现有列表无法排除传播延迟,结果标记为 UNKNOWN,禁止自动重复修改"
|
|
375
|
+
emit_control "SERVICE_SAVE_RESULT=UNKNOWN"
|
|
373
376
|
exit 1
|
|
374
377
|
elif [ "$RETRY_RC" -ne 0 ]; then
|
|
375
378
|
if [ -z "$SERVICE_ID" ]; then
|
|
@@ -377,11 +380,13 @@ service_save() {
|
|
|
377
380
|
else
|
|
378
381
|
echo "❌ 服务修改因明确未发送的网络异常在自动重试两次后仍未恢复"
|
|
379
382
|
fi
|
|
383
|
+
emit_control "SERVICE_SAVE_RESULT=FAILED"
|
|
380
384
|
exit 1
|
|
381
385
|
fi
|
|
382
386
|
|
|
383
387
|
# 错误检测
|
|
384
388
|
if ! handle_error "$RESULT"; then
|
|
389
|
+
emit_control "SERVICE_SAVE_RESULT=FAILED"
|
|
385
390
|
exit 1
|
|
386
391
|
fi
|
|
387
392
|
|
|
@@ -418,10 +423,12 @@ service_save() {
|
|
|
418
423
|
|
|
419
424
|
if ! validate_control_value "$NEW_SERVICE_ID"; then
|
|
420
425
|
echo "❌ 服务创建返回成功,但未解析到 serviceId,禁止继续后续流程"
|
|
426
|
+
emit_control "SERVICE_SAVE_RESULT=UNKNOWN"
|
|
421
427
|
exit 1
|
|
422
428
|
fi
|
|
423
429
|
emit_control "✅ 服务${OPERATION}成功"
|
|
424
430
|
emit_control "📋 服务ID: $NEW_SERVICE_ID"
|
|
431
|
+
emit_control "SERVICE_SAVE_RESULT=SUCCESS"
|
|
425
432
|
emit_control_field "SERVICE_ID" "$NEW_SERVICE_ID" || exit 1
|
|
426
433
|
else
|
|
427
434
|
OPERATION="创建"
|
|
@@ -437,8 +444,10 @@ service_save() {
|
|
|
437
444
|
' 2>/dev/null)
|
|
438
445
|
ERROR_MSG=$(sanitize_customer_error_text "$ERROR_MSG")
|
|
439
446
|
echo "❌ 服务${OPERATION}失败: ${BUSINESS_CODE} / ${ERROR_MSG}"
|
|
447
|
+
emit_control "SERVICE_SAVE_RESULT=FAILED"
|
|
440
448
|
else
|
|
441
449
|
echo "❌ 服务${OPERATION}返回结构异常,无法确认操作结果"
|
|
450
|
+
emit_control "SERVICE_SAVE_RESULT=UNKNOWN"
|
|
442
451
|
fi
|
|
443
452
|
exit 1
|
|
444
453
|
fi
|
|
@@ -33,6 +33,7 @@ case "$PRODUCT_TYPE" in
|
|
|
33
33
|
*) echo "❌ 截图上传仅支持 webpay|apppay"; exit 1 ;;
|
|
34
34
|
esac
|
|
35
35
|
init_alipay_cli_product "$PRODUCT_TYPE" || exit 1
|
|
36
|
+
export ALIPAY_AIPAY_SUPPRESS_AUTH_LOGOUT=1
|
|
36
37
|
|
|
37
38
|
HOME_IMG="${1:-}"
|
|
38
39
|
SHOP_IMG="${2:-}"
|
|
@@ -83,7 +84,7 @@ wait
|
|
|
83
84
|
# 解析图片引用值(兼容 file upload 返回 fileKey 或 URL 字段)
|
|
84
85
|
parse_image_ref() {
|
|
85
86
|
local RAW=$(cat "$1")
|
|
86
|
-
if
|
|
87
|
+
if [ "$(detect_error "$RAW")" != "SUCCESS" ]; then
|
|
87
88
|
return 1
|
|
88
89
|
fi
|
|
89
90
|
# alipay-cli file upload 可能返回 MCP 信封,先解包
|
|
@@ -120,7 +121,15 @@ PAY_REF=$(parse_image_ref "$TMP_PAY")
|
|
|
120
121
|
# 校验上传结果(含认证错误检测)
|
|
121
122
|
check_upload_error() {
|
|
122
123
|
local RAW=$(cat "$1")
|
|
124
|
+
local ERROR_TYPE
|
|
125
|
+
ERROR_TYPE=$(detect_error "$RAW")
|
|
126
|
+
if [ "$ERROR_TYPE" = "MCP_AUTH_ERROR" ] && [ "${UPLOAD_AUTH_ERROR_SEEN:-false}" = "true" ]; then
|
|
127
|
+
return 1
|
|
128
|
+
fi
|
|
123
129
|
if ! handle_error "$RAW"; then
|
|
130
|
+
if [ "$ERROR_TYPE" = "MCP_AUTH_ERROR" ]; then
|
|
131
|
+
UPLOAD_AUTH_ERROR_SEEN=true
|
|
132
|
+
fi
|
|
124
133
|
return 1
|
|
125
134
|
fi
|
|
126
135
|
return 0
|
|
@@ -29,7 +29,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" service validate --name
|
|
|
29
29
|
- 修改:必须传当前候选完整 `serviceId` 和全部五项资料;保存前使用 `onboarding.write.confirm/SERVICE_UPDATE_ONLY` 绑定当前登录会话、脱敏主体、产品、动作、目标与参数,并等待当前摘要后的明确肯定回复;`1` 只是兼容快捷输入。
|
|
30
30
|
- 任何目标、参数、主体或会话变化都使旧修改确认失效。创建不得进入修改确认。
|
|
31
31
|
|
|
32
|
-
保存写操作调用 `a2a-pay-service.saveBazaarServiceForMcp`。明确 `NOT_SENT` 才能自动重试;`MAYBE_SENT` 时用列表按 `serviceId` 和完整五项资料核验,不能靠同名服务猜测。创建结果未解析到实际 `serviceId`
|
|
32
|
+
保存写操作调用 `a2a-pay-service.saveBazaarServiceForMcp`。明确 `NOT_SENT` 才能自动重试;`MAYBE_SENT` 时用列表按 `serviceId` 和完整五项资料核验,不能靠同名服务猜测。创建结果未解析到实际 `serviceId` 时禁止宣称完成。脚本必须输出 `SERVICE_SAVE_RESULT=SUCCESS|FAILED|UNKNOWN`;`UNKNOWN` 只表示禁止自动重复保存并进入服务分支待办,不清除其他分支事实。
|
|
33
33
|
|
|
34
34
|
成功结果只用登记 renderer:
|
|
35
35
|
|
|
@@ -41,20 +41,20 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" render <messageId> --va
|
|
|
41
41
|
| 意图 | 判定 | 入口 | 后续按需加载 |
|
|
42
42
|
|---|---|---|---|
|
|
43
43
|
| `integration_only` | 只要求代码开发、支付集成或集成排障,未同时要求产品开通 | 执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" flow integration entry` | 当前阶段、产品和语言的契约、示例与脚本 |
|
|
44
|
-
| `onboarding_only` | 只要求签约、入驻或产品开通,未同时要求代码开发 | 执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs"
|
|
44
|
+
| `onboarding_only` | 只要求签约、入驻或产品开通,未同时要求代码开发 | 执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-bootstrap --root-intent onboarding_only` | 当前阶段的授权、查询或写分支模块 |
|
|
45
45
|
| `full_process` | 明确要求完整接入、一站式,或同一请求同时要求代码开发和产品开通 | `normal/full-process-routing.md` | 先 Integration,满足衔接条件后 Onboarding |
|
|
46
46
|
|
|
47
|
-
`onboarding_only`
|
|
47
|
+
`onboarding_only` 正常入口使用 `onboarding-bootstrap --root-intent onboarding_only`,该组合动作只串行执行 `telemetry start-run`、`onboarding-message platform-entry` 和 `flow onboarding entry` 三个既有无业务写入动作;Windows 只发送登记引导并停止 Skill 内产品开通,非 Windows 继续返回 entry 执行契约。执行该组合动作后不得再单独重复执行这三条原子命令。`full_process` 衔接 Onboarding 时已存在同一根流程 run,不使用带 `start-run` 的 bootstrap;仍先执行 flow 登记的 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message platform-entry`,再按需加载 Onboarding entry,避免重复 `run.intent_detected`。
|
|
48
48
|
|
|
49
|
-
识别根意图后立即执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" telemetry start-run --root-intent <integration_only|onboarding_only|full_process>`;产品已唯一确定时追加 `--product-type <aipay|webpay|apppay
|
|
49
|
+
识别根意图后立即执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" telemetry start-run --root-intent <integration_only|onboarding_only|full_process>`;产品已唯一确定时追加 `--product-type <aipay|webpay|apppay>`。唯一例外是 `onboarding_only` 已按上表执行 `onboarding-bootstrap --root-intent onboarding_only` 时,bootstrap 已包含同一次 `start-run`,不得重复执行本条 telemetry。命令创建/复用当前流程 `run_id`,并在遥测开启时记录 `run.intent_detected`:同一意图和产品继续复用,`UNKNOWN` 补全产品仍复用同一 `run_id`;明确换产品、换根意图或既有 run 已终态才重建。第三方平台项目 ID 仍优先固定为 `run_id`,不得被显式 run ID 或本地生成值覆盖。业务 stage 只由登记 runner/script 异步记录。
|
|
50
50
|
|
|
51
51
|
遥测默认开启,`ALIPAY_AIPAY_TELEMETRY=0|false|off|no` 可关闭 `METRIC_EVENT`、outbox、上传和 flush;私有流程 run context 仍用于 `PLATFORM_ID` 和终态生命周期。落盘/网络/上传/flush 失败静默,不改变业务、消息或确认,stdout 为空且 stderr 内部行不对客。`integration_only` 由 checklist 终态结束 run,其他意图由 Onboarding closeout 结束。脚本始终以当前流程 `run_id` 为 `PLATFORM_ID`,Agent 不手写环境前缀或改 MCP payload;维护诊断、outbox、上传和 flush 细节只在 `normal/telemetry.md` 按需读取。
|
|
52
52
|
|
|
53
|
-
三种入口均只在无法从已有产品名称或业务描述可靠确定唯一产品时执行 flow 登记的 `product-clarify` runner;不得推定或手写澄清。产品明确但 MCC 尚无检索输入时使用 `mcc-clarify
|
|
53
|
+
三种入口均只在无法从已有产品名称或业务描述可靠确定唯一产品时执行 flow 登记的 `product-clarify` runner;不得推定或手写澄清。产品明确但 MCC 尚无检索输入时使用 `mcc-clarify`;已有经营描述后先按 Onboarding flow 读取一次当前 `modules/mcc-reference.md` 做 Agent 语义候选,再统一调用 `mcc-resolve-plan` 由 runner 复核、合并和渲染;用户直接提供当前表内完整 MCC 编码时可跳过 Agent 泛化并由 runner 精确校验。不得拆回 `mcc-search + mcc-candidates`、先旧检索失败后临时猜测,或为方案展示再拼 renderer 输入。
|
|
54
54
|
|
|
55
55
|
代码开发或完整接入缺项目位置/技术栈时,执行 `integration.context.required` 或 `integration.project_path.required` 标准消息。先区分新建与复用;`PROJECT_INPUT` 保留原话,明确新建项目/网站/商城/应用等产物时必须 `resolve-project --intent new`,不得因当前目录有项目改为复用。未知现有项目先轻量定位,所有路径再由 `resolve-project` 解析;补齐前不初始化、装依赖或改文件。
|
|
56
56
|
|
|
57
|
-
每轮代码开发只渲染一次非阻塞 `integration.start.confirm`,两种意图复用同一次检查器事实并覆盖 Integration 步骤 1 至步骤8:代码开发校验;`full_process` 在方案展示前已完成步骤 1,方案送达且 receipt 有效后进入步骤 3。Onboarding
|
|
57
|
+
每轮代码开发只渲染一次非阻塞 `integration.start.confirm`,两种意图复用同一次检查器事实并覆盖 Integration 步骤 1 至步骤8:代码开发校验;`full_process` 在方案展示前已完成步骤 1,方案送达且 receipt 有效后进入步骤 3。Onboarding 只保留一次非阻塞方案展示;方案送达且 MCC 唯一事实有效后,正常路径执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-execute-ready --product-type "$PRODUCT_TYPE"`,该组合动作只串行加载 `flow onboarding execute` 并执行既有 `env check`,不跨用户输入、授权页或任何写操作;执行后不得再重复单独加载 execute phase 或重复环境检查。服务修改仍保留绑定当前摘要的阻塞确认。签约材料完整后直接提交,服务创建摘要后直接创建,新建应用公钥校验成功后直接提审。
|
|
58
58
|
|
|
59
59
|
扫码页非阻塞:授权最多 12 次/间隔 5 秒,公钥最多 20 次/间隔 2 秒,成功即推进;耗尽后经统一回复判定只查一次,不重启轮询。
|
|
60
60
|
|