moltspay 1.6.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +1 -0
  2. package/dist/cdp/index.js.map +1 -1
  3. package/dist/cdp/index.mjs.map +1 -1
  4. package/dist/{cdp-DeohBe1o.d.ts → cdp-B5PhDUTC.d.ts} +1 -1
  5. package/dist/{cdp-p_eHuQpb.d.mts → cdp-D-5Hg3y_.d.mts} +1 -1
  6. package/dist/chains/index.d.mts +37 -1
  7. package/dist/chains/index.d.ts +37 -1
  8. package/dist/chains/index.js +22 -0
  9. package/dist/chains/index.js.map +1 -1
  10. package/dist/chains/index.mjs +19 -0
  11. package/dist/chains/index.mjs.map +1 -1
  12. package/dist/cli/index.js +1466 -61
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/cli/index.mjs +1466 -61
  15. package/dist/cli/index.mjs.map +1 -1
  16. package/dist/client/index.d.mts +41 -0
  17. package/dist/client/index.d.ts +41 -0
  18. package/dist/client/index.js +824 -10
  19. package/dist/client/index.js.map +1 -1
  20. package/dist/client/index.mjs +824 -10
  21. package/dist/client/index.mjs.map +1 -1
  22. package/dist/client/web/index.d.mts +17 -1
  23. package/dist/client/web/index.mjs +11 -0
  24. package/dist/client/web/index.mjs.map +1 -1
  25. package/dist/facilitators/index.d.mts +161 -4
  26. package/dist/facilitators/index.d.ts +161 -4
  27. package/dist/facilitators/index.js +370 -0
  28. package/dist/facilitators/index.js.map +1 -1
  29. package/dist/facilitators/index.mjs +365 -0
  30. package/dist/facilitators/index.mjs.map +1 -1
  31. package/dist/index.d.mts +77 -2
  32. package/dist/index.d.ts +77 -2
  33. package/dist/index.js +1412 -31
  34. package/dist/index.js.map +1 -1
  35. package/dist/index.mjs +1406 -31
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/mcp/index.js +828 -14
  38. package/dist/mcp/index.js.map +1 -1
  39. package/dist/mcp/index.mjs +828 -14
  40. package/dist/mcp/index.mjs.map +1 -1
  41. package/dist/{registry-OsEO2dOu.d.mts → registry-DIo0WNoO.d.mts} +8 -1
  42. package/dist/{registry-OsEO2dOu.d.ts → registry-DIo0WNoO.d.ts} +8 -1
  43. package/dist/server/index.d.mts +95 -2
  44. package/dist/server/index.d.ts +95 -2
  45. package/dist/server/index.js +554 -14
  46. package/dist/server/index.js.map +1 -1
  47. package/dist/server/index.mjs +554 -14
  48. package/dist/server/index.mjs.map +1 -1
  49. package/dist/verify/index.js.map +1 -1
  50. package/dist/verify/index.mjs.map +1 -1
  51. package/dist/wallet/index.js.map +1 -1
  52. package/dist/wallet/index.mjs.map +1 -1
  53. package/package.json +7 -1
  54. package/schemas/moltspay.services.schema.json +100 -6
  55. package/scripts/postinstall.js +91 -0
