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
package/dist/cli/index.js CHANGED
@@ -37,10 +37,10 @@ var init_cjs_shims = __esm({
37
37
  init_cjs_shims();
38
38
  var import_crypto = require("crypto");
39
39
  var import_commander = require("commander");
40
- var import_os3 = require("os");
41
- var import_path3 = require("path");
40
+ var import_os4 = require("os");
41
+ var import_path4 = require("path");
42
42
  var import_fs5 = require("fs");
43
- var import_child_process = require("child_process");
43
+ var import_child_process3 = require("child_process");
44
44
  var import_ethers4 = require("ethers");
45
45
 
46
46
  // src/client/index.ts
@@ -49,8 +49,8 @@ init_cjs_shims();
49
49
  // src/client/node/index.ts
50
50
  init_cjs_shims();
51
51
  var import_fs2 = require("fs");
52
- var import_os2 = require("os");
53
- var import_path2 = require("path");
52
+ var import_os3 = require("os");
53
+ var import_path3 = require("path");
54
54
  var import_ethers2 = require("ethers");
55
55
 
56
56
  // src/chains/index.ts
@@ -227,6 +227,10 @@ function getChain(name) {
227
227
  }
228
228
  return config;
229
229
  }
230
+ var ALIPAY_CHAIN_ID = "alipay";
231
+ function isAlipayChainId(id) {
232
+ return id === ALIPAY_CHAIN_ID;
233
+ }
230
234
 
231
235
  // src/wallet/solana.ts
232
236
  init_cjs_shims();
@@ -618,6 +622,14 @@ var BufferCtor = globalThis.Buffer;
618
622
 
619
623
  // src/client/core/errors.ts
620
624
  init_cjs_shims();
625
+ var MoltsPayError = class extends Error {
626
+ code;
627
+ constructor(code, message) {
628
+ super(message);
629
+ this.name = "MoltsPayError";
630
+ this.code = code;
631
+ }
632
+ };
621
633
 
622
634
  // src/client/core/eip3009.ts
623
635
  init_cjs_shims();
@@ -767,6 +779,740 @@ function findChainByChainId(chainId) {
767
779
  return void 0;
768
780
  }
769
781
 
782
+ // src/client/alipay/index.ts
783
+ init_cjs_shims();
784
+ var import_node_crypto = require("crypto");
785
+ var import_promises = require("fs/promises");
786
+ var import_os2 = require("os");
787
+ var import_path2 = require("path");
788
+
789
+ // src/client/alipay/cli.ts
790
+ init_cjs_shims();
791
+ var import_child_process = require("child_process");
792
+
793
+ // src/client/alipay/log.ts
794
+ init_cjs_shims();
795
+ var RANK = { off: 0, info: 1, debug: 2 };
796
+ function normalizeLevel(v) {
797
+ const s = (v ?? "").toLowerCase();
798
+ return s === "info" || s === "debug" ? s : "off";
799
+ }
800
+ var level = normalizeLevel(process.env.MOLTSPAY_ALIPAY_LOG);
801
+ var sink = (line) => {
802
+ process.stderr.write(line + "\n");
803
+ };
804
+ function enabledFor(want) {
805
+ return RANK[level] >= RANK[want];
806
+ }
807
+ function render(event, fields) {
808
+ const parts = ["[moltspay:alipay]", String(fields.ts), event];
809
+ if (fields.flow) parts.push(`flow=${fields.flow}`);
810
+ if (fields.step) parts.push(`step=${fields.step}`);
811
+ if (typeof fields.ms === "number") parts.push(`${fields.ms}ms`);
812
+ for (const [k, v] of Object.entries(fields)) {
813
+ if (["ts", "level", "event", "flow", "step", "ms"].includes(k)) continue;
814
+ parts.push(`${k}=${typeof v === "string" ? v : JSON.stringify(v)}`);
815
+ }
816
+ return parts.join(" ");
817
+ }
818
+ function emit(lvl, event, fields) {
819
+ if (!enabledFor(lvl)) return;
820
+ const full = { ts: (/* @__PURE__ */ new Date()).toISOString(), level: lvl, event, ...fields };
821
+ try {
822
+ sink(render(event, full), full);
823
+ } catch {
824
+ }
825
+ }
826
+ var alipayLog = {
827
+ info: (event, fields = {}) => emit("info", event, fields),
828
+ debug: (event, fields = {}) => emit("debug", event, fields),
829
+ /** True if anything at all would be logged (cheap guard for hot paths). */
830
+ enabled: () => level !== "off"
831
+ };
832
+ async function timeStep(step, flow, fn) {
833
+ if (!alipayLog.enabled()) return fn();
834
+ const t0 = Date.now();
835
+ alipayLog.debug("step.start", { flow, step });
836
+ try {
837
+ const out = await fn();
838
+ alipayLog.info("step.end", { flow, step, ms: Date.now() - t0, ok: true });
839
+ return out;
840
+ } catch (err) {
841
+ alipayLog.info("step.end", {
842
+ flow,
843
+ step,
844
+ ms: Date.now() - t0,
845
+ ok: false,
846
+ err: err instanceof Error ? err.message : String(err)
847
+ });
848
+ throw err;
849
+ }
850
+ }
851
+
852
+ // src/client/alipay/cli.ts
853
+ var ALLOWED_ENV = /* @__PURE__ */ new Set([
854
+ "AIPAY_OUTPUT_CHANNEL",
855
+ "AIPAY_SESSION_ID",
856
+ "AIPAY_FRAMEWORK",
857
+ "AIPAY_MODEL",
858
+ "AIPAY_OS",
859
+ // Minimal survival set for spawn to find the binary and a home dir.
860
+ "PATH",
861
+ "HOME"
862
+ ]);
863
+ function filterEnv(env) {
864
+ return Object.fromEntries(
865
+ Object.entries(env).filter(([k]) => ALLOWED_ENV.has(k))
866
+ );
867
+ }
868
+ function makeLineSplitter(onLine) {
869
+ let buf = "";
870
+ return (chunk) => {
871
+ buf += chunk.toString("utf-8");
872
+ let nl;
873
+ while ((nl = buf.indexOf("\n")) !== -1) {
874
+ onLine(buf.slice(0, nl));
875
+ buf = buf.slice(nl + 1);
876
+ }
877
+ };
878
+ }
879
+ function profileEnv(step, flow) {
880
+ const want = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE;
881
+ const hook = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE_HOOK;
882
+ if (!want || !hook) return {};
883
+ const steps = want.split(",").map((s) => s.trim());
884
+ if (!steps.includes("all") && !steps.includes(step)) return {};
885
+ const dir = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE_DIR || "/tmp";
886
+ const safe = `${flow ?? "noflow"}-${step}-${Date.now()}`.replace(/[^a-zA-Z0-9._-]/g, "_");
887
+ const out = `${dir}/cli-profile-${safe}.json`;
888
+ alipayLog.info("cli.profile", { flow, step, out });
889
+ return {
890
+ NODE_OPTIONS: `--require=${hook}`,
891
+ MOLTSPAY_CLI_PROFILE_OUT: out
892
+ };
893
+ }
894
+ var runCli = (args, opts = {}) => {
895
+ const bin = opts.bin ?? "alipay-bot";
896
+ const step = opts.step ?? args[0] ?? "(no-arg)";
897
+ const startedAt = Date.now();
898
+ const lines = [];
899
+ const collect = (line) => {
900
+ lines.push(line);
901
+ alipayLog.debug("cli.line", {
902
+ flow: opts.flow,
903
+ step,
904
+ ms: Date.now() - startedAt,
905
+ preview: line.slice(0, 120)
906
+ });
907
+ opts.onLine?.(line);
908
+ };
909
+ alipayLog.debug("step.start", { flow: opts.flow, step });
910
+ return new Promise((resolve3, reject) => {
911
+ const child = (0, import_child_process.spawn)(bin, args, {
912
+ env: { ...filterEnv(process.env), ...profileEnv(step, opts.flow), ...opts.env ?? {} }
913
+ });
914
+ if (opts.signal) {
915
+ if (opts.signal.aborted) child.kill("SIGTERM");
916
+ opts.signal.addEventListener("abort", () => child.kill("SIGTERM"), { once: true });
917
+ }
918
+ let sawByte = false;
919
+ const onChunk = (stream) => (chunk) => {
920
+ const ms = Date.now() - startedAt;
921
+ if (!sawByte) {
922
+ sawByte = true;
923
+ alipayLog.debug("cli.firstbyte", { flow: opts.flow, step, ms });
924
+ }
925
+ alipayLog.debug("cli.chunk", { flow: opts.flow, step, ms, stream, bytes: chunk.length });
926
+ };
927
+ const onStdout = makeLineSplitter(collect);
928
+ const onStderr = makeLineSplitter(collect);
929
+ child.stdout?.on("data", onChunk("out"));
930
+ child.stderr?.on("data", onChunk("err"));
931
+ child.stdout?.on("data", onStdout);
932
+ child.stderr?.on("data", onStderr);
933
+ child.on("error", (err) => {
934
+ alipayLog.info("cli.exit", {
935
+ flow: opts.flow,
936
+ step,
937
+ ms: Date.now() - startedAt,
938
+ ok: false,
939
+ err: err.message
940
+ });
941
+ reject(err);
942
+ });
943
+ child.on("close", (code) => {
944
+ alipayLog.info("cli.exit", {
945
+ flow: opts.flow,
946
+ step,
947
+ ms: Date.now() - startedAt,
948
+ ok: (code ?? 1) === 0,
949
+ code: code ?? 1
950
+ });
951
+ resolve3({ exitCode: code ?? 1, lines });
952
+ });
953
+ });
954
+ };
955
+
956
+ // src/client/alipay/install.ts
957
+ init_cjs_shims();
958
+ var import_child_process2 = require("child_process");
959
+ var import_util = require("util");
960
+
961
+ // src/client/alipay/errors.ts
962
+ init_cjs_shims();
963
+ var AlipayCliNotFoundError = class extends MoltsPayError {
964
+ constructor(message) {
965
+ super("ALIPAY_CLI_NOT_FOUND", message);
966
+ this.name = "AlipayCliNotFoundError";
967
+ }
968
+ };
969
+ var AlipayCliVersionError = class extends MoltsPayError {
970
+ constructor(message) {
971
+ super("ALIPAY_CLI_VERSION", message);
972
+ this.name = "AlipayCliVersionError";
973
+ }
974
+ };
975
+ var NeedsWalletSetupError = class extends MoltsPayError {
976
+ constructor(message = "Alipay wallet not set up. Run: moltspay alipay apply") {
977
+ super("ALIPAY_NEEDS_WALLET_SETUP", message);
978
+ this.name = "NeedsWalletSetupError";
979
+ }
980
+ };
981
+ var AlipayPaymentRejectedError = class extends MoltsPayError {
982
+ constructor(message) {
983
+ super("ALIPAY_PAYMENT_REJECTED", message);
984
+ this.name = "AlipayPaymentRejectedError";
985
+ }
986
+ };
987
+ var AlipayPaymentTimeoutError = class extends MoltsPayError {
988
+ constructor(message) {
989
+ super("ALIPAY_PAYMENT_TIMEOUT", message);
990
+ this.name = "AlipayPaymentTimeoutError";
991
+ }
992
+ };
993
+ var AlipayProtocolError = class extends MoltsPayError {
994
+ constructor(message) {
995
+ super("ALIPAY_PROTOCOL", message);
996
+ this.name = "AlipayProtocolError";
997
+ }
998
+ };
999
+ var UnsupportedRailError = class extends MoltsPayError {
1000
+ constructor(rail, message) {
1001
+ super("UNSUPPORTED_RAIL", message ?? `Rail not supported by server: ${rail}`);
1002
+ this.rail = rail;
1003
+ this.name = "UnsupportedRailError";
1004
+ }
1005
+ };
1006
+
1007
+ // src/client/alipay/install.ts
1008
+ var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
1009
+ var MIN_CLI_VERSION = "0.3.15";
1010
+ function semverLt(a, b) {
1011
+ const pa = a.split(".").map((n) => parseInt(n, 10));
1012
+ const pb = b.split(".").map((n) => parseInt(n, 10));
1013
+ for (let i = 0; i < 3; i++) {
1014
+ const x = pa[i] ?? 0;
1015
+ const y = pb[i] ?? 0;
1016
+ if (x !== y) return x < y;
1017
+ }
1018
+ return false;
1019
+ }
1020
+ function parseVersion(stdout) {
1021
+ return stdout.match(/v?(\d+\.\d+\.\d+)/)?.[1] ?? null;
1022
+ }
1023
+ var defaultGetVersion = async () => {
1024
+ const { stdout } = await execFileAsync("alipay-bot", ["--version"]);
1025
+ return stdout;
1026
+ };
1027
+ async function ensureCliUncached(getVersion2) {
1028
+ let stdout;
1029
+ try {
1030
+ stdout = await getVersion2();
1031
+ } catch (e) {
1032
+ if (e?.code === "ENOENT") {
1033
+ throw new AlipayCliNotFoundError(
1034
+ "alipay-bot not installed. Run: npx -y @alipay/agent-payment install-cli"
1035
+ );
1036
+ }
1037
+ throw e;
1038
+ }
1039
+ const version = parseVersion(stdout);
1040
+ if (!version || semverLt(version, MIN_CLI_VERSION)) {
1041
+ throw new AlipayCliVersionError(
1042
+ `alipay-bot ${version ?? "?"} found, need \u2265 ${MIN_CLI_VERSION}. Run: npx -y @alipay/agent-payment@latest update`
1043
+ );
1044
+ }
1045
+ return version;
1046
+ }
1047
+ var cachedVersion = null;
1048
+ var inflight = null;
1049
+ async function ensureCli(getVersion2 = defaultGetVersion) {
1050
+ if (getVersion2 !== defaultGetVersion) {
1051
+ return ensureCliUncached(getVersion2);
1052
+ }
1053
+ if (cachedVersion) return cachedVersion;
1054
+ if (!inflight) {
1055
+ inflight = ensureCliUncached(getVersion2).then((v) => {
1056
+ cachedVersion = v;
1057
+ return v;
1058
+ }).finally(() => {
1059
+ inflight = null;
1060
+ });
1061
+ }
1062
+ return inflight;
1063
+ }
1064
+
1065
+ // src/client/alipay/poll.ts
1066
+ init_cjs_shims();
1067
+ var POLL_INTERVAL_MS = 3e3;
1068
+ var POLL_MAX_INFLIGHT = 2;
1069
+ function resolveMaxInflight(override) {
1070
+ if (override !== void 0) return Math.max(1, Math.floor(override));
1071
+ const raw = process.env.MOLTSPAY_ALIPAY_POLL_MAX_INFLIGHT;
1072
+ if (raw !== void 0 && raw.trim() !== "") {
1073
+ const n = Number(raw);
1074
+ if (Number.isFinite(n) && n >= 1) return Math.floor(n);
1075
+ }
1076
+ return POLL_MAX_INFLIGHT;
1077
+ }
1078
+ function resolveLaunchGap(override) {
1079
+ if (override !== void 0) return Math.max(0, override);
1080
+ const raw = process.env.MOLTSPAY_ALIPAY_POLL_LAUNCH_MS;
1081
+ if (raw !== void 0 && raw.trim() !== "") {
1082
+ const n = Number(raw);
1083
+ if (Number.isFinite(n) && n >= 0) return n;
1084
+ }
1085
+ return POLL_INTERVAL_MS;
1086
+ }
1087
+ function parseStatus(lines) {
1088
+ const raw = lines.join("\n").trim();
1089
+ try {
1090
+ const json = JSON.parse(raw);
1091
+ if (json && typeof json === "object") {
1092
+ if (typeof json.success === "boolean") {
1093
+ if (json.success) return "paid";
1094
+ const err = String(json.errorCode ?? "").toUpperCase();
1095
+ if (/UNPAID|WAIT|PENDING|PROCESS|NOTPAY/.test(err)) return "pending";
1096
+ if (/CLOSED|CANCEL|FAIL|REJECT|REFUSE|TIMEOUT|EXPIRE/.test(err)) return "rejected";
1097
+ return "pending";
1098
+ }
1099
+ if (typeof json.code !== "undefined") {
1100
+ if (Number(json.code) === 200) return "paid";
1101
+ const msg = `${json.message ?? ""}${json.reason ?? ""}`;
1102
+ if (/关闭|失败|拒绝|取消|超时|已撤销/.test(msg)) return "rejected";
1103
+ return "pending";
1104
+ }
1105
+ if (typeof json.body === "string") {
1106
+ return classifyReportText(json.body);
1107
+ }
1108
+ }
1109
+ } catch {
1110
+ }
1111
+ return classifyReportText(raw);
1112
+ }
1113
+ function classifyReportText(s) {
1114
+ const text = s.toUpperCase();
1115
+ if (/查询支付状态成功并获取资源/.test(s) || /资源响应状态[^0-9\n]{0,8}200/.test(s) || /TRADE_SUCCESS|TRADE_FINISHED|"STATUS":\s*"FULFILLED"/.test(text)) return "paid";
1116
+ if (/TRADE_CLOSED|REJECTED|REFUSE|CANCEL/.test(text) || /交易关闭|支付失败|已拒绝|已取消|已撤销|交易超时/.test(s)) return "rejected";
1117
+ if (/WAIT_BUYER_PAY|UNPAID|PENDING|WAITING/.test(text) || /交易未支付|等待付款|待支付|未支付/.test(s)) return "pending";
1118
+ return "unknown";
1119
+ }
1120
+ function defaultSleep(ms, signal) {
1121
+ return new Promise((resolve3, reject) => {
1122
+ if (signal?.aborted) return reject(new Error("aborted"));
1123
+ const t = setTimeout(resolve3, ms);
1124
+ signal?.addEventListener(
1125
+ "abort",
1126
+ () => {
1127
+ clearTimeout(t);
1128
+ reject(new Error("aborted"));
1129
+ },
1130
+ { once: true }
1131
+ );
1132
+ });
1133
+ }
1134
+ var LAUNCH = /* @__PURE__ */ Symbol("launch");
1135
+ async function pollUntil(tradeNo, resourceUrl, opts) {
1136
+ const runner = opts.runner ?? runCli;
1137
+ const sleep = opts.sleep ?? defaultSleep;
1138
+ const now = opts.now ?? Date.now;
1139
+ const maxInflight = resolveMaxInflight(opts.maxInflight);
1140
+ const launchGap = resolveLaunchGap(opts.launchIntervalMs);
1141
+ const args = [
1142
+ "402-query-payment-status",
1143
+ "-t",
1144
+ tradeNo,
1145
+ "-r",
1146
+ resourceUrl,
1147
+ ...opts.method ? ["-m", opts.method] : [],
1148
+ ...opts.data ? ["-d", opts.data] : []
1149
+ ];
1150
+ const internal = new AbortController();
1151
+ const signal = opts.signal ? AbortSignal.any([opts.signal, internal.signal]) : internal.signal;
1152
+ let tick = 0;
1153
+ let lastLaunch = -Infinity;
1154
+ const inflight2 = /* @__PURE__ */ new Set();
1155
+ const launch = () => {
1156
+ tick += 1;
1157
+ const myTick = tick;
1158
+ lastLaunch = now();
1159
+ const run = (async () => {
1160
+ const { lines } = await runner(args, {
1161
+ signal,
1162
+ step: "query-payment-status",
1163
+ flow: tradeNo
1164
+ });
1165
+ const status = parseStatus(lines);
1166
+ alipayLog.debug("poll.tick", { flow: tradeNo, tick: myTick, status });
1167
+ return { status, lines };
1168
+ })();
1169
+ const tracked = run.finally(() => {
1170
+ inflight2.delete(tracked);
1171
+ });
1172
+ inflight2.add(tracked);
1173
+ };
1174
+ try {
1175
+ for (; ; ) {
1176
+ if (opts.signal?.aborted) throw new Error("aborted");
1177
+ const expired = now() >= opts.deadline;
1178
+ if (expired && inflight2.size === 0) {
1179
+ throw new AlipayPaymentTimeoutError(
1180
+ `Payment ${tradeNo} not completed before pay_before deadline`
1181
+ );
1182
+ }
1183
+ while (!expired && inflight2.size < maxInflight && now() - lastLaunch >= launchGap) {
1184
+ launch();
1185
+ }
1186
+ const waiters = [...inflight2];
1187
+ if (!expired && inflight2.size < maxInflight) {
1188
+ const untilNext = Math.max(0, launchGap - (now() - lastLaunch));
1189
+ waiters.push(sleep(untilNext, signal).then(() => LAUNCH, () => LAUNCH));
1190
+ }
1191
+ const winner = await Promise.race(waiters);
1192
+ if (winner === LAUNCH) continue;
1193
+ if (winner.status === "paid") return { status: "paid", lines: winner.lines };
1194
+ if (winner.status === "rejected") {
1195
+ throw new AlipayPaymentRejectedError(`Payment ${tradeNo} was rejected`);
1196
+ }
1197
+ }
1198
+ } finally {
1199
+ internal.abort();
1200
+ for (const p of inflight2) p.catch(() => void 0);
1201
+ }
1202
+ }
1203
+
1204
+ // src/client/alipay/index.ts
1205
+ var TRADE_NO_RE = /^\d{32}$/;
1206
+ function resolveSessionId(explicit, envSession) {
1207
+ return explicit ?? envSession ?? (0, import_node_crypto.randomUUID)();
1208
+ }
1209
+ function assertTradeNo(t) {
1210
+ if (!TRADE_NO_RE.test(t)) {
1211
+ throw new AlipayProtocolError(`invalid tradeNo (expect 32 digits): ${t}`);
1212
+ }
1213
+ }
1214
+ function parseTradeNo(lines) {
1215
+ for (const line of lines) {
1216
+ const labeled = line.match(/trade[_-]?no["'\s:=]+(\d{32})/i);
1217
+ if (labeled) return labeled[1];
1218
+ const bare = line.match(/\b(\d{32})\b/);
1219
+ if (bare) return bare[1];
1220
+ }
1221
+ return null;
1222
+ }
1223
+ function parsePaymentUrl(lines) {
1224
+ let paymentUrl;
1225
+ let shortenUrl;
1226
+ for (const line of lines) {
1227
+ const m = line.match(/(alipays?:\/\/\S+|https?:\/\/\S+)/i);
1228
+ if (!m) continue;
1229
+ const url = m[1].replace(/[)\]`>]+$/, "");
1230
+ if (/short|qr\.alipay|surl|\/s\//i.test(line) && !shortenUrl) shortenUrl = url;
1231
+ else if (!paymentUrl) paymentUrl = url;
1232
+ }
1233
+ if (!paymentUrl && shortenUrl) paymentUrl = shortenUrl;
1234
+ return { paymentUrl, shortenUrl };
1235
+ }
1236
+ function extractMedia(line) {
1237
+ const m = line.match(/^\s*MEDIA:\s*(.+?)\s*$/);
1238
+ return m ? m[1] : null;
1239
+ }
1240
+ function isWalletReady(lines) {
1241
+ const text = lines.join("\n").trim();
1242
+ try {
1243
+ const json = JSON.parse(text);
1244
+ if (json && typeof json.code !== "undefined") return Number(json.code) === 200;
1245
+ } catch {
1246
+ }
1247
+ return !/未开通|未开启|NOT[_\s-]*(OPEN|BOUND|SET)|NEEDS?[_\s-]*SETUP|NO[_\s-]*WALLET/i.test(text);
1248
+ }
1249
+ function extractResourceFromReport(report) {
1250
+ const label = report.search(/资源响应体/);
1251
+ const start = report.indexOf("{", label === -1 ? 0 : label);
1252
+ if (start === -1) return void 0;
1253
+ let depth = 0, inStr = false, esc = false;
1254
+ for (let i = start; i < report.length; i++) {
1255
+ const ch = report[i];
1256
+ if (inStr) {
1257
+ if (esc) esc = false;
1258
+ else if (ch === "\\") esc = true;
1259
+ else if (ch === '"') inStr = false;
1260
+ continue;
1261
+ }
1262
+ if (ch === '"') inStr = true;
1263
+ else if (ch === "{") depth++;
1264
+ else if (ch === "}" && --depth === 0) {
1265
+ const slice = report.slice(start, i + 1);
1266
+ try {
1267
+ const obj = JSON.parse(slice);
1268
+ const r = obj?.resourceResponse ?? obj?.result ?? obj?.data ?? obj?.body ?? obj;
1269
+ return typeof r === "string" ? r : JSON.stringify(r);
1270
+ } catch {
1271
+ return slice;
1272
+ }
1273
+ }
1274
+ }
1275
+ return void 0;
1276
+ }
1277
+ function extractBody(lines) {
1278
+ const text = lines.join("\n").trim();
1279
+ try {
1280
+ const json = JSON.parse(text);
1281
+ if (json && typeof json === "object") {
1282
+ if (json.resourceResponse !== void 0 && json.resourceResponse !== null) {
1283
+ const rr = json.resourceResponse;
1284
+ const r = typeof rr === "object" ? rr.result ?? rr.data ?? rr.body ?? rr : rr;
1285
+ return typeof r === "string" ? r : JSON.stringify(r);
1286
+ }
1287
+ if (typeof json.body === "string") {
1288
+ const inner = extractResourceFromReport(json.body);
1289
+ return inner ?? json.body;
1290
+ }
1291
+ const body = json.data ?? json.result ?? json.body ?? json.resource;
1292
+ if (body !== void 0) return typeof body === "string" ? body : JSON.stringify(body);
1293
+ return text;
1294
+ }
1295
+ } catch {
1296
+ }
1297
+ const idx = lines.findIndex((l) => /^\s*BODY:/.test(l));
1298
+ if (idx !== -1) {
1299
+ const first = lines[idx].replace(/^\s*BODY:\s*/, "");
1300
+ return [first, ...lines.slice(idx + 1)].join("\n").trim();
1301
+ }
1302
+ return lines.filter((l) => !/^\s*(MEDIA|STATUS|INFO):/.test(l)).join("\n").trim();
1303
+ }
1304
+ var DEFAULT_WALLET_TTL_MS = 10 * 60 * 1e3;
1305
+ function resolveWalletTtlMs() {
1306
+ const raw = process.env.MOLTSPAY_ALIPAY_WALLET_TTL_MS;
1307
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_WALLET_TTL_MS;
1308
+ const n = Number(raw);
1309
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_WALLET_TTL_MS;
1310
+ }
1311
+ var walletReadyUntil = /* @__PURE__ */ new Map();
1312
+ var DEFAULT_INTENT_TTL_MS = 10 * 60 * 1e3;
1313
+ function resolveIntentTtlMs() {
1314
+ const raw = process.env.MOLTSPAY_ALIPAY_INTENT_TTL_MS;
1315
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_INTENT_TTL_MS;
1316
+ const n = Number(raw);
1317
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_INTENT_TTL_MS;
1318
+ }
1319
+ var intentDoneUntil = /* @__PURE__ */ new Map();
1320
+ var AlipayClient = class {
1321
+ sessionId;
1322
+ configDir;
1323
+ framework;
1324
+ runner;
1325
+ getVersion;
1326
+ now;
1327
+ /** Only the default runner may use the process-level wallet cache. */
1328
+ walletCacheable;
1329
+ /** Only the default runner may use the process-level payment-intent cache. */
1330
+ intentCacheable;
1331
+ constructor(opts = {}) {
1332
+ this.sessionId = resolveSessionId(opts.sessionId, process.env.AIPAY_SESSION_ID);
1333
+ this.configDir = opts.configDir ?? (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
1334
+ this.framework = opts.framework ?? process.env.AIPAY_FRAMEWORK ?? "openclaw";
1335
+ this.runner = opts.runner ?? runCli;
1336
+ this.getVersion = opts.getVersion;
1337
+ this.now = opts.now ?? Date.now;
1338
+ this.walletCacheable = opts.cacheWallet ?? !opts.runner;
1339
+ this.intentCacheable = opts.cacheIntent ?? !opts.runner;
1340
+ }
1341
+ /**
1342
+ * Throws NeedsWalletSetupError unless alipay-bot reports an opened wallet.
1343
+ *
1344
+ * Skips the ~22s `check-wallet` spawn entirely when a prior call cached a
1345
+ * "ready" verdict within the TTL (see {@link DEFAULT_WALLET_TTL_MS}).
1346
+ */
1347
+ async checkWallet(signal, flow) {
1348
+ const ttlMs = resolveWalletTtlMs();
1349
+ const cacheKey = `${this.configDir}::${this.framework}`;
1350
+ const useCache = this.walletCacheable && ttlMs > 0;
1351
+ if (useCache) {
1352
+ const until = walletReadyUntil.get(cacheKey);
1353
+ if (until !== void 0 && this.now() < until) {
1354
+ alipayLog.info("wallet.cache", { flow, step: "check-wallet", hit: true, ttlMsLeft: until - this.now() });
1355
+ return;
1356
+ }
1357
+ }
1358
+ const { lines } = await this.runner(["check-wallet"], { signal, step: "check-wallet", flow });
1359
+ if (!isWalletReady(lines)) {
1360
+ if (this.walletCacheable) walletReadyUntil.delete(cacheKey);
1361
+ throw new NeedsWalletSetupError(
1362
+ "Alipay wallet not opened. Run: moltspay alipay apply (then: moltspay alipay bind)"
1363
+ );
1364
+ }
1365
+ if (useCache) {
1366
+ walletReadyUntil.set(cacheKey, this.now() + ttlMs);
1367
+ alipayLog.info("wallet.cache", { flow, step: "check-wallet", hit: false, cachedForMs: ttlMs });
1368
+ }
1369
+ }
1370
+ /** Run the full 8-step flow and resolve with the resource body. */
1371
+ async pay402(opts) {
1372
+ const { resourceUrl, requirement, signal } = opts;
1373
+ const extra = requirement.extra ?? {};
1374
+ const paymentNeededHeader = String(extra.payment_needed_header ?? "");
1375
+ if (!paymentNeededHeader) {
1376
+ throw new AlipayProtocolError("alipay requirement missing extra.payment_needed_header");
1377
+ }
1378
+ const flow = this.sessionId;
1379
+ const flowStart = this.now();
1380
+ alipayLog.info("flow.start", { flow, resource: resourceUrl });
1381
+ const media = [];
1382
+ const onLine = (line) => {
1383
+ const m = extractMedia(line);
1384
+ if (m) media.push(m);
1385
+ else opts.onLine?.(line);
1386
+ };
1387
+ const intentSummary = opts.intentSummary?.trim() || `\u652F\u4ED8 ${requirement.amount ?? ""} ${requirement.asset ?? "CNY"}`.trim();
1388
+ await timeStep("ensure-cli", flow, () => ensureCli(this.getVersion));
1389
+ const intentTtlMs = resolveIntentTtlMs();
1390
+ const intentKey = `${this.configDir}::${this.framework}`;
1391
+ const useIntentCache = this.intentCacheable && intentTtlMs > 0;
1392
+ const intentUntil = useIntentCache ? intentDoneUntil.get(intentKey) : void 0;
1393
+ if (intentUntil !== void 0 && this.now() < intentUntil) {
1394
+ alipayLog.info("intent.cache", { flow, step: "payment-intent", hit: true, ttlMsLeft: intentUntil - this.now() });
1395
+ } else {
1396
+ await this.runner(
1397
+ [
1398
+ "payment-intent",
1399
+ "--session-id",
1400
+ this.sessionId,
1401
+ "--intent-summary",
1402
+ intentSummary,
1403
+ "--framework",
1404
+ this.framework
1405
+ ],
1406
+ { onLine, signal, step: "payment-intent", flow }
1407
+ );
1408
+ if (useIntentCache) {
1409
+ intentDoneUntil.set(intentKey, this.now() + intentTtlMs);
1410
+ alipayLog.info("intent.cache", { flow, step: "payment-intent", hit: false, cachedForMs: intentTtlMs });
1411
+ }
1412
+ }
1413
+ await this.checkWallet(signal, flow);
1414
+ const reqId = String(extra.out_trade_no ?? (0, import_node_crypto.randomUUID)());
1415
+ const dir = (0, import_path2.join)(this.configDir, "alipay");
1416
+ await (0, import_promises.mkdir)(dir, { recursive: true });
1417
+ const challengeFile = (0, import_path2.join)(dir, `402_${reqId}.txt`);
1418
+ await (0, import_promises.writeFile)(challengeFile, paymentNeededHeader, "utf-8");
1419
+ const payArgs = [
1420
+ "402-buyer-pay",
1421
+ "-f",
1422
+ challengeFile,
1423
+ "-r",
1424
+ resourceUrl,
1425
+ "-s",
1426
+ this.sessionId,
1427
+ "-i",
1428
+ intentSummary,
1429
+ "-w",
1430
+ this.framework
1431
+ ];
1432
+ if (opts.method) payArgs.push("-m", opts.method);
1433
+ if (opts.data) payArgs.push("-d", opts.data);
1434
+ const payRun = await this.runner(payArgs, { onLine, signal, step: "402-buyer-pay", flow });
1435
+ const tradeNo = parseTradeNo(payRun.lines);
1436
+ if (!tradeNo) {
1437
+ throw new AlipayProtocolError("402-buyer-pay did not return a tradeNo");
1438
+ }
1439
+ assertTradeNo(tradeNo);
1440
+ const { paymentUrl, shortenUrl } = parsePaymentUrl(payRun.lines);
1441
+ if (paymentUrl) {
1442
+ alipayLog.info("flow.pending", { flow, tradeNo, ms: this.now() - flowStart });
1443
+ opts.onPaymentPending?.({ paymentUrl, shortenUrl, tradeNo });
1444
+ }
1445
+ const pendingAt = this.now();
1446
+ const windowMs = (requirement.maxTimeoutSeconds ?? 30 * 60) * 1e3;
1447
+ const deadline = this.now() + (opts.timeoutMs ?? windowMs);
1448
+ const poll = await pollUntil(tradeNo, resourceUrl, {
1449
+ // No onLine: the status-poll output embeds the resource and must not reach
1450
+ // the log stream — the body is surfaced via the return value below (§9.3).
1451
+ deadline,
1452
+ signal,
1453
+ runner: this.runner,
1454
+ now: this.now,
1455
+ // Re-fetch the resource the same way it was paid (POST + body), else the
1456
+ // status poll defaults to GET and 404s on a POST-only `/execute`.
1457
+ method: opts.method,
1458
+ data: opts.data
1459
+ });
1460
+ alipayLog.info("flow.settled", { flow, tradeNo, ms: this.now() - pendingAt });
1461
+ const body = extractBody(poll.lines);
1462
+ void this.runner(["402-buyer-fulfillment-ack", "-t", tradeNo], {
1463
+ onLine,
1464
+ signal,
1465
+ step: "402-buyer-fulfillment-ack",
1466
+ flow
1467
+ }).catch(() => void 0);
1468
+ return { body, payment: { tradeNo, outTradeNo: reqId }, media };
1469
+ }
1470
+ };
1471
+
1472
+ // src/client/alipay/router.ts
1473
+ init_cjs_shims();
1474
+ var ALIPAY_RAIL = "alipay";
1475
+ function railOf(req) {
1476
+ if (req.scheme === "alipay-aipay" || req.network === ALIPAY_RAIL) return ALIPAY_RAIL;
1477
+ return networkToChainName(req.network) ?? req.network;
1478
+ }
1479
+ function findRail(accepts, rail) {
1480
+ const requirement = accepts.find((r) => railOf(r) === rail);
1481
+ return requirement ? { rail, requirement } : null;
1482
+ }
1483
+ function selectRail(input) {
1484
+ const { serverAccepts, explicitRail, preference, availability } = input;
1485
+ if (!serverAccepts || serverAccepts.length === 0) {
1486
+ throw new UnsupportedRailError(explicitRail ?? "unknown", "Server offered no payment options");
1487
+ }
1488
+ if (explicitRail) {
1489
+ const hit = findRail(serverAccepts, explicitRail);
1490
+ if (!hit) {
1491
+ const offered = serverAccepts.map(railOf);
1492
+ throw new UnsupportedRailError(
1493
+ explicitRail,
1494
+ `Server doesn't accept rail '${explicitRail}'. Offered: ${offered.join(", ")}`
1495
+ );
1496
+ }
1497
+ return hit;
1498
+ }
1499
+ if (preference) {
1500
+ for (const pref of preference) {
1501
+ const hit = findRail(serverAccepts, pref);
1502
+ if (hit) return hit;
1503
+ }
1504
+ }
1505
+ if (availability?.evmReady) {
1506
+ const evm = serverAccepts.find((r) => railOf(r) !== ALIPAY_RAIL);
1507
+ if (evm) return { rail: railOf(evm), requirement: evm };
1508
+ }
1509
+ if (availability?.alipayReady) {
1510
+ const hit = findRail(serverAccepts, ALIPAY_RAIL);
1511
+ if (hit) return hit;
1512
+ }
1513
+ return { rail: railOf(serverAccepts[0]), requirement: serverAccepts[0] };
1514
+ }
1515
+
770
1516
  // src/client/types.ts
771
1517
  init_cjs_shims();
772
1518
 
@@ -786,9 +1532,13 @@ var MoltsPayClient = class {
786
1532
  signer = null;
787
1533
  todaySpending = 0;
788
1534
  lastSpendingReset = 0;
1535
+ railPreference;
1536
+ alipaySessionId;
789
1537
  constructor(options = {}) {
790
- this.configDir = options.configDir || (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
1538
+ this.configDir = options.configDir || (0, import_path3.join)((0, import_os3.homedir)(), ".moltspay");
791
1539
  this.config = this.loadConfig();
1540
+ this.railPreference = options.railPreference ?? this.config.railPreference;
1541
+ this.alipaySessionId = options.alipaySessionId;
792
1542
  this.walletData = this.loadWallet();
793
1543
  this.loadSpending();
794
1544
  if (this.walletData) {
@@ -866,6 +1616,9 @@ var MoltsPayClient = class {
866
1616
  * @param options - Payment options (token selection)
867
1617
  */
868
1618
  async pay(serverUrl, service, params, options = {}) {
1619
+ if (options.rail === ALIPAY_RAIL) {
1620
+ return this.payViaAlipay(serverUrl, service, params, options);
1621
+ }
869
1622
  if (!this.wallet || !this.walletData) {
870
1623
  throw new Error("Client not initialized. Run: npx moltspay init");
871
1624
  }
@@ -1057,6 +1810,76 @@ Please specify: --chain <chain_name>`
1057
1810
  console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
1058
1811
  return result.result || result;
1059
1812
  }
1813
+ /**
1814
+ * Pay for a service over the Alipay fiat rail (2.0.0).
1815
+ *
1816
+ * Unlike the crypto path this needs no EVM wallet — it shells out to
1817
+ * alipay-bot via {@link AlipayClient}. Flow: hit the resource with no
1818
+ * payment to get the 402 challenge, confirm the server actually offers the
1819
+ * alipay rail (selectRail), then run the 8-step state machine and return the
1820
+ * resource body.
1821
+ */
1822
+ async payViaAlipay(serverUrl, service, params, options) {
1823
+ const flow = this.alipaySessionId;
1824
+ let executeUrl = `${serverUrl}/execute`;
1825
+ try {
1826
+ const services = await timeStep(
1827
+ "discover-services",
1828
+ flow,
1829
+ () => this.getServices(serverUrl)
1830
+ );
1831
+ const svc = services.services?.find((s) => s.id === service);
1832
+ if (svc?.endpoint) executeUrl = `${serverUrl}${svc.endpoint}`;
1833
+ } catch {
1834
+ }
1835
+ const requestBody = options.rawData ? { service, ...params } : { service, params };
1836
+ const bodyJson = JSON.stringify(requestBody);
1837
+ const res = await timeStep(
1838
+ "challenge-402",
1839
+ flow,
1840
+ () => fetch(executeUrl, {
1841
+ method: "POST",
1842
+ headers: { "Content-Type": "application/json" },
1843
+ body: bodyJson
1844
+ })
1845
+ );
1846
+ if (res.status !== 402) {
1847
+ const data = await res.json().catch(() => ({}));
1848
+ if (res.ok && data.result) return data.result;
1849
+ throw new Error(data.error || `Expected 402, got ${res.status}`);
1850
+ }
1851
+ const header = res.headers.get(PAYMENT_REQUIRED_HEADER);
1852
+ if (!header) throw new Error("Missing x-payment-required header on 402");
1853
+ const parsed = JSON.parse(Buffer.from(header, "base64").toString("utf-8"));
1854
+ const accepts = Array.isArray(parsed) ? parsed : parsed.accepts ?? [parsed];
1855
+ const { requirement } = selectRail({
1856
+ serverAccepts: accepts,
1857
+ explicitRail: ALIPAY_RAIL,
1858
+ preference: this.railPreference,
1859
+ availability: { evmReady: this.isInitialized }
1860
+ });
1861
+ const onLine = options.onLine ?? ((line) => process.stdout.write(line + "\n"));
1862
+ const alipay = new AlipayClient({
1863
+ sessionId: this.alipaySessionId,
1864
+ configDir: this.configDir
1865
+ });
1866
+ const result = await alipay.pay402({
1867
+ resourceUrl: executeUrl,
1868
+ requirement,
1869
+ method: "POST",
1870
+ data: bodyJson,
1871
+ onLine,
1872
+ onPaymentPending: options.onPaymentPending,
1873
+ timeoutMs: options.timeoutMs,
1874
+ signal: options.signal
1875
+ });
1876
+ try {
1877
+ const json = JSON.parse(result.body);
1878
+ return json.result ?? json;
1879
+ } catch {
1880
+ return { body: result.body, payment: result.payment, media: result.media };
1881
+ }
1882
+ }
1060
1883
  /**
1061
1884
  * Handle MPP (Machine Payments Protocol) payment flow
1062
1885
  * Called when pay() detects WWW-Authenticate header in 402 response
@@ -1397,7 +2220,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1397
2220
  }
1398
2221
  // --- Config & Wallet Management ---
1399
2222
  loadConfig() {
1400
- const configPath = (0, import_path2.join)(this.configDir, "config.json");
2223
+ const configPath = (0, import_path3.join)(this.configDir, "config.json");
1401
2224
  if ((0, import_fs2.existsSync)(configPath)) {
1402
2225
  const content = (0, import_fs2.readFileSync)(configPath, "utf-8");
1403
2226
  return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
@@ -1406,14 +2229,14 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1406
2229
  }
1407
2230
  saveConfig() {
1408
2231
  (0, import_fs2.mkdirSync)(this.configDir, { recursive: true });
1409
- const configPath = (0, import_path2.join)(this.configDir, "config.json");
2232
+ const configPath = (0, import_path3.join)(this.configDir, "config.json");
1410
2233
  (0, import_fs2.writeFileSync)(configPath, JSON.stringify(this.config, null, 2));
1411
2234
  }
1412
2235
  /**
1413
2236
  * Load spending data from disk
1414
2237
  */
1415
2238
  loadSpending() {
1416
- const spendingPath = (0, import_path2.join)(this.configDir, "spending.json");
2239
+ const spendingPath = (0, import_path3.join)(this.configDir, "spending.json");
1417
2240
  if ((0, import_fs2.existsSync)(spendingPath)) {
1418
2241
  try {
1419
2242
  const data = JSON.parse((0, import_fs2.readFileSync)(spendingPath, "utf-8"));
@@ -1436,7 +2259,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1436
2259
  */
1437
2260
  saveSpending() {
1438
2261
  (0, import_fs2.mkdirSync)(this.configDir, { recursive: true });
1439
- const spendingPath = (0, import_path2.join)(this.configDir, "spending.json");
2262
+ const spendingPath = (0, import_path3.join)(this.configDir, "spending.json");
1440
2263
  const data = {
1441
2264
  date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
1442
2265
  amount: this.todaySpending,
@@ -1445,7 +2268,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1445
2268
  (0, import_fs2.writeFileSync)(spendingPath, JSON.stringify(data, null, 2));
1446
2269
  }
1447
2270
  loadWallet() {
1448
- const walletPath = (0, import_path2.join)(this.configDir, "wallet.json");
2271
+ const walletPath = (0, import_path3.join)(this.configDir, "wallet.json");
1449
2272
  if ((0, import_fs2.existsSync)(walletPath)) {
1450
2273
  if (process.platform !== "win32") {
1451
2274
  try {
@@ -1475,7 +2298,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1475
2298
  privateKey: wallet.privateKey,
1476
2299
  createdAt: Date.now()
1477
2300
  };
1478
- const walletPath = (0, import_path2.join)(configDir, "wallet.json");
2301
+ const walletPath = (0, import_path3.join)(configDir, "wallet.json");
1479
2302
  (0, import_fs2.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
1480
2303
  const config = {
1481
2304
  chain: options.chain,
@@ -1484,7 +2307,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1484
2307
  maxPerDay: options.maxPerDay
1485
2308
  }
1486
2309
  };
1487
- const configPath = (0, import_path2.join)(configDir, "config.json");
2310
+ const configPath = (0, import_path3.join)(configDir, "config.json");
1488
2311
  (0, import_fs2.writeFileSync)(configPath, JSON.stringify(config, null, 2));
1489
2312
  return { address: wallet.address, configDir };
1490
2313
  }
@@ -2470,6 +3293,378 @@ var BNBFacilitator = class extends BaseFacilitator {
2470
3293
  }
2471
3294
  };
2472
3295
 
3296
+ // src/facilitators/alipay.ts
3297
+ init_cjs_shims();
3298
+ var import_node_crypto3 = __toESM(require("crypto"));
3299
+
3300
+ // src/facilitators/alipay/rsa2.ts
3301
+ init_cjs_shims();
3302
+ var import_node_crypto2 = __toESM(require("crypto"));
3303
+ function rsa2Sign(data, privateKeyPem) {
3304
+ const signer = import_node_crypto2.default.createSign("RSA-SHA256");
3305
+ signer.update(data, "utf-8");
3306
+ signer.end();
3307
+ return signer.sign(privateKeyPem, "base64");
3308
+ }
3309
+
3310
+ // src/facilitators/alipay/encoding.ts
3311
+ init_cjs_shims();
3312
+ function base64url(input) {
3313
+ return Buffer.from(input, "utf-8").toString("base64url");
3314
+ }
3315
+ function decodeBase64UrlWithPadFix(input) {
3316
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
3317
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
3318
+ return Buffer.from(padded, "base64").toString("utf-8");
3319
+ }
3320
+ function toPem(key, kind) {
3321
+ const trimmed = key.trim();
3322
+ if (trimmed.includes("-----BEGIN")) return trimmed;
3323
+ const label = kind === "PRIVATE" ? "PRIVATE KEY" : "PUBLIC KEY";
3324
+ const body = trimmed.replace(/\s+/g, "").match(/.{1,64}/g)?.join("\n") ?? "";
3325
+ return `-----BEGIN ${label}-----
3326
+ ${body}
3327
+ -----END ${label}-----
3328
+ `;
3329
+ }
3330
+
3331
+ // src/facilitators/alipay/openapi.ts
3332
+ init_cjs_shims();
3333
+ function formatAlipayTimestamp(d = /* @__PURE__ */ new Date()) {
3334
+ const pad = (n) => String(n).padStart(2, "0");
3335
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
3336
+ }
3337
+ function buildSigningString(params) {
3338
+ return Object.keys(params).sort().map((k) => `${k}=${params[k]}`).join("&");
3339
+ }
3340
+ function responseWrapperKey(method) {
3341
+ return `${method.replace(/\./g, "_")}_response`;
3342
+ }
3343
+ async function alipayOpenApiCall(method, bizContent, config) {
3344
+ const publicParams = {
3345
+ app_id: config.app_id,
3346
+ method,
3347
+ format: "JSON",
3348
+ charset: "utf-8",
3349
+ sign_type: config.sign_type ?? "RSA2",
3350
+ timestamp: formatAlipayTimestamp(),
3351
+ version: "1.0",
3352
+ biz_content: JSON.stringify(bizContent)
3353
+ };
3354
+ const signingString = buildSigningString(publicParams);
3355
+ const sign = rsa2Sign(signingString, config.private_key_pem);
3356
+ const body = new URLSearchParams({ ...publicParams, sign }).toString();
3357
+ const response = await fetch(config.gateway_url, {
3358
+ method: "POST",
3359
+ headers: {
3360
+ "Content-Type": "application/x-www-form-urlencoded;charset=utf-8"
3361
+ },
3362
+ body
3363
+ });
3364
+ if (!response.ok) {
3365
+ const text = await response.text().catch(() => "<unreadable>");
3366
+ throw new Error(
3367
+ `Alipay Open API HTTP ${response.status} for ${method}: ${text.slice(0, 500)}`
3368
+ );
3369
+ }
3370
+ const json = await response.json();
3371
+ const wrapperKey = responseWrapperKey(method);
3372
+ const business = json[wrapperKey];
3373
+ if (business === void 0 || business === null || typeof business !== "object") {
3374
+ throw new Error(
3375
+ `Alipay Open API response missing "${wrapperKey}" wrapper for ${method}: ${JSON.stringify(json).slice(0, 500)}`
3376
+ );
3377
+ }
3378
+ return business;
3379
+ }
3380
+
3381
+ // src/facilitators/alipay.ts
3382
+ var ALIPAY_NETWORK = "alipay";
3383
+ var ALIPAY_SCHEME = "alipay-aipay";
3384
+ var ALIPAY_GATEWAY_PROD = "https://openapi.alipay.com/gateway.do";
3385
+ var ALIPAY_AMOUNT_REGEX = /^\d+(\.\d{1,2})?$/;
3386
+ var ALIPAY_PAY_BEFORE_MS = 30 * 60 * 1e3;
3387
+ var ALIPAY_SIGNING_FIELDS = [
3388
+ "amount",
3389
+ "currency",
3390
+ "goods_name",
3391
+ "out_trade_no",
3392
+ "pay_before",
3393
+ "resource_id",
3394
+ "seller_id",
3395
+ "service_id"
3396
+ ];
3397
+ var AlipayFacilitator = class extends BaseFacilitator {
3398
+ name = "alipay";
3399
+ displayName = "Alipay AI \u6536";
3400
+ supportedNetworks = [ALIPAY_NETWORK];
3401
+ config;
3402
+ constructor(config) {
3403
+ super();
3404
+ this.config = {
3405
+ gateway_url: ALIPAY_GATEWAY_PROD,
3406
+ sign_type: "RSA2",
3407
+ ...config
3408
+ };
3409
+ }
3410
+ /**
3411
+ * Build the 402 challenge for a service: signs the 8-field payload with
3412
+ * RSA2, packages the nested `{protocol, method}` JSON as Base64URL for
3413
+ * `Payment-Needed`, and emits the parallel x402 `accepts[]` entry.
3414
+ */
3415
+ async createPaymentRequirements(opts) {
3416
+ if (!ALIPAY_AMOUNT_REGEX.test(opts.priceCny)) {
3417
+ throw new Error(
3418
+ `AlipayFacilitator.createPaymentRequirements: priceCny "${opts.priceCny}" does not match /^\\d+(\\.\\d{1,2})?$/ (unit is \u5143, not \u5206; e.g. "1.00" not "100")`
3419
+ );
3420
+ }
3421
+ const now = /* @__PURE__ */ new Date();
3422
+ const outTradeNo = opts.outTradeNo ?? generateOutTradeNo();
3423
+ const payBefore = formatPayBefore(now);
3424
+ const signedFields = {
3425
+ amount: opts.priceCny,
3426
+ currency: "CNY",
3427
+ goods_name: opts.goodsName,
3428
+ out_trade_no: outTradeNo,
3429
+ pay_before: payBefore,
3430
+ resource_id: opts.resourceId,
3431
+ seller_id: this.config.seller_id,
3432
+ service_id: opts.serviceId
3433
+ };
3434
+ const signingString = ALIPAY_SIGNING_FIELDS.map((k) => `${k}=${signedFields[k]}`).join("&");
3435
+ const seller_signature = rsa2Sign(signingString, this.config.private_key_pem);
3436
+ const challenge = {
3437
+ protocol: {
3438
+ out_trade_no: outTradeNo,
3439
+ amount: signedFields.amount,
3440
+ currency: signedFields.currency,
3441
+ resource_id: signedFields.resource_id,
3442
+ pay_before: payBefore,
3443
+ seller_signature,
3444
+ seller_sign_type: this.config.sign_type ?? "RSA2",
3445
+ seller_unique_id: this.config.seller_id
3446
+ },
3447
+ method: {
3448
+ seller_name: this.config.seller_name,
3449
+ seller_id: this.config.seller_id,
3450
+ seller_app_id: this.config.app_id,
3451
+ goods_name: signedFields.goods_name,
3452
+ seller_unique_id_key: "seller_id",
3453
+ service_id: signedFields.service_id
3454
+ }
3455
+ };
3456
+ const paymentNeededHeader = base64url(JSON.stringify(challenge));
3457
+ const x402Accepts = {
3458
+ scheme: ALIPAY_SCHEME,
3459
+ network: ALIPAY_NETWORK,
3460
+ asset: "CNY",
3461
+ amount: opts.priceCny,
3462
+ payTo: this.config.seller_id,
3463
+ maxTimeoutSeconds: ALIPAY_PAY_BEFORE_MS / 1e3,
3464
+ extra: {
3465
+ payment_needed_header: paymentNeededHeader,
3466
+ out_trade_no: outTradeNo,
3467
+ pay_before: payBefore,
3468
+ service_id: signedFields.service_id
3469
+ }
3470
+ };
3471
+ return { x402Accepts, paymentNeededHeader };
3472
+ }
3473
+ /**
3474
+ * Verify a `Payment-Proof` by calling
3475
+ * `alipay.aipay.agent.payment.verify` against the Alipay Open API.
3476
+ *
3477
+ * The proof is conveyed via `paymentPayload.payload`, which may be:
3478
+ * - a raw Base64URL string (the `Payment-Proof` header value), or
3479
+ * - an object `{ paymentProof: string }` / `{ proofHeader: string }`.
3480
+ *
3481
+ * All failure modes (malformed payload, network errors, Alipay
3482
+ * `code != 10000`) return `{ valid: false, error }`. No exception
3483
+ * escapes, regardless of client-supplied input.
3484
+ */
3485
+ async verify(paymentPayload, _requirements) {
3486
+ try {
3487
+ const proofHeader = extractProofHeader(paymentPayload.payload);
3488
+ const decoded = decodeProof(proofHeader);
3489
+ const response = await alipayOpenApiCall(
3490
+ "alipay.aipay.agent.payment.verify",
3491
+ {
3492
+ payment_proof: decoded.protocol.payment_proof,
3493
+ trade_no: decoded.protocol.trade_no,
3494
+ client_session: decoded.method.client_session
3495
+ },
3496
+ this.getOpenApiConfig()
3497
+ );
3498
+ if (response.code !== "10000") {
3499
+ return {
3500
+ valid: false,
3501
+ error: `alipay verify ${response.code}: ${response.sub_msg ?? response.msg ?? "unknown"}`,
3502
+ details: {
3503
+ code: response.code,
3504
+ sub_code: response.sub_code,
3505
+ sub_msg: response.sub_msg
3506
+ }
3507
+ };
3508
+ }
3509
+ return {
3510
+ valid: true,
3511
+ details: {
3512
+ trade_no: response.trade_no ?? decoded.protocol.trade_no,
3513
+ amount: response.amount,
3514
+ out_trade_no: response.out_trade_no,
3515
+ resource_id: response.resource_id,
3516
+ active: response.active
3517
+ }
3518
+ };
3519
+ } catch (e) {
3520
+ return {
3521
+ valid: false,
3522
+ error: e instanceof Error ? e.message : String(e)
3523
+ };
3524
+ }
3525
+ }
3526
+ /**
3527
+ * Settle by calling `alipay.aipay.agent.fulfillment.confirm` after the
3528
+ * service resource has been returned to the buyer.
3529
+ *
3530
+ * Per the design (see ALIPAY-INTEGRATION-DESIGN.md §5.1, risk row
3531
+ * "履约确认失败"), this is **fire-and-forget**: the caller (registry /
3532
+ * server) is expected to log fulfillment failures but NOT roll back
3533
+ * the already-delivered resource.
3534
+ *
3535
+ * Re-decodes `paymentPayload.payload` to extract `trade_no` (verify
3536
+ * does the same; the redundant Base64URL decode is negligible).
3537
+ */
3538
+ async settle(paymentPayload, _requirements) {
3539
+ try {
3540
+ const proofHeader = extractProofHeader(paymentPayload.payload);
3541
+ const decoded = decodeProof(proofHeader);
3542
+ const tradeNo = decoded.protocol.trade_no;
3543
+ const response = await alipayOpenApiCall(
3544
+ "alipay.aipay.agent.fulfillment.confirm",
3545
+ { trade_no: tradeNo },
3546
+ this.getOpenApiConfig()
3547
+ );
3548
+ if (response.code !== "10000") {
3549
+ return {
3550
+ success: false,
3551
+ transaction: tradeNo,
3552
+ error: `alipay fulfillment ${response.code}: ${response.sub_msg ?? response.msg ?? "unknown"}`,
3553
+ status: "fulfillment_failed"
3554
+ };
3555
+ }
3556
+ return {
3557
+ success: true,
3558
+ transaction: tradeNo,
3559
+ status: "fulfilled"
3560
+ };
3561
+ } catch (e) {
3562
+ return {
3563
+ success: false,
3564
+ error: e instanceof Error ? e.message : String(e)
3565
+ };
3566
+ }
3567
+ }
3568
+ /**
3569
+ * Validate that the configured RSA keys parse and that the Open API
3570
+ * gateway is reachable. Does NOT make a real business API call (would
3571
+ * burn quota and could appear as a legitimate verify attempt in logs).
3572
+ */
3573
+ async healthCheck() {
3574
+ const start = Date.now();
3575
+ try {
3576
+ import_node_crypto3.default.createPrivateKey(this.config.private_key_pem);
3577
+ } catch (e) {
3578
+ return {
3579
+ healthy: false,
3580
+ error: `merchant private_key_pem parse failed: ${e instanceof Error ? e.message : String(e)}`
3581
+ };
3582
+ }
3583
+ try {
3584
+ import_node_crypto3.default.createPublicKey(this.config.alipay_public_key_pem);
3585
+ } catch (e) {
3586
+ return {
3587
+ healthy: false,
3588
+ error: `alipay_public_key_pem parse failed: ${e instanceof Error ? e.message : String(e)}`
3589
+ };
3590
+ }
3591
+ const gatewayUrl = this.config.gateway_url ?? ALIPAY_GATEWAY_PROD;
3592
+ const controller = new AbortController();
3593
+ const timeout = setTimeout(() => controller.abort(), 5e3);
3594
+ const response = await fetch(gatewayUrl, {
3595
+ method: "HEAD",
3596
+ signal: controller.signal
3597
+ }).catch(() => null);
3598
+ clearTimeout(timeout);
3599
+ const latencyMs = Date.now() - start;
3600
+ if (!response) {
3601
+ return { healthy: false, error: `gateway unreachable: ${gatewayUrl}`, latencyMs };
3602
+ }
3603
+ return { healthy: true, latencyMs };
3604
+ }
3605
+ /** Bundle the facilitator config into the shape openapi.ts wants. */
3606
+ getOpenApiConfig() {
3607
+ return {
3608
+ gateway_url: this.config.gateway_url ?? ALIPAY_GATEWAY_PROD,
3609
+ app_id: this.config.app_id,
3610
+ private_key_pem: this.config.private_key_pem,
3611
+ alipay_public_key_pem: this.config.alipay_public_key_pem,
3612
+ sign_type: this.config.sign_type
3613
+ };
3614
+ }
3615
+ };
3616
+ function generateOutTradeNo() {
3617
+ return "VID" + import_node_crypto3.default.randomBytes(22).toString("base64url").slice(0, 29);
3618
+ }
3619
+ function formatPayBefore(now) {
3620
+ const expiry = new Date(now.getTime() + ALIPAY_PAY_BEFORE_MS);
3621
+ return expiry.toISOString().replace(/\.\d{3}Z$/, "Z");
3622
+ }
3623
+ function extractProofHeader(payload) {
3624
+ if (typeof payload === "string") {
3625
+ if (payload.length === 0) {
3626
+ throw new Error("alipay Payment-Proof is empty");
3627
+ }
3628
+ return payload;
3629
+ }
3630
+ if (payload !== null && typeof payload === "object") {
3631
+ const obj = payload;
3632
+ const candidate = obj.paymentProof ?? obj.proofHeader;
3633
+ if (typeof candidate === "string" && candidate.length > 0) {
3634
+ return candidate;
3635
+ }
3636
+ }
3637
+ throw new Error(
3638
+ "alipay payment payload must be a Base64URL string or {paymentProof: string} / {proofHeader: string}"
3639
+ );
3640
+ }
3641
+ function decodeProof(proofHeader) {
3642
+ let parsed;
3643
+ try {
3644
+ parsed = JSON.parse(decodeBase64UrlWithPadFix(proofHeader));
3645
+ } catch (e) {
3646
+ throw new Error(
3647
+ `failed to decode Payment-Proof: ${e instanceof Error ? e.message : String(e)}`
3648
+ );
3649
+ }
3650
+ if (parsed === null || typeof parsed !== "object") {
3651
+ throw new Error("decoded Payment-Proof is not an object");
3652
+ }
3653
+ const obj = parsed;
3654
+ const protocol = obj.protocol;
3655
+ const method = obj.method;
3656
+ if (!protocol || typeof protocol.payment_proof !== "string") {
3657
+ throw new Error("decoded Payment-Proof missing protocol.payment_proof");
3658
+ }
3659
+ if (typeof protocol.trade_no !== "string") {
3660
+ throw new Error("decoded Payment-Proof missing protocol.trade_no");
3661
+ }
3662
+ if (!method || typeof method.client_session !== "string") {
3663
+ throw new Error("decoded Payment-Proof missing method.client_session");
3664
+ }
3665
+ return parsed;
3666
+ }
3667
+
2473
3668
  // src/facilitators/registry.ts
2474
3669
  init_cjs_shims();
2475
3670
  var import_web36 = require("@solana/web3.js");
@@ -2495,6 +3690,7 @@ var FacilitatorRegistry = class {
2495
3690
  }
2496
3691
  return new SolanaFacilitator({ feePayerKeypair });
2497
3692
  });
3693
+ this.registerFactory("alipay", (config) => new AlipayFacilitator(config));
2498
3694
  this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
2499
3695
  }
2500
3696
  /**
@@ -2722,6 +3918,11 @@ var PAYMENT_RESPONSE_HEADER = "x-payment-response";
2722
3918
  var MPP_AUTH_HEADER = "authorization";
2723
3919
  var MPP_WWW_AUTH_HEADER = "www-authenticate";
2724
3920
  var MPP_RECEIPT_HEADER = "payment-receipt";
3921
+ var ALIPAY_PAYMENT_NEEDED_HEADER = "payment-needed";
3922
+ var ALIPAY_PAYMENT_PROOF_HEADER = "payment-proof";
3923
+ function headerSafe(value) {
3924
+ return String(value ?? "").replace(/[^\x20-\x7E]/g, (ch) => encodeURIComponent(ch)).replace(/"/g, "%22");
3925
+ }
2725
3926
  var TOKEN_ADDRESSES = {
2726
3927
  "eip155:8453": {
2727
3928
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -2857,6 +4058,8 @@ var MoltsPayServer = class {
2857
4058
  registry;
2858
4059
  networkId;
2859
4060
  useMainnet;
4061
+ /** Alipay AI 收 facilitator instance, set when `provider.alipay` is configured (2.0.0). */
4062
+ alipayFacilitator = null;
2860
4063
  constructor(servicesPath, options = {}) {
2861
4064
  loadEnvFile2();
2862
4065
  const content = (0, import_fs4.readFileSync)(servicesPath, "utf-8");
@@ -2878,7 +4081,38 @@ var MoltsPayServer = class {
2878
4081
  cdp: { useMainnet: this.useMainnet }
2879
4082
  }
2880
4083
  };
4084
+ const providerAlipay = this.manifest.provider.alipay;
4085
+ if (providerAlipay) {
4086
+ try {
4087
+ const baseDir = path2.dirname(servicesPath);
4088
+ const resolvePem = (p, kind) => toPem((0, import_fs4.readFileSync)(path2.isAbsolute(p) ? p : path2.resolve(baseDir, p), "utf-8"), kind);
4089
+ const alipayFacilitatorConfig = {
4090
+ seller_id: providerAlipay.seller_id,
4091
+ app_id: providerAlipay.app_id,
4092
+ seller_name: providerAlipay.seller_name,
4093
+ service_id_default: providerAlipay.service_id_default,
4094
+ private_key_pem: resolvePem(providerAlipay.private_key_path, "PRIVATE"),
4095
+ alipay_public_key_pem: resolvePem(providerAlipay.alipay_public_key_path, "PUBLIC"),
4096
+ gateway_url: providerAlipay.gateway_url,
4097
+ sign_type: providerAlipay.sign_type
4098
+ };
4099
+ facilitatorConfig.config = {
4100
+ ...facilitatorConfig.config,
4101
+ alipay: alipayFacilitatorConfig
4102
+ };
4103
+ facilitatorConfig.fallback = facilitatorConfig.fallback || [];
4104
+ if (facilitatorConfig.primary !== "alipay" && !facilitatorConfig.fallback.includes("alipay")) {
4105
+ facilitatorConfig.fallback.push("alipay");
4106
+ }
4107
+ } catch (err) {
4108
+ throw new Error(`[MoltsPay] Alipay rail configured but key load failed: ${err.message}`);
4109
+ }
4110
+ }
2881
4111
  this.registry = new FacilitatorRegistry(facilitatorConfig);
4112
+ if (providerAlipay) {
4113
+ this.alipayFacilitator = this.registry.get("alipay");
4114
+ console.log(`[MoltsPay] Alipay AI \u6536 rail enabled (seller ${providerAlipay.seller_id})`);
4115
+ }
2882
4116
  const primaryFacilitator = this.registry.get(facilitatorConfig.primary);
2883
4117
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
2884
4118
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
@@ -3014,10 +4248,10 @@ var MoltsPayServer = class {
3014
4248
  writeCorsHeaders(res, origin) {
3015
4249
  res.setHeader("Access-Control-Allow-Origin", origin);
3016
4250
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
3017
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
4251
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization, Payment-Proof");
3018
4252
  res.setHeader(
3019
4253
  "Access-Control-Expose-Headers",
3020
- "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt"
4254
+ "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt, Payment-Needed"
3021
4255
  );
3022
4256
  }
3023
4257
  /**
@@ -3044,7 +4278,8 @@ var MoltsPayServer = class {
3044
4278
  if (url.pathname === "/execute" && req.method === "POST") {
3045
4279
  const body = await this.readBody(req);
3046
4280
  const paymentHeader = req.headers[PAYMENT_HEADER2];
3047
- return await this.handleExecute(body, paymentHeader, res);
4281
+ const proofHeader = req.headers[ALIPAY_PAYMENT_PROOF_HEADER];
4282
+ return await this.handleExecute(body, paymentHeader, res, proofHeader);
3048
4283
  }
3049
4284
  if (url.pathname === "/proxy" && req.method === "POST") {
3050
4285
  const clientIP = req.headers["x-real-ip"] || req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket.remoteAddress || "";
@@ -3062,7 +4297,8 @@ var MoltsPayServer = class {
3062
4297
  const body = req.method === "POST" ? await this.readBody(req) : {};
3063
4298
  const authHeader = req.headers[MPP_AUTH_HEADER];
3064
4299
  const x402Header = req.headers[PAYMENT_HEADER2];
3065
- return await this.handleMPPRequest(skill, body, authHeader, x402Header, res);
4300
+ const proofHeader = req.headers[ALIPAY_PAYMENT_PROOF_HEADER];
4301
+ return await this.handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader);
3066
4302
  }
3067
4303
  this.sendJson(res, 404, { error: "Not found" });
3068
4304
  } catch (err) {
@@ -3159,7 +4395,7 @@ var MoltsPayServer = class {
3159
4395
  /**
3160
4396
  * POST /execute - Execute service with x402 payment
3161
4397
  */
3162
- async handleExecute(body, paymentHeader, res) {
4398
+ async handleExecute(body, paymentHeader, res, proofHeader) {
3163
4399
  const { service, params } = body;
3164
4400
  if (!service) {
3165
4401
  return this.sendJson(res, 400, { error: "Missing service" });
@@ -3173,6 +4409,15 @@ var MoltsPayServer = class {
3173
4409
  return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
3174
4410
  }
3175
4411
  }
4412
+ if (proofHeader) {
4413
+ const alipayPayment = {
4414
+ x402Version: X402_VERSION3,
4415
+ scheme: ALIPAY_SCHEME,
4416
+ network: ALIPAY_NETWORK,
4417
+ payload: proofHeader
4418
+ };
4419
+ return this.handleAlipayExecute(skill, params || {}, alipayPayment, res);
4420
+ }
3176
4421
  if (!paymentHeader) {
3177
4422
  return this.sendPaymentRequired(skill.config, res);
3178
4423
  }
@@ -3183,6 +4428,11 @@ var MoltsPayServer = class {
3183
4428
  } catch {
3184
4429
  return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
3185
4430
  }
4431
+ const payScheme = payment.accepted?.scheme || payment.scheme;
4432
+ const payNetwork = payment.accepted?.network || payment.network;
4433
+ if (payScheme === ALIPAY_SCHEME || (payNetwork ? isAlipayChainId(payNetwork) : false)) {
4434
+ return this.handleAlipayExecute(skill, params || {}, payment, res);
4435
+ }
3186
4436
  const validation = this.validatePayment(payment, skill.config);
3187
4437
  if (!validation.valid) {
3188
4438
  return this.sendJson(res, 402, { error: validation.error });
@@ -3275,13 +4525,111 @@ var MoltsPayServer = class {
3275
4525
  payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
3276
4526
  }, responseHeaders);
3277
4527
  }
4528
+ /**
4529
+ * Execute a service paid via the Alipay AI 收 fiat rail (2.0.0).
4530
+ *
4531
+ * Differs from the EVM/SVM path: no token detection, no EIP-3009/permit
4532
+ * validation. Verify hits the Alipay Open API (`payment.verify`). Settlement
4533
+ * (`fulfillment.confirm`) is FIRE-AND-FORGET per ALIPAY-INTEGRATION-DESIGN
4534
+ * §5.1: a confirm failure is logged but does NOT fail the already-delivered
4535
+ * response (the buyer's payment proof was already verified).
4536
+ */
4537
+ async handleAlipayExecute(skill, params, payment, res) {
4538
+ if (!this.alipayFacilitator) {
4539
+ return this.sendJson(res, 402, { error: "Alipay rail not configured on this server" });
4540
+ }
4541
+ const requirements = {
4542
+ scheme: ALIPAY_SCHEME,
4543
+ network: ALIPAY_NETWORK,
4544
+ asset: "CNY",
4545
+ amount: skill.config.alipay?.price_cny || "0",
4546
+ payTo: this.manifest.provider.alipay?.seller_id || "",
4547
+ maxTimeoutSeconds: 1800
4548
+ };
4549
+ console.log(`[MoltsPay] Verifying Alipay payment...`);
4550
+ const verifyResult = await this.registry.verify(payment, requirements);
4551
+ if (!verifyResult.valid) {
4552
+ return this.sendJson(res, 402, {
4553
+ error: `Payment verification failed: ${verifyResult.error}`,
4554
+ facilitator: verifyResult.facilitator
4555
+ });
4556
+ }
4557
+ console.log(`[MoltsPay] Alipay payment verified by ${verifyResult.facilitator}`);
4558
+ const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
4559
+ console.log(`[MoltsPay] Executing skill: ${skill.id} (timeout: ${timeoutSeconds}s)`);
4560
+ let result;
4561
+ try {
4562
+ result = await Promise.race([
4563
+ skill.handler(params),
4564
+ new Promise(
4565
+ (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
4566
+ )
4567
+ ]);
4568
+ } catch (err) {
4569
+ console.error("[MoltsPay] Skill execution failed:", err.message);
4570
+ return this.sendJson(res, 500, {
4571
+ error: "Service execution failed",
4572
+ message: err.message
4573
+ });
4574
+ }
4575
+ let settlement;
4576
+ try {
4577
+ settlement = await this.registry.settle(payment, requirements);
4578
+ if (settlement.success) {
4579
+ console.log(`[MoltsPay] Alipay fulfillment confirmed: ${settlement.transaction}`);
4580
+ } else {
4581
+ console.error(`[MoltsPay] Alipay fulfillment confirm failed (non-fatal): ${settlement.error}`);
4582
+ }
4583
+ } catch (err) {
4584
+ console.error(`[MoltsPay] Alipay fulfillment confirm threw (non-fatal): ${err.message}`);
4585
+ settlement = { success: false, error: err.message, facilitator: "alipay" };
4586
+ }
4587
+ const responseHeaders = {};
4588
+ if (settlement.success) {
4589
+ responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(JSON.stringify({
4590
+ success: true,
4591
+ transaction: settlement.transaction,
4592
+ network: ALIPAY_NETWORK,
4593
+ facilitator: settlement.facilitator
4594
+ })).toString("base64");
4595
+ }
4596
+ this.sendJson(res, 200, {
4597
+ success: true,
4598
+ result,
4599
+ payment: settlement.success ? { transaction: settlement.transaction, status: "fulfilled", facilitator: settlement.facilitator } : { status: "delivered_unconfirmed", error: settlement.error }
4600
+ }, responseHeaders);
4601
+ }
4602
+ /**
4603
+ * Build the Alipay 402 challenge for a service, or null when the alipay rail
4604
+ * isn't configured for this server or this service. Returns the x402
4605
+ * `accepts[]` entry plus the Base64URL `Payment-Needed` header value so the
4606
+ * 402 responders can dual-emit both the x402 and legacy alipay-bot formats.
4607
+ */
4608
+ async buildAlipayChallenge(config) {
4609
+ if (!this.alipayFacilitator || !config.alipay) return null;
4610
+ try {
4611
+ const req = await this.alipayFacilitator.createPaymentRequirements({
4612
+ serviceId: config.alipay.service_id || this.manifest.provider.alipay.service_id_default,
4613
+ priceCny: config.alipay.price_cny,
4614
+ goodsName: config.alipay.goods_name,
4615
+ resourceId: `/execute?service=${config.id}`
4616
+ });
4617
+ return { accepts: req.x402Accepts, paymentNeededHeader: req.paymentNeededHeader };
4618
+ } catch (err) {
4619
+ console.error(`[MoltsPay] Alipay challenge build failed for ${config.id}: ${err.message}`);
4620
+ return null;
4621
+ }
4622
+ }
3278
4623
  /**
3279
4624
  * Handle MPP (Machine Payments Protocol) request
3280
4625
  * Supports both x402 and MPP protocols on service endpoints
3281
4626
  */
3282
- async handleMPPRequest(skill, body, authHeader, x402Header, res) {
4627
+ async handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader) {
3283
4628
  const config = skill.config;
3284
4629
  const params = body || {};
4630
+ if (proofHeader) {
4631
+ return await this.handleExecute({ service: config.id, params }, void 0, res, proofHeader);
4632
+ }
3285
4633
  if (x402Header) {
3286
4634
  return await this.handleExecute({ service: config.id, params }, x402Header, res);
3287
4635
  }
@@ -3387,7 +4735,7 @@ var MoltsPayServer = class {
3387
4735
  /**
3388
4736
  * Return 402 with both x402 and MPP payment requirements
3389
4737
  */
3390
- sendMPPPaymentRequired(config, res) {
4738
+ async sendMPPPaymentRequired(config, res) {
3391
4739
  const acceptedTokens = getAcceptedCurrencies(config);
3392
4740
  const providerChains = this.getProviderChains();
3393
4741
  const accepts = [];
@@ -3398,6 +4746,10 @@ var MoltsPayServer = class {
3398
4746
  }
3399
4747
  }
3400
4748
  }
4749
+ const alipayChallenge = await this.buildAlipayChallenge(config);
4750
+ if (alipayChallenge) {
4751
+ accepts.push(alipayChallenge.accepts);
4752
+ }
3401
4753
  const x402PaymentRequired = {
3402
4754
  x402Version: X402_VERSION3,
3403
4755
  accepts,
@@ -3425,7 +4777,7 @@ var MoltsPayServer = class {
3425
4777
  };
3426
4778
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
3427
4779
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
3428
- mppWwwAuth = `Payment id="${challengeId}", realm="${this.manifest.provider.name}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
4780
+ mppWwwAuth = `Payment id="${challengeId}", realm="${headerSafe(this.manifest.provider.name)}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${headerSafe(config.name)}", expires="${expiresAt}"`;
3429
4781
  }
3430
4782
  const headers = {
3431
4783
  "Content-Type": "application/problem+json",
@@ -3434,6 +4786,9 @@ var MoltsPayServer = class {
3434
4786
  if (mppWwwAuth) {
3435
4787
  headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
3436
4788
  }
4789
+ if (alipayChallenge) {
4790
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
4791
+ }
3437
4792
  res.writeHead(402, headers);
3438
4793
  res.end(JSON.stringify({
3439
4794
  type: "https://paymentauth.org/problems/payment-required",
@@ -3460,7 +4815,7 @@ var MoltsPayServer = class {
3460
4815
  * Return 402 with x402 payment requirements (v2 format)
3461
4816
  * Includes requirements for all chains and all accepted currencies
3462
4817
  */
3463
- sendPaymentRequired(config, res) {
4818
+ async sendPaymentRequired(config, res) {
3464
4819
  const acceptedTokens = getAcceptedCurrencies(config);
3465
4820
  const providerChains = this.getProviderChains();
3466
4821
  const accepts = [];
@@ -3471,6 +4826,10 @@ var MoltsPayServer = class {
3471
4826
  }
3472
4827
  }
3473
4828
  }
4829
+ const alipayChallenge = await this.buildAlipayChallenge(config);
4830
+ if (alipayChallenge) {
4831
+ accepts.push(alipayChallenge.accepts);
4832
+ }
3474
4833
  const acceptedChains = providerChains.map((c) => {
3475
4834
  if (c.network === "eip155:8453") return "base";
3476
4835
  if (c.network === "eip155:137") return "polygon";
@@ -3488,10 +4847,14 @@ var MoltsPayServer = class {
3488
4847
  }
3489
4848
  };
3490
4849
  const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
3491
- res.writeHead(402, {
4850
+ const headers = {
3492
4851
  "Content-Type": "application/json",
3493
4852
  [PAYMENT_REQUIRED_HEADER2]: encoded
3494
- });
4853
+ };
4854
+ if (alipayChallenge) {
4855
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
4856
+ }
4857
+ res.writeHead(402, headers);
3495
4858
  res.end(JSON.stringify({
3496
4859
  error: "Payment required",
3497
4860
  message: `Service requires $${config.price} ${config.currency}`,
@@ -3598,12 +4961,12 @@ var MoltsPayServer = class {
3598
4961
  return accepted.includes(token);
3599
4962
  }
3600
4963
  async readBody(req) {
3601
- return new Promise((resolve2, reject) => {
4964
+ return new Promise((resolve3, reject) => {
3602
4965
  let body = "";
3603
4966
  req.on("data", (chunk) => body += chunk);
3604
4967
  req.on("end", () => {
3605
4968
  try {
3606
- resolve2(body ? JSON.parse(body) : {});
4969
+ resolve3(body ? JSON.parse(body) : {});
3607
4970
  } catch {
3608
4971
  reject(new Error("Invalid JSON"));
3609
4972
  }
@@ -3861,7 +5224,7 @@ var MoltsPayServer = class {
3861
5224
  };
3862
5225
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
3863
5226
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
3864
- const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
5227
+ const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${headerSafe(config.name)}", expires="${expiresAt}"`;
3865
5228
  res.writeHead(402, {
3866
5229
  "Content-Type": "application/problem+json",
3867
5230
  [MPP_WWW_AUTH_HEADER]: wwwAuth
@@ -4031,10 +5394,10 @@ init_cjs_shims();
4031
5394
  async function printQRCode(url) {
4032
5395
  const qrcodeModule = await import("qrcode-terminal");
4033
5396
  const qrcode = qrcodeModule.default || qrcodeModule;
4034
- return new Promise((resolve2) => {
5397
+ return new Promise((resolve3) => {
4035
5398
  qrcode.generate(url, { small: true }, (qr) => {
4036
5399
  console.log(qr);
4037
- resolve2();
5400
+ resolve3();
4038
5401
  });
4039
5402
  });
4040
5403
  }
@@ -4046,9 +5409,9 @@ if (!globalThis.crypto) {
4046
5409
  }
4047
5410
  function getVersion() {
4048
5411
  const locations = [
4049
- (0, import_path3.join)(__dirname, "../../package.json"),
4050
- (0, import_path3.join)(__dirname, "../package.json"),
4051
- (0, import_path3.join)(process.cwd(), "node_modules/moltspay/package.json")
5412
+ (0, import_path4.join)(__dirname, "../../package.json"),
5413
+ (0, import_path4.join)(__dirname, "../package.json"),
5414
+ (0, import_path4.join)(process.cwd(), "node_modules/moltspay/package.json")
4052
5415
  ];
4053
5416
  for (const loc of locations) {
4054
5417
  try {
@@ -4127,7 +5490,7 @@ async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2
4127
5490
  const provider = new import_ethers4.ethers.JsonRpcProvider(chainConfig.rpc);
4128
5491
  let spenderAddress = null;
4129
5492
  try {
4130
- const walletPath = (0, import_path3.join)(configDir, "wallet.json");
5493
+ const walletPath = (0, import_path4.join)(configDir, "wallet.json");
4131
5494
  const walletData = JSON.parse((0, import_fs5.readFileSync)(walletPath, "utf-8"));
4132
5495
  spenderAddress = walletData.approvals?.[chain] || null;
4133
5496
  } catch {
@@ -4145,8 +5508,8 @@ async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2
4145
5508
  return result;
4146
5509
  }
4147
5510
  var program = new import_commander.Command();
4148
- var DEFAULT_CONFIG_DIR2 = (0, import_path3.join)((0, import_os3.homedir)(), ".moltspay");
4149
- var PID_FILE = (0, import_path3.join)(DEFAULT_CONFIG_DIR2, "server.pid");
5511
+ var DEFAULT_CONFIG_DIR2 = (0, import_path4.join)((0, import_os4.homedir)(), ".moltspay");
5512
+ var PID_FILE = (0, import_path4.join)(DEFAULT_CONFIG_DIR2, "server.pid");
4150
5513
  if (!(0, import_fs5.existsSync)(DEFAULT_CONFIG_DIR2)) {
4151
5514
  (0, import_fs5.mkdirSync)(DEFAULT_CONFIG_DIR2, { recursive: true });
4152
5515
  }
@@ -4155,10 +5518,10 @@ function prompt(question) {
4155
5518
  input: process.stdin,
4156
5519
  output: process.stdout
4157
5520
  });
4158
- return new Promise((resolve2) => {
5521
+ return new Promise((resolve3) => {
4159
5522
  rl.question(question, (answer) => {
4160
5523
  rl.close();
4161
- resolve2(answer.trim());
5524
+ resolve3(answer.trim());
4162
5525
  });
4163
5526
  });
4164
5527
  }
@@ -4186,7 +5549,7 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
4186
5549
  console.log(`
4187
5550
  \u2705 Solana wallet created: ${address}`);
4188
5551
  console.log(`
4189
- \u{1F4C1} Config saved to: ${(0, import_path3.join)(options.configDir, "wallet-solana.json")}`);
5552
+ \u{1F4C1} Config saved to: ${(0, import_path4.join)(options.configDir, "wallet-solana.json")}`);
4190
5553
  console.log(`
4191
5554
  \u26A0\uFE0F IMPORTANT: Back up your wallet file!`);
4192
5555
  console.log(` This file contains your private key!
@@ -4201,7 +5564,7 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
4201
5564
  return;
4202
5565
  }
4203
5566
  console.log("\n\u{1F510} MoltsPay Client Setup\n");
4204
- if ((0, import_fs5.existsSync)((0, import_path3.join)(options.configDir, "wallet.json"))) {
5567
+ if ((0, import_fs5.existsSync)((0, import_path4.join)(options.configDir, "wallet.json"))) {
4205
5568
  console.log('\u26A0\uFE0F EVM wallet already initialized. Use "moltspay config" to update settings.');
4206
5569
  console.log(` Config dir: ${options.configDir}`);
4207
5570
  return;
@@ -4227,7 +5590,7 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
4227
5590
  console.log(`
4228
5591
  \u{1F4C1} Config saved to: ${result.configDir}`);
4229
5592
  console.log(`
4230
- \u26A0\uFE0F IMPORTANT: Back up ${(0, import_path3.join)(result.configDir, "wallet.json")}`);
5593
+ \u26A0\uFE0F IMPORTANT: Back up ${(0, import_path4.join)(result.configDir, "wallet.json")}`);
4231
5594
  console.log(` This file contains your private key!
4232
5595
  `);
4233
5596
  if (chain === "bnb" || chain === "bnb_testnet") {
@@ -4392,7 +5755,7 @@ program.command("approve").description("Approve a spender address for BNB chain
4392
5755
  \u{1F510} Approving spender for ${chain}...
4393
5756
  `);
4394
5757
  await setupBNBApprovals(client, chain, options.spender, false);
4395
- const walletPath = (0, import_path3.join)(options.configDir || DEFAULT_CONFIG_DIR2, "wallet.json");
5758
+ const walletPath = (0, import_path4.join)(options.configDir || DEFAULT_CONFIG_DIR2, "wallet.json");
4396
5759
  try {
4397
5760
  const walletData = JSON.parse((0, import_fs5.readFileSync)(walletPath, "utf-8"));
4398
5761
  walletData.approvals = walletData.approvals || {};
@@ -5029,17 +6392,17 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
5029
6392
  const handlers = /* @__PURE__ */ new Map();
5030
6393
  let provider = null;
5031
6394
  for (const inputPath of allPaths) {
5032
- const resolvedPath = (0, import_path3.resolve)(inputPath);
6395
+ const resolvedPath = (0, import_path4.resolve)(inputPath);
5033
6396
  let manifestPath;
5034
6397
  let skillDir;
5035
6398
  let isSkillDir = false;
5036
- if ((0, import_fs5.existsSync)((0, import_path3.join)(resolvedPath, "moltspay.services.json"))) {
5037
- manifestPath = (0, import_path3.join)(resolvedPath, "moltspay.services.json");
6399
+ if ((0, import_fs5.existsSync)((0, import_path4.join)(resolvedPath, "moltspay.services.json"))) {
6400
+ manifestPath = (0, import_path4.join)(resolvedPath, "moltspay.services.json");
5038
6401
  skillDir = resolvedPath;
5039
6402
  isSkillDir = true;
5040
6403
  } else if ((0, import_fs5.existsSync)(resolvedPath) && resolvedPath.endsWith(".json")) {
5041
6404
  manifestPath = resolvedPath;
5042
- skillDir = (0, import_path3.dirname)(resolvedPath);
6405
+ skillDir = (0, import_path4.dirname)(resolvedPath);
5043
6406
  } else if ((0, import_fs5.existsSync)(resolvedPath)) {
5044
6407
  console.error(`\u274C No moltspay.services.json found in: ${resolvedPath}`);
5045
6408
  continue;
@@ -5056,7 +6419,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
5056
6419
  let skillModule = null;
5057
6420
  if (isSkillDir) {
5058
6421
  let entryPoint = "index.js";
5059
- const pkgJsonPath = (0, import_path3.join)(skillDir, "package.json");
6422
+ const pkgJsonPath = (0, import_path4.join)(skillDir, "package.json");
5060
6423
  if ((0, import_fs5.existsSync)(pkgJsonPath)) {
5061
6424
  try {
5062
6425
  const pkgJson = JSON.parse((0, import_fs5.readFileSync)(pkgJsonPath, "utf-8"));
@@ -5066,7 +6429,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
5066
6429
  } catch {
5067
6430
  }
5068
6431
  }
5069
- const modulePath = (0, import_path3.join)(skillDir, entryPoint);
6432
+ const modulePath = (0, import_path4.join)(skillDir, entryPoint);
5070
6433
  if ((0, import_fs5.existsSync)(modulePath)) {
5071
6434
  try {
5072
6435
  skillModule = await import(modulePath);
@@ -5092,7 +6455,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
5092
6455
  const workdir = skillDir;
5093
6456
  handlers.set(service.id, async (params) => {
5094
6457
  return new Promise((resolvePromise, reject) => {
5095
- const proc = (0, import_child_process.spawn)("sh", ["-c", service.command], {
6458
+ const proc = (0, import_child_process3.spawn)("sh", ["-c", service.command], {
5096
6459
  cwd: workdir,
5097
6460
  stdio: ["pipe", "pipe", "pipe"]
5098
6461
  });
@@ -5145,7 +6508,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
5145
6508
  provider,
5146
6509
  services: allServices
5147
6510
  };
5148
- const tempManifestPath = (0, import_path3.join)(DEFAULT_CONFIG_DIR2, "combined-manifest.json");
6511
+ const tempManifestPath = (0, import_path4.join)(DEFAULT_CONFIG_DIR2, "combined-manifest.json");
5149
6512
  (0, import_fs5.writeFileSync)(tempManifestPath, JSON.stringify(combinedManifest, null, 2));
5150
6513
  console.log(`
5151
6514
  \u{1F4CB} Combined manifest: ${allServices.length} services`);
@@ -5208,7 +6571,7 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
5208
6571
  }
5209
6572
  process.kill(pid, "SIGTERM");
5210
6573
  console.log("\u2705 Sent SIGTERM to server");
5211
- await new Promise((resolve2) => setTimeout(resolve2, 1e3));
6574
+ await new Promise((resolve3) => setTimeout(resolve3, 1e3));
5212
6575
  try {
5213
6576
  process.kill(pid, 0);
5214
6577
  console.log("\u26A0\uFE0F Server still running, sending SIGKILL...");
@@ -5224,9 +6587,10 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
5224
6587
  process.exit(1);
5225
6588
  }
5226
6589
  });
5227
- 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) => {
6590
+ 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) => {
5228
6591
  const client = new MoltsPayClient({ configDir: options.configDir });
5229
- if (!client.isInitialized) {
6592
+ const useAlipay = options.rail?.toLowerCase() === "alipay";
6593
+ if (!useAlipay && !client.isInitialized) {
5230
6594
  console.error("\u274C Wallet not initialized. Run: npx moltspay init");
5231
6595
  process.exit(1);
5232
6596
  }
@@ -5254,7 +6618,7 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
5254
6618
  if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
5255
6619
  params.image_url = imagePath;
5256
6620
  } else {
5257
- const filePath = (0, import_path3.resolve)(imagePath);
6621
+ const filePath = (0, import_path4.resolve)(imagePath);
5258
6622
  if (!(0, import_fs5.existsSync)(filePath)) {
5259
6623
  console.error(`\u274C Image file not found: ${filePath}`);
5260
6624
  process.exit(1);
@@ -5271,7 +6635,7 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
5271
6635
  }
5272
6636
  const imageDisplay = params.image_url || (params.image_base64 ? `[local file: ${options.image}]` : null);
5273
6637
  const token = (options.token || "USDC").toUpperCase();
5274
- if (token === "USDT") {
6638
+ if (!useAlipay && token === "USDT") {
5275
6639
  const balance = await client.getBalance();
5276
6640
  if (balance.native < 1e-4) {
5277
6641
  console.log("\n\u26A0\uFE0F USDT requires a small amount of ETH for gas (~$0.01)");
@@ -5295,13 +6659,31 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
5295
6659
  console.log(` Prompt: ${params.prompt}`);
5296
6660
  }
5297
6661
  if (imageDisplay) console.log(` Image: ${imageDisplay}`);
5298
- console.log(` Chain: ${chain || "(auto)"}`);
5299
- console.log(` Token: ${token}`);
5300
- console.log(` Wallet: ${client.address}`);
6662
+ if (useAlipay) {
6663
+ console.log(` Rail: alipay (CNY via alipay-bot)`);
6664
+ } else {
6665
+ console.log(` Chain: ${chain || "(auto)"}`);
6666
+ console.log(` Token: ${token}`);
6667
+ console.log(` Wallet: ${client.address}`);
6668
+ }
5301
6669
  console.log("");
5302
6670
  }
5303
6671
  try {
5304
- const result = await client.pay(server, service, params, {
6672
+ const result = await client.pay(server, service, params, useAlipay ? {
6673
+ rail: "alipay",
6674
+ rawData: useRawData,
6675
+ onPaymentPending: ({ paymentUrl, shortenUrl }) => {
6676
+ if (!options.json) {
6677
+ process.stdout.write(`
6678
+ \u{1F4F2} \u8BF7\u7528\u652F\u4ED8\u5B9D\u626B\u7801\u6216\u8BBF\u95EE\uFF1A${shortenUrl ?? paymentUrl}
6679
+
6680
+ `);
6681
+ }
6682
+ },
6683
+ onLine: (line) => {
6684
+ if (!options.json) process.stdout.write(line + "\n");
6685
+ }
6686
+ } : {
5305
6687
  token,
5306
6688
  chain,
5307
6689
  rawData: useRawData
@@ -5322,11 +6704,34 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
5322
6704
  process.exit(1);
5323
6705
  }
5324
6706
  });
6707
+ program.command("alipay <action> [args...]").description("Alipay wallet setup via alipay-bot: check | apply | bind").allowUnknownOption().action(async (action, args) => {
6708
+ const map = {
6709
+ check: "check-wallet",
6710
+ apply: "apply-wallet",
6711
+ bind: "bind-wallet"
6712
+ };
6713
+ const sub = map[action] ?? action;
6714
+ const child = (0, import_child_process3.spawn)("alipay-bot", [sub, ...args ?? []], {
6715
+ stdio: "inherit",
6716
+ env: filterEnv(process.env)
6717
+ });
6718
+ child.on("error", (e) => {
6719
+ if (e?.code === "ENOENT") {
6720
+ console.error(
6721
+ "\u274C alipay-bot not installed. Run: npx -y @alipay/agent-payment install-cli"
6722
+ );
6723
+ } else {
6724
+ console.error(`\u274C ${e.message}`);
6725
+ }
6726
+ process.exit(1);
6727
+ });
6728
+ child.on("exit", (code) => process.exit(code ?? 1));
6729
+ });
5325
6730
  program.command("validate <path>").description("Validate a moltspay.services.json file against the schema").action(async (inputPath) => {
5326
- const resolvedPath = (0, import_path3.resolve)(inputPath);
6731
+ const resolvedPath = (0, import_path4.resolve)(inputPath);
5327
6732
  let manifestPath;
5328
- if ((0, import_fs5.existsSync)((0, import_path3.join)(resolvedPath, "moltspay.services.json"))) {
5329
- manifestPath = (0, import_path3.join)(resolvedPath, "moltspay.services.json");
6733
+ if ((0, import_fs5.existsSync)((0, import_path4.join)(resolvedPath, "moltspay.services.json"))) {
6734
+ manifestPath = (0, import_path4.join)(resolvedPath, "moltspay.services.json");
5330
6735
  } else if (resolvedPath.endsWith(".json") && (0, import_fs5.existsSync)(resolvedPath)) {
5331
6736
  manifestPath = resolvedPath;
5332
6737
  } else {