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/mcp/index.js
CHANGED
|
@@ -29,14 +29,14 @@ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
|
29
29
|
|
|
30
30
|
// src/mcp/server.ts
|
|
31
31
|
var import_fs3 = require("fs");
|
|
32
|
-
var
|
|
32
|
+
var import_path4 = require("path");
|
|
33
33
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
34
34
|
var import_zod = require("zod");
|
|
35
35
|
|
|
36
36
|
// src/client/node/index.ts
|
|
37
37
|
var import_fs2 = require("fs");
|
|
38
|
-
var
|
|
39
|
-
var
|
|
38
|
+
var import_os3 = require("os");
|
|
39
|
+
var import_path3 = require("path");
|
|
40
40
|
var import_ethers2 = require("ethers");
|
|
41
41
|
|
|
42
42
|
// src/chains/index.ts
|
|
@@ -353,6 +353,16 @@ function networkToChainName(network) {
|
|
|
353
353
|
// src/client/core/base64.ts
|
|
354
354
|
var BufferCtor = globalThis.Buffer;
|
|
355
355
|
|
|
356
|
+
// src/client/core/errors.ts
|
|
357
|
+
var MoltsPayError = class extends Error {
|
|
358
|
+
code;
|
|
359
|
+
constructor(code, message) {
|
|
360
|
+
super(message);
|
|
361
|
+
this.name = "MoltsPayError";
|
|
362
|
+
this.code = code;
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
356
366
|
// src/client/core/eip3009.ts
|
|
357
367
|
var EIP3009_TYPES = {
|
|
358
368
|
TransferWithAuthorization: [
|
|
@@ -489,6 +499,733 @@ function findChainByChainId(chainId) {
|
|
|
489
499
|
return void 0;
|
|
490
500
|
}
|
|
491
501
|
|
|
502
|
+
// src/client/alipay/index.ts
|
|
503
|
+
var import_node_crypto = require("crypto");
|
|
504
|
+
var import_promises = require("fs/promises");
|
|
505
|
+
var import_os2 = require("os");
|
|
506
|
+
var import_path2 = require("path");
|
|
507
|
+
|
|
508
|
+
// src/client/alipay/cli.ts
|
|
509
|
+
var import_child_process = require("child_process");
|
|
510
|
+
|
|
511
|
+
// src/client/alipay/log.ts
|
|
512
|
+
var RANK = { off: 0, info: 1, debug: 2 };
|
|
513
|
+
function normalizeLevel(v) {
|
|
514
|
+
const s = (v ?? "").toLowerCase();
|
|
515
|
+
return s === "info" || s === "debug" ? s : "off";
|
|
516
|
+
}
|
|
517
|
+
var level = normalizeLevel(process.env.MOLTSPAY_ALIPAY_LOG);
|
|
518
|
+
var sink = (line) => {
|
|
519
|
+
process.stderr.write(line + "\n");
|
|
520
|
+
};
|
|
521
|
+
function enabledFor(want) {
|
|
522
|
+
return RANK[level] >= RANK[want];
|
|
523
|
+
}
|
|
524
|
+
function render(event, fields) {
|
|
525
|
+
const parts = ["[moltspay:alipay]", String(fields.ts), event];
|
|
526
|
+
if (fields.flow) parts.push(`flow=${fields.flow}`);
|
|
527
|
+
if (fields.step) parts.push(`step=${fields.step}`);
|
|
528
|
+
if (typeof fields.ms === "number") parts.push(`${fields.ms}ms`);
|
|
529
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
530
|
+
if (["ts", "level", "event", "flow", "step", "ms"].includes(k)) continue;
|
|
531
|
+
parts.push(`${k}=${typeof v === "string" ? v : JSON.stringify(v)}`);
|
|
532
|
+
}
|
|
533
|
+
return parts.join(" ");
|
|
534
|
+
}
|
|
535
|
+
function emit(lvl, event, fields) {
|
|
536
|
+
if (!enabledFor(lvl)) return;
|
|
537
|
+
const full = { ts: (/* @__PURE__ */ new Date()).toISOString(), level: lvl, event, ...fields };
|
|
538
|
+
try {
|
|
539
|
+
sink(render(event, full), full);
|
|
540
|
+
} catch {
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
var alipayLog = {
|
|
544
|
+
info: (event, fields = {}) => emit("info", event, fields),
|
|
545
|
+
debug: (event, fields = {}) => emit("debug", event, fields),
|
|
546
|
+
/** True if anything at all would be logged (cheap guard for hot paths). */
|
|
547
|
+
enabled: () => level !== "off"
|
|
548
|
+
};
|
|
549
|
+
async function timeStep(step, flow, fn) {
|
|
550
|
+
if (!alipayLog.enabled()) return fn();
|
|
551
|
+
const t0 = Date.now();
|
|
552
|
+
alipayLog.debug("step.start", { flow, step });
|
|
553
|
+
try {
|
|
554
|
+
const out = await fn();
|
|
555
|
+
alipayLog.info("step.end", { flow, step, ms: Date.now() - t0, ok: true });
|
|
556
|
+
return out;
|
|
557
|
+
} catch (err2) {
|
|
558
|
+
alipayLog.info("step.end", {
|
|
559
|
+
flow,
|
|
560
|
+
step,
|
|
561
|
+
ms: Date.now() - t0,
|
|
562
|
+
ok: false,
|
|
563
|
+
err: err2 instanceof Error ? err2.message : String(err2)
|
|
564
|
+
});
|
|
565
|
+
throw err2;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/client/alipay/cli.ts
|
|
570
|
+
var ALLOWED_ENV = /* @__PURE__ */ new Set([
|
|
571
|
+
"AIPAY_OUTPUT_CHANNEL",
|
|
572
|
+
"AIPAY_SESSION_ID",
|
|
573
|
+
"AIPAY_FRAMEWORK",
|
|
574
|
+
"AIPAY_MODEL",
|
|
575
|
+
"AIPAY_OS",
|
|
576
|
+
// Minimal survival set for spawn to find the binary and a home dir.
|
|
577
|
+
"PATH",
|
|
578
|
+
"HOME"
|
|
579
|
+
]);
|
|
580
|
+
function filterEnv(env) {
|
|
581
|
+
return Object.fromEntries(
|
|
582
|
+
Object.entries(env).filter(([k]) => ALLOWED_ENV.has(k))
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
function makeLineSplitter(onLine) {
|
|
586
|
+
let buf = "";
|
|
587
|
+
return (chunk) => {
|
|
588
|
+
buf += chunk.toString("utf-8");
|
|
589
|
+
let nl;
|
|
590
|
+
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
591
|
+
onLine(buf.slice(0, nl));
|
|
592
|
+
buf = buf.slice(nl + 1);
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
function profileEnv(step, flow) {
|
|
597
|
+
const want = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE;
|
|
598
|
+
const hook = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE_HOOK;
|
|
599
|
+
if (!want || !hook) return {};
|
|
600
|
+
const steps = want.split(",").map((s) => s.trim());
|
|
601
|
+
if (!steps.includes("all") && !steps.includes(step)) return {};
|
|
602
|
+
const dir = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE_DIR || "/tmp";
|
|
603
|
+
const safe = `${flow ?? "noflow"}-${step}-${Date.now()}`.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
604
|
+
const out = `${dir}/cli-profile-${safe}.json`;
|
|
605
|
+
alipayLog.info("cli.profile", { flow, step, out });
|
|
606
|
+
return {
|
|
607
|
+
NODE_OPTIONS: `--require=${hook}`,
|
|
608
|
+
MOLTSPAY_CLI_PROFILE_OUT: out
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
var runCli = (args, opts = {}) => {
|
|
612
|
+
const bin = opts.bin ?? "alipay-bot";
|
|
613
|
+
const step = opts.step ?? args[0] ?? "(no-arg)";
|
|
614
|
+
const startedAt = Date.now();
|
|
615
|
+
const lines = [];
|
|
616
|
+
const collect = (line) => {
|
|
617
|
+
lines.push(line);
|
|
618
|
+
alipayLog.debug("cli.line", {
|
|
619
|
+
flow: opts.flow,
|
|
620
|
+
step,
|
|
621
|
+
ms: Date.now() - startedAt,
|
|
622
|
+
preview: line.slice(0, 120)
|
|
623
|
+
});
|
|
624
|
+
opts.onLine?.(line);
|
|
625
|
+
};
|
|
626
|
+
alipayLog.debug("step.start", { flow: opts.flow, step });
|
|
627
|
+
return new Promise((resolve, reject) => {
|
|
628
|
+
const child = (0, import_child_process.spawn)(bin, args, {
|
|
629
|
+
env: { ...filterEnv(process.env), ...profileEnv(step, opts.flow), ...opts.env ?? {} }
|
|
630
|
+
});
|
|
631
|
+
if (opts.signal) {
|
|
632
|
+
if (opts.signal.aborted) child.kill("SIGTERM");
|
|
633
|
+
opts.signal.addEventListener("abort", () => child.kill("SIGTERM"), { once: true });
|
|
634
|
+
}
|
|
635
|
+
let sawByte = false;
|
|
636
|
+
const onChunk = (stream) => (chunk) => {
|
|
637
|
+
const ms = Date.now() - startedAt;
|
|
638
|
+
if (!sawByte) {
|
|
639
|
+
sawByte = true;
|
|
640
|
+
alipayLog.debug("cli.firstbyte", { flow: opts.flow, step, ms });
|
|
641
|
+
}
|
|
642
|
+
alipayLog.debug("cli.chunk", { flow: opts.flow, step, ms, stream, bytes: chunk.length });
|
|
643
|
+
};
|
|
644
|
+
const onStdout = makeLineSplitter(collect);
|
|
645
|
+
const onStderr = makeLineSplitter(collect);
|
|
646
|
+
child.stdout?.on("data", onChunk("out"));
|
|
647
|
+
child.stderr?.on("data", onChunk("err"));
|
|
648
|
+
child.stdout?.on("data", onStdout);
|
|
649
|
+
child.stderr?.on("data", onStderr);
|
|
650
|
+
child.on("error", (err2) => {
|
|
651
|
+
alipayLog.info("cli.exit", {
|
|
652
|
+
flow: opts.flow,
|
|
653
|
+
step,
|
|
654
|
+
ms: Date.now() - startedAt,
|
|
655
|
+
ok: false,
|
|
656
|
+
err: err2.message
|
|
657
|
+
});
|
|
658
|
+
reject(err2);
|
|
659
|
+
});
|
|
660
|
+
child.on("close", (code) => {
|
|
661
|
+
alipayLog.info("cli.exit", {
|
|
662
|
+
flow: opts.flow,
|
|
663
|
+
step,
|
|
664
|
+
ms: Date.now() - startedAt,
|
|
665
|
+
ok: (code ?? 1) === 0,
|
|
666
|
+
code: code ?? 1
|
|
667
|
+
});
|
|
668
|
+
resolve({ exitCode: code ?? 1, lines });
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
// src/client/alipay/install.ts
|
|
674
|
+
var import_child_process2 = require("child_process");
|
|
675
|
+
var import_util = require("util");
|
|
676
|
+
|
|
677
|
+
// src/client/alipay/errors.ts
|
|
678
|
+
var AlipayCliNotFoundError = class extends MoltsPayError {
|
|
679
|
+
constructor(message) {
|
|
680
|
+
super("ALIPAY_CLI_NOT_FOUND", message);
|
|
681
|
+
this.name = "AlipayCliNotFoundError";
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
var AlipayCliVersionError = class extends MoltsPayError {
|
|
685
|
+
constructor(message) {
|
|
686
|
+
super("ALIPAY_CLI_VERSION", message);
|
|
687
|
+
this.name = "AlipayCliVersionError";
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
var NeedsWalletSetupError = class extends MoltsPayError {
|
|
691
|
+
constructor(message = "Alipay wallet not set up. Run: moltspay alipay apply") {
|
|
692
|
+
super("ALIPAY_NEEDS_WALLET_SETUP", message);
|
|
693
|
+
this.name = "NeedsWalletSetupError";
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
var AlipayPaymentRejectedError = class extends MoltsPayError {
|
|
697
|
+
constructor(message) {
|
|
698
|
+
super("ALIPAY_PAYMENT_REJECTED", message);
|
|
699
|
+
this.name = "AlipayPaymentRejectedError";
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
var AlipayPaymentTimeoutError = class extends MoltsPayError {
|
|
703
|
+
constructor(message) {
|
|
704
|
+
super("ALIPAY_PAYMENT_TIMEOUT", message);
|
|
705
|
+
this.name = "AlipayPaymentTimeoutError";
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
var AlipayProtocolError = class extends MoltsPayError {
|
|
709
|
+
constructor(message) {
|
|
710
|
+
super("ALIPAY_PROTOCOL", message);
|
|
711
|
+
this.name = "AlipayProtocolError";
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
var UnsupportedRailError = class extends MoltsPayError {
|
|
715
|
+
constructor(rail, message) {
|
|
716
|
+
super("UNSUPPORTED_RAIL", message ?? `Rail not supported by server: ${rail}`);
|
|
717
|
+
this.rail = rail;
|
|
718
|
+
this.name = "UnsupportedRailError";
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
// src/client/alipay/install.ts
|
|
723
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
|
|
724
|
+
var MIN_CLI_VERSION = "0.3.15";
|
|
725
|
+
function semverLt(a, b) {
|
|
726
|
+
const pa = a.split(".").map((n) => parseInt(n, 10));
|
|
727
|
+
const pb = b.split(".").map((n) => parseInt(n, 10));
|
|
728
|
+
for (let i = 0; i < 3; i++) {
|
|
729
|
+
const x = pa[i] ?? 0;
|
|
730
|
+
const y = pb[i] ?? 0;
|
|
731
|
+
if (x !== y) return x < y;
|
|
732
|
+
}
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
function parseVersion(stdout) {
|
|
736
|
+
return stdout.match(/v?(\d+\.\d+\.\d+)/)?.[1] ?? null;
|
|
737
|
+
}
|
|
738
|
+
var defaultGetVersion = async () => {
|
|
739
|
+
const { stdout } = await execFileAsync("alipay-bot", ["--version"]);
|
|
740
|
+
return stdout;
|
|
741
|
+
};
|
|
742
|
+
async function ensureCliUncached(getVersion) {
|
|
743
|
+
let stdout;
|
|
744
|
+
try {
|
|
745
|
+
stdout = await getVersion();
|
|
746
|
+
} catch (e) {
|
|
747
|
+
if (e?.code === "ENOENT") {
|
|
748
|
+
throw new AlipayCliNotFoundError(
|
|
749
|
+
"alipay-bot not installed. Run: npx -y @alipay/agent-payment install-cli"
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
throw e;
|
|
753
|
+
}
|
|
754
|
+
const version = parseVersion(stdout);
|
|
755
|
+
if (!version || semverLt(version, MIN_CLI_VERSION)) {
|
|
756
|
+
throw new AlipayCliVersionError(
|
|
757
|
+
`alipay-bot ${version ?? "?"} found, need \u2265 ${MIN_CLI_VERSION}. Run: npx -y @alipay/agent-payment@latest update`
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
return version;
|
|
761
|
+
}
|
|
762
|
+
var cachedVersion = null;
|
|
763
|
+
var inflight = null;
|
|
764
|
+
async function ensureCli(getVersion = defaultGetVersion) {
|
|
765
|
+
if (getVersion !== defaultGetVersion) {
|
|
766
|
+
return ensureCliUncached(getVersion);
|
|
767
|
+
}
|
|
768
|
+
if (cachedVersion) return cachedVersion;
|
|
769
|
+
if (!inflight) {
|
|
770
|
+
inflight = ensureCliUncached(getVersion).then((v) => {
|
|
771
|
+
cachedVersion = v;
|
|
772
|
+
return v;
|
|
773
|
+
}).finally(() => {
|
|
774
|
+
inflight = null;
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
return inflight;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/client/alipay/poll.ts
|
|
781
|
+
var POLL_INTERVAL_MS = 3e3;
|
|
782
|
+
var POLL_MAX_INFLIGHT = 2;
|
|
783
|
+
function resolveMaxInflight(override) {
|
|
784
|
+
if (override !== void 0) return Math.max(1, Math.floor(override));
|
|
785
|
+
const raw = process.env.MOLTSPAY_ALIPAY_POLL_MAX_INFLIGHT;
|
|
786
|
+
if (raw !== void 0 && raw.trim() !== "") {
|
|
787
|
+
const n = Number(raw);
|
|
788
|
+
if (Number.isFinite(n) && n >= 1) return Math.floor(n);
|
|
789
|
+
}
|
|
790
|
+
return POLL_MAX_INFLIGHT;
|
|
791
|
+
}
|
|
792
|
+
function resolveLaunchGap(override) {
|
|
793
|
+
if (override !== void 0) return Math.max(0, override);
|
|
794
|
+
const raw = process.env.MOLTSPAY_ALIPAY_POLL_LAUNCH_MS;
|
|
795
|
+
if (raw !== void 0 && raw.trim() !== "") {
|
|
796
|
+
const n = Number(raw);
|
|
797
|
+
if (Number.isFinite(n) && n >= 0) return n;
|
|
798
|
+
}
|
|
799
|
+
return POLL_INTERVAL_MS;
|
|
800
|
+
}
|
|
801
|
+
function parseStatus(lines) {
|
|
802
|
+
const raw = lines.join("\n").trim();
|
|
803
|
+
try {
|
|
804
|
+
const json = JSON.parse(raw);
|
|
805
|
+
if (json && typeof json === "object") {
|
|
806
|
+
if (typeof json.success === "boolean") {
|
|
807
|
+
if (json.success) return "paid";
|
|
808
|
+
const err2 = String(json.errorCode ?? "").toUpperCase();
|
|
809
|
+
if (/UNPAID|WAIT|PENDING|PROCESS|NOTPAY/.test(err2)) return "pending";
|
|
810
|
+
if (/CLOSED|CANCEL|FAIL|REJECT|REFUSE|TIMEOUT|EXPIRE/.test(err2)) return "rejected";
|
|
811
|
+
return "pending";
|
|
812
|
+
}
|
|
813
|
+
if (typeof json.code !== "undefined") {
|
|
814
|
+
if (Number(json.code) === 200) return "paid";
|
|
815
|
+
const msg = `${json.message ?? ""}${json.reason ?? ""}`;
|
|
816
|
+
if (/关闭|失败|拒绝|取消|超时|已撤销/.test(msg)) return "rejected";
|
|
817
|
+
return "pending";
|
|
818
|
+
}
|
|
819
|
+
if (typeof json.body === "string") {
|
|
820
|
+
return classifyReportText(json.body);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
} catch {
|
|
824
|
+
}
|
|
825
|
+
return classifyReportText(raw);
|
|
826
|
+
}
|
|
827
|
+
function classifyReportText(s) {
|
|
828
|
+
const text = s.toUpperCase();
|
|
829
|
+
if (/查询支付状态成功并获取资源/.test(s) || /资源响应状态[^0-9\n]{0,8}200/.test(s) || /TRADE_SUCCESS|TRADE_FINISHED|"STATUS":\s*"FULFILLED"/.test(text)) return "paid";
|
|
830
|
+
if (/TRADE_CLOSED|REJECTED|REFUSE|CANCEL/.test(text) || /交易关闭|支付失败|已拒绝|已取消|已撤销|交易超时/.test(s)) return "rejected";
|
|
831
|
+
if (/WAIT_BUYER_PAY|UNPAID|PENDING|WAITING/.test(text) || /交易未支付|等待付款|待支付|未支付/.test(s)) return "pending";
|
|
832
|
+
return "unknown";
|
|
833
|
+
}
|
|
834
|
+
function defaultSleep(ms, signal) {
|
|
835
|
+
return new Promise((resolve, reject) => {
|
|
836
|
+
if (signal?.aborted) return reject(new Error("aborted"));
|
|
837
|
+
const t = setTimeout(resolve, ms);
|
|
838
|
+
signal?.addEventListener(
|
|
839
|
+
"abort",
|
|
840
|
+
() => {
|
|
841
|
+
clearTimeout(t);
|
|
842
|
+
reject(new Error("aborted"));
|
|
843
|
+
},
|
|
844
|
+
{ once: true }
|
|
845
|
+
);
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
var LAUNCH = /* @__PURE__ */ Symbol("launch");
|
|
849
|
+
async function pollUntil(tradeNo, resourceUrl, opts) {
|
|
850
|
+
const runner = opts.runner ?? runCli;
|
|
851
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
852
|
+
const now = opts.now ?? Date.now;
|
|
853
|
+
const maxInflight = resolveMaxInflight(opts.maxInflight);
|
|
854
|
+
const launchGap = resolveLaunchGap(opts.launchIntervalMs);
|
|
855
|
+
const args = [
|
|
856
|
+
"402-query-payment-status",
|
|
857
|
+
"-t",
|
|
858
|
+
tradeNo,
|
|
859
|
+
"-r",
|
|
860
|
+
resourceUrl,
|
|
861
|
+
...opts.method ? ["-m", opts.method] : [],
|
|
862
|
+
...opts.data ? ["-d", opts.data] : []
|
|
863
|
+
];
|
|
864
|
+
const internal = new AbortController();
|
|
865
|
+
const signal = opts.signal ? AbortSignal.any([opts.signal, internal.signal]) : internal.signal;
|
|
866
|
+
let tick = 0;
|
|
867
|
+
let lastLaunch = -Infinity;
|
|
868
|
+
const inflight2 = /* @__PURE__ */ new Set();
|
|
869
|
+
const launch = () => {
|
|
870
|
+
tick += 1;
|
|
871
|
+
const myTick = tick;
|
|
872
|
+
lastLaunch = now();
|
|
873
|
+
const run = (async () => {
|
|
874
|
+
const { lines } = await runner(args, {
|
|
875
|
+
signal,
|
|
876
|
+
step: "query-payment-status",
|
|
877
|
+
flow: tradeNo
|
|
878
|
+
});
|
|
879
|
+
const status = parseStatus(lines);
|
|
880
|
+
alipayLog.debug("poll.tick", { flow: tradeNo, tick: myTick, status });
|
|
881
|
+
return { status, lines };
|
|
882
|
+
})();
|
|
883
|
+
const tracked = run.finally(() => {
|
|
884
|
+
inflight2.delete(tracked);
|
|
885
|
+
});
|
|
886
|
+
inflight2.add(tracked);
|
|
887
|
+
};
|
|
888
|
+
try {
|
|
889
|
+
for (; ; ) {
|
|
890
|
+
if (opts.signal?.aborted) throw new Error("aborted");
|
|
891
|
+
const expired = now() >= opts.deadline;
|
|
892
|
+
if (expired && inflight2.size === 0) {
|
|
893
|
+
throw new AlipayPaymentTimeoutError(
|
|
894
|
+
`Payment ${tradeNo} not completed before pay_before deadline`
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
while (!expired && inflight2.size < maxInflight && now() - lastLaunch >= launchGap) {
|
|
898
|
+
launch();
|
|
899
|
+
}
|
|
900
|
+
const waiters = [...inflight2];
|
|
901
|
+
if (!expired && inflight2.size < maxInflight) {
|
|
902
|
+
const untilNext = Math.max(0, launchGap - (now() - lastLaunch));
|
|
903
|
+
waiters.push(sleep(untilNext, signal).then(() => LAUNCH, () => LAUNCH));
|
|
904
|
+
}
|
|
905
|
+
const winner = await Promise.race(waiters);
|
|
906
|
+
if (winner === LAUNCH) continue;
|
|
907
|
+
if (winner.status === "paid") return { status: "paid", lines: winner.lines };
|
|
908
|
+
if (winner.status === "rejected") {
|
|
909
|
+
throw new AlipayPaymentRejectedError(`Payment ${tradeNo} was rejected`);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
} finally {
|
|
913
|
+
internal.abort();
|
|
914
|
+
for (const p of inflight2) p.catch(() => void 0);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// src/client/alipay/index.ts
|
|
919
|
+
var TRADE_NO_RE = /^\d{32}$/;
|
|
920
|
+
function resolveSessionId(explicit, envSession) {
|
|
921
|
+
return explicit ?? envSession ?? (0, import_node_crypto.randomUUID)();
|
|
922
|
+
}
|
|
923
|
+
function assertTradeNo(t) {
|
|
924
|
+
if (!TRADE_NO_RE.test(t)) {
|
|
925
|
+
throw new AlipayProtocolError(`invalid tradeNo (expect 32 digits): ${t}`);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
function parseTradeNo(lines) {
|
|
929
|
+
for (const line of lines) {
|
|
930
|
+
const labeled = line.match(/trade[_-]?no["'\s:=]+(\d{32})/i);
|
|
931
|
+
if (labeled) return labeled[1];
|
|
932
|
+
const bare = line.match(/\b(\d{32})\b/);
|
|
933
|
+
if (bare) return bare[1];
|
|
934
|
+
}
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
function parsePaymentUrl(lines) {
|
|
938
|
+
let paymentUrl;
|
|
939
|
+
let shortenUrl;
|
|
940
|
+
for (const line of lines) {
|
|
941
|
+
const m = line.match(/(alipays?:\/\/\S+|https?:\/\/\S+)/i);
|
|
942
|
+
if (!m) continue;
|
|
943
|
+
const url = m[1].replace(/[)\]`>]+$/, "");
|
|
944
|
+
if (/short|qr\.alipay|surl|\/s\//i.test(line) && !shortenUrl) shortenUrl = url;
|
|
945
|
+
else if (!paymentUrl) paymentUrl = url;
|
|
946
|
+
}
|
|
947
|
+
if (!paymentUrl && shortenUrl) paymentUrl = shortenUrl;
|
|
948
|
+
return { paymentUrl, shortenUrl };
|
|
949
|
+
}
|
|
950
|
+
function extractMedia(line) {
|
|
951
|
+
const m = line.match(/^\s*MEDIA:\s*(.+?)\s*$/);
|
|
952
|
+
return m ? m[1] : null;
|
|
953
|
+
}
|
|
954
|
+
function isWalletReady(lines) {
|
|
955
|
+
const text = lines.join("\n").trim();
|
|
956
|
+
try {
|
|
957
|
+
const json = JSON.parse(text);
|
|
958
|
+
if (json && typeof json.code !== "undefined") return Number(json.code) === 200;
|
|
959
|
+
} catch {
|
|
960
|
+
}
|
|
961
|
+
return !/未开通|未开启|NOT[_\s-]*(OPEN|BOUND|SET)|NEEDS?[_\s-]*SETUP|NO[_\s-]*WALLET/i.test(text);
|
|
962
|
+
}
|
|
963
|
+
function extractResourceFromReport(report) {
|
|
964
|
+
const label = report.search(/资源响应体/);
|
|
965
|
+
const start = report.indexOf("{", label === -1 ? 0 : label);
|
|
966
|
+
if (start === -1) return void 0;
|
|
967
|
+
let depth = 0, inStr = false, esc = false;
|
|
968
|
+
for (let i = start; i < report.length; i++) {
|
|
969
|
+
const ch = report[i];
|
|
970
|
+
if (inStr) {
|
|
971
|
+
if (esc) esc = false;
|
|
972
|
+
else if (ch === "\\") esc = true;
|
|
973
|
+
else if (ch === '"') inStr = false;
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
if (ch === '"') inStr = true;
|
|
977
|
+
else if (ch === "{") depth++;
|
|
978
|
+
else if (ch === "}" && --depth === 0) {
|
|
979
|
+
const slice = report.slice(start, i + 1);
|
|
980
|
+
try {
|
|
981
|
+
const obj = JSON.parse(slice);
|
|
982
|
+
const r = obj?.resourceResponse ?? obj?.result ?? obj?.data ?? obj?.body ?? obj;
|
|
983
|
+
return typeof r === "string" ? r : JSON.stringify(r);
|
|
984
|
+
} catch {
|
|
985
|
+
return slice;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
return void 0;
|
|
990
|
+
}
|
|
991
|
+
function extractBody(lines) {
|
|
992
|
+
const text = lines.join("\n").trim();
|
|
993
|
+
try {
|
|
994
|
+
const json = JSON.parse(text);
|
|
995
|
+
if (json && typeof json === "object") {
|
|
996
|
+
if (json.resourceResponse !== void 0 && json.resourceResponse !== null) {
|
|
997
|
+
const rr = json.resourceResponse;
|
|
998
|
+
const r = typeof rr === "object" ? rr.result ?? rr.data ?? rr.body ?? rr : rr;
|
|
999
|
+
return typeof r === "string" ? r : JSON.stringify(r);
|
|
1000
|
+
}
|
|
1001
|
+
if (typeof json.body === "string") {
|
|
1002
|
+
const inner = extractResourceFromReport(json.body);
|
|
1003
|
+
return inner ?? json.body;
|
|
1004
|
+
}
|
|
1005
|
+
const body = json.data ?? json.result ?? json.body ?? json.resource;
|
|
1006
|
+
if (body !== void 0) return typeof body === "string" ? body : JSON.stringify(body);
|
|
1007
|
+
return text;
|
|
1008
|
+
}
|
|
1009
|
+
} catch {
|
|
1010
|
+
}
|
|
1011
|
+
const idx = lines.findIndex((l) => /^\s*BODY:/.test(l));
|
|
1012
|
+
if (idx !== -1) {
|
|
1013
|
+
const first = lines[idx].replace(/^\s*BODY:\s*/, "");
|
|
1014
|
+
return [first, ...lines.slice(idx + 1)].join("\n").trim();
|
|
1015
|
+
}
|
|
1016
|
+
return lines.filter((l) => !/^\s*(MEDIA|STATUS|INFO):/.test(l)).join("\n").trim();
|
|
1017
|
+
}
|
|
1018
|
+
var DEFAULT_WALLET_TTL_MS = 10 * 60 * 1e3;
|
|
1019
|
+
function resolveWalletTtlMs() {
|
|
1020
|
+
const raw = process.env.MOLTSPAY_ALIPAY_WALLET_TTL_MS;
|
|
1021
|
+
if (raw === void 0 || raw.trim() === "") return DEFAULT_WALLET_TTL_MS;
|
|
1022
|
+
const n = Number(raw);
|
|
1023
|
+
return Number.isFinite(n) && n >= 0 ? n : DEFAULT_WALLET_TTL_MS;
|
|
1024
|
+
}
|
|
1025
|
+
var walletReadyUntil = /* @__PURE__ */ new Map();
|
|
1026
|
+
var DEFAULT_INTENT_TTL_MS = 10 * 60 * 1e3;
|
|
1027
|
+
function resolveIntentTtlMs() {
|
|
1028
|
+
const raw = process.env.MOLTSPAY_ALIPAY_INTENT_TTL_MS;
|
|
1029
|
+
if (raw === void 0 || raw.trim() === "") return DEFAULT_INTENT_TTL_MS;
|
|
1030
|
+
const n = Number(raw);
|
|
1031
|
+
return Number.isFinite(n) && n >= 0 ? n : DEFAULT_INTENT_TTL_MS;
|
|
1032
|
+
}
|
|
1033
|
+
var intentDoneUntil = /* @__PURE__ */ new Map();
|
|
1034
|
+
var AlipayClient = class {
|
|
1035
|
+
sessionId;
|
|
1036
|
+
configDir;
|
|
1037
|
+
framework;
|
|
1038
|
+
runner;
|
|
1039
|
+
getVersion;
|
|
1040
|
+
now;
|
|
1041
|
+
/** Only the default runner may use the process-level wallet cache. */
|
|
1042
|
+
walletCacheable;
|
|
1043
|
+
/** Only the default runner may use the process-level payment-intent cache. */
|
|
1044
|
+
intentCacheable;
|
|
1045
|
+
constructor(opts = {}) {
|
|
1046
|
+
this.sessionId = resolveSessionId(opts.sessionId, process.env.AIPAY_SESSION_ID);
|
|
1047
|
+
this.configDir = opts.configDir ?? (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
|
|
1048
|
+
this.framework = opts.framework ?? process.env.AIPAY_FRAMEWORK ?? "openclaw";
|
|
1049
|
+
this.runner = opts.runner ?? runCli;
|
|
1050
|
+
this.getVersion = opts.getVersion;
|
|
1051
|
+
this.now = opts.now ?? Date.now;
|
|
1052
|
+
this.walletCacheable = opts.cacheWallet ?? !opts.runner;
|
|
1053
|
+
this.intentCacheable = opts.cacheIntent ?? !opts.runner;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Throws NeedsWalletSetupError unless alipay-bot reports an opened wallet.
|
|
1057
|
+
*
|
|
1058
|
+
* Skips the ~22s `check-wallet` spawn entirely when a prior call cached a
|
|
1059
|
+
* "ready" verdict within the TTL (see {@link DEFAULT_WALLET_TTL_MS}).
|
|
1060
|
+
*/
|
|
1061
|
+
async checkWallet(signal, flow) {
|
|
1062
|
+
const ttlMs = resolveWalletTtlMs();
|
|
1063
|
+
const cacheKey = `${this.configDir}::${this.framework}`;
|
|
1064
|
+
const useCache = this.walletCacheable && ttlMs > 0;
|
|
1065
|
+
if (useCache) {
|
|
1066
|
+
const until = walletReadyUntil.get(cacheKey);
|
|
1067
|
+
if (until !== void 0 && this.now() < until) {
|
|
1068
|
+
alipayLog.info("wallet.cache", { flow, step: "check-wallet", hit: true, ttlMsLeft: until - this.now() });
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
const { lines } = await this.runner(["check-wallet"], { signal, step: "check-wallet", flow });
|
|
1073
|
+
if (!isWalletReady(lines)) {
|
|
1074
|
+
if (this.walletCacheable) walletReadyUntil.delete(cacheKey);
|
|
1075
|
+
throw new NeedsWalletSetupError(
|
|
1076
|
+
"Alipay wallet not opened. Run: moltspay alipay apply (then: moltspay alipay bind)"
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
if (useCache) {
|
|
1080
|
+
walletReadyUntil.set(cacheKey, this.now() + ttlMs);
|
|
1081
|
+
alipayLog.info("wallet.cache", { flow, step: "check-wallet", hit: false, cachedForMs: ttlMs });
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
/** Run the full 8-step flow and resolve with the resource body. */
|
|
1085
|
+
async pay402(opts) {
|
|
1086
|
+
const { resourceUrl, requirement, signal } = opts;
|
|
1087
|
+
const extra = requirement.extra ?? {};
|
|
1088
|
+
const paymentNeededHeader = String(extra.payment_needed_header ?? "");
|
|
1089
|
+
if (!paymentNeededHeader) {
|
|
1090
|
+
throw new AlipayProtocolError("alipay requirement missing extra.payment_needed_header");
|
|
1091
|
+
}
|
|
1092
|
+
const flow = this.sessionId;
|
|
1093
|
+
const flowStart = this.now();
|
|
1094
|
+
alipayLog.info("flow.start", { flow, resource: resourceUrl });
|
|
1095
|
+
const media = [];
|
|
1096
|
+
const onLine = (line) => {
|
|
1097
|
+
const m = extractMedia(line);
|
|
1098
|
+
if (m) media.push(m);
|
|
1099
|
+
else opts.onLine?.(line);
|
|
1100
|
+
};
|
|
1101
|
+
const intentSummary = opts.intentSummary?.trim() || `\u652F\u4ED8 ${requirement.amount ?? ""} ${requirement.asset ?? "CNY"}`.trim();
|
|
1102
|
+
await timeStep("ensure-cli", flow, () => ensureCli(this.getVersion));
|
|
1103
|
+
const intentTtlMs = resolveIntentTtlMs();
|
|
1104
|
+
const intentKey = `${this.configDir}::${this.framework}`;
|
|
1105
|
+
const useIntentCache = this.intentCacheable && intentTtlMs > 0;
|
|
1106
|
+
const intentUntil = useIntentCache ? intentDoneUntil.get(intentKey) : void 0;
|
|
1107
|
+
if (intentUntil !== void 0 && this.now() < intentUntil) {
|
|
1108
|
+
alipayLog.info("intent.cache", { flow, step: "payment-intent", hit: true, ttlMsLeft: intentUntil - this.now() });
|
|
1109
|
+
} else {
|
|
1110
|
+
await this.runner(
|
|
1111
|
+
[
|
|
1112
|
+
"payment-intent",
|
|
1113
|
+
"--session-id",
|
|
1114
|
+
this.sessionId,
|
|
1115
|
+
"--intent-summary",
|
|
1116
|
+
intentSummary,
|
|
1117
|
+
"--framework",
|
|
1118
|
+
this.framework
|
|
1119
|
+
],
|
|
1120
|
+
{ onLine, signal, step: "payment-intent", flow }
|
|
1121
|
+
);
|
|
1122
|
+
if (useIntentCache) {
|
|
1123
|
+
intentDoneUntil.set(intentKey, this.now() + intentTtlMs);
|
|
1124
|
+
alipayLog.info("intent.cache", { flow, step: "payment-intent", hit: false, cachedForMs: intentTtlMs });
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
await this.checkWallet(signal, flow);
|
|
1128
|
+
const reqId = String(extra.out_trade_no ?? (0, import_node_crypto.randomUUID)());
|
|
1129
|
+
const dir = (0, import_path2.join)(this.configDir, "alipay");
|
|
1130
|
+
await (0, import_promises.mkdir)(dir, { recursive: true });
|
|
1131
|
+
const challengeFile = (0, import_path2.join)(dir, `402_${reqId}.txt`);
|
|
1132
|
+
await (0, import_promises.writeFile)(challengeFile, paymentNeededHeader, "utf-8");
|
|
1133
|
+
const payArgs = [
|
|
1134
|
+
"402-buyer-pay",
|
|
1135
|
+
"-f",
|
|
1136
|
+
challengeFile,
|
|
1137
|
+
"-r",
|
|
1138
|
+
resourceUrl,
|
|
1139
|
+
"-s",
|
|
1140
|
+
this.sessionId,
|
|
1141
|
+
"-i",
|
|
1142
|
+
intentSummary,
|
|
1143
|
+
"-w",
|
|
1144
|
+
this.framework
|
|
1145
|
+
];
|
|
1146
|
+
if (opts.method) payArgs.push("-m", opts.method);
|
|
1147
|
+
if (opts.data) payArgs.push("-d", opts.data);
|
|
1148
|
+
const payRun = await this.runner(payArgs, { onLine, signal, step: "402-buyer-pay", flow });
|
|
1149
|
+
const tradeNo = parseTradeNo(payRun.lines);
|
|
1150
|
+
if (!tradeNo) {
|
|
1151
|
+
throw new AlipayProtocolError("402-buyer-pay did not return a tradeNo");
|
|
1152
|
+
}
|
|
1153
|
+
assertTradeNo(tradeNo);
|
|
1154
|
+
const { paymentUrl, shortenUrl } = parsePaymentUrl(payRun.lines);
|
|
1155
|
+
if (paymentUrl) {
|
|
1156
|
+
alipayLog.info("flow.pending", { flow, tradeNo, ms: this.now() - flowStart });
|
|
1157
|
+
opts.onPaymentPending?.({ paymentUrl, shortenUrl, tradeNo });
|
|
1158
|
+
}
|
|
1159
|
+
const pendingAt = this.now();
|
|
1160
|
+
const windowMs = (requirement.maxTimeoutSeconds ?? 30 * 60) * 1e3;
|
|
1161
|
+
const deadline = this.now() + (opts.timeoutMs ?? windowMs);
|
|
1162
|
+
const poll = await pollUntil(tradeNo, resourceUrl, {
|
|
1163
|
+
// No onLine: the status-poll output embeds the resource and must not reach
|
|
1164
|
+
// the log stream — the body is surfaced via the return value below (§9.3).
|
|
1165
|
+
deadline,
|
|
1166
|
+
signal,
|
|
1167
|
+
runner: this.runner,
|
|
1168
|
+
now: this.now,
|
|
1169
|
+
// Re-fetch the resource the same way it was paid (POST + body), else the
|
|
1170
|
+
// status poll defaults to GET and 404s on a POST-only `/execute`.
|
|
1171
|
+
method: opts.method,
|
|
1172
|
+
data: opts.data
|
|
1173
|
+
});
|
|
1174
|
+
alipayLog.info("flow.settled", { flow, tradeNo, ms: this.now() - pendingAt });
|
|
1175
|
+
const body = extractBody(poll.lines);
|
|
1176
|
+
void this.runner(["402-buyer-fulfillment-ack", "-t", tradeNo], {
|
|
1177
|
+
onLine,
|
|
1178
|
+
signal,
|
|
1179
|
+
step: "402-buyer-fulfillment-ack",
|
|
1180
|
+
flow
|
|
1181
|
+
}).catch(() => void 0);
|
|
1182
|
+
return { body, payment: { tradeNo, outTradeNo: reqId }, media };
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
// src/client/alipay/router.ts
|
|
1187
|
+
var ALIPAY_RAIL = "alipay";
|
|
1188
|
+
function railOf(req) {
|
|
1189
|
+
if (req.scheme === "alipay-aipay" || req.network === ALIPAY_RAIL) return ALIPAY_RAIL;
|
|
1190
|
+
return networkToChainName(req.network) ?? req.network;
|
|
1191
|
+
}
|
|
1192
|
+
function findRail(accepts, rail) {
|
|
1193
|
+
const requirement = accepts.find((r) => railOf(r) === rail);
|
|
1194
|
+
return requirement ? { rail, requirement } : null;
|
|
1195
|
+
}
|
|
1196
|
+
function selectRail(input) {
|
|
1197
|
+
const { serverAccepts, explicitRail, preference, availability } = input;
|
|
1198
|
+
if (!serverAccepts || serverAccepts.length === 0) {
|
|
1199
|
+
throw new UnsupportedRailError(explicitRail ?? "unknown", "Server offered no payment options");
|
|
1200
|
+
}
|
|
1201
|
+
if (explicitRail) {
|
|
1202
|
+
const hit = findRail(serverAccepts, explicitRail);
|
|
1203
|
+
if (!hit) {
|
|
1204
|
+
const offered = serverAccepts.map(railOf);
|
|
1205
|
+
throw new UnsupportedRailError(
|
|
1206
|
+
explicitRail,
|
|
1207
|
+
`Server doesn't accept rail '${explicitRail}'. Offered: ${offered.join(", ")}`
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
return hit;
|
|
1211
|
+
}
|
|
1212
|
+
if (preference) {
|
|
1213
|
+
for (const pref of preference) {
|
|
1214
|
+
const hit = findRail(serverAccepts, pref);
|
|
1215
|
+
if (hit) return hit;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
if (availability?.evmReady) {
|
|
1219
|
+
const evm = serverAccepts.find((r) => railOf(r) !== ALIPAY_RAIL);
|
|
1220
|
+
if (evm) return { rail: railOf(evm), requirement: evm };
|
|
1221
|
+
}
|
|
1222
|
+
if (availability?.alipayReady) {
|
|
1223
|
+
const hit = findRail(serverAccepts, ALIPAY_RAIL);
|
|
1224
|
+
if (hit) return hit;
|
|
1225
|
+
}
|
|
1226
|
+
return { rail: railOf(serverAccepts[0]), requirement: serverAccepts[0] };
|
|
1227
|
+
}
|
|
1228
|
+
|
|
492
1229
|
// src/client/node/index.ts
|
|
493
1230
|
var DEFAULT_CONFIG = {
|
|
494
1231
|
chain: "base",
|
|
@@ -505,9 +1242,13 @@ var MoltsPayClient = class {
|
|
|
505
1242
|
signer = null;
|
|
506
1243
|
todaySpending = 0;
|
|
507
1244
|
lastSpendingReset = 0;
|
|
1245
|
+
railPreference;
|
|
1246
|
+
alipaySessionId;
|
|
508
1247
|
constructor(options = {}) {
|
|
509
|
-
this.configDir = options.configDir || (0,
|
|
1248
|
+
this.configDir = options.configDir || (0, import_path3.join)((0, import_os3.homedir)(), ".moltspay");
|
|
510
1249
|
this.config = this.loadConfig();
|
|
1250
|
+
this.railPreference = options.railPreference ?? this.config.railPreference;
|
|
1251
|
+
this.alipaySessionId = options.alipaySessionId;
|
|
511
1252
|
this.walletData = this.loadWallet();
|
|
512
1253
|
this.loadSpending();
|
|
513
1254
|
if (this.walletData) {
|
|
@@ -585,6 +1326,9 @@ var MoltsPayClient = class {
|
|
|
585
1326
|
* @param options - Payment options (token selection)
|
|
586
1327
|
*/
|
|
587
1328
|
async pay(serverUrl, service, params, options = {}) {
|
|
1329
|
+
if (options.rail === ALIPAY_RAIL) {
|
|
1330
|
+
return this.payViaAlipay(serverUrl, service, params, options);
|
|
1331
|
+
}
|
|
588
1332
|
if (!this.wallet || !this.walletData) {
|
|
589
1333
|
throw new Error("Client not initialized. Run: npx moltspay init");
|
|
590
1334
|
}
|
|
@@ -776,6 +1520,76 @@ Please specify: --chain <chain_name>`
|
|
|
776
1520
|
console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
|
|
777
1521
|
return result.result || result;
|
|
778
1522
|
}
|
|
1523
|
+
/**
|
|
1524
|
+
* Pay for a service over the Alipay fiat rail (2.0.0).
|
|
1525
|
+
*
|
|
1526
|
+
* Unlike the crypto path this needs no EVM wallet — it shells out to
|
|
1527
|
+
* alipay-bot via {@link AlipayClient}. Flow: hit the resource with no
|
|
1528
|
+
* payment to get the 402 challenge, confirm the server actually offers the
|
|
1529
|
+
* alipay rail (selectRail), then run the 8-step state machine and return the
|
|
1530
|
+
* resource body.
|
|
1531
|
+
*/
|
|
1532
|
+
async payViaAlipay(serverUrl, service, params, options) {
|
|
1533
|
+
const flow = this.alipaySessionId;
|
|
1534
|
+
let executeUrl = `${serverUrl}/execute`;
|
|
1535
|
+
try {
|
|
1536
|
+
const services = await timeStep(
|
|
1537
|
+
"discover-services",
|
|
1538
|
+
flow,
|
|
1539
|
+
() => this.getServices(serverUrl)
|
|
1540
|
+
);
|
|
1541
|
+
const svc = services.services?.find((s) => s.id === service);
|
|
1542
|
+
if (svc?.endpoint) executeUrl = `${serverUrl}${svc.endpoint}`;
|
|
1543
|
+
} catch {
|
|
1544
|
+
}
|
|
1545
|
+
const requestBody = options.rawData ? { service, ...params } : { service, params };
|
|
1546
|
+
const bodyJson = JSON.stringify(requestBody);
|
|
1547
|
+
const res = await timeStep(
|
|
1548
|
+
"challenge-402",
|
|
1549
|
+
flow,
|
|
1550
|
+
() => fetch(executeUrl, {
|
|
1551
|
+
method: "POST",
|
|
1552
|
+
headers: { "Content-Type": "application/json" },
|
|
1553
|
+
body: bodyJson
|
|
1554
|
+
})
|
|
1555
|
+
);
|
|
1556
|
+
if (res.status !== 402) {
|
|
1557
|
+
const data = await res.json().catch(() => ({}));
|
|
1558
|
+
if (res.ok && data.result) return data.result;
|
|
1559
|
+
throw new Error(data.error || `Expected 402, got ${res.status}`);
|
|
1560
|
+
}
|
|
1561
|
+
const header = res.headers.get(PAYMENT_REQUIRED_HEADER);
|
|
1562
|
+
if (!header) throw new Error("Missing x-payment-required header on 402");
|
|
1563
|
+
const parsed = JSON.parse(Buffer.from(header, "base64").toString("utf-8"));
|
|
1564
|
+
const accepts = Array.isArray(parsed) ? parsed : parsed.accepts ?? [parsed];
|
|
1565
|
+
const { requirement } = selectRail({
|
|
1566
|
+
serverAccepts: accepts,
|
|
1567
|
+
explicitRail: ALIPAY_RAIL,
|
|
1568
|
+
preference: this.railPreference,
|
|
1569
|
+
availability: { evmReady: this.isInitialized }
|
|
1570
|
+
});
|
|
1571
|
+
const onLine = options.onLine ?? ((line) => process.stdout.write(line + "\n"));
|
|
1572
|
+
const alipay = new AlipayClient({
|
|
1573
|
+
sessionId: this.alipaySessionId,
|
|
1574
|
+
configDir: this.configDir
|
|
1575
|
+
});
|
|
1576
|
+
const result = await alipay.pay402({
|
|
1577
|
+
resourceUrl: executeUrl,
|
|
1578
|
+
requirement,
|
|
1579
|
+
method: "POST",
|
|
1580
|
+
data: bodyJson,
|
|
1581
|
+
onLine,
|
|
1582
|
+
onPaymentPending: options.onPaymentPending,
|
|
1583
|
+
timeoutMs: options.timeoutMs,
|
|
1584
|
+
signal: options.signal
|
|
1585
|
+
});
|
|
1586
|
+
try {
|
|
1587
|
+
const json = JSON.parse(result.body);
|
|
1588
|
+
return json.result ?? json;
|
|
1589
|
+
} catch {
|
|
1590
|
+
return { body: result.body, payment: result.payment, media: result.media };
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
779
1593
|
/**
|
|
780
1594
|
* Handle MPP (Machine Payments Protocol) payment flow
|
|
781
1595
|
* Called when pay() detects WWW-Authenticate header in 402 response
|
|
@@ -1116,7 +1930,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1116
1930
|
}
|
|
1117
1931
|
// --- Config & Wallet Management ---
|
|
1118
1932
|
loadConfig() {
|
|
1119
|
-
const configPath = (0,
|
|
1933
|
+
const configPath = (0, import_path3.join)(this.configDir, "config.json");
|
|
1120
1934
|
if ((0, import_fs2.existsSync)(configPath)) {
|
|
1121
1935
|
const content = (0, import_fs2.readFileSync)(configPath, "utf-8");
|
|
1122
1936
|
return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
|
|
@@ -1125,14 +1939,14 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1125
1939
|
}
|
|
1126
1940
|
saveConfig() {
|
|
1127
1941
|
(0, import_fs2.mkdirSync)(this.configDir, { recursive: true });
|
|
1128
|
-
const configPath = (0,
|
|
1942
|
+
const configPath = (0, import_path3.join)(this.configDir, "config.json");
|
|
1129
1943
|
(0, import_fs2.writeFileSync)(configPath, JSON.stringify(this.config, null, 2));
|
|
1130
1944
|
}
|
|
1131
1945
|
/**
|
|
1132
1946
|
* Load spending data from disk
|
|
1133
1947
|
*/
|
|
1134
1948
|
loadSpending() {
|
|
1135
|
-
const spendingPath = (0,
|
|
1949
|
+
const spendingPath = (0, import_path3.join)(this.configDir, "spending.json");
|
|
1136
1950
|
if ((0, import_fs2.existsSync)(spendingPath)) {
|
|
1137
1951
|
try {
|
|
1138
1952
|
const data = JSON.parse((0, import_fs2.readFileSync)(spendingPath, "utf-8"));
|
|
@@ -1155,7 +1969,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1155
1969
|
*/
|
|
1156
1970
|
saveSpending() {
|
|
1157
1971
|
(0, import_fs2.mkdirSync)(this.configDir, { recursive: true });
|
|
1158
|
-
const spendingPath = (0,
|
|
1972
|
+
const spendingPath = (0, import_path3.join)(this.configDir, "spending.json");
|
|
1159
1973
|
const data = {
|
|
1160
1974
|
date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
|
|
1161
1975
|
amount: this.todaySpending,
|
|
@@ -1164,7 +1978,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1164
1978
|
(0, import_fs2.writeFileSync)(spendingPath, JSON.stringify(data, null, 2));
|
|
1165
1979
|
}
|
|
1166
1980
|
loadWallet() {
|
|
1167
|
-
const walletPath = (0,
|
|
1981
|
+
const walletPath = (0, import_path3.join)(this.configDir, "wallet.json");
|
|
1168
1982
|
if ((0, import_fs2.existsSync)(walletPath)) {
|
|
1169
1983
|
if (process.platform !== "win32") {
|
|
1170
1984
|
try {
|
|
@@ -1194,7 +2008,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1194
2008
|
privateKey: wallet.privateKey,
|
|
1195
2009
|
createdAt: Date.now()
|
|
1196
2010
|
};
|
|
1197
|
-
const walletPath = (0,
|
|
2011
|
+
const walletPath = (0, import_path3.join)(configDir, "wallet.json");
|
|
1198
2012
|
(0, import_fs2.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
|
|
1199
2013
|
const config = {
|
|
1200
2014
|
chain: options.chain,
|
|
@@ -1203,7 +2017,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1203
2017
|
maxPerDay: options.maxPerDay
|
|
1204
2018
|
}
|
|
1205
2019
|
};
|
|
1206
|
-
const configPath = (0,
|
|
2020
|
+
const configPath = (0, import_path3.join)(configDir, "config.json");
|
|
1207
2021
|
(0, import_fs2.writeFileSync)(configPath, JSON.stringify(config, null, 2));
|
|
1208
2022
|
return { address: wallet.address, configDir };
|
|
1209
2023
|
}
|
|
@@ -1442,9 +2256,9 @@ function wrap(fn) {
|
|
|
1442
2256
|
}
|
|
1443
2257
|
function getPackageVersion() {
|
|
1444
2258
|
const candidates = [
|
|
1445
|
-
(0,
|
|
1446
|
-
(0,
|
|
1447
|
-
(0,
|
|
2259
|
+
(0, import_path4.join)(__dirname, "../../package.json"),
|
|
2260
|
+
(0, import_path4.join)(__dirname, "../package.json"),
|
|
2261
|
+
(0, import_path4.join)(process.cwd(), "node_modules/moltspay/package.json")
|
|
1448
2262
|
];
|
|
1449
2263
|
for (const loc of candidates) {
|
|
1450
2264
|
try {
|