@@ -21,10 +21,10 @@ var init_esm_shims = __esm({
21
21
  init_esm_shims();
22
22
  import { webcrypto } from "crypto";
23
23
  import { Command } from "commander";
24
- import { homedir as homedir3 } from "os";
25
- import { join as join5, dirname, resolve } from "path";
24
+ import { homedir as homedir4 } from "os";
25
+ import { join as join6, dirname as dirname2, resolve as resolve2 } from "path";
26
26
  import { existsSync as existsSync5, writeFileSync as writeFileSync3, readFileSync as readFileSync5, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
27
- import { spawn } from "child_process";
27
+ import { spawn as spawn2 } from "child_process";
28
28
  import { ethers as ethers4 } from "ethers";
29
29
 
30
30
  // src/client/index.ts
@@ -33,8 +33,8 @@ init_esm_shims();
33
33
  // src/client/node/index.ts
34
34
  init_esm_shims();
35
35
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, statSync, chmodSync } from "fs";
36
- import { homedir as homedir2 } from "os";
37
- import { join as join2 } from "path";
36
+ import { homedir as homedir3 } from "os";
37
+ import { join as join3 } from "path";
38
38
  import { Wallet as Wallet2, ethers as ethers2 } from "ethers";
39
39
 
40
40
  // src/chains/index.ts
@@ -211,6 +211,10 @@ function getChain(name) {
211
211
  }
212
212
  return config;
213
213
  }
214
+ var ALIPAY_CHAIN_ID = "alipay";
215
+ function isAlipayChainId(id) {
216
+ return id === ALIPAY_CHAIN_ID;
217
+ }
214
218
 
215
219
  // src/wallet/solana.ts
216
220
  init_esm_shims();
@@ -612,6 +616,14 @@ var BufferCtor = globalThis.Buffer;
612
616
 
613
617
  // src/client/core/errors.ts
614
618
  init_esm_shims();
619
+ var MoltsPayError = class extends Error {
620
+ code;
621
+ constructor(code, message) {
622
+ super(message);
623
+ this.name = "MoltsPayError";
624
+ this.code = code;
625
+ }
626
+ };
615
627
 
616
628
  // src/client/core/eip3009.ts
617
629
  init_esm_shims();
@@ -761,6 +773,740 @@ function findChainByChainId(chainId) {
761
773
  return void 0;
762
774
  }
763
775
 
776
+ // src/client/alipay/index.ts
777
+ init_esm_shims();
778
+ import { randomUUID } from "crypto";
779
+ import { mkdir, writeFile } from "fs/promises";
780
+ import { homedir as homedir2 } from "os";
781
+ import { join as join2 } from "path";
782
+
783
+ // src/client/alipay/cli.ts
784
+ init_esm_shims();
785
+ import { spawn } from "child_process";
786
+
787
+ // src/client/alipay/log.ts
788
+ init_esm_shims();
789
+ var RANK = { off: 0, info: 1, debug: 2 };
790
+ function normalizeLevel(v) {
791
+ const s = (v ?? "").toLowerCase();
792
+ return s === "info" || s === "debug" ? s : "off";
793
+ }
794
+ var level = normalizeLevel(process.env.MOLTSPAY_ALIPAY_LOG);
795
+ var sink = (line) => {
796
+ process.stderr.write(line + "\n");
797
+ };
798
+ function enabledFor(want) {
799
+ return RANK[level] >= RANK[want];
800
+ }
801
+ function render(event, fields) {
802
+ const parts = ["[moltspay:alipay]", String(fields.ts), event];
803
+ if (fields.flow) parts.push(`flow=${fields.flow}`);
804
+ if (fields.step) parts.push(`step=${fields.step}`);
805
+ if (typeof fields.ms === "number") parts.push(`${fields.ms}ms`);
806
+ for (const [k, v] of Object.entries(fields)) {
807
+ if (["ts", "level", "event", "flow", "step", "ms"].includes(k)) continue;
808
+ parts.push(`${k}=${typeof v === "string" ? v : JSON.stringify(v)}`);
809
+ }
810
+ return parts.join(" ");
811
+ }
812
+ function emit(lvl, event, fields) {
813
+ if (!enabledFor(lvl)) return;
814
+ const full = { ts: (/* @__PURE__ */ new Date()).toISOString(), level: lvl, event, ...fields };
815
+ try {
816
+ sink(render(event, full), full);
817
+ } catch {
818
+ }
819
+ }
820
+ var alipayLog = {
821
+ info: (event, fields = {}) => emit("info", event, fields),
822
+ debug: (event, fields = {}) => emit("debug", event, fields),
823
+ /** True if anything at all would be logged (cheap guard for hot paths). */
824
+ enabled: () => level !== "off"
825
+ };
826
+ async function timeStep(step, flow, fn) {
827
+ if (!alipayLog.enabled()) return fn();
828
+ const t0 = Date.now();
829
+ alipayLog.debug("step.start", { flow, step });
830
+ try {
831
+ const out = await fn();
832
+ alipayLog.info("step.end", { flow, step, ms: Date.now() - t0, ok: true });
833
+ return out;
834
+ } catch (err) {
835
+ alipayLog.info("step.end", {
836
+ flow,
837
+ step,
838
+ ms: Date.now() - t0,
839
+ ok: false,
840
+ err: err instanceof Error ? err.message : String(err)
841
+ });
842
+ throw err;
843
+ }
844
+ }
845
+
846
+ // src/client/alipay/cli.ts
847
+ var ALLOWED_ENV = /* @__PURE__ */ new Set([
848
+ "AIPAY_OUTPUT_CHANNEL",
849
+ "AIPAY_SESSION_ID",
850
+ "AIPAY_FRAMEWORK",
851
+ "AIPAY_MODEL",
852
+ "AIPAY_OS",
853
+ // Minimal survival set for spawn to find the binary and a home dir.
854
+ "PATH",
855
+ "HOME"
856
+ ]);
857
+ function filterEnv(env) {
858
+ return Object.fromEntries(
859
+ Object.entries(env).filter(([k]) => ALLOWED_ENV.has(k))
860
+ );
861
+ }
862
+ function makeLineSplitter(onLine) {
863
+ let buf = "";
864
+ return (chunk) => {
865
+ buf += chunk.toString("utf-8");
866
+ let nl;
867
+ while ((nl = buf.indexOf("\n")) !== -1) {
868
+ onLine(buf.slice(0, nl));
869
+ buf = buf.slice(nl + 1);
870
+ }
871
+ };
872
+ }
873
+ function profileEnv(step, flow) {
874
+ const want = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE;
875
+ const hook = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE_HOOK;
876
+ if (!want || !hook) return {};
877
+ const steps = want.split(",").map((s) => s.trim());
878
+ if (!steps.includes("all") && !steps.includes(step)) return {};
879
+ const dir = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE_DIR || "/tmp";
880
+ const safe = `${flow ?? "noflow"}-${step}-${Date.now()}`.replace(/[^a-zA-Z0-9._-]/g, "_");
881
+ const out = `${dir}/cli-profile-${safe}.json`;
882
+ alipayLog.info("cli.profile", { flow, step, out });
883
+ return {
884
+ NODE_OPTIONS: `--require=${hook}`,
885
+ MOLTSPAY_CLI_PROFILE_OUT: out
886
+ };
887
+ }
888
+ var runCli = (args, opts = {}) => {
889
+ const bin = opts.bin ?? "alipay-bot";
890
+ const step = opts.step ?? args[0] ?? "(no-arg)";
891
+ const startedAt = Date.now();
892
+ const lines = [];
893
+ const collect = (line) => {
894
+ lines.push(line);
895
+ alipayLog.debug("cli.line", {
896
+ flow: opts.flow,
897
+ step,
898
+ ms: Date.now() - startedAt,
899
+ preview: line.slice(0, 120)
900
+ });
901
+ opts.onLine?.(line);
902
+ };
903
+ alipayLog.debug("step.start", { flow: opts.flow, step });
904
+ return new Promise((resolve3, reject) => {
905
+ const child = spawn(bin, args, {
906
+ env: { ...filterEnv(process.env), ...profileEnv(step, opts.flow), ...opts.env ?? {} }
907
+ });
908
+ if (opts.signal) {
909
+ if (opts.signal.aborted) child.kill("SIGTERM");
910
+ opts.signal.addEventListener("abort", () => child.kill("SIGTERM"), { once: true });
911
+ }
912
+ let sawByte = false;
913
+ const onChunk = (stream) => (chunk) => {
914
+ const ms = Date.now() - startedAt;
915
+ if (!sawByte) {
916
+ sawByte = true;
917
+ alipayLog.debug("cli.firstbyte", { flow: opts.flow, step, ms });
918
+ }
919
+ alipayLog.debug("cli.chunk", { flow: opts.flow, step, ms, stream, bytes: chunk.length });
920
+ };
921
+ const onStdout = makeLineSplitter(collect);
922
+ const onStderr = makeLineSplitter(collect);
923
+ child.stdout?.on("data", onChunk("out"));
924
+ child.stderr?.on("data", onChunk("err"));
925
+ child.stdout?.on("data", onStdout);
926
+ child.stderr?.on("data", onStderr);
927
+ child.on("error", (err) => {
928
+ alipayLog.info("cli.exit", {
929
+ flow: opts.flow,
930
+ step,
931
+ ms: Date.now() - startedAt,
932
+ ok: false,
933
+ err: err.message
934
+ });
935
+ reject(err);
936
+ });
937
+ child.on("close", (code) => {
938
+ alipayLog.info("cli.exit", {
939
+ flow: opts.flow,
940
+ step,
941
+ ms: Date.now() - startedAt,
942
+ ok: (code ?? 1) === 0,
943
+ code: code ?? 1
944
+ });
945
+ resolve3({ exitCode: code ?? 1, lines });
946
+ });
947
+ });
948
+ };
949
+
950
+ // src/client/alipay/install.ts
951
+ init_esm_shims();
952
+ import { execFile } from "child_process";
953
+ import { promisify } from "util";
954
+
955
+ // src/client/alipay/errors.ts
956
+ init_esm_shims();
957
+ var AlipayCliNotFoundError = class extends MoltsPayError {
958
+ constructor(message) {
959
+ super("ALIPAY_CLI_NOT_FOUND", message);
960
+ this.name = "AlipayCliNotFoundError";
961
+ }
962
+ };
963
+ var AlipayCliVersionError = class extends MoltsPayError {
964
+ constructor(message) {
965
+ super("ALIPAY_CLI_VERSION", message);
966
+ this.name = "AlipayCliVersionError";
967
+ }
968
+ };
969
+ var NeedsWalletSetupError = class extends MoltsPayError {
970
+ constructor(message = "Alipay wallet not set up. Run: moltspay alipay apply") {
971
+ super("ALIPAY_NEEDS_WALLET_SETUP", message);
972
+ this.name = "NeedsWalletSetupError";
973
+ }
974
+ };
975
+ var AlipayPaymentRejectedError = class extends MoltsPayError {
976
+ constructor(message) {
977
+ super("ALIPAY_PAYMENT_REJECTED", message);
978
+ this.name = "AlipayPaymentRejectedError";
979
+ }
980
+ };
981
+ var AlipayPaymentTimeoutError = class extends MoltsPayError {
982
+ constructor(message) {
983
+ super("ALIPAY_PAYMENT_TIMEOUT", message);
984
+ this.name = "AlipayPaymentTimeoutError";
985
+ }
986
+ };
987
+ var AlipayProtocolError = class extends MoltsPayError {
988
+ constructor(message) {
989
+ super("ALIPAY_PROTOCOL", message);
990
+ this.name = "AlipayProtocolError";
991
+ }
992
+ };
993
+ var UnsupportedRailError = class extends MoltsPayError {
994
+ constructor(rail, message) {
995
+ super("UNSUPPORTED_RAIL", message ?? `Rail not supported by server: ${rail}`);
996
+ this.rail = rail;
997
+ this.name = "UnsupportedRailError";
998
+ }
999
+ };
1000
+
1001
+ // src/client/alipay/install.ts
1002
+ var execFileAsync = promisify(execFile);
1003
+ var MIN_CLI_VERSION = "0.3.15";
1004
+ function semverLt(a, b) {
1005
+ const pa = a.split(".").map((n) => parseInt(n, 10));
1006
+ const pb = b.split(".").map((n) => parseInt(n, 10));
1007
+ for (let i = 0; i < 3; i++) {
1008
+ const x = pa[i] ?? 0;
1009
+ const y = pb[i] ?? 0;
1010
+ if (x !== y) return x < y;
1011
+ }
1012
+ return false;
1013
+ }
1014
+ function parseVersion(stdout) {
1015
+ return stdout.match(/v?(\d+\.\d+\.\d+)/)?.[1] ?? null;
1016
+ }
1017
+ var defaultGetVersion = async () => {
1018
+ const { stdout } = await execFileAsync("alipay-bot", ["--version"]);
1019
+ return stdout;
1020
+ };
1021
+ async function ensureCliUncached(getVersion2) {
1022
+ let stdout;
1023
+ try {
1024
+ stdout = await getVersion2();
1025
+ } catch (e) {
1026
+ if (e?.code === "ENOENT") {
1027
+ throw new AlipayCliNotFoundError(
1028
+ "alipay-bot not installed. Run: npx -y @alipay/agent-payment install-cli"
1029
+ );
1030
+ }
1031
+ throw e;
1032
+ }
1033
+ const version = parseVersion(stdout);
1034
+ if (!version || semverLt(version, MIN_CLI_VERSION)) {
1035
+ throw new AlipayCliVersionError(
1036
+ `alipay-bot ${version ?? "?"} found, need \u2265 ${MIN_CLI_VERSION}. Run: npx -y @alipay/agent-payment@latest update`
1037
+ );
1038
+ }
1039
+ return version;
1040
+ }
1041
+ var cachedVersion = null;
1042
+ var inflight = null;
1043
+ async function ensureCli(getVersion2 = defaultGetVersion) {
1044
+ if (getVersion2 !== defaultGetVersion) {
1045
+ return ensureCliUncached(getVersion2);
1046
+ }
1047
+ if (cachedVersion) return cachedVersion;
1048
+ if (!inflight) {
1049
+ inflight = ensureCliUncached(getVersion2).then((v) => {
1050
+ cachedVersion = v;
1051
+ return v;
1052
+ }).finally(() => {
1053
+ inflight = null;
1054
+ });
1055
+ }
1056
+ return inflight;
1057
+ }
1058
+
1059
+ // src/client/alipay/poll.ts
1060
+ init_esm_shims();
1061
+ var POLL_INTERVAL_MS = 3e3;
1062
+ var POLL_MAX_INFLIGHT = 2;
1063
+ function resolveMaxInflight(override) {
1064
+ if (override !== void 0) return Math.max(1, Math.floor(override));
1065
+ const raw = process.env.MOLTSPAY_ALIPAY_POLL_MAX_INFLIGHT;
1066
+ if (raw !== void 0 && raw.trim() !== "") {
1067
+ const n = Number(raw);
1068
+ if (Number.isFinite(n) && n >= 1) return Math.floor(n);
1069
+ }
1070
+ return POLL_MAX_INFLIGHT;
1071
+ }
1072
+ function resolveLaunchGap(override) {
1073
+ if (override !== void 0) return Math.max(0, override);
1074
+ const raw = process.env.MOLTSPAY_ALIPAY_POLL_LAUNCH_MS;
1075
+ if (raw !== void 0 && raw.trim() !== "") {
1076
+ const n = Number(raw);
1077
+ if (Number.isFinite(n) && n >= 0) return n;
1078
+ }
1079
+ return POLL_INTERVAL_MS;
1080
+ }
1081
+ function parseStatus(lines) {
1082
+ const raw = lines.join("\n").trim();
1083
+ try {
1084
+ const json = JSON.parse(raw);
1085
+ if (json && typeof json === "object") {
1086
+ if (typeof json.success === "boolean") {
1087
+ if (json.success) return "paid";
1088
+ const err = String(json.errorCode ?? "").toUpperCase();
1089
+ if (/UNPAID|WAIT|PENDING|PROCESS|NOTPAY/.test(err)) return "pending";
1090
+ if (/CLOSED|CANCEL|FAIL|REJECT|REFUSE|TIMEOUT|EXPIRE/.test(err)) return "rejected";
1091
+ return "pending";
1092
+ }
1093
+ if (typeof json.code !== "undefined") {
1094
+ if (Number(json.code) === 200) return "paid";
1095
+ const msg = `${json.message ?? ""}${json.reason ?? ""}`;
1096
+ if (/关闭|失败|拒绝|取消|超时|已撤销/.test(msg)) return "rejected";
1097
+ return "pending";
1098
+ }
1099
+ if (typeof json.body === "string") {
1100
+ return classifyReportText(json.body);
1101
+ }
1102
+ }
1103
+ } catch {
1104
+ }
1105
+ return classifyReportText(raw);
1106
+ }
1107
+ function classifyReportText(s) {
1108
+ const text = s.toUpperCase();
1109
+ if (/查询支付状态成功并获取资源/.test(s) || /资源响应状态[^0-9\n]{0,8}200/.test(s) || /TRADE_SUCCESS|TRADE_FINISHED|"STATUS":\s*"FULFILLED"/.test(text)) return "paid";
1110
+ if (/TRADE_CLOSED|REJECTED|REFUSE|CANCEL/.test(text) || /交易关闭|支付失败|已拒绝|已取消|已撤销|交易超时/.test(s)) return "rejected";
1111
+ if (/WAIT_BUYER_PAY|UNPAID|PENDING|WAITING/.test(text) || /交易未支付|等待付款|待支付|未支付/.test(s)) return "pending";
1112
+ return "unknown";
1113
+ }
1114
+ function defaultSleep(ms, signal) {
1115
+ return new Promise((resolve3, reject) => {
1116
+ if (signal?.aborted) return reject(new Error("aborted"));
1117
+ const t = setTimeout(resolve3, ms);
1118
+ signal?.addEventListener(
1119
+ "abort",
1120
+ () => {
1121
+ clearTimeout(t);
1122
+ reject(new Error("aborted"));
1123
+ },
1124
+ { once: true }
1125
+ );
1126
+ });
1127
+ }
1128
+ var LAUNCH = /* @__PURE__ */ Symbol("launch");
1129
+ async function pollUntil(tradeNo, resourceUrl, opts) {
1130
+ const runner = opts.runner ?? runCli;
1131
+ const sleep = opts.sleep ?? defaultSleep;
1132
+ const now = opts.now ?? Date.now;
1133
+ const maxInflight = resolveMaxInflight(opts.maxInflight);
1134
+ const launchGap = resolveLaunchGap(opts.launchIntervalMs);
1135
+ const args = [
1136
+ "402-query-payment-status",
1137
+ "-t",
1138
+ tradeNo,
1139
+ "-r",
1140
+ resourceUrl,
1141
+ ...opts.method ? ["-m", opts.method] : [],
1142
+ ...opts.data ? ["-d", opts.data] : []
1143
+ ];
1144
+ const internal = new AbortController();
1145
+ const signal = opts.signal ? AbortSignal.any([opts.signal, internal.signal]) : internal.signal;
1146
+ let tick = 0;
1147
+ let lastLaunch = -Infinity;
1148
+ const inflight2 = /* @__PURE__ */ new Set();
1149
+ const launch = () => {
1150
+ tick += 1;
1151
+ const myTick = tick;
1152
+ lastLaunch = now();
1153
+ const run = (async () => {
1154
+ const { lines } = await runner(args, {
1155
+ signal,
1156
+ step: "query-payment-status",
1157
+ flow: tradeNo
1158
+ });
1159
+ const status = parseStatus(lines);
1160
+ alipayLog.debug("poll.tick", { flow: tradeNo, tick: myTick, status });
1161
+ return { status, lines };
1162
+ })();
1163
+ const tracked = run.finally(() => {
1164
+ inflight2.delete(tracked);
1165
+ });
1166
+ inflight2.add(tracked);
1167
+ };
1168
+ try {
1169
+ for (; ; ) {
1170
+ if (opts.signal?.aborted) throw new Error("aborted");
1171
+ const expired = now() >= opts.deadline;
1172
+ if (expired && inflight2.size === 0) {
1173
+ throw new AlipayPaymentTimeoutError(
1174
+ `Payment ${tradeNo} not completed before pay_before deadline`
1175
+ );
1176
+ }
1177
+ while (!expired && inflight2.size < maxInflight && now() - lastLaunch >= launchGap) {
1178
+ launch();
1179
+ }
1180
+ const waiters = [...inflight2];
1181
+ if (!expired && inflight2.size < maxInflight) {
1182
+ const untilNext = Math.max(0, launchGap - (now() - lastLaunch));
1183
+ waiters.push(sleep(untilNext, signal).then(() => LAUNCH, () => LAUNCH));
1184
+ }
1185
+ const winner = await Promise.race(waiters);
1186
+ if (winner === LAUNCH) continue;
1187
+ if (winner.status === "paid") return { status: "paid", lines: winner.lines };
1188
+ if (winner.status === "rejected") {
1189
+ throw new AlipayPaymentRejectedError(`Payment ${tradeNo} was rejected`);
1190
+ }
1191
+ }
1192
+ } finally {
1193
+ internal.abort();
1194
+ for (const p of inflight2) p.catch(() => void 0);
1195
+ }
1196
+ }
1197
+
1198
+ // src/client/alipay/index.ts
1199
+ var TRADE_NO_RE = /^\d{32}$/;
1200
+ function resolveSessionId(explicit, envSession) {
1201
+ return explicit ?? envSession ?? randomUUID();
1202
+ }
1203
+ function assertTradeNo(t) {
1204
+ if (!TRADE_NO_RE.test(t)) {
1205
+ throw new AlipayProtocolError(`invalid tradeNo (expect 32 digits): ${t}`);
1206
+ }
1207
+ }
1208
+ function parseTradeNo(lines) {
1209
+ for (const line of lines) {
1210
+ const labeled = line.match(/trade[_-]?no["'\s:=]+(\d{32})/i);
1211
+ if (labeled) return labeled[1];
1212
+ const bare = line.match(/\b(\d{32})\b/);
1213
+ if (bare) return bare[1];
1214
+ }
1215
+ return null;
1216
+ }
1217
+ function parsePaymentUrl(lines) {
1218
+ let paymentUrl;
1219
+ let shortenUrl;
1220
+ for (const line of lines) {
1221
+ const m = line.match(/(alipays?:\/\/\S+|https?:\/\/\S+)/i);
1222
+ if (!m) continue;
1223
+ const url = m[1].replace(/[)\]`>]+$/, "");
1224
+ if (/short|qr\.alipay|surl|\/s\//i.test(line) && !shortenUrl) shortenUrl = url;
1225
+ else if (!paymentUrl) paymentUrl = url;
1226
+ }
1227
+ if (!paymentUrl && shortenUrl) paymentUrl = shortenUrl;
1228
+ return { paymentUrl, shortenUrl };
1229
+ }
1230
+ function extractMedia(line) {
1231
+ const m = line.match(/^\s*MEDIA:\s*(.+?)\s*$/);
1232
+ return m ? m[1] : null;
1233
+ }
1234
+ function isWalletReady(lines) {
1235
+ const text = lines.join("\n").trim();
1236
+ try {
1237
+ const json = JSON.parse(text);
1238
+ if (json && typeof json.code !== "undefined") return Number(json.code) === 200;
1239
+ } catch {
1240
+ }
1241
+ return !/未开通|未开启|NOT[_\s-]*(OPEN|BOUND|SET)|NEEDS?[_\s-]*SETUP|NO[_\s-]*WALLET/i.test(text);
1242
+ }
1243
+ function extractResourceFromReport(report) {
1244
+ const label = report.search(/资源响应体/);
1245
+ const start = report.indexOf("{", label === -1 ? 0 : label);
1246
+ if (start === -1) return void 0;
1247
+ let depth = 0, inStr = false, esc = false;
1248
+ for (let i = start; i < report.length; i++) {
1249
+ const ch = report[i];
1250
+ if (inStr) {
1251
+ if (esc) esc = false;
1252
+ else if (ch === "\\") esc = true;
1253
+ else if (ch === '"') inStr = false;
1254
+ continue;
1255
+ }
1256
+ if (ch === '"') inStr = true;
1257
+ else if (ch === "{") depth++;
1258
+ else if (ch === "}" && --depth === 0) {
1259
+ const slice = report.slice(start, i + 1);
1260
+ try {
1261
+ const obj = JSON.parse(slice);
1262
+ const r = obj?.resourceResponse ?? obj?.result ?? obj?.data ?? obj?.body ?? obj;
1263
+ return typeof r === "string" ? r : JSON.stringify(r);
1264
+ } catch {
1265
+ return slice;
1266
+ }
1267
+ }
1268
+ }
1269
+ return void 0;
1270
+ }
1271
+ function extractBody(lines) {
1272
+ const text = lines.join("\n").trim();
1273
+ try {
1274
+ const json = JSON.parse(text);
1275
+ if (json && typeof json === "object") {
1276
+ if (json.resourceResponse !== void 0 && json.resourceResponse !== null) {
1277
+ const rr = json.resourceResponse;
1278
+ const r = typeof rr === "object" ? rr.result ?? rr.data ?? rr.body ?? rr : rr;
1279
+ return typeof r === "string" ? r : JSON.stringify(r);
1280
+ }
1281
+ if (typeof json.body === "string") {
1282
+ const inner = extractResourceFromReport(json.body);
1283
+ return inner ?? json.body;
1284
+ }
1285
+ const body = json.data ?? json.result ?? json.body ?? json.resource;
1286
+ if (body !== void 0) return typeof body === "string" ? body : JSON.stringify(body);
1287
+ return text;
1288
+ }
1289
+ } catch {
1290
+ }
1291
+ const idx = lines.findIndex((l) => /^\s*BODY:/.test(l));
1292
+ if (idx !== -1) {
1293
+ const first = lines[idx].replace(/^\s*BODY:\s*/, "");
1294
+ return [first, ...lines.slice(idx + 1)].join("\n").trim();
1295
+ }
1296
+ return lines.filter((l) => !/^\s*(MEDIA|STATUS|INFO):/.test(l)).join("\n").trim();
1297
+ }
1298
+ var DEFAULT_WALLET_TTL_MS = 10 * 60 * 1e3;
1299
+ function resolveWalletTtlMs() {
1300
+ const raw = process.env.MOLTSPAY_ALIPAY_WALLET_TTL_MS;
1301
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_WALLET_TTL_MS;
1302
+ const n = Number(raw);
1303
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_WALLET_TTL_MS;
1304
+ }
1305
+ var walletReadyUntil = /* @__PURE__ */ new Map();
1306
+ var DEFAULT_INTENT_TTL_MS = 10 * 60 * 1e3;
1307
+ function resolveIntentTtlMs() {
1308
+ const raw = process.env.MOLTSPAY_ALIPAY_INTENT_TTL_MS;
1309
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_INTENT_TTL_MS;
1310
+ const n = Number(raw);
1311
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_INTENT_TTL_MS;
1312
+ }
1313
+ var intentDoneUntil = /* @__PURE__ */ new Map();
1314
+ var AlipayClient = class {
1315
+ sessionId;
1316
+ configDir;
1317
+ framework;
1318
+ runner;
1319
+ getVersion;
1320
+ now;
1321
+ /** Only the default runner may use the process-level wallet cache. */
1322
+ walletCacheable;
1323
+ /** Only the default runner may use the process-level payment-intent cache. */
1324
+ intentCacheable;
1325
+ constructor(opts = {}) {
1326
+ this.sessionId = resolveSessionId(opts.sessionId, process.env.AIPAY_SESSION_ID);
1327
+ this.configDir = opts.configDir ?? join2(homedir2(), ".moltspay");
1328
+ this.framework = opts.framework ?? process.env.AIPAY_FRAMEWORK ?? "openclaw";
1329
+ this.runner = opts.runner ?? runCli;
1330
+ this.getVersion = opts.getVersion;
1331
+ this.now = opts.now ?? Date.now;
1332
+ this.walletCacheable = opts.cacheWallet ?? !opts.runner;
1333
+ this.intentCacheable = opts.cacheIntent ?? !opts.runner;
1334
+ }
1335
+ /**
1336
+ * Throws NeedsWalletSetupError unless alipay-bot reports an opened wallet.
1337
+ *
1338
+ * Skips the ~22s `check-wallet` spawn entirely when a prior call cached a
1339
+ * "ready" verdict within the TTL (see {@link DEFAULT_WALLET_TTL_MS}).
1340
+ */
1341
+ async checkWallet(signal, flow) {
1342
+ const ttlMs = resolveWalletTtlMs();
1343
+ const cacheKey = `${this.configDir}::${this.framework}`;
1344
+ const useCache = this.walletCacheable && ttlMs > 0;
1345
+ if (useCache) {
1346
+ const until = walletReadyUntil.get(cacheKey);
1347
+ if (until !== void 0 && this.now() < until) {
1348
+ alipayLog.info("wallet.cache", { flow, step: "check-wallet", hit: true, ttlMsLeft: until - this.now() });
1349
+ return;
1350
+ }
1351
+ }
1352
+ const { lines } = await this.runner(["check-wallet"], { signal, step: "check-wallet", flow });
1353
+ if (!isWalletReady(lines)) {
1354
+ if (this.walletCacheable) walletReadyUntil.delete(cacheKey);
1355
+ throw new NeedsWalletSetupError(
1356
+ "Alipay wallet not opened. Run: moltspay alipay apply (then: moltspay alipay bind)"
1357
+ );
1358
+ }
1359
+ if (useCache) {
1360
+ walletReadyUntil.set(cacheKey, this.now() + ttlMs);
1361
+ alipayLog.info("wallet.cache", { flow, step: "check-wallet", hit: false, cachedForMs: ttlMs });
1362
+ }
1363
+ }
1364
+ /** Run the full 8-step flow and resolve with the resource body. */
1365
+ async pay402(opts) {
1366
+ const { resourceUrl, requirement, signal } = opts;
1367
+ const extra = requirement.extra ?? {};
1368
+ const paymentNeededHeader = String(extra.payment_needed_header ?? "");
1369
+ if (!paymentNeededHeader) {
1370
+ throw new AlipayProtocolError("alipay requirement missing extra.payment_needed_header");
1371
+ }
1372
+ const flow = this.sessionId;
1373
+ const flowStart = this.now();
1374
+ alipayLog.info("flow.start", { flow, resource: resourceUrl });
1375
+ const media = [];
1376
+ const onLine = (line) => {
1377
+ const m = extractMedia(line);
1378
+ if (m) media.push(m);
1379
+ else opts.onLine?.(line);
1380
+ };
1381
+ const intentSummary = opts.intentSummary?.trim() || `\u652F\u4ED8 ${requirement.amount ?? ""} ${requirement.asset ?? "CNY"}`.trim();
1382
+ await timeStep("ensure-cli", flow, () => ensureCli(this.getVersion));
1383
+ const intentTtlMs = resolveIntentTtlMs();
1384
+ const intentKey = `${this.configDir}::${this.framework}`;
1385
+ const useIntentCache = this.intentCacheable && intentTtlMs > 0;
1386
+ const intentUntil = useIntentCache ? intentDoneUntil.get(intentKey) : void 0;
1387
+ if (intentUntil !== void 0 && this.now() < intentUntil) {
1388
+ alipayLog.info("intent.cache", { flow, step: "payment-intent", hit: true, ttlMsLeft: intentUntil - this.now() });
1389
+ } else {
1390
+ await this.runner(
1391
+ [
1392
+ "payment-intent",
1393
+ "--session-id",
1394
+ this.sessionId,
1395
+ "--intent-summary",
1396
+ intentSummary,
1397
+ "--framework",
1398
+ this.framework
1399
+ ],
1400
+ { onLine, signal, step: "payment-intent", flow }
1401
+ );
1402
+ if (useIntentCache) {
1403
+ intentDoneUntil.set(intentKey, this.now() + intentTtlMs);
1404
+ alipayLog.info("intent.cache", { flow, step: "payment-intent", hit: false, cachedForMs: intentTtlMs });
1405
+ }
1406
+ }
1407
+ await this.checkWallet(signal, flow);
1408
+ const reqId = String(extra.out_trade_no ?? randomUUID());
1409
+ const dir = join2(this.configDir, "alipay");
1410
+ await mkdir(dir, { recursive: true });
1411
+ const challengeFile = join2(dir, `402_${reqId}.txt`);
1412
+ await writeFile(challengeFile, paymentNeededHeader, "utf-8");
1413
+ const payArgs = [
1414
+ "402-buyer-pay",
1415
+ "-f",
1416
+ challengeFile,
1417
+ "-r",
1418
+ resourceUrl,
1419
+ "-s",
1420
+ this.sessionId,
1421
+ "-i",
1422
+ intentSummary,
1423
+ "-w",
1424
+ this.framework
1425
+ ];
1426
+ if (opts.method) payArgs.push("-m", opts.method);
1427
+ if (opts.data) payArgs.push("-d", opts.data);
1428
+ const payRun = await this.runner(payArgs, { onLine, signal, step: "402-buyer-pay", flow });
1429
+ const tradeNo = parseTradeNo(payRun.lines);
1430
+ if (!tradeNo) {
1431
+ throw new AlipayProtocolError("402-buyer-pay did not return a tradeNo");
1432
+ }
1433
+ assertTradeNo(tradeNo);
1434
+ const { paymentUrl, shortenUrl } = parsePaymentUrl(payRun.lines);
1435
+ if (paymentUrl) {
1436
+ alipayLog.info("flow.pending", { flow, tradeNo, ms: this.now() - flowStart });
1437
+ opts.onPaymentPending?.({ paymentUrl, shortenUrl, tradeNo });
1438
+ }
1439
+ const pendingAt = this.now();
1440
+ const windowMs = (requirement.maxTimeoutSeconds ?? 30 * 60) * 1e3;
1441
+ const deadline = this.now() + (opts.timeoutMs ?? windowMs);
1442
+ const poll = await pollUntil(tradeNo, resourceUrl, {
1443
+ // No onLine: the status-poll output embeds the resource and must not reach
1444
+ // the log stream — the body is surfaced via the return value below (§9.3).
1445
+ deadline,
1446
+ signal,
1447
+ runner: this.runner,
1448
+ now: this.now,
1449
+ // Re-fetch the resource the same way it was paid (POST + body), else the
1450
+ // status poll defaults to GET and 404s on a POST-only `/execute`.
1451
+ method: opts.method,
1452
+ data: opts.data
1453
+ });
1454
+ alipayLog.info("flow.settled", { flow, tradeNo, ms: this.now() - pendingAt });
1455
+ const body = extractBody(poll.lines);
1456
+ void this.runner(["402-buyer-fulfillment-ack", "-t", tradeNo], {
1457
+ onLine,
1458
+ signal,
1459
+ step: "402-buyer-fulfillment-ack",
1460
+ flow
1461
+ }).catch(() => void 0);
1462
+ return { body, payment: { tradeNo, outTradeNo: reqId }, media };
1463
+ }
1464
+ };
1465
+
1466
+ // src/client/alipay/router.ts
1467
+ init_esm_shims();
1468
+ var ALIPAY_RAIL = "alipay";
1469
+ function railOf(req) {
1470
+ if (req.scheme === "alipay-aipay" || req.network === ALIPAY_RAIL) return ALIPAY_RAIL;
1471
+ return networkToChainName(req.network) ?? req.network;
1472
+ }
1473
+ function findRail(accepts, rail) {
1474
+ const requirement = accepts.find((r) => railOf(r) === rail);
1475
+ return requirement ? { rail, requirement } : null;
1476
+ }
1477
+ function selectRail(input) {
1478
+ const { serverAccepts, explicitRail, preference, availability } = input;
1479
+ if (!serverAccepts || serverAccepts.length === 0) {
1480
+ throw new UnsupportedRailError(explicitRail ?? "unknown", "Server offered no payment options");
1481
+ }
1482
+ if (explicitRail) {
1483
+ const hit = findRail(serverAccepts, explicitRail);
1484
+ if (!hit) {
1485
+ const offered = serverAccepts.map(railOf);
1486
+ throw new UnsupportedRailError(
1487
+ explicitRail,
1488
+ `Server doesn't accept rail '${explicitRail}'. Offered: ${offered.join(", ")}`
1489
+ );
1490
+ }
1491
+ return hit;
1492
+ }
1493
+ if (preference) {
1494
+ for (const pref of preference) {
1495
+ const hit = findRail(serverAccepts, pref);
1496
+ if (hit) return hit;
1497
+ }
1498
+ }
1499
+ if (availability?.evmReady) {
1500
+ const evm = serverAccepts.find((r) => railOf(r) !== ALIPAY_RAIL);
1501
+ if (evm) return { rail: railOf(evm), requirement: evm };
1502
+ }
1503
+ if (availability?.alipayReady) {
1504
+ const hit = findRail(serverAccepts, ALIPAY_RAIL);
1505
+ if (hit) return hit;
1506
+ }
1507
+ return { rail: railOf(serverAccepts[0]), requirement: serverAccepts[0] };
1508
+ }
1509
+
764
1510
  // src/client/types.ts
765
1511
  init_esm_shims();
766
1512
 
@@ -780,9 +1526,13 @@ var MoltsPayClient = class {
780
1526
  signer = null;
781
1527
  todaySpending = 0;
782
1528
  lastSpendingReset = 0;
1529
+ railPreference;
1530
+ alipaySessionId;
783
1531
  constructor(options = {}) {
784
- this.configDir = options.configDir || join2(homedir2(), ".moltspay");
1532
+ this.configDir = options.configDir || join3(homedir3(), ".moltspay");
785
1533
  this.config = this.loadConfig();
1534
+ this.railPreference = options.railPreference ?? this.config.railPreference;
1535
+ this.alipaySessionId = options.alipaySessionId;
786
1536
  this.walletData = this.loadWallet();
787
1537
  this.loadSpending();
788
1538
  if (this.walletData) {
@@ -860,6 +1610,9 @@ var MoltsPayClient = class {
860
1610
  * @param options - Payment options (token selection)
861
1611
  */
862
1612
  async pay(serverUrl, service, params, options = {}) {
1613
+ if (options.rail === ALIPAY_RAIL) {
1614
+ return this.payViaAlipay(serverUrl, service, params, options);
1615
+ }
863
1616
  if (!this.wallet || !this.walletData) {
864
1617
  throw new Error("Client not initialized. Run: npx moltspay init");
865
1618
  }
@@ -1051,6 +1804,76 @@ Please specify: --chain <chain_name>`
1051
1804
  console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
1052
1805
  return result.result || result;
1053
1806
  }
1807
+ /**
1808
+ * Pay for a service over the Alipay fiat rail (2.0.0).
1809
+ *
1810
+ * Unlike the crypto path this needs no EVM wallet — it shells out to
1811
+ * alipay-bot via {@link AlipayClient}. Flow: hit the resource with no
1812
+ * payment to get the 402 challenge, confirm the server actually offers the
1813
+ * alipay rail (selectRail), then run the 8-step state machine and return the
1814
+ * resource body.
1815
+ */
1816
+ async payViaAlipay(serverUrl, service, params, options) {
1817
+ const flow = this.alipaySessionId;
1818
+ let executeUrl = `${serverUrl}/execute`;
1819
+ try {
1820
+ const services = await timeStep(
1821
+ "discover-services",
1822
+ flow,
1823
+ () => this.getServices(serverUrl)
1824
+ );
1825
+ const svc = services.services?.find((s) => s.id === service);
1826
+ if (svc?.endpoint) executeUrl = `${serverUrl}${svc.endpoint}`;
1827
+ } catch {
1828
+ }
1829
+ const requestBody = options.rawData ? { service, ...params } : { service, params };
1830
+ const bodyJson = JSON.stringify(requestBody);
1831
+ const res = await timeStep(
1832
+ "challenge-402",
1833
+ flow,
1834
+ () => fetch(executeUrl, {
1835
+ method: "POST",
1836
+ headers: { "Content-Type": "application/json" },
1837
+ body: bodyJson
1838
+ })
1839
+ );
1840
+ if (res.status !== 402) {
1841
+ const data = await res.json().catch(() => ({}));
1842
+ if (res.ok && data.result) return data.result;
1843
+ throw new Error(data.error || `Expected 402, got ${res.status}`);
1844
+ }
1845
+ const header = res.headers.get(PAYMENT_REQUIRED_HEADER);
1846
+ if (!header) throw new Error("Missing x-payment-required header on 402");
1847
+ const parsed = JSON.parse(Buffer.from(header, "base64").toString("utf-8"));
1848
+ const accepts = Array.isArray(parsed) ? parsed : parsed.accepts ?? [parsed];
1849
+ const { requirement } = selectRail({
1850
+ serverAccepts: accepts,
1851
+ explicitRail: ALIPAY_RAIL,
1852
+ preference: this.railPreference,
1853
+ availability: { evmReady: this.isInitialized }
1854
+ });
1855
+ const onLine = options.onLine ?? ((line) => process.stdout.write(line + "\n"));
1856
+ const alipay = new AlipayClient({
1857
+ sessionId: this.alipaySessionId,
1858
+ configDir: this.configDir
1859
+ });
1860
+ const result = await alipay.pay402({
1861
+ resourceUrl: executeUrl,
1862
+ requirement,
1863
+ method: "POST",
1864
+ data: bodyJson,
1865
+ onLine,
1866
+ onPaymentPending: options.onPaymentPending,
1867
+ timeoutMs: options.timeoutMs,
1868
+ signal: options.signal
1869
+ });
1870
+ try {
1871
+ const json = JSON.parse(result.body);
1872
+ return json.result ?? json;
1873
+ } catch {
1874
+ return { body: result.body, payment: result.payment, media: result.media };
1875
+ }
1876
+ }
1054
1877
  /**
1055
1878
  * Handle MPP (Machine Payments Protocol) payment flow
1056
1879
  * Called when pay() detects WWW-Authenticate header in 402 response
@@ -1391,7 +2214,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1391
2214
  }
1392
2215
  // --- Config & Wallet Management ---
1393
2216
  loadConfig() {
1394
- const configPath = join2(this.configDir, "config.json");
2217
+ const configPath = join3(this.configDir, "config.json");
1395
2218
  if (existsSync2(configPath)) {
1396
2219
  const content = readFileSync2(configPath, "utf-8");
1397
2220
  return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
@@ -1400,14 +2223,14 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1400
2223
  }
1401
2224
  saveConfig() {
1402
2225
  mkdirSync2(this.configDir, { recursive: true });
1403
- const configPath = join2(this.configDir, "config.json");
2226
+ const configPath = join3(this.configDir, "config.json");
1404
2227
  writeFileSync2(configPath, JSON.stringify(this.config, null, 2));
1405
2228
  }
1406
2229
  /**
1407
2230
  * Load spending data from disk
1408
2231
  */
1409
2232
  loadSpending() {
1410
- const spendingPath = join2(this.configDir, "spending.json");
2233
+ const spendingPath = join3(this.configDir, "spending.json");
1411
2234
  if (existsSync2(spendingPath)) {
1412
2235
  try {
1413
2236
  const data = JSON.parse(readFileSync2(spendingPath, "utf-8"));
@@ -1430,7 +2253,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1430
2253
  */
1431
2254
  saveSpending() {
1432
2255
  mkdirSync2(this.configDir, { recursive: true });
1433
- const spendingPath = join2(this.configDir, "spending.json");
2256
+ const spendingPath = join3(this.configDir, "spending.json");
1434
2257
  const data = {
1435
2258
  date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
1436
2259
  amount: this.todaySpending,
@@ -1439,7 +2262,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1439
2262
  writeFileSync2(spendingPath, JSON.stringify(data, null, 2));
1440
2263
  }
1441
2264
  loadWallet() {
1442
- const walletPath = join2(this.configDir, "wallet.json");
2265
+ const walletPath = join3(this.configDir, "wallet.json");
1443
2266
  if (existsSync2(walletPath)) {
1444
2267
  if (process.platform !== "win32") {
1445
2268
  try {
@@ -1469,7 +2292,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1469
2292
  privateKey: wallet.privateKey,
1470
2293
  createdAt: Date.now()
1471
2294
  };
1472
- const walletPath = join2(configDir, "wallet.json");
2295
+ const walletPath = join3(configDir, "wallet.json");
1473
2296
  writeFileSync2(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
1474
2297
  const config = {
1475
2298
  chain: options.chain,
@@ -1478,7 +2301,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1478
2301
  maxPerDay: options.maxPerDay
1479
2302
  }
1480
2303
  };
1481
- const configPath = join2(configDir, "config.json");
2304
+ const configPath = join3(configDir, "config.json");
1482
2305
  writeFileSync2(configPath, JSON.stringify(config, null, 2));
1483
2306
  return { address: wallet.address, configDir };
1484
2307
  }
@@ -2464,6 +3287,378 @@ var BNBFacilitator = class extends BaseFacilitator {
2464
3287
  }
2465
3288
  };
2466
3289
 
3290
+ // src/facilitators/alipay.ts
3291
+ init_esm_shims();
3292
+ import crypto2 from "crypto";
3293
+
3294
+ // src/facilitators/alipay/rsa2.ts
3295
+ init_esm_shims();
3296
+ import crypto from "crypto";
3297
+ function rsa2Sign(data, privateKeyPem) {
3298
+ const signer = crypto.createSign("RSA-SHA256");
3299
+ signer.update(data, "utf-8");
3300
+ signer.end();
3301
+ return signer.sign(privateKeyPem, "base64");
3302
+ }
3303
+
3304
+ // src/facilitators/alipay/encoding.ts
3305
+ init_esm_shims();
3306
+ function base64url(input) {
3307
+ return Buffer.from(input, "utf-8").toString("base64url");
3308
+ }
3309
+ function decodeBase64UrlWithPadFix(input) {
3310
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
3311
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
3312
+ return Buffer.from(padded, "base64").toString("utf-8");
3313
+ }
3314
+ function toPem(key, kind) {
3315
+ const trimmed = key.trim();
3316
+ if (trimmed.includes("-----BEGIN")) return trimmed;
3317
+ const label = kind === "PRIVATE" ? "PRIVATE KEY" : "PUBLIC KEY";
3318
+ const body = trimmed.replace(/\s+/g, "").match(/.{1,64}/g)?.join("\n") ?? "";
3319
+ return `-----BEGIN ${label}-----
3320
+ ${body}
3321
+ -----END ${label}-----
3322
+ `;
3323
+ }
3324
+
3325
+ // src/facilitators/alipay/openapi.ts
3326
+ init_esm_shims();
3327
+ function formatAlipayTimestamp(d = /* @__PURE__ */ new Date()) {
3328
+ const pad = (n) => String(n).padStart(2, "0");
3329
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
3330
+ }
3331
+ function buildSigningString(params) {
3332
+ return Object.keys(params).sort().map((k) => `${k}=${params[k]}`).join("&");
3333
+ }
3334
+ function responseWrapperKey(method) {
3335
+ return `${method.replace(/\./g, "_")}_response`;
3336
+ }
3337
+ async function alipayOpenApiCall(method, bizContent, config) {
3338
+ const publicParams = {
3339
+ app_id: config.app_id,
3340
+ method,
3341
+ format: "JSON",
3342
+ charset: "utf-8",
3343
+ sign_type: config.sign_type ?? "RSA2",
3344
+ timestamp: formatAlipayTimestamp(),
3345
+ version: "1.0",
3346
+ biz_content: JSON.stringify(bizContent)
3347
+ };
3348
+ const signingString = buildSigningString(publicParams);
3349
+ const sign = rsa2Sign(signingString, config.private_key_pem);
3350
+ const body = new URLSearchParams({ ...publicParams, sign }).toString();
3351
+ const response = await fetch(config.gateway_url, {
3352
+ method: "POST",
3353
+ headers: {
3354
+ "Content-Type": "application/x-www-form-urlencoded;charset=utf-8"
3355
+ },
3356
+ body
3357
+ });
3358
+ if (!response.ok) {
3359
+ const text = await response.text().catch(() => "<unreadable>");
3360
+ throw new Error(
3361
+ `Alipay Open API HTTP ${response.status} for ${method}: ${text.slice(0, 500)}`
3362
+ );
3363
+ }
3364
+ const json = await response.json();
3365
+ const wrapperKey = responseWrapperKey(method);
3366
+ const business = json[wrapperKey];
3367
+ if (business === void 0 || business === null || typeof business !== "object") {
3368
+ throw new Error(
3369
+ `Alipay Open API response missing "${wrapperKey}" wrapper for ${method}: ${JSON.stringify(json).slice(0, 500)}`
3370
+ );
3371
+ }
3372
+ return business;
3373
+ }
3374
+
3375
+ // src/facilitators/alipay.ts
3376
+ var ALIPAY_NETWORK = "alipay";
3377
+ var ALIPAY_SCHEME = "alipay-aipay";
3378
+ var ALIPAY_GATEWAY_PROD = "https://openapi.alipay.com/gateway.do";
3379
+ var ALIPAY_AMOUNT_REGEX = /^\d+(\.\d{1,2})?$/;
3380
+ var ALIPAY_PAY_BEFORE_MS = 30 * 60 * 1e3;
3381
+ var ALIPAY_SIGNING_FIELDS = [
3382
+ "amount",
3383
+ "currency",
3384
+ "goods_name",
3385
+ "out_trade_no",
3386
+ "pay_before",
3387
+ "resource_id",
3388
+ "seller_id",
3389
+ "service_id"
3390
+ ];
3391
+ var AlipayFacilitator = class extends BaseFacilitator {
3392
+ name = "alipay";
3393
+ displayName = "Alipay AI \u6536";
3394
+ supportedNetworks = [ALIPAY_NETWORK];
3395
+ config;
3396
+ constructor(config) {
3397
+ super();
3398
+ this.config = {
3399
+ gateway_url: ALIPAY_GATEWAY_PROD,
3400
+ sign_type: "RSA2",
3401
+ ...config
3402
+ };
3403
+ }
3404
+ /**
3405
+ * Build the 402 challenge for a service: signs the 8-field payload with
3406
+ * RSA2, packages the nested `{protocol, method}` JSON as Base64URL for
3407
+ * `Payment-Needed`, and emits the parallel x402 `accepts[]` entry.
3408
+ */
3409
+ async createPaymentRequirements(opts) {
3410
+ if (!ALIPAY_AMOUNT_REGEX.test(opts.priceCny)) {
3411
+ throw new Error(
3412
+ `AlipayFacilitator.createPaymentRequirements: priceCny "${opts.priceCny}" does not match /^\\d+(\\.\\d{1,2})?$/ (unit is \u5143, not \u5206; e.g. "1.00" not "100")`
3413
+ );
3414
+ }
3415
+ const now = /* @__PURE__ */ new Date();
3416
+ const outTradeNo = opts.outTradeNo ?? generateOutTradeNo();
3417
+ const payBefore = formatPayBefore(now);
3418
+ const signedFields = {
3419
+ amount: opts.priceCny,
3420
+ currency: "CNY",
3421
+ goods_name: opts.goodsName,
3422
+ out_trade_no: outTradeNo,
3423
+ pay_before: payBefore,
3424
+ resource_id: opts.resourceId,
3425
+ seller_id: this.config.seller_id,
3426
+ service_id: opts.serviceId
3427
+ };
3428
+ const signingString = ALIPAY_SIGNING_FIELDS.map((k) => `${k}=${signedFields[k]}`).join("&");
3429
+ const seller_signature = rsa2Sign(signingString, this.config.private_key_pem);
3430
+ const challenge = {
3431
+ protocol: {
3432
+ out_trade_no: outTradeNo,
3433
+ amount: signedFields.amount,
3434
+ currency: signedFields.currency,
3435
+ resource_id: signedFields.resource_id,
3436
+ pay_before: payBefore,
3437
+ seller_signature,
3438
+ seller_sign_type: this.config.sign_type ?? "RSA2",
3439
+ seller_unique_id: this.config.seller_id
3440
+ },
3441
+ method: {
3442
+ seller_name: this.config.seller_name,
3443
+ seller_id: this.config.seller_id,
3444
+ seller_app_id: this.config.app_id,
3445
+ goods_name: signedFields.goods_name,
3446
+ seller_unique_id_key: "seller_id",
3447
+ service_id: signedFields.service_id
3448
+ }
3449
+ };
3450
+ const paymentNeededHeader = base64url(JSON.stringify(challenge));
3451
+ const x402Accepts = {
3452
+ scheme: ALIPAY_SCHEME,
3453
+ network: ALIPAY_NETWORK,
3454
+ asset: "CNY",
3455
+ amount: opts.priceCny,
3456
+ payTo: this.config.seller_id,
3457
+ maxTimeoutSeconds: ALIPAY_PAY_BEFORE_MS / 1e3,
3458
+ extra: {
3459
+ payment_needed_header: paymentNeededHeader,
3460
+ out_trade_no: outTradeNo,
3461
+ pay_before: payBefore,
3462
+ service_id: signedFields.service_id
3463
+ }
3464
+ };
3465
+ return { x402Accepts, paymentNeededHeader };
3466
+ }
3467
+ /**
3468
+ * Verify a `Payment-Proof` by calling
3469
+ * `alipay.aipay.agent.payment.verify` against the Alipay Open API.
3470
+ *
3471
+ * The proof is conveyed via `paymentPayload.payload`, which may be:
3472
+ * - a raw Base64URL string (the `Payment-Proof` header value), or
3473
+ * - an object `{ paymentProof: string }` / `{ proofHeader: string }`.
3474
+ *
3475
+ * All failure modes (malformed payload, network errors, Alipay
3476
+ * `code != 10000`) return `{ valid: false, error }`. No exception
3477
+ * escapes, regardless of client-supplied input.
3478
+ */
3479
+ async verify(paymentPayload, _requirements) {
3480
+ try {
3481
+ const proofHeader = extractProofHeader(paymentPayload.payload);
3482
+ const decoded = decodeProof(proofHeader);
3483
+ const response = await alipayOpenApiCall(
3484
+ "alipay.aipay.agent.payment.verify",
3485
+ {
3486
+ payment_proof: decoded.protocol.payment_proof,
3487
+ trade_no: decoded.protocol.trade_no,
3488
+ client_session: decoded.method.client_session
3489
+ },
3490
+ this.getOpenApiConfig()
3491
+ );
3492
+ if (response.code !== "10000") {
3493
+ return {
3494
+ valid: false,
3495
+ error: `alipay verify ${response.code}: ${response.sub_msg ?? response.msg ?? "unknown"}`,
3496
+ details: {
3497
+ code: response.code,
3498
+ sub_code: response.sub_code,
3499
+ sub_msg: response.sub_msg
3500
+ }
3501
+ };
3502
+ }
3503
+ return {
3504
+ valid: true,
3505
+ details: {
3506
+ trade_no: response.trade_no ?? decoded.protocol.trade_no,
3507
+ amount: response.amount,
3508
+ out_trade_no: response.out_trade_no,
3509
+ resource_id: response.resource_id,
3510
+ active: response.active
3511
+ }
3512
+ };
3513
+ } catch (e) {
3514
+ return {
3515
+ valid: false,
3516
+ error: e instanceof Error ? e.message : String(e)
3517
+ };
3518
+ }
3519
+ }
3520
+ /**
3521
+ * Settle by calling `alipay.aipay.agent.fulfillment.confirm` after the
3522
+ * service resource has been returned to the buyer.
3523
+ *
3524
+ * Per the design (see ALIPAY-INTEGRATION-DESIGN.md §5.1, risk row
3525
+ * "履约确认失败"), this is **fire-and-forget**: the caller (registry /
3526
+ * server) is expected to log fulfillment failures but NOT roll back
3527
+ * the already-delivered resource.
3528
+ *
3529
+ * Re-decodes `paymentPayload.payload` to extract `trade_no` (verify
3530
+ * does the same; the redundant Base64URL decode is negligible).
3531
+ */
3532
+ async settle(paymentPayload, _requirements) {
3533
+ try {
3534
+ const proofHeader = extractProofHeader(paymentPayload.payload);
3535
+ const decoded = decodeProof(proofHeader);
3536
+ const tradeNo = decoded.protocol.trade_no;
3537
+ const response = await alipayOpenApiCall(
3538
+ "alipay.aipay.agent.fulfillment.confirm",
3539
+ { trade_no: tradeNo },
3540
+ this.getOpenApiConfig()
3541
+ );
3542
+ if (response.code !== "10000") {
3543
+ return {
3544
+ success: false,
3545
+ transaction: tradeNo,
3546
+ error: `alipay fulfillment ${response.code}: ${response.sub_msg ?? response.msg ?? "unknown"}`,
3547
+ status: "fulfillment_failed"
3548
+ };
3549
+ }
3550
+ return {
3551
+ success: true,
3552
+ transaction: tradeNo,
3553
+ status: "fulfilled"
3554
+ };
3555
+ } catch (e) {
3556
+ return {
3557
+ success: false,
3558
+ error: e instanceof Error ? e.message : String(e)
3559
+ };
3560
+ }
3561
+ }
3562
+ /**
3563
+ * Validate that the configured RSA keys parse and that the Open API
3564
+ * gateway is reachable. Does NOT make a real business API call (would
3565
+ * burn quota and could appear as a legitimate verify attempt in logs).
3566
+ */
3567
+ async healthCheck() {
3568
+ const start = Date.now();
3569
+ try {
3570
+ crypto2.createPrivateKey(this.config.private_key_pem);
3571
+ } catch (e) {
3572
+ return {
3573
+ healthy: false,
3574
+ error: `merchant private_key_pem parse failed: ${e instanceof Error ? e.message : String(e)}`
3575
+ };
3576
+ }
3577
+ try {
3578
+ crypto2.createPublicKey(this.config.alipay_public_key_pem);
3579
+ } catch (e) {
3580
+ return {
3581
+ healthy: false,
3582
+ error: `alipay_public_key_pem parse failed: ${e instanceof Error ? e.message : String(e)}`
3583
+ };
3584
+ }
3585
+ const gatewayUrl = this.config.gateway_url ?? ALIPAY_GATEWAY_PROD;
3586
+ const controller = new AbortController();
3587
+ const timeout = setTimeout(() => controller.abort(), 5e3);
3588
+ const response = await fetch(gatewayUrl, {
3589
+ method: "HEAD",
3590
+ signal: controller.signal
3591
+ }).catch(() => null);
3592
+ clearTimeout(timeout);
3593
+ const latencyMs = Date.now() - start;
3594
+ if (!response) {
3595
+ return { healthy: false, error: `gateway unreachable: ${gatewayUrl}`, latencyMs };
3596
+ }
3597
+ return { healthy: true, latencyMs };
3598
+ }
3599
+ /** Bundle the facilitator config into the shape openapi.ts wants. */
3600
+ getOpenApiConfig() {
3601
+ return {
3602
+ gateway_url: this.config.gateway_url ?? ALIPAY_GATEWAY_PROD,
3603
+ app_id: this.config.app_id,
3604
+ private_key_pem: this.config.private_key_pem,
3605
+ alipay_public_key_pem: this.config.alipay_public_key_pem,
3606
+ sign_type: this.config.sign_type
3607
+ };
3608
+ }
3609
+ };
3610
+ function generateOutTradeNo() {
3611
+ return "VID" + crypto2.randomBytes(22).toString("base64url").slice(0, 29);
3612
+ }
3613
+ function formatPayBefore(now) {
3614
+ const expiry = new Date(now.getTime() + ALIPAY_PAY_BEFORE_MS);
3615
+ return expiry.toISOString().replace(/\.\d{3}Z$/, "Z");
3616
+ }
3617
+ function extractProofHeader(payload) {
3618
+ if (typeof payload === "string") {
3619
+ if (payload.length === 0) {
3620
+ throw new Error("alipay Payment-Proof is empty");
3621
+ }
3622
+ return payload;
3623
+ }
3624
+ if (payload !== null && typeof payload === "object") {
3625
+ const obj = payload;
3626
+ const candidate = obj.paymentProof ?? obj.proofHeader;
3627
+ if (typeof candidate === "string" && candidate.length > 0) {
3628
+ return candidate;
3629
+ }
3630
+ }
3631
+ throw new Error(
3632
+ "alipay payment payload must be a Base64URL string or {paymentProof: string} / {proofHeader: string}"
3633
+ );
3634
+ }
3635
+ function decodeProof(proofHeader) {
3636
+ let parsed;
3637
+ try {
3638
+ parsed = JSON.parse(decodeBase64UrlWithPadFix(proofHeader));
3639
+ } catch (e) {
3640
+ throw new Error(
3641
+ `failed to decode Payment-Proof: ${e instanceof Error ? e.message : String(e)}`
3642
+ );
3643
+ }
3644
+ if (parsed === null || typeof parsed !== "object") {
3645
+ throw new Error("decoded Payment-Proof is not an object");
3646
+ }
3647
+ const obj = parsed;
3648
+ const protocol = obj.protocol;
3649
+ const method = obj.method;
3650
+ if (!protocol || typeof protocol.payment_proof !== "string") {
3651
+ throw new Error("decoded Payment-Proof missing protocol.payment_proof");
3652
+ }
3653
+ if (typeof protocol.trade_no !== "string") {
3654
+ throw new Error("decoded Payment-Proof missing protocol.trade_no");
3655
+ }
3656
+ if (!method || typeof method.client_session !== "string") {
3657
+ throw new Error("decoded Payment-Proof missing method.client_session");
3658
+ }
3659
+ return parsed;
3660
+ }
3661
+
2467
3662
  // src/facilitators/registry.ts
2468
3663
  init_esm_shims();
2469
3664
  import { Keypair as Keypair5 } from "@solana/web3.js";
@@ -2489,6 +3684,7 @@ var FacilitatorRegistry = class {
2489
3684
  }
2490
3685
  return new SolanaFacilitator({ feePayerKeypair });
2491
3686
  });
3687
+ this.registerFactory("alipay", (config) => new AlipayFacilitator(config));
2492
3688
  this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
2493
3689
  }
2494
3690
  /**
@@ -2716,6 +3912,11 @@ var PAYMENT_RESPONSE_HEADER = "x-payment-response";
2716
3912
  var MPP_AUTH_HEADER = "authorization";
2717
3913
  var MPP_WWW_AUTH_HEADER = "www-authenticate";
2718
3914
  var MPP_RECEIPT_HEADER = "payment-receipt";
3915
+ var ALIPAY_PAYMENT_NEEDED_HEADER = "payment-needed";
3916
+ var ALIPAY_PAYMENT_PROOF_HEADER = "payment-proof";
3917
+ function headerSafe(value) {
3918
+ return String(value ?? "").replace(/[^\x20-\x7E]/g, (ch) => encodeURIComponent(ch)).replace(/"/g, "%22");
3919
+ }
2719
3920
  var TOKEN_ADDRESSES = {
2720
3921
  "eip155:8453": {
2721
3922
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -2851,6 +4052,8 @@ var MoltsPayServer = class {
2851
4052
  registry;
2852
4053
  networkId;
2853
4054
  useMainnet;
4055
+ /** Alipay AI 收 facilitator instance, set when `provider.alipay` is configured (2.0.0). */
4056
+ alipayFacilitator = null;
2854
4057
  constructor(servicesPath, options = {}) {
2855
4058
  loadEnvFile2();
2856
4059
  const content = readFileSync4(servicesPath, "utf-8");
@@ -2872,7 +4075,38 @@ var MoltsPayServer = class {
2872
4075
  cdp: { useMainnet: this.useMainnet }
2873
4076
  }
2874
4077
  };
4078
+ const providerAlipay = this.manifest.provider.alipay;
4079
+ if (providerAlipay) {
4080
+ try {
4081
+ const baseDir = path3.dirname(servicesPath);
4082
+ const resolvePem = (p, kind) => toPem(readFileSync4(path3.isAbsolute(p) ? p : path3.resolve(baseDir, p), "utf-8"), kind);
4083
+ const alipayFacilitatorConfig = {
4084
+ seller_id: providerAlipay.seller_id,
4085
+ app_id: providerAlipay.app_id,
4086
+ seller_name: providerAlipay.seller_name,
4087
+ service_id_default: providerAlipay.service_id_default,
4088
+ private_key_pem: resolvePem(providerAlipay.private_key_path, "PRIVATE"),
4089
+ alipay_public_key_pem: resolvePem(providerAlipay.alipay_public_key_path, "PUBLIC"),
4090
+ gateway_url: providerAlipay.gateway_url,
4091
+ sign_type: providerAlipay.sign_type
4092
+ };
4093
+ facilitatorConfig.config = {
4094
+ ...facilitatorConfig.config,
4095
+ alipay: alipayFacilitatorConfig
4096
+ };
4097
+ facilitatorConfig.fallback = facilitatorConfig.fallback || [];
4098
+ if (facilitatorConfig.primary !== "alipay" && !facilitatorConfig.fallback.includes("alipay")) {
4099
+ facilitatorConfig.fallback.push("alipay");
4100
+ }
4101
+ } catch (err) {
4102
+ throw new Error(`[MoltsPay] Alipay rail configured but key load failed: ${err.message}`);
4103
+ }
4104
+ }
2875
4105
  this.registry = new FacilitatorRegistry(facilitatorConfig);
4106
+ if (providerAlipay) {
4107
+ this.alipayFacilitator = this.registry.get("alipay");
4108
+ console.log(`[MoltsPay] Alipay AI \u6536 rail enabled (seller ${providerAlipay.seller_id})`);
4109
+ }
2876
4110
  const primaryFacilitator = this.registry.get(facilitatorConfig.primary);
2877
4111
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
2878
4112
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
@@ -3008,10 +4242,10 @@ var MoltsPayServer = class {
3008
4242
  writeCorsHeaders(res, origin) {
3009
4243
  res.setHeader("Access-Control-Allow-Origin", origin);
3010
4244
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
3011
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
4245
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization, Payment-Proof");
3012
4246
  res.setHeader(
3013
4247
  "Access-Control-Expose-Headers",
3014
- "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt"
4248
+ "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt, Payment-Needed"
3015
4249
  );
3016
4250
  }
3017
4251
  /**
@@ -3038,7 +4272,8 @@ var MoltsPayServer = class {
3038
4272
  if (url.pathname === "/execute" && req.method === "POST") {
3039
4273
  const body = await this.readBody(req);
3040
4274
  const paymentHeader = req.headers[PAYMENT_HEADER2];
3041
- return await this.handleExecute(body, paymentHeader, res);
4275
+ const proofHeader = req.headers[ALIPAY_PAYMENT_PROOF_HEADER];
4276
+ return await this.handleExecute(body, paymentHeader, res, proofHeader);
3042
4277
  }
3043
4278
  if (url.pathname === "/proxy" && req.method === "POST") {
3044
4279
  const clientIP = req.headers["x-real-ip"] || req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket.remoteAddress || "";
@@ -3056,7 +4291,8 @@ var MoltsPayServer = class {
3056
4291
  const body = req.method === "POST" ? await this.readBody(req) : {};
3057
4292
  const authHeader = req.headers[MPP_AUTH_HEADER];
3058
4293
  const x402Header = req.headers[PAYMENT_HEADER2];
3059
- return await this.handleMPPRequest(skill, body, authHeader, x402Header, res);
4294
+ const proofHeader = req.headers[ALIPAY_PAYMENT_PROOF_HEADER];
4295
+ return await this.handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader);
3060
4296
  }
3061
4297
  this.sendJson(res, 404, { error: "Not found" });
3062
4298
  } catch (err) {
@@ -3153,7 +4389,7 @@ var MoltsPayServer = class {
3153
4389
  /**
3154
4390
  * POST /execute - Execute service with x402 payment
3155
4391
  */
3156
- async handleExecute(body, paymentHeader, res) {
4392
+ async handleExecute(body, paymentHeader, res, proofHeader) {
3157
4393
  const { service, params } = body;
3158
4394
  if (!service) {
3159
4395
  return this.sendJson(res, 400, { error: "Missing service" });
@@ -3167,6 +4403,15 @@ var MoltsPayServer = class {
3167
4403
  return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
3168
4404
  }
3169
4405
  }
4406
+ if (proofHeader) {
4407
+ const alipayPayment = {
4408
+ x402Version: X402_VERSION3,
4409
+ scheme: ALIPAY_SCHEME,
4410
+ network: ALIPAY_NETWORK,
4411
+ payload: proofHeader
4412
+ };
4413
+ return this.handleAlipayExecute(skill, params || {}, alipayPayment, res);
4414
+ }
3170
4415
  if (!paymentHeader) {
3171
4416
  return this.sendPaymentRequired(skill.config, res);
3172
4417
  }
@@ -3177,6 +4422,11 @@ var MoltsPayServer = class {
3177
4422
  } catch {
3178
4423
  return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
3179
4424
  }
4425
+ const payScheme = payment.accepted?.scheme || payment.scheme;
4426
+ const payNetwork = payment.accepted?.network || payment.network;
4427
+ if (payScheme === ALIPAY_SCHEME || (payNetwork ? isAlipayChainId(payNetwork) : false)) {
4428
+ return this.handleAlipayExecute(skill, params || {}, payment, res);
4429
+ }
3180
4430
  const validation = this.validatePayment(payment, skill.config);
3181
4431
  if (!validation.valid) {
3182
4432
  return this.sendJson(res, 402, { error: validation.error });
@@ -3269,13 +4519,111 @@ var MoltsPayServer = class {
3269
4519
  payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
3270
4520
  }, responseHeaders);
3271
4521
  }
4522
+ /**
4523
+ * Execute a service paid via the Alipay AI 收 fiat rail (2.0.0).
4524
+ *
4525
+ * Differs from the EVM/SVM path: no token detection, no EIP-3009/permit
4526
+ * validation. Verify hits the Alipay Open API (`payment.verify`). Settlement
4527
+ * (`fulfillment.confirm`) is FIRE-AND-FORGET per ALIPAY-INTEGRATION-DESIGN
4528
+ * §5.1: a confirm failure is logged but does NOT fail the already-delivered
4529
+ * response (the buyer's payment proof was already verified).
4530
+ */
4531
+ async handleAlipayExecute(skill, params, payment, res) {
4532
+ if (!this.alipayFacilitator) {
4533
+ return this.sendJson(res, 402, { error: "Alipay rail not configured on this server" });
4534
+ }
4535
+ const requirements = {
4536
+ scheme: ALIPAY_SCHEME,
4537
+ network: ALIPAY_NETWORK,
4538
+ asset: "CNY",
4539
+ amount: skill.config.alipay?.price_cny || "0",
4540
+ payTo: this.manifest.provider.alipay?.seller_id || "",
4541
+ maxTimeoutSeconds: 1800
4542
+ };
4543
+ console.log(`[MoltsPay] Verifying Alipay payment...`);
4544
+ const verifyResult = await this.registry.verify(payment, requirements);
4545
+ if (!verifyResult.valid) {
4546
+ return this.sendJson(res, 402, {
4547
+ error: `Payment verification failed: ${verifyResult.error}`,
4548
+ facilitator: verifyResult.facilitator
4549
+ });
4550
+ }
4551
+ console.log(`[MoltsPay] Alipay payment verified by ${verifyResult.facilitator}`);
4552
+ const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
4553
+ console.log(`[MoltsPay] Executing skill: ${skill.id} (timeout: ${timeoutSeconds}s)`);
4554
+ let result;
4555
+ try {
4556
+ result = await Promise.race([
4557
+ skill.handler(params),
4558
+ new Promise(
4559
+ (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
4560
+ )
4561
+ ]);
4562
+ } catch (err) {
4563
+ console.error("[MoltsPay] Skill execution failed:", err.message);
4564
+ return this.sendJson(res, 500, {
4565
+ error: "Service execution failed",
4566
+ message: err.message
4567
+ });
4568
+ }
4569
+ let settlement;
4570
+ try {
4571
+ settlement = await this.registry.settle(payment, requirements);
4572
+ if (settlement.success) {
4573
+ console.log(`[MoltsPay] Alipay fulfillment confirmed: ${settlement.transaction}`);
4574
+ } else {
4575
+ console.error(`[MoltsPay] Alipay fulfillment confirm failed (non-fatal): ${settlement.error}`);
4576
+ }
4577
+ } catch (err) {
4578
+ console.error(`[MoltsPay] Alipay fulfillment confirm threw (non-fatal): ${err.message}`);
4579
+ settlement = { success: false, error: err.message, facilitator: "alipay" };
4580
+ }
4581
+ const responseHeaders = {};
4582
+ if (settlement.success) {
4583
+ responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(JSON.stringify({
4584
+ success: true,
4585
+ transaction: settlement.transaction,
4586
+ network: ALIPAY_NETWORK,
4587
+ facilitator: settlement.facilitator
4588
+ })).toString("base64");
4589
+ }
4590
+ this.sendJson(res, 200, {
4591
+ success: true,
4592
+ result,
4593
+ payment: settlement.success ? { transaction: settlement.transaction, status: "fulfilled", facilitator: settlement.facilitator } : { status: "delivered_unconfirmed", error: settlement.error }
4594
+ }, responseHeaders);
4595
+ }
4596
+ /**
4597
+ * Build the Alipay 402 challenge for a service, or null when the alipay rail
4598
+ * isn't configured for this server or this service. Returns the x402
4599
+ * `accepts[]` entry plus the Base64URL `Payment-Needed` header value so the
4600
+ * 402 responders can dual-emit both the x402 and legacy alipay-bot formats.
4601
+ */
4602
+ async buildAlipayChallenge(config) {
4603
+ if (!this.alipayFacilitator || !config.alipay) return null;
4604
+ try {
4605
+ const req = await this.alipayFacilitator.createPaymentRequirements({
4606
+ serviceId: config.alipay.service_id || this.manifest.provider.alipay.service_id_default,
4607
+ priceCny: config.alipay.price_cny,
4608
+ goodsName: config.alipay.goods_name,
4609
+ resourceId: `/execute?service=${config.id}`
4610
+ });
4611
+ return { accepts: req.x402Accepts, paymentNeededHeader: req.paymentNeededHeader };
4612
+ } catch (err) {
4613
+ console.error(`[MoltsPay] Alipay challenge build failed for ${config.id}: ${err.message}`);
4614
+ return null;
4615
+ }
4616
+ }
3272
4617
  /**
3273
4618
  * Handle MPP (Machine Payments Protocol) request
3274
4619
  * Supports both x402 and MPP protocols on service endpoints
3275
4620
  */
3276
- async handleMPPRequest(skill, body, authHeader, x402Header, res) {
4621
+ async handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader) {
3277
4622
  const config = skill.config;
3278
4623
  const params = body || {};
4624
+ if (proofHeader) {
4625
+ return await this.handleExecute({ service: config.id, params }, void 0, res, proofHeader);
4626
+ }
3279
4627
  if (x402Header) {
3280
4628
  return await this.handleExecute({ service: config.id, params }, x402Header, res);
3281
4629
  }
@@ -3381,7 +4729,7 @@ var MoltsPayServer = class {
3381
4729
  /**
3382
4730
  * Return 402 with both x402 and MPP payment requirements
3383
4731
  */
3384
- sendMPPPaymentRequired(config, res) {
4732
+ async sendMPPPaymentRequired(config, res) {
3385
4733
  const acceptedTokens = getAcceptedCurrencies(config);
3386
4734
  const providerChains = this.getProviderChains();
3387
4735
  const accepts = [];
@@ -3392,6 +4740,10 @@ var MoltsPayServer = class {
3392
4740
  }
3393
4741
  }
3394
4742
  }
4743
+ const alipayChallenge = await this.buildAlipayChallenge(config);
4744
+ if (alipayChallenge) {
4745
+ accepts.push(alipayChallenge.accepts);
4746
+ }
3395
4747
  const x402PaymentRequired = {
3396
4748
  x402Version: X402_VERSION3,
3397
4749
  accepts,
@@ -3419,7 +4771,7 @@ var MoltsPayServer = class {
3419
4771
  };
3420
4772
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
3421
4773
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
3422
- mppWwwAuth = `Payment id="${challengeId}", realm="${this.manifest.provider.name}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
4774
+ mppWwwAuth = `Payment id="${challengeId}", realm="${headerSafe(this.manifest.provider.name)}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${headerSafe(config.name)}", expires="${expiresAt}"`;
3423
4775
  }
3424
4776
  const headers = {
3425
4777
  "Content-Type": "application/problem+json",
@@ -3428,6 +4780,9 @@ var MoltsPayServer = class {
3428
4780
  if (mppWwwAuth) {
3429
4781
  headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
3430
4782
  }
4783
+ if (alipayChallenge) {
4784
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
4785
+ }
3431
4786
  res.writeHead(402, headers);
3432
4787
  res.end(JSON.stringify({
3433
4788
  type: "https://paymentauth.org/problems/payment-required",
@@ -3454,7 +4809,7 @@ var MoltsPayServer = class {
3454
4809
  * Return 402 with x402 payment requirements (v2 format)
3455
4810
  * Includes requirements for all chains and all accepted currencies
3456
4811
  */
3457
- sendPaymentRequired(config, res) {
4812
+ async sendPaymentRequired(config, res) {
3458
4813
  const acceptedTokens = getAcceptedCurrencies(config);
3459
4814
  const providerChains = this.getProviderChains();
3460
4815
  const accepts = [];
@@ -3465,6 +4820,10 @@ var MoltsPayServer = class {
3465
4820
  }
3466
4821
  }
3467
4822
  }
4823
+ const alipayChallenge = await this.buildAlipayChallenge(config);
4824
+ if (alipayChallenge) {
4825
+ accepts.push(alipayChallenge.accepts);
4826
+ }
3468
4827
  const acceptedChains = providerChains.map((c) => {
3469
4828
  if (c.network === "eip155:8453") return "base";
3470
4829
  if (c.network === "eip155:137") return "polygon";
@@ -3482,10 +4841,14 @@ var MoltsPayServer = class {
3482
4841
  }
3483
4842
  };
3484
4843
  const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
3485
- res.writeHead(402, {
4844
+ const headers = {
3486
4845
  "Content-Type": "application/json",
3487
4846
  [PAYMENT_REQUIRED_HEADER2]: encoded
3488
- });
4847
+ };
4848
+ if (alipayChallenge) {
4849
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
4850
+ }
4851
+ res.writeHead(402, headers);
3489
4852
  res.end(JSON.stringify({
3490
4853
  error: "Payment required",
3491
4854
  message: `Service requires $${config.price} ${config.currency}`,
@@ -3592,12 +4955,12 @@ var MoltsPayServer = class {
3592
4955
  return accepted.includes(token);
3593
4956
  }
3594
4957
  async readBody(req) {
3595
- return new Promise((resolve2, reject) => {
4958
+ return new Promise((resolve3, reject) => {
3596
4959
  let body = "";
3597
4960
  req.on("data", (chunk) => body += chunk);
3598
4961
  req.on("end", () => {
3599
4962
  try {
3600
- resolve2(body ? JSON.parse(body) : {});
4963
+ resolve3(body ? JSON.parse(body) : {});
3601
4964
  } catch {
3602
4965
  reject(new Error("Invalid JSON"));
3603
4966
  }
@@ -3855,7 +5218,7 @@ var MoltsPayServer = class {
3855
5218
  };
3856
5219
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
3857
5220
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
3858
- const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
5221
+ const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${headerSafe(config.name)}", expires="${expiresAt}"`;
3859
5222
  res.writeHead(402, {
3860
5223
  "Content-Type": "application/problem+json",
3861
5224
  [MPP_WWW_AUTH_HEADER]: wwwAuth
@@ -4025,10 +5388,10 @@ init_esm_shims();
4025
5388
  async function printQRCode(url) {
4026
5389
  const qrcodeModule = await import("qrcode-terminal");
4027
5390
  const qrcode = qrcodeModule.default || qrcodeModule;
4028
- return new Promise((resolve2) => {
5391
+ return new Promise((resolve3) => {
4029
5392
  qrcode.generate(url, { small: true }, (qr) => {
4030
5393
  console.log(qr);
4031
- resolve2();
5394
+ resolve3();
4032
5395
  });
4033
5396
  });
4034
5397
  }
@@ -4040,9 +5403,9 @@ if (!globalThis.crypto) {
4040
5403
  }
4041
5404
  function getVersion() {
4042
5405
  const locations = [
4043
- join5(__dirname, "../../package.json"),
4044
- join5(__dirname, "../package.json"),
4045
- join5(process.cwd(), "node_modules/moltspay/package.json")
5406
+ join6(__dirname, "../../package.json"),
5407
+ join6(__dirname, "../package.json"),
5408
+ join6(process.cwd(), "node_modules/moltspay/package.json")
4046
5409
  ];
4047
5410
  for (const loc of locations) {
4048
5411
  try {
@@ -4121,7 +5484,7 @@ async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2
4121
5484
  const provider = new ethers4.JsonRpcProvider(chainConfig.rpc);
4122
5485
  let spenderAddress = null;
4123
5486
  try {
4124
- const walletPath = join5(configDir, "wallet.json");
5487
+ const walletPath = join6(configDir, "wallet.json");
4125
5488
  const walletData = JSON.parse(readFileSync5(walletPath, "utf-8"));
4126
5489
  spenderAddress = walletData.approvals?.[chain] || null;
4127
5490
  } catch {
@@ -4139,8 +5502,8 @@ async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2
4139
5502
  return result;
4140
5503
  }
4141
5504
  var program = new Command();
4142
- var DEFAULT_CONFIG_DIR2 = join5(homedir3(), ".moltspay");
4143
- var PID_FILE = join5(DEFAULT_CONFIG_DIR2, "server.pid");
5505
+ var DEFAULT_CONFIG_DIR2 = join6(homedir4(), ".moltspay");
5506
+ var PID_FILE = join6(DEFAULT_CONFIG_DIR2, "server.pid");
4144
5507
  if (!existsSync5(DEFAULT_CONFIG_DIR2)) {
4145
5508
  mkdirSync3(DEFAULT_CONFIG_DIR2, { recursive: true });
4146
5509
  }
@@ -4149,10 +5512,10 @@ function prompt(question) {
4149
5512
  input: process.stdin,
4150
5513
  output: process.stdout
4151
5514
  });
4152
- return new Promise((resolve2) => {
5515
+ return new Promise((resolve3) => {
4153
5516
  rl.question(question, (answer) => {
4154
5517
  rl.close();
4155
- resolve2(answer.trim());
5518
+ resolve3(answer.trim());
4156
5519
  });
4157
5520
  });
4158
5521
  }
@@ -4180,7 +5543,7 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
4180
5543
  console.log(`
4181
5544
  \u2705 Solana wallet created: ${address}`);
4182
5545
  console.log(`
4183
- \u{1F4C1} Config saved to: ${join5(options.configDir, "wallet-solana.json")}`);
5546
+ \u{1F4C1} Config saved to: ${join6(options.configDir, "wallet-solana.json")}`);
4184
5547
  console.log(`
4185
5548
  \u26A0\uFE0F IMPORTANT: Back up your wallet file!`);
4186
5549
  console.log(` This file contains your private key!
@@ -4195,7 +5558,7 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
4195
5558
  return;
4196
5559
  }
4197
5560
  console.log("\n\u{1F510} MoltsPay Client Setup\n");
4198
- if (existsSync5(join5(options.configDir, "wallet.json"))) {
5561
+ if (existsSync5(join6(options.configDir, "wallet.json"))) {
4199
5562
  console.log('\u26A0\uFE0F EVM wallet already initialized. Use "moltspay config" to update settings.');
4200
5563
  console.log(` Config dir: ${options.configDir}`);
4201
5564
  return;
@@ -4221,7 +5584,7 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
4221
5584
  console.log(`
4222
5585
  \u{1F4C1} Config saved to: ${result.configDir}`);
4223
5586
  console.log(`
4224
- \u26A0\uFE0F IMPORTANT: Back up ${join5(result.configDir, "wallet.json")}`);
5587
+ \u26A0\uFE0F IMPORTANT: Back up ${join6(result.configDir, "wallet.json")}`);
4225
5588
  console.log(` This file contains your private key!
4226
5589
  `);
4227
5590
  if (chain === "bnb" || chain === "bnb_testnet") {
@@ -4386,7 +5749,7 @@ program.command("approve").description("Approve a spender address for BNB chain
4386
5749
  \u{1F510} Approving spender for ${chain}...
4387
5750
  `);
4388
5751
  await setupBNBApprovals(client, chain, options.spender, false);
4389
- const walletPath = join5(options.configDir || DEFAULT_CONFIG_DIR2, "wallet.json");
5752
+ const walletPath = join6(options.configDir || DEFAULT_CONFIG_DIR2, "wallet.json");
4390
5753
  try {
4391
5754
  const walletData = JSON.parse(readFileSync5(walletPath, "utf-8"));
4392
5755
  walletData.approvals = walletData.approvals || {};
@@ -5023,17 +6386,17 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
5023
6386
  const handlers = /* @__PURE__ */ new Map();
5024
6387
  let provider = null;
5025
6388
  for (const inputPath of allPaths) {
5026
- const resolvedPath = resolve(inputPath);
6389
+ const resolvedPath = resolve2(inputPath);
5027
6390
  let manifestPath;
5028
6391
  let skillDir;
5029
6392
  let isSkillDir = false;
5030
- if (existsSync5(join5(resolvedPath, "moltspay.services.json"))) {
5031
- manifestPath = join5(resolvedPath, "moltspay.services.json");
6393
+ if (existsSync5(join6(resolvedPath, "moltspay.services.json"))) {
6394
+ manifestPath = join6(resolvedPath, "moltspay.services.json");
5032
6395
  skillDir = resolvedPath;
5033
6396
  isSkillDir = true;
5034
6397
  } else if (existsSync5(resolvedPath) && resolvedPath.endsWith(".json")) {
5035
6398
  manifestPath = resolvedPath;
5036
- skillDir = dirname(resolvedPath);
6399
+ skillDir = dirname2(resolvedPath);
5037
6400
  } else if (existsSync5(resolvedPath)) {
5038
6401
  console.error(`\u274C No moltspay.services.json found in: ${resolvedPath}`);
5039
6402
  continue;
@@ -5050,7 +6413,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
5050
6413
  let skillModule = null;
5051
6414
  if (isSkillDir) {
5052
6415
  let entryPoint = "index.js";
5053
- const pkgJsonPath = join5(skillDir, "package.json");
6416
+ const pkgJsonPath = join6(skillDir, "package.json");
5054
6417
  if (existsSync5(pkgJsonPath)) {
5055
6418
  try {
5056
6419
  const pkgJson = JSON.parse(readFileSync5(pkgJsonPath, "utf-8"));
@@ -5060,7 +6423,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
5060
6423
  } catch {
5061
6424
  }
5062
6425
  }
5063
- const modulePath = join5(skillDir, entryPoint);
6426
+ const modulePath = join6(skillDir, entryPoint);
5064
6427
  if (existsSync5(modulePath)) {
5065
6428
  try {
5066
6429
  skillModule = await import(modulePath);
@@ -5086,7 +6449,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
5086
6449
  const workdir = skillDir;
5087
6450
  handlers.set(service.id, async (params) => {
5088
6451
  return new Promise((resolvePromise, reject) => {
5089
- const proc = spawn("sh", ["-c", service.command], {
6452
+ const proc = spawn2("sh", ["-c", service.command], {
5090
6453
  cwd: workdir,
5091
6454
  stdio: ["pipe", "pipe", "pipe"]
5092
6455
  });
@@ -5139,7 +6502,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
5139
6502
  provider,
5140
6503
  services: allServices
5141
6504
  };
5142
- const tempManifestPath = join5(DEFAULT_CONFIG_DIR2, "combined-manifest.json");
6505
+ const tempManifestPath = join6(DEFAULT_CONFIG_DIR2, "combined-manifest.json");
5143
6506
  writeFileSync3(tempManifestPath, JSON.stringify(combinedManifest, null, 2));
5144
6507
  console.log(`
5145
6508
  \u{1F4CB} Combined manifest: ${allServices.length} services`);
@@ -5202,7 +6565,7 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
5202
6565
  }
5203
6566
  process.kill(pid, "SIGTERM");
5204
6567
  console.log("\u2705 Sent SIGTERM to server");
5205
- await new Promise((resolve2) => setTimeout(resolve2, 1e3));
6568
+ await new Promise((resolve3) => setTimeout(resolve3, 1e3));
5206
6569
  try {
5207
6570
  process.kill(pid, 0);
5208
6571
  console.log("\u26A0\uFE0F Server still running, sending SIGKILL...");
@@ -5218,9 +6581,10 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
5218
6581
  process.exit(1);
5219
6582
  }
5220
6583
  });
5221
- program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <path>", "Image URL or local file path").option("--data <json>", "Raw JSON data to send (for custom input formats)").option("--token <token>", "Token to pay with (USDC or USDT)", "USDC").option("--chain <chain>", "Chain to pay on (base, polygon, base_sepolia, tempo_moderato, solana, or solana_devnet).").option("--config-dir <dir>", "Config directory with wallet.json", DEFAULT_CONFIG_DIR2).option("--json", "Output raw JSON only").action(async (server, service, paramsJson, options) => {
6584
+ program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <path>", "Image URL or local file path").option("--data <json>", "Raw JSON data to send (for custom input formats)").option("--token <token>", "Token to pay with (USDC or USDT)", "USDC").option("--chain <chain>", "Chain to pay on (base, polygon, base_sepolia, tempo_moderato, solana, or solana_devnet).").option("--rail <rail>", 'Payment rail: a chain name, or "alipay" for the Alipay fiat rail (CNY via alipay-bot)').option("--config-dir <dir>", "Config directory with wallet.json", DEFAULT_CONFIG_DIR2).option("--json", "Output raw JSON only").action(async (server, service, paramsJson, options) => {
5222
6585
  const client = new MoltsPayClient({ configDir: options.configDir });
5223
- if (!client.isInitialized) {
6586
+ const useAlipay = options.rail?.toLowerCase() === "alipay";
6587
+ if (!useAlipay && !client.isInitialized) {
5224
6588
  console.error("\u274C Wallet not initialized. Run: npx moltspay init");
5225
6589
  process.exit(1);
5226
6590
  }
@@ -5248,7 +6612,7 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
5248
6612
  if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
5249
6613
  params.image_url = imagePath;
5250
6614
  } else {
5251
- const filePath = resolve(imagePath);
6615
+ const filePath = resolve2(imagePath);
5252
6616
  if (!existsSync5(filePath)) {
5253
6617
  console.error(`\u274C Image file not found: ${filePath}`);
5254
6618
  process.exit(1);
@@ -5265,7 +6629,7 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
5265
6629
  }
5266
6630
  const imageDisplay = params.image_url || (params.image_base64 ? `[local file: ${options.image}]` : null);
5267
6631
  const token = (options.token || "USDC").toUpperCase();
5268
- if (token === "USDT") {
6632
+ if (!useAlipay && token === "USDT") {
5269
6633
  const balance = await client.getBalance();
5270
6634
  if (balance.native < 1e-4) {
5271
6635
  console.log("\n\u26A0\uFE0F USDT requires a small amount of ETH for gas (~$0.01)");
@@ -5289,13 +6653,31 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
5289
6653
  console.log(` Prompt: ${params.prompt}`);
5290
6654
  }
5291
6655
  if (imageDisplay) console.log(` Image: ${imageDisplay}`);
5292
- console.log(` Chain: ${chain || "(auto)"}`);
5293
- console.log(` Token: ${token}`);
5294
- console.log(` Wallet: ${client.address}`);
6656
+ if (useAlipay) {
6657
+ console.log(` Rail: alipay (CNY via alipay-bot)`);
6658
+ } else {
6659
+ console.log(` Chain: ${chain || "(auto)"}`);
6660
+ console.log(` Token: ${token}`);
6661
+ console.log(` Wallet: ${client.address}`);
6662
+ }
5295
6663
  console.log("");
5296
6664
  }
5297
6665
  try {
5298
- const result = await client.pay(server, service, params, {
6666
+ const result = await client.pay(server, service, params, useAlipay ? {
6667
+ rail: "alipay",
6668
+ rawData: useRawData,
6669
+ onPaymentPending: ({ paymentUrl, shortenUrl }) => {
6670
+ if (!options.json) {
6671
+ process.stdout.write(`
6672
+ \u{1F4F2} \u8BF7\u7528\u652F\u4ED8\u5B9D\u626B\u7801\u6216\u8BBF\u95EE\uFF1A${shortenUrl ?? paymentUrl}
6673
+
6674
+ `);
6675
+ }
6676
+ },
6677
+ onLine: (line) => {
6678
+ if (!options.json) process.stdout.write(line + "\n");
6679
+ }
6680
+ } : {
5299
6681
  token,
5300
6682
  chain,
5301
6683
  rawData: useRawData
@@ -5316,11 +6698,34 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
5316
6698
  process.exit(1);
5317
6699
  }
5318
6700
  });
6701
+ program.command("alipay <action> [args...]").description("Alipay wallet setup via alipay-bot: check | apply | bind").allowUnknownOption().action(async (action, args) => {
6702
+ const map = {
6703
+ check: "check-wallet",
6704
+ apply: "apply-wallet",
6705
+ bind: "bind-wallet"
6706
+ };
6707
+ const sub = map[action] ?? action;
6708
+ const child = spawn2("alipay-bot", [sub, ...args ?? []], {
6709
+ stdio: "inherit",
6710
+ env: filterEnv(process.env)
6711
+ });
6712
+ child.on("error", (e) => {
6713
+ if (e?.code === "ENOENT") {
6714
+ console.error(
6715
+ "\u274C alipay-bot not installed. Run: npx -y @alipay/agent-payment install-cli"
6716
+ );
6717
+ } else {
6718
+ console.error(`\u274C ${e.message}`);
6719
+ }
6720
+ process.exit(1);
6721
+ });
6722
+ child.on("exit", (code) => process.exit(code ?? 1));
6723
+ });
5319
6724
  program.command("validate <path>").description("Validate a moltspay.services.json file against the schema").action(async (inputPath) => {
5320
- const resolvedPath = resolve(inputPath);
6725
+ const resolvedPath = resolve2(inputPath);
5321
6726
  let manifestPath;
5322
- if (existsSync5(join5(resolvedPath, "moltspay.services.json"))) {
5323
- manifestPath = join5(resolvedPath, "moltspay.services.json");
6727
+ if (existsSync5(join6(resolvedPath, "moltspay.services.json"))) {
6728
+ manifestPath = join6(resolvedPath, "moltspay.services.json");
5324
6729
  } else if (resolvedPath.endsWith(".json") && existsSync5(resolvedPath)) {
5325
6730
  manifestPath = resolvedPath;
5326
6731
  } else {