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.
- package/README.md +1 -0
- package/dist/cdp/index.js.map +1 -1
- package/dist/cdp/index.mjs.map +1 -1
- package/dist/{cdp-DeohBe1o.d.ts → cdp-B5PhDUTC.d.ts} +1 -1
- package/dist/{cdp-p_eHuQpb.d.mts → cdp-D-5Hg3y_.d.mts} +1 -1
- package/dist/chains/index.d.mts +37 -1
- package/dist/chains/index.d.ts +37 -1
- package/dist/chains/index.js +22 -0
- package/dist/chains/index.js.map +1 -1
- package/dist/chains/index.mjs +19 -0
- package/dist/chains/index.mjs.map +1 -1
- package/dist/cli/index.js +1466 -61
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +1466 -61
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.d.mts +41 -0
- package/dist/client/index.d.ts +41 -0
- package/dist/client/index.js +824 -10
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +824 -10
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/web/index.d.mts +17 -1
- package/dist/client/web/index.mjs +11 -0
- package/dist/client/web/index.mjs.map +1 -1
- package/dist/facilitators/index.d.mts +161 -4
- package/dist/facilitators/index.d.ts +161 -4
- package/dist/facilitators/index.js +370 -0
- package/dist/facilitators/index.js.map +1 -1
- package/dist/facilitators/index.mjs +365 -0
- package/dist/facilitators/index.mjs.map +1 -1
- package/dist/index.d.mts +77 -2
- package/dist/index.d.ts +77 -2
- package/dist/index.js +1412 -31
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1406 -31
- package/dist/index.mjs.map +1 -1
- package/dist/mcp/index.js +828 -14
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/index.mjs +828 -14
- package/dist/mcp/index.mjs.map +1 -1
- package/dist/{registry-OsEO2dOu.d.mts → registry-DIo0WNoO.d.mts} +8 -1
- package/dist/{registry-OsEO2dOu.d.ts → registry-DIo0WNoO.d.ts} +8 -1
- package/dist/server/index.d.mts +95 -2
- package/dist/server/index.d.ts +95 -2
- package/dist/server/index.js +554 -14
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +554 -14
- package/dist/server/index.mjs.map +1 -1
- package/dist/verify/index.js.map +1 -1
- package/dist/verify/index.mjs.map +1 -1
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +7 -1
- package/schemas/moltspay.services.schema.json +100 -6
- 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
|
|
41
|
-
var
|
|
40
|
+
var import_os4 = require("os");
|
|
41
|
+
var import_path4 = require("path");
|
|
42
42
|
var import_fs5 = require("fs");
|
|
43
|
-
var
|
|
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
|
|
53
|
-
var
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
5397
|
+
return new Promise((resolve3) => {
|
|
4035
5398
|
qrcode.generate(url, { small: true }, (qr) => {
|
|
4036
5399
|
console.log(qr);
|
|
4037
|
-
|
|
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,
|
|
4050
|
-
(0,
|
|
4051
|
-
(0,
|
|
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,
|
|
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,
|
|
4149
|
-
var PID_FILE = (0,
|
|
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((
|
|
5521
|
+
return new Promise((resolve3) => {
|
|
4159
5522
|
rl.question(question, (answer) => {
|
|
4160
5523
|
rl.close();
|
|
4161
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
5037
|
-
manifestPath = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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((
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
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,
|
|
6731
|
+
const resolvedPath = (0, import_path4.resolve)(inputPath);
|
|
5327
6732
|
let manifestPath;
|
|
5328
|
-
if ((0, import_fs5.existsSync)((0,
|
|
5329
|
-
manifestPath = (0,
|
|
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 {
|