moltspay 1.6.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +1 -0
  2. package/dist/cdp/index.js.map +1 -1
  3. package/dist/cdp/index.mjs.map +1 -1
  4. package/dist/{cdp-DeohBe1o.d.ts → cdp-B5PhDUTC.d.ts} +1 -1
  5. package/dist/{cdp-p_eHuQpb.d.mts → cdp-D-5Hg3y_.d.mts} +1 -1
  6. package/dist/chains/index.d.mts +37 -1
  7. package/dist/chains/index.d.ts +37 -1
  8. package/dist/chains/index.js +22 -0
  9. package/dist/chains/index.js.map +1 -1
  10. package/dist/chains/index.mjs +19 -0
  11. package/dist/chains/index.mjs.map +1 -1
  12. package/dist/cli/index.js +1466 -61
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/cli/index.mjs +1466 -61
  15. package/dist/cli/index.mjs.map +1 -1
  16. package/dist/client/index.d.mts +41 -0
  17. package/dist/client/index.d.ts +41 -0
  18. package/dist/client/index.js +824 -10
  19. package/dist/client/index.js.map +1 -1
  20. package/dist/client/index.mjs +824 -10
  21. package/dist/client/index.mjs.map +1 -1
  22. package/dist/client/web/index.d.mts +17 -1
  23. package/dist/client/web/index.mjs +11 -0
  24. package/dist/client/web/index.mjs.map +1 -1
  25. package/dist/facilitators/index.d.mts +161 -4
  26. package/dist/facilitators/index.d.ts +161 -4
  27. package/dist/facilitators/index.js +370 -0
  28. package/dist/facilitators/index.js.map +1 -1
  29. package/dist/facilitators/index.mjs +365 -0
  30. package/dist/facilitators/index.mjs.map +1 -1
  31. package/dist/index.d.mts +77 -2
  32. package/dist/index.d.ts +77 -2
  33. package/dist/index.js +1412 -31
  34. package/dist/index.js.map +1 -1
  35. package/dist/index.mjs +1406 -31
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/mcp/index.js +828 -14
  38. package/dist/mcp/index.js.map +1 -1
  39. package/dist/mcp/index.mjs +828 -14
  40. package/dist/mcp/index.mjs.map +1 -1
  41. package/dist/{registry-OsEO2dOu.d.mts → registry-DIo0WNoO.d.mts} +8 -1
  42. package/dist/{registry-OsEO2dOu.d.ts → registry-DIo0WNoO.d.ts} +8 -1
  43. package/dist/server/index.d.mts +95 -2
  44. package/dist/server/index.d.ts +95 -2
  45. package/dist/server/index.js +554 -14
  46. package/dist/server/index.js.map +1 -1
  47. package/dist/server/index.mjs +554 -14
  48. package/dist/server/index.mjs.map +1 -1
  49. package/dist/verify/index.js.map +1 -1
  50. package/dist/verify/index.mjs.map +1 -1
  51. package/dist/wallet/index.js.map +1 -1
  52. package/dist/wallet/index.mjs.map +1 -1
  53. package/package.json +7 -1
  54. package/schemas/moltspay.services.schema.json +100 -6
  55. package/scripts/postinstall.js +91 -0
package/dist/index.js CHANGED
@@ -38,8 +38,10 @@ __export(index_exports, {
38
38
  FacilitatorRegistry: () => FacilitatorRegistry,
39
39
  MoltsPayClient: () => MoltsPayClient,
40
40
  MoltsPayServer: () => MoltsPayServer,
41
+ alipayLog: () => alipayLog,
41
42
  createRegistry: () => createRegistry,
42
43
  createWallet: () => createWallet,
44
+ getAlipayLogLevel: () => getAlipayLogLevel,
43
45
  getCDPWalletAddress: () => getCDPWalletAddress,
44
46
  getChain: () => getChain,
45
47
  getChainById: () => getChainById,
@@ -51,6 +53,10 @@ __export(index_exports, {
51
53
  listChains: () => listChains,
52
54
  loadCDPWallet: () => loadCDPWallet,
53
55
  loadWallet: () => loadWallet,
56
+ resetIntentCache: () => resetIntentCache,
57
+ resetWalletCache: () => resetWalletCache,
58
+ setAlipayLogLevel: () => setAlipayLogLevel,
59
+ setAlipayLogSink: () => setAlipayLogSink,
54
60
  verifyPayment: () => verifyPayment,
55
61
  waitForTransaction: () => waitForTransaction,
56
62
  walletExists: () => walletExists
@@ -470,6 +476,10 @@ function listChains() {
470
476
  function getChainById(chainId) {
471
477
  return Object.values(CHAINS).find((c) => c.chainId === chainId);
472
478
  }
479
+ var ALIPAY_CHAIN_ID = "alipay";
480
+ function isAlipayChainId(id) {
481
+ return id === ALIPAY_CHAIN_ID;
482
+ }
473
483
  var ERC20_ABI = [
474
484
  "function balanceOf(address owner) view returns (uint256)",
475
485
  "function transfer(address to, uint256 amount) returns (bool)",
@@ -1279,6 +1289,374 @@ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amo
1279
1289
  return transaction;
1280
1290
  }
1281
1291
 
1292
+ // src/facilitators/alipay.ts
1293
+ var import_node_crypto2 = __toESM(require("crypto"));
1294
+
1295
+ // src/facilitators/alipay/rsa2.ts
1296
+ var import_node_crypto = __toESM(require("crypto"));
1297
+ function rsa2Sign(data, privateKeyPem) {
1298
+ const signer = import_node_crypto.default.createSign("RSA-SHA256");
1299
+ signer.update(data, "utf-8");
1300
+ signer.end();
1301
+ return signer.sign(privateKeyPem, "base64");
1302
+ }
1303
+
1304
+ // src/facilitators/alipay/encoding.ts
1305
+ function base64url(input) {
1306
+ return Buffer.from(input, "utf-8").toString("base64url");
1307
+ }
1308
+ function decodeBase64UrlWithPadFix(input) {
1309
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
1310
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
1311
+ return Buffer.from(padded, "base64").toString("utf-8");
1312
+ }
1313
+ function toPem(key, kind) {
1314
+ const trimmed = key.trim();
1315
+ if (trimmed.includes("-----BEGIN")) return trimmed;
1316
+ const label = kind === "PRIVATE" ? "PRIVATE KEY" : "PUBLIC KEY";
1317
+ const body = trimmed.replace(/\s+/g, "").match(/.{1,64}/g)?.join("\n") ?? "";
1318
+ return `-----BEGIN ${label}-----
1319
+ ${body}
1320
+ -----END ${label}-----
1321
+ `;
1322
+ }
1323
+
1324
+ // src/facilitators/alipay/openapi.ts
1325
+ function formatAlipayTimestamp(d = /* @__PURE__ */ new Date()) {
1326
+ const pad = (n) => String(n).padStart(2, "0");
1327
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
1328
+ }
1329
+ function buildSigningString(params) {
1330
+ return Object.keys(params).sort().map((k) => `${k}=${params[k]}`).join("&");
1331
+ }
1332
+ function responseWrapperKey(method) {
1333
+ return `${method.replace(/\./g, "_")}_response`;
1334
+ }
1335
+ async function alipayOpenApiCall(method, bizContent, config) {
1336
+ const publicParams = {
1337
+ app_id: config.app_id,
1338
+ method,
1339
+ format: "JSON",
1340
+ charset: "utf-8",
1341
+ sign_type: config.sign_type ?? "RSA2",
1342
+ timestamp: formatAlipayTimestamp(),
1343
+ version: "1.0",
1344
+ biz_content: JSON.stringify(bizContent)
1345
+ };
1346
+ const signingString = buildSigningString(publicParams);
1347
+ const sign = rsa2Sign(signingString, config.private_key_pem);
1348
+ const body = new URLSearchParams({ ...publicParams, sign }).toString();
1349
+ const response = await fetch(config.gateway_url, {
1350
+ method: "POST",
1351
+ headers: {
1352
+ "Content-Type": "application/x-www-form-urlencoded;charset=utf-8"
1353
+ },
1354
+ body
1355
+ });
1356
+ if (!response.ok) {
1357
+ const text = await response.text().catch(() => "<unreadable>");
1358
+ throw new Error(
1359
+ `Alipay Open API HTTP ${response.status} for ${method}: ${text.slice(0, 500)}`
1360
+ );
1361
+ }
1362
+ const json = await response.json();
1363
+ const wrapperKey = responseWrapperKey(method);
1364
+ const business = json[wrapperKey];
1365
+ if (business === void 0 || business === null || typeof business !== "object") {
1366
+ throw new Error(
1367
+ `Alipay Open API response missing "${wrapperKey}" wrapper for ${method}: ${JSON.stringify(json).slice(0, 500)}`
1368
+ );
1369
+ }
1370
+ return business;
1371
+ }
1372
+
1373
+ // src/facilitators/alipay.ts
1374
+ var ALIPAY_NETWORK = "alipay";
1375
+ var ALIPAY_SCHEME = "alipay-aipay";
1376
+ var ALIPAY_GATEWAY_PROD = "https://openapi.alipay.com/gateway.do";
1377
+ var ALIPAY_AMOUNT_REGEX = /^\d+(\.\d{1,2})?$/;
1378
+ var ALIPAY_PAY_BEFORE_MS = 30 * 60 * 1e3;
1379
+ var ALIPAY_SIGNING_FIELDS = [
1380
+ "amount",
1381
+ "currency",
1382
+ "goods_name",
1383
+ "out_trade_no",
1384
+ "pay_before",
1385
+ "resource_id",
1386
+ "seller_id",
1387
+ "service_id"
1388
+ ];
1389
+ var AlipayFacilitator = class extends BaseFacilitator {
1390
+ name = "alipay";
1391
+ displayName = "Alipay AI \u6536";
1392
+ supportedNetworks = [ALIPAY_NETWORK];
1393
+ config;
1394
+ constructor(config) {
1395
+ super();
1396
+ this.config = {
1397
+ gateway_url: ALIPAY_GATEWAY_PROD,
1398
+ sign_type: "RSA2",
1399
+ ...config
1400
+ };
1401
+ }
1402
+ /**
1403
+ * Build the 402 challenge for a service: signs the 8-field payload with
1404
+ * RSA2, packages the nested `{protocol, method}` JSON as Base64URL for
1405
+ * `Payment-Needed`, and emits the parallel x402 `accepts[]` entry.
1406
+ */
1407
+ async createPaymentRequirements(opts) {
1408
+ if (!ALIPAY_AMOUNT_REGEX.test(opts.priceCny)) {
1409
+ throw new Error(
1410
+ `AlipayFacilitator.createPaymentRequirements: priceCny "${opts.priceCny}" does not match /^\\d+(\\.\\d{1,2})?$/ (unit is \u5143, not \u5206; e.g. "1.00" not "100")`
1411
+ );
1412
+ }
1413
+ const now = /* @__PURE__ */ new Date();
1414
+ const outTradeNo = opts.outTradeNo ?? generateOutTradeNo();
1415
+ const payBefore = formatPayBefore(now);
1416
+ const signedFields = {
1417
+ amount: opts.priceCny,
1418
+ currency: "CNY",
1419
+ goods_name: opts.goodsName,
1420
+ out_trade_no: outTradeNo,
1421
+ pay_before: payBefore,
1422
+ resource_id: opts.resourceId,
1423
+ seller_id: this.config.seller_id,
1424
+ service_id: opts.serviceId
1425
+ };
1426
+ const signingString = ALIPAY_SIGNING_FIELDS.map((k) => `${k}=${signedFields[k]}`).join("&");
1427
+ const seller_signature = rsa2Sign(signingString, this.config.private_key_pem);
1428
+ const challenge = {
1429
+ protocol: {
1430
+ out_trade_no: outTradeNo,
1431
+ amount: signedFields.amount,
1432
+ currency: signedFields.currency,
1433
+ resource_id: signedFields.resource_id,
1434
+ pay_before: payBefore,
1435
+ seller_signature,
1436
+ seller_sign_type: this.config.sign_type ?? "RSA2",
1437
+ seller_unique_id: this.config.seller_id
1438
+ },
1439
+ method: {
1440
+ seller_name: this.config.seller_name,
1441
+ seller_id: this.config.seller_id,
1442
+ seller_app_id: this.config.app_id,
1443
+ goods_name: signedFields.goods_name,
1444
+ seller_unique_id_key: "seller_id",
1445
+ service_id: signedFields.service_id
1446
+ }
1447
+ };
1448
+ const paymentNeededHeader = base64url(JSON.stringify(challenge));
1449
+ const x402Accepts = {
1450
+ scheme: ALIPAY_SCHEME,
1451
+ network: ALIPAY_NETWORK,
1452
+ asset: "CNY",
1453
+ amount: opts.priceCny,
1454
+ payTo: this.config.seller_id,
1455
+ maxTimeoutSeconds: ALIPAY_PAY_BEFORE_MS / 1e3,
1456
+ extra: {
1457
+ payment_needed_header: paymentNeededHeader,
1458
+ out_trade_no: outTradeNo,
1459
+ pay_before: payBefore,
1460
+ service_id: signedFields.service_id
1461
+ }
1462
+ };
1463
+ return { x402Accepts, paymentNeededHeader };
1464
+ }
1465
+ /**
1466
+ * Verify a `Payment-Proof` by calling
1467
+ * `alipay.aipay.agent.payment.verify` against the Alipay Open API.
1468
+ *
1469
+ * The proof is conveyed via `paymentPayload.payload`, which may be:
1470
+ * - a raw Base64URL string (the `Payment-Proof` header value), or
1471
+ * - an object `{ paymentProof: string }` / `{ proofHeader: string }`.
1472
+ *
1473
+ * All failure modes (malformed payload, network errors, Alipay
1474
+ * `code != 10000`) return `{ valid: false, error }`. No exception
1475
+ * escapes, regardless of client-supplied input.
1476
+ */
1477
+ async verify(paymentPayload, _requirements) {
1478
+ try {
1479
+ const proofHeader = extractProofHeader(paymentPayload.payload);
1480
+ const decoded = decodeProof(proofHeader);
1481
+ const response = await alipayOpenApiCall(
1482
+ "alipay.aipay.agent.payment.verify",
1483
+ {
1484
+ payment_proof: decoded.protocol.payment_proof,
1485
+ trade_no: decoded.protocol.trade_no,
1486
+ client_session: decoded.method.client_session
1487
+ },
1488
+ this.getOpenApiConfig()
1489
+ );
1490
+ if (response.code !== "10000") {
1491
+ return {
1492
+ valid: false,
1493
+ error: `alipay verify ${response.code}: ${response.sub_msg ?? response.msg ?? "unknown"}`,
1494
+ details: {
1495
+ code: response.code,
1496
+ sub_code: response.sub_code,
1497
+ sub_msg: response.sub_msg
1498
+ }
1499
+ };
1500
+ }
1501
+ return {
1502
+ valid: true,
1503
+ details: {
1504
+ trade_no: response.trade_no ?? decoded.protocol.trade_no,
1505
+ amount: response.amount,
1506
+ out_trade_no: response.out_trade_no,
1507
+ resource_id: response.resource_id,
1508
+ active: response.active
1509
+ }
1510
+ };
1511
+ } catch (e) {
1512
+ return {
1513
+ valid: false,
1514
+ error: e instanceof Error ? e.message : String(e)
1515
+ };
1516
+ }
1517
+ }
1518
+ /**
1519
+ * Settle by calling `alipay.aipay.agent.fulfillment.confirm` after the
1520
+ * service resource has been returned to the buyer.
1521
+ *
1522
+ * Per the design (see ALIPAY-INTEGRATION-DESIGN.md §5.1, risk row
1523
+ * "履约确认失败"), this is **fire-and-forget**: the caller (registry /
1524
+ * server) is expected to log fulfillment failures but NOT roll back
1525
+ * the already-delivered resource.
1526
+ *
1527
+ * Re-decodes `paymentPayload.payload` to extract `trade_no` (verify
1528
+ * does the same; the redundant Base64URL decode is negligible).
1529
+ */
1530
+ async settle(paymentPayload, _requirements) {
1531
+ try {
1532
+ const proofHeader = extractProofHeader(paymentPayload.payload);
1533
+ const decoded = decodeProof(proofHeader);
1534
+ const tradeNo = decoded.protocol.trade_no;
1535
+ const response = await alipayOpenApiCall(
1536
+ "alipay.aipay.agent.fulfillment.confirm",
1537
+ { trade_no: tradeNo },
1538
+ this.getOpenApiConfig()
1539
+ );
1540
+ if (response.code !== "10000") {
1541
+ return {
1542
+ success: false,
1543
+ transaction: tradeNo,
1544
+ error: `alipay fulfillment ${response.code}: ${response.sub_msg ?? response.msg ?? "unknown"}`,
1545
+ status: "fulfillment_failed"
1546
+ };
1547
+ }
1548
+ return {
1549
+ success: true,
1550
+ transaction: tradeNo,
1551
+ status: "fulfilled"
1552
+ };
1553
+ } catch (e) {
1554
+ return {
1555
+ success: false,
1556
+ error: e instanceof Error ? e.message : String(e)
1557
+ };
1558
+ }
1559
+ }
1560
+ /**
1561
+ * Validate that the configured RSA keys parse and that the Open API
1562
+ * gateway is reachable. Does NOT make a real business API call (would
1563
+ * burn quota and could appear as a legitimate verify attempt in logs).
1564
+ */
1565
+ async healthCheck() {
1566
+ const start = Date.now();
1567
+ try {
1568
+ import_node_crypto2.default.createPrivateKey(this.config.private_key_pem);
1569
+ } catch (e) {
1570
+ return {
1571
+ healthy: false,
1572
+ error: `merchant private_key_pem parse failed: ${e instanceof Error ? e.message : String(e)}`
1573
+ };
1574
+ }
1575
+ try {
1576
+ import_node_crypto2.default.createPublicKey(this.config.alipay_public_key_pem);
1577
+ } catch (e) {
1578
+ return {
1579
+ healthy: false,
1580
+ error: `alipay_public_key_pem parse failed: ${e instanceof Error ? e.message : String(e)}`
1581
+ };
1582
+ }
1583
+ const gatewayUrl = this.config.gateway_url ?? ALIPAY_GATEWAY_PROD;
1584
+ const controller = new AbortController();
1585
+ const timeout = setTimeout(() => controller.abort(), 5e3);
1586
+ const response = await fetch(gatewayUrl, {
1587
+ method: "HEAD",
1588
+ signal: controller.signal
1589
+ }).catch(() => null);
1590
+ clearTimeout(timeout);
1591
+ const latencyMs = Date.now() - start;
1592
+ if (!response) {
1593
+ return { healthy: false, error: `gateway unreachable: ${gatewayUrl}`, latencyMs };
1594
+ }
1595
+ return { healthy: true, latencyMs };
1596
+ }
1597
+ /** Bundle the facilitator config into the shape openapi.ts wants. */
1598
+ getOpenApiConfig() {
1599
+ return {
1600
+ gateway_url: this.config.gateway_url ?? ALIPAY_GATEWAY_PROD,
1601
+ app_id: this.config.app_id,
1602
+ private_key_pem: this.config.private_key_pem,
1603
+ alipay_public_key_pem: this.config.alipay_public_key_pem,
1604
+ sign_type: this.config.sign_type
1605
+ };
1606
+ }
1607
+ };
1608
+ function generateOutTradeNo() {
1609
+ return "VID" + import_node_crypto2.default.randomBytes(22).toString("base64url").slice(0, 29);
1610
+ }
1611
+ function formatPayBefore(now) {
1612
+ const expiry = new Date(now.getTime() + ALIPAY_PAY_BEFORE_MS);
1613
+ return expiry.toISOString().replace(/\.\d{3}Z$/, "Z");
1614
+ }
1615
+ function extractProofHeader(payload) {
1616
+ if (typeof payload === "string") {
1617
+ if (payload.length === 0) {
1618
+ throw new Error("alipay Payment-Proof is empty");
1619
+ }
1620
+ return payload;
1621
+ }
1622
+ if (payload !== null && typeof payload === "object") {
1623
+ const obj = payload;
1624
+ const candidate = obj.paymentProof ?? obj.proofHeader;
1625
+ if (typeof candidate === "string" && candidate.length > 0) {
1626
+ return candidate;
1627
+ }
1628
+ }
1629
+ throw new Error(
1630
+ "alipay payment payload must be a Base64URL string or {paymentProof: string} / {proofHeader: string}"
1631
+ );
1632
+ }
1633
+ function decodeProof(proofHeader) {
1634
+ let parsed;
1635
+ try {
1636
+ parsed = JSON.parse(decodeBase64UrlWithPadFix(proofHeader));
1637
+ } catch (e) {
1638
+ throw new Error(
1639
+ `failed to decode Payment-Proof: ${e instanceof Error ? e.message : String(e)}`
1640
+ );
1641
+ }
1642
+ if (parsed === null || typeof parsed !== "object") {
1643
+ throw new Error("decoded Payment-Proof is not an object");
1644
+ }
1645
+ const obj = parsed;
1646
+ const protocol = obj.protocol;
1647
+ const method = obj.method;
1648
+ if (!protocol || typeof protocol.payment_proof !== "string") {
1649
+ throw new Error("decoded Payment-Proof missing protocol.payment_proof");
1650
+ }
1651
+ if (typeof protocol.trade_no !== "string") {
1652
+ throw new Error("decoded Payment-Proof missing protocol.trade_no");
1653
+ }
1654
+ if (!method || typeof method.client_session !== "string") {
1655
+ throw new Error("decoded Payment-Proof missing method.client_session");
1656
+ }
1657
+ return parsed;
1658
+ }
1659
+
1282
1660
  // src/facilitators/registry.ts
1283
1661
  var import_web33 = require("@solana/web3.js");
1284
1662
  var import_bs58 = __toESM(require("bs58"));
@@ -1303,6 +1681,7 @@ var FacilitatorRegistry = class {
1303
1681
  }
1304
1682
  return new SolanaFacilitator({ feePayerKeypair });
1305
1683
  });
1684
+ this.registerFactory("alipay", (config) => new AlipayFacilitator(config));
1306
1685
  this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
1307
1686
  }
1308
1687
  /**
@@ -1537,6 +1916,11 @@ var PAYMENT_RESPONSE_HEADER = "x-payment-response";
1537
1916
  var MPP_AUTH_HEADER = "authorization";
1538
1917
  var MPP_WWW_AUTH_HEADER = "www-authenticate";
1539
1918
  var MPP_RECEIPT_HEADER = "payment-receipt";
1919
+ var ALIPAY_PAYMENT_NEEDED_HEADER = "payment-needed";
1920
+ var ALIPAY_PAYMENT_PROOF_HEADER = "payment-proof";
1921
+ function headerSafe(value) {
1922
+ return String(value ?? "").replace(/[^\x20-\x7E]/g, (ch) => encodeURIComponent(ch)).replace(/"/g, "%22");
1923
+ }
1540
1924
  var TOKEN_ADDRESSES = {
1541
1925
  "eip155:8453": {
1542
1926
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -1672,6 +2056,8 @@ var MoltsPayServer = class {
1672
2056
  registry;
1673
2057
  networkId;
1674
2058
  useMainnet;
2059
+ /** Alipay AI 收 facilitator instance, set when `provider.alipay` is configured (2.0.0). */
2060
+ alipayFacilitator = null;
1675
2061
  constructor(servicesPath, options = {}) {
1676
2062
  loadEnvFile2();
1677
2063
  const content = (0, import_fs2.readFileSync)(servicesPath, "utf-8");
@@ -1693,7 +2079,38 @@ var MoltsPayServer = class {
1693
2079
  cdp: { useMainnet: this.useMainnet }
1694
2080
  }
1695
2081
  };
2082
+ const providerAlipay = this.manifest.provider.alipay;
2083
+ if (providerAlipay) {
2084
+ try {
2085
+ const baseDir = path2.dirname(servicesPath);
2086
+ const resolvePem = (p, kind) => toPem((0, import_fs2.readFileSync)(path2.isAbsolute(p) ? p : path2.resolve(baseDir, p), "utf-8"), kind);
2087
+ const alipayFacilitatorConfig = {
2088
+ seller_id: providerAlipay.seller_id,
2089
+ app_id: providerAlipay.app_id,
2090
+ seller_name: providerAlipay.seller_name,
2091
+ service_id_default: providerAlipay.service_id_default,
2092
+ private_key_pem: resolvePem(providerAlipay.private_key_path, "PRIVATE"),
2093
+ alipay_public_key_pem: resolvePem(providerAlipay.alipay_public_key_path, "PUBLIC"),
2094
+ gateway_url: providerAlipay.gateway_url,
2095
+ sign_type: providerAlipay.sign_type
2096
+ };
2097
+ facilitatorConfig.config = {
2098
+ ...facilitatorConfig.config,
2099
+ alipay: alipayFacilitatorConfig
2100
+ };
2101
+ facilitatorConfig.fallback = facilitatorConfig.fallback || [];
2102
+ if (facilitatorConfig.primary !== "alipay" && !facilitatorConfig.fallback.includes("alipay")) {
2103
+ facilitatorConfig.fallback.push("alipay");
2104
+ }
2105
+ } catch (err) {
2106
+ throw new Error(`[MoltsPay] Alipay rail configured but key load failed: ${err.message}`);
2107
+ }
2108
+ }
1696
2109
  this.registry = new FacilitatorRegistry(facilitatorConfig);
2110
+ if (providerAlipay) {
2111
+ this.alipayFacilitator = this.registry.get("alipay");
2112
+ console.log(`[MoltsPay] Alipay AI \u6536 rail enabled (seller ${providerAlipay.seller_id})`);
2113
+ }
1697
2114
  const primaryFacilitator = this.registry.get(facilitatorConfig.primary);
1698
2115
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
1699
2116
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
@@ -1829,10 +2246,10 @@ var MoltsPayServer = class {
1829
2246
  writeCorsHeaders(res, origin) {
1830
2247
  res.setHeader("Access-Control-Allow-Origin", origin);
1831
2248
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
1832
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
2249
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization, Payment-Proof");
1833
2250
  res.setHeader(
1834
2251
  "Access-Control-Expose-Headers",
1835
- "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt"
2252
+ "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt, Payment-Needed"
1836
2253
  );
1837
2254
  }
1838
2255
  /**
@@ -1859,7 +2276,8 @@ var MoltsPayServer = class {
1859
2276
  if (url.pathname === "/execute" && req.method === "POST") {
1860
2277
  const body = await this.readBody(req);
1861
2278
  const paymentHeader = req.headers[PAYMENT_HEADER];
1862
- return await this.handleExecute(body, paymentHeader, res);
2279
+ const proofHeader = req.headers[ALIPAY_PAYMENT_PROOF_HEADER];
2280
+ return await this.handleExecute(body, paymentHeader, res, proofHeader);
1863
2281
  }
1864
2282
  if (url.pathname === "/proxy" && req.method === "POST") {
1865
2283
  const clientIP = req.headers["x-real-ip"] || req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket.remoteAddress || "";
@@ -1877,7 +2295,8 @@ var MoltsPayServer = class {
1877
2295
  const body = req.method === "POST" ? await this.readBody(req) : {};
1878
2296
  const authHeader = req.headers[MPP_AUTH_HEADER];
1879
2297
  const x402Header = req.headers[PAYMENT_HEADER];
1880
- return await this.handleMPPRequest(skill, body, authHeader, x402Header, res);
2298
+ const proofHeader = req.headers[ALIPAY_PAYMENT_PROOF_HEADER];
2299
+ return await this.handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader);
1881
2300
  }
1882
2301
  this.sendJson(res, 404, { error: "Not found" });
1883
2302
  } catch (err) {
@@ -1974,7 +2393,7 @@ var MoltsPayServer = class {
1974
2393
  /**
1975
2394
  * POST /execute - Execute service with x402 payment
1976
2395
  */
1977
- async handleExecute(body, paymentHeader, res) {
2396
+ async handleExecute(body, paymentHeader, res, proofHeader) {
1978
2397
  const { service, params } = body;
1979
2398
  if (!service) {
1980
2399
  return this.sendJson(res, 400, { error: "Missing service" });
@@ -1988,6 +2407,15 @@ var MoltsPayServer = class {
1988
2407
  return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
1989
2408
  }
1990
2409
  }
2410
+ if (proofHeader) {
2411
+ const alipayPayment = {
2412
+ x402Version: X402_VERSION2,
2413
+ scheme: ALIPAY_SCHEME,
2414
+ network: ALIPAY_NETWORK,
2415
+ payload: proofHeader
2416
+ };
2417
+ return this.handleAlipayExecute(skill, params || {}, alipayPayment, res);
2418
+ }
1991
2419
  if (!paymentHeader) {
1992
2420
  return this.sendPaymentRequired(skill.config, res);
1993
2421
  }
@@ -1998,6 +2426,11 @@ var MoltsPayServer = class {
1998
2426
  } catch {
1999
2427
  return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
2000
2428
  }
2429
+ const payScheme = payment.accepted?.scheme || payment.scheme;
2430
+ const payNetwork = payment.accepted?.network || payment.network;
2431
+ if (payScheme === ALIPAY_SCHEME || (payNetwork ? isAlipayChainId(payNetwork) : false)) {
2432
+ return this.handleAlipayExecute(skill, params || {}, payment, res);
2433
+ }
2001
2434
  const validation = this.validatePayment(payment, skill.config);
2002
2435
  if (!validation.valid) {
2003
2436
  return this.sendJson(res, 402, { error: validation.error });
@@ -2090,13 +2523,111 @@ var MoltsPayServer = class {
2090
2523
  payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
2091
2524
  }, responseHeaders);
2092
2525
  }
2526
+ /**
2527
+ * Execute a service paid via the Alipay AI 收 fiat rail (2.0.0).
2528
+ *
2529
+ * Differs from the EVM/SVM path: no token detection, no EIP-3009/permit
2530
+ * validation. Verify hits the Alipay Open API (`payment.verify`). Settlement
2531
+ * (`fulfillment.confirm`) is FIRE-AND-FORGET per ALIPAY-INTEGRATION-DESIGN
2532
+ * §5.1: a confirm failure is logged but does NOT fail the already-delivered
2533
+ * response (the buyer's payment proof was already verified).
2534
+ */
2535
+ async handleAlipayExecute(skill, params, payment, res) {
2536
+ if (!this.alipayFacilitator) {
2537
+ return this.sendJson(res, 402, { error: "Alipay rail not configured on this server" });
2538
+ }
2539
+ const requirements = {
2540
+ scheme: ALIPAY_SCHEME,
2541
+ network: ALIPAY_NETWORK,
2542
+ asset: "CNY",
2543
+ amount: skill.config.alipay?.price_cny || "0",
2544
+ payTo: this.manifest.provider.alipay?.seller_id || "",
2545
+ maxTimeoutSeconds: 1800
2546
+ };
2547
+ console.log(`[MoltsPay] Verifying Alipay payment...`);
2548
+ const verifyResult = await this.registry.verify(payment, requirements);
2549
+ if (!verifyResult.valid) {
2550
+ return this.sendJson(res, 402, {
2551
+ error: `Payment verification failed: ${verifyResult.error}`,
2552
+ facilitator: verifyResult.facilitator
2553
+ });
2554
+ }
2555
+ console.log(`[MoltsPay] Alipay payment verified by ${verifyResult.facilitator}`);
2556
+ const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
2557
+ console.log(`[MoltsPay] Executing skill: ${skill.id} (timeout: ${timeoutSeconds}s)`);
2558
+ let result;
2559
+ try {
2560
+ result = await Promise.race([
2561
+ skill.handler(params),
2562
+ new Promise(
2563
+ (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
2564
+ )
2565
+ ]);
2566
+ } catch (err) {
2567
+ console.error("[MoltsPay] Skill execution failed:", err.message);
2568
+ return this.sendJson(res, 500, {
2569
+ error: "Service execution failed",
2570
+ message: err.message
2571
+ });
2572
+ }
2573
+ let settlement;
2574
+ try {
2575
+ settlement = await this.registry.settle(payment, requirements);
2576
+ if (settlement.success) {
2577
+ console.log(`[MoltsPay] Alipay fulfillment confirmed: ${settlement.transaction}`);
2578
+ } else {
2579
+ console.error(`[MoltsPay] Alipay fulfillment confirm failed (non-fatal): ${settlement.error}`);
2580
+ }
2581
+ } catch (err) {
2582
+ console.error(`[MoltsPay] Alipay fulfillment confirm threw (non-fatal): ${err.message}`);
2583
+ settlement = { success: false, error: err.message, facilitator: "alipay" };
2584
+ }
2585
+ const responseHeaders = {};
2586
+ if (settlement.success) {
2587
+ responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(JSON.stringify({
2588
+ success: true,
2589
+ transaction: settlement.transaction,
2590
+ network: ALIPAY_NETWORK,
2591
+ facilitator: settlement.facilitator
2592
+ })).toString("base64");
2593
+ }
2594
+ this.sendJson(res, 200, {
2595
+ success: true,
2596
+ result,
2597
+ payment: settlement.success ? { transaction: settlement.transaction, status: "fulfilled", facilitator: settlement.facilitator } : { status: "delivered_unconfirmed", error: settlement.error }
2598
+ }, responseHeaders);
2599
+ }
2600
+ /**
2601
+ * Build the Alipay 402 challenge for a service, or null when the alipay rail
2602
+ * isn't configured for this server or this service. Returns the x402
2603
+ * `accepts[]` entry plus the Base64URL `Payment-Needed` header value so the
2604
+ * 402 responders can dual-emit both the x402 and legacy alipay-bot formats.
2605
+ */
2606
+ async buildAlipayChallenge(config) {
2607
+ if (!this.alipayFacilitator || !config.alipay) return null;
2608
+ try {
2609
+ const req = await this.alipayFacilitator.createPaymentRequirements({
2610
+ serviceId: config.alipay.service_id || this.manifest.provider.alipay.service_id_default,
2611
+ priceCny: config.alipay.price_cny,
2612
+ goodsName: config.alipay.goods_name,
2613
+ resourceId: `/execute?service=${config.id}`
2614
+ });
2615
+ return { accepts: req.x402Accepts, paymentNeededHeader: req.paymentNeededHeader };
2616
+ } catch (err) {
2617
+ console.error(`[MoltsPay] Alipay challenge build failed for ${config.id}: ${err.message}`);
2618
+ return null;
2619
+ }
2620
+ }
2093
2621
  /**
2094
2622
  * Handle MPP (Machine Payments Protocol) request
2095
2623
  * Supports both x402 and MPP protocols on service endpoints
2096
2624
  */
2097
- async handleMPPRequest(skill, body, authHeader, x402Header, res) {
2625
+ async handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader) {
2098
2626
  const config = skill.config;
2099
2627
  const params = body || {};
2628
+ if (proofHeader) {
2629
+ return await this.handleExecute({ service: config.id, params }, void 0, res, proofHeader);
2630
+ }
2100
2631
  if (x402Header) {
2101
2632
  return await this.handleExecute({ service: config.id, params }, x402Header, res);
2102
2633
  }
@@ -2202,7 +2733,7 @@ var MoltsPayServer = class {
2202
2733
  /**
2203
2734
  * Return 402 with both x402 and MPP payment requirements
2204
2735
  */
2205
- sendMPPPaymentRequired(config, res) {
2736
+ async sendMPPPaymentRequired(config, res) {
2206
2737
  const acceptedTokens = getAcceptedCurrencies(config);
2207
2738
  const providerChains = this.getProviderChains();
2208
2739
  const accepts = [];
@@ -2213,6 +2744,10 @@ var MoltsPayServer = class {
2213
2744
  }
2214
2745
  }
2215
2746
  }
2747
+ const alipayChallenge = await this.buildAlipayChallenge(config);
2748
+ if (alipayChallenge) {
2749
+ accepts.push(alipayChallenge.accepts);
2750
+ }
2216
2751
  const x402PaymentRequired = {
2217
2752
  x402Version: X402_VERSION2,
2218
2753
  accepts,
@@ -2240,7 +2775,7 @@ var MoltsPayServer = class {
2240
2775
  };
2241
2776
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
2242
2777
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
2243
- mppWwwAuth = `Payment id="${challengeId}", realm="${this.manifest.provider.name}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
2778
+ mppWwwAuth = `Payment id="${challengeId}", realm="${headerSafe(this.manifest.provider.name)}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${headerSafe(config.name)}", expires="${expiresAt}"`;
2244
2779
  }
2245
2780
  const headers = {
2246
2781
  "Content-Type": "application/problem+json",
@@ -2249,6 +2784,9 @@ var MoltsPayServer = class {
2249
2784
  if (mppWwwAuth) {
2250
2785
  headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
2251
2786
  }
2787
+ if (alipayChallenge) {
2788
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
2789
+ }
2252
2790
  res.writeHead(402, headers);
2253
2791
  res.end(JSON.stringify({
2254
2792
  type: "https://paymentauth.org/problems/payment-required",
@@ -2275,7 +2813,7 @@ var MoltsPayServer = class {
2275
2813
  * Return 402 with x402 payment requirements (v2 format)
2276
2814
  * Includes requirements for all chains and all accepted currencies
2277
2815
  */
2278
- sendPaymentRequired(config, res) {
2816
+ async sendPaymentRequired(config, res) {
2279
2817
  const acceptedTokens = getAcceptedCurrencies(config);
2280
2818
  const providerChains = this.getProviderChains();
2281
2819
  const accepts = [];
@@ -2286,6 +2824,10 @@ var MoltsPayServer = class {
2286
2824
  }
2287
2825
  }
2288
2826
  }
2827
+ const alipayChallenge = await this.buildAlipayChallenge(config);
2828
+ if (alipayChallenge) {
2829
+ accepts.push(alipayChallenge.accepts);
2830
+ }
2289
2831
  const acceptedChains = providerChains.map((c) => {
2290
2832
  if (c.network === "eip155:8453") return "base";
2291
2833
  if (c.network === "eip155:137") return "polygon";
@@ -2303,10 +2845,14 @@ var MoltsPayServer = class {
2303
2845
  }
2304
2846
  };
2305
2847
  const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
2306
- res.writeHead(402, {
2848
+ const headers = {
2307
2849
  "Content-Type": "application/json",
2308
2850
  [PAYMENT_REQUIRED_HEADER]: encoded
2309
- });
2851
+ };
2852
+ if (alipayChallenge) {
2853
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
2854
+ }
2855
+ res.writeHead(402, headers);
2310
2856
  res.end(JSON.stringify({
2311
2857
  error: "Payment required",
2312
2858
  message: `Service requires $${config.price} ${config.currency}`,
@@ -2413,12 +2959,12 @@ var MoltsPayServer = class {
2413
2959
  return accepted.includes(token);
2414
2960
  }
2415
2961
  async readBody(req) {
2416
- return new Promise((resolve, reject) => {
2962
+ return new Promise((resolve2, reject) => {
2417
2963
  let body = "";
2418
2964
  req.on("data", (chunk) => body += chunk);
2419
2965
  req.on("end", () => {
2420
2966
  try {
2421
- resolve(body ? JSON.parse(body) : {});
2967
+ resolve2(body ? JSON.parse(body) : {});
2422
2968
  } catch {
2423
2969
  reject(new Error("Invalid JSON"));
2424
2970
  }
@@ -2676,7 +3222,7 @@ var MoltsPayServer = class {
2676
3222
  };
2677
3223
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
2678
3224
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
2679
- const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
3225
+ const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${headerSafe(config.name)}", expires="${expiresAt}"`;
2680
3226
  res.writeHead(402, {
2681
3227
  "Content-Type": "application/problem+json",
2682
3228
  [MPP_WWW_AUTH_HEADER]: wwwAuth
@@ -2843,8 +3389,8 @@ var MoltsPayServer = class {
2843
3389
 
2844
3390
  // src/client/node/index.ts
2845
3391
  var import_fs4 = require("fs");
2846
- var import_os2 = require("os");
2847
- var import_path2 = require("path");
3392
+ var import_os3 = require("os");
3393
+ var import_path3 = require("path");
2848
3394
  var import_ethers3 = require("ethers");
2849
3395
 
2850
3396
  // src/wallet/solana.ts
@@ -2903,6 +3449,16 @@ function networkToChainName(network) {
2903
3449
  // src/client/core/base64.ts
2904
3450
  var BufferCtor = globalThis.Buffer;
2905
3451
 
3452
+ // src/client/core/errors.ts
3453
+ var MoltsPayError = class extends Error {
3454
+ code;
3455
+ constructor(code, message) {
3456
+ super(message);
3457
+ this.name = "MoltsPayError";
3458
+ this.code = code;
3459
+ }
3460
+ };
3461
+
2906
3462
  // src/client/core/eip3009.ts
2907
3463
  var EIP3009_TYPES = {
2908
3464
  TransferWithAuthorization: [
@@ -3039,6 +3595,748 @@ function findChainByChainId(chainId) {
3039
3595
  return void 0;
3040
3596
  }
3041
3597
 
3598
+ // src/client/alipay/index.ts
3599
+ var import_node_crypto3 = require("crypto");
3600
+ var import_promises = require("fs/promises");
3601
+ var import_os2 = require("os");
3602
+ var import_path2 = require("path");
3603
+
3604
+ // src/client/alipay/cli.ts
3605
+ var import_child_process = require("child_process");
3606
+
3607
+ // src/client/alipay/log.ts
3608
+ var RANK = { off: 0, info: 1, debug: 2 };
3609
+ function normalizeLevel(v) {
3610
+ const s = (v ?? "").toLowerCase();
3611
+ return s === "info" || s === "debug" ? s : "off";
3612
+ }
3613
+ var level = normalizeLevel(process.env.MOLTSPAY_ALIPAY_LOG);
3614
+ var sink = (line) => {
3615
+ process.stderr.write(line + "\n");
3616
+ };
3617
+ function setAlipayLogLevel(next) {
3618
+ level = next;
3619
+ }
3620
+ function getAlipayLogLevel() {
3621
+ return level;
3622
+ }
3623
+ function setAlipayLogSink(fn) {
3624
+ sink = fn;
3625
+ }
3626
+ function enabledFor(want) {
3627
+ return RANK[level] >= RANK[want];
3628
+ }
3629
+ function render(event, fields) {
3630
+ const parts = ["[moltspay:alipay]", String(fields.ts), event];
3631
+ if (fields.flow) parts.push(`flow=${fields.flow}`);
3632
+ if (fields.step) parts.push(`step=${fields.step}`);
3633
+ if (typeof fields.ms === "number") parts.push(`${fields.ms}ms`);
3634
+ for (const [k, v] of Object.entries(fields)) {
3635
+ if (["ts", "level", "event", "flow", "step", "ms"].includes(k)) continue;
3636
+ parts.push(`${k}=${typeof v === "string" ? v : JSON.stringify(v)}`);
3637
+ }
3638
+ return parts.join(" ");
3639
+ }
3640
+ function emit(lvl, event, fields) {
3641
+ if (!enabledFor(lvl)) return;
3642
+ const full = { ts: (/* @__PURE__ */ new Date()).toISOString(), level: lvl, event, ...fields };
3643
+ try {
3644
+ sink(render(event, full), full);
3645
+ } catch {
3646
+ }
3647
+ }
3648
+ var alipayLog = {
3649
+ info: (event, fields = {}) => emit("info", event, fields),
3650
+ debug: (event, fields = {}) => emit("debug", event, fields),
3651
+ /** True if anything at all would be logged (cheap guard for hot paths). */
3652
+ enabled: () => level !== "off"
3653
+ };
3654
+ async function timeStep(step, flow, fn) {
3655
+ if (!alipayLog.enabled()) return fn();
3656
+ const t0 = Date.now();
3657
+ alipayLog.debug("step.start", { flow, step });
3658
+ try {
3659
+ const out = await fn();
3660
+ alipayLog.info("step.end", { flow, step, ms: Date.now() - t0, ok: true });
3661
+ return out;
3662
+ } catch (err) {
3663
+ alipayLog.info("step.end", {
3664
+ flow,
3665
+ step,
3666
+ ms: Date.now() - t0,
3667
+ ok: false,
3668
+ err: err instanceof Error ? err.message : String(err)
3669
+ });
3670
+ throw err;
3671
+ }
3672
+ }
3673
+
3674
+ // src/client/alipay/cli.ts
3675
+ var ALLOWED_ENV = /* @__PURE__ */ new Set([
3676
+ "AIPAY_OUTPUT_CHANNEL",
3677
+ "AIPAY_SESSION_ID",
3678
+ "AIPAY_FRAMEWORK",
3679
+ "AIPAY_MODEL",
3680
+ "AIPAY_OS",
3681
+ // Minimal survival set for spawn to find the binary and a home dir.
3682
+ "PATH",
3683
+ "HOME"
3684
+ ]);
3685
+ function filterEnv(env) {
3686
+ return Object.fromEntries(
3687
+ Object.entries(env).filter(([k]) => ALLOWED_ENV.has(k))
3688
+ );
3689
+ }
3690
+ function makeLineSplitter(onLine) {
3691
+ let buf = "";
3692
+ return (chunk) => {
3693
+ buf += chunk.toString("utf-8");
3694
+ let nl;
3695
+ while ((nl = buf.indexOf("\n")) !== -1) {
3696
+ onLine(buf.slice(0, nl));
3697
+ buf = buf.slice(nl + 1);
3698
+ }
3699
+ };
3700
+ }
3701
+ function profileEnv(step, flow) {
3702
+ const want = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE;
3703
+ const hook = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE_HOOK;
3704
+ if (!want || !hook) return {};
3705
+ const steps = want.split(",").map((s) => s.trim());
3706
+ if (!steps.includes("all") && !steps.includes(step)) return {};
3707
+ const dir = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE_DIR || "/tmp";
3708
+ const safe = `${flow ?? "noflow"}-${step}-${Date.now()}`.replace(/[^a-zA-Z0-9._-]/g, "_");
3709
+ const out = `${dir}/cli-profile-${safe}.json`;
3710
+ alipayLog.info("cli.profile", { flow, step, out });
3711
+ return {
3712
+ NODE_OPTIONS: `--require=${hook}`,
3713
+ MOLTSPAY_CLI_PROFILE_OUT: out
3714
+ };
3715
+ }
3716
+ var runCli = (args, opts = {}) => {
3717
+ const bin = opts.bin ?? "alipay-bot";
3718
+ const step = opts.step ?? args[0] ?? "(no-arg)";
3719
+ const startedAt = Date.now();
3720
+ const lines = [];
3721
+ const collect = (line) => {
3722
+ lines.push(line);
3723
+ alipayLog.debug("cli.line", {
3724
+ flow: opts.flow,
3725
+ step,
3726
+ ms: Date.now() - startedAt,
3727
+ preview: line.slice(0, 120)
3728
+ });
3729
+ opts.onLine?.(line);
3730
+ };
3731
+ alipayLog.debug("step.start", { flow: opts.flow, step });
3732
+ return new Promise((resolve2, reject) => {
3733
+ const child = (0, import_child_process.spawn)(bin, args, {
3734
+ env: { ...filterEnv(process.env), ...profileEnv(step, opts.flow), ...opts.env ?? {} }
3735
+ });
3736
+ if (opts.signal) {
3737
+ if (opts.signal.aborted) child.kill("SIGTERM");
3738
+ opts.signal.addEventListener("abort", () => child.kill("SIGTERM"), { once: true });
3739
+ }
3740
+ let sawByte = false;
3741
+ const onChunk = (stream) => (chunk) => {
3742
+ const ms = Date.now() - startedAt;
3743
+ if (!sawByte) {
3744
+ sawByte = true;
3745
+ alipayLog.debug("cli.firstbyte", { flow: opts.flow, step, ms });
3746
+ }
3747
+ alipayLog.debug("cli.chunk", { flow: opts.flow, step, ms, stream, bytes: chunk.length });
3748
+ };
3749
+ const onStdout = makeLineSplitter(collect);
3750
+ const onStderr = makeLineSplitter(collect);
3751
+ child.stdout?.on("data", onChunk("out"));
3752
+ child.stderr?.on("data", onChunk("err"));
3753
+ child.stdout?.on("data", onStdout);
3754
+ child.stderr?.on("data", onStderr);
3755
+ child.on("error", (err) => {
3756
+ alipayLog.info("cli.exit", {
3757
+ flow: opts.flow,
3758
+ step,
3759
+ ms: Date.now() - startedAt,
3760
+ ok: false,
3761
+ err: err.message
3762
+ });
3763
+ reject(err);
3764
+ });
3765
+ child.on("close", (code) => {
3766
+ alipayLog.info("cli.exit", {
3767
+ flow: opts.flow,
3768
+ step,
3769
+ ms: Date.now() - startedAt,
3770
+ ok: (code ?? 1) === 0,
3771
+ code: code ?? 1
3772
+ });
3773
+ resolve2({ exitCode: code ?? 1, lines });
3774
+ });
3775
+ });
3776
+ };
3777
+
3778
+ // src/client/alipay/install.ts
3779
+ var import_child_process2 = require("child_process");
3780
+ var import_util = require("util");
3781
+
3782
+ // src/client/alipay/errors.ts
3783
+ var AlipayCliNotFoundError = class extends MoltsPayError {
3784
+ constructor(message) {
3785
+ super("ALIPAY_CLI_NOT_FOUND", message);
3786
+ this.name = "AlipayCliNotFoundError";
3787
+ }
3788
+ };
3789
+ var AlipayCliVersionError = class extends MoltsPayError {
3790
+ constructor(message) {
3791
+ super("ALIPAY_CLI_VERSION", message);
3792
+ this.name = "AlipayCliVersionError";
3793
+ }
3794
+ };
3795
+ var NeedsWalletSetupError = class extends MoltsPayError {
3796
+ constructor(message = "Alipay wallet not set up. Run: moltspay alipay apply") {
3797
+ super("ALIPAY_NEEDS_WALLET_SETUP", message);
3798
+ this.name = "NeedsWalletSetupError";
3799
+ }
3800
+ };
3801
+ var AlipayPaymentRejectedError = class extends MoltsPayError {
3802
+ constructor(message) {
3803
+ super("ALIPAY_PAYMENT_REJECTED", message);
3804
+ this.name = "AlipayPaymentRejectedError";
3805
+ }
3806
+ };
3807
+ var AlipayPaymentTimeoutError = class extends MoltsPayError {
3808
+ constructor(message) {
3809
+ super("ALIPAY_PAYMENT_TIMEOUT", message);
3810
+ this.name = "AlipayPaymentTimeoutError";
3811
+ }
3812
+ };
3813
+ var AlipayProtocolError = class extends MoltsPayError {
3814
+ constructor(message) {
3815
+ super("ALIPAY_PROTOCOL", message);
3816
+ this.name = "AlipayProtocolError";
3817
+ }
3818
+ };
3819
+ var UnsupportedRailError = class extends MoltsPayError {
3820
+ constructor(rail, message) {
3821
+ super("UNSUPPORTED_RAIL", message ?? `Rail not supported by server: ${rail}`);
3822
+ this.rail = rail;
3823
+ this.name = "UnsupportedRailError";
3824
+ }
3825
+ };
3826
+
3827
+ // src/client/alipay/install.ts
3828
+ var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
3829
+ var MIN_CLI_VERSION = "0.3.15";
3830
+ function semverLt(a, b) {
3831
+ const pa = a.split(".").map((n) => parseInt(n, 10));
3832
+ const pb = b.split(".").map((n) => parseInt(n, 10));
3833
+ for (let i = 0; i < 3; i++) {
3834
+ const x = pa[i] ?? 0;
3835
+ const y = pb[i] ?? 0;
3836
+ if (x !== y) return x < y;
3837
+ }
3838
+ return false;
3839
+ }
3840
+ function parseVersion(stdout) {
3841
+ return stdout.match(/v?(\d+\.\d+\.\d+)/)?.[1] ?? null;
3842
+ }
3843
+ var defaultGetVersion = async () => {
3844
+ const { stdout } = await execFileAsync("alipay-bot", ["--version"]);
3845
+ return stdout;
3846
+ };
3847
+ async function ensureCliUncached(getVersion) {
3848
+ let stdout;
3849
+ try {
3850
+ stdout = await getVersion();
3851
+ } catch (e) {
3852
+ if (e?.code === "ENOENT") {
3853
+ throw new AlipayCliNotFoundError(
3854
+ "alipay-bot not installed. Run: npx -y @alipay/agent-payment install-cli"
3855
+ );
3856
+ }
3857
+ throw e;
3858
+ }
3859
+ const version = parseVersion(stdout);
3860
+ if (!version || semverLt(version, MIN_CLI_VERSION)) {
3861
+ throw new AlipayCliVersionError(
3862
+ `alipay-bot ${version ?? "?"} found, need \u2265 ${MIN_CLI_VERSION}. Run: npx -y @alipay/agent-payment@latest update`
3863
+ );
3864
+ }
3865
+ return version;
3866
+ }
3867
+ var cachedVersion = null;
3868
+ var inflight = null;
3869
+ async function ensureCli(getVersion = defaultGetVersion) {
3870
+ if (getVersion !== defaultGetVersion) {
3871
+ return ensureCliUncached(getVersion);
3872
+ }
3873
+ if (cachedVersion) return cachedVersion;
3874
+ if (!inflight) {
3875
+ inflight = ensureCliUncached(getVersion).then((v) => {
3876
+ cachedVersion = v;
3877
+ return v;
3878
+ }).finally(() => {
3879
+ inflight = null;
3880
+ });
3881
+ }
3882
+ return inflight;
3883
+ }
3884
+
3885
+ // src/client/alipay/poll.ts
3886
+ var POLL_INTERVAL_MS = 3e3;
3887
+ var POLL_MAX_INFLIGHT = 2;
3888
+ function resolveMaxInflight(override) {
3889
+ if (override !== void 0) return Math.max(1, Math.floor(override));
3890
+ const raw = process.env.MOLTSPAY_ALIPAY_POLL_MAX_INFLIGHT;
3891
+ if (raw !== void 0 && raw.trim() !== "") {
3892
+ const n = Number(raw);
3893
+ if (Number.isFinite(n) && n >= 1) return Math.floor(n);
3894
+ }
3895
+ return POLL_MAX_INFLIGHT;
3896
+ }
3897
+ function resolveLaunchGap(override) {
3898
+ if (override !== void 0) return Math.max(0, override);
3899
+ const raw = process.env.MOLTSPAY_ALIPAY_POLL_LAUNCH_MS;
3900
+ if (raw !== void 0 && raw.trim() !== "") {
3901
+ const n = Number(raw);
3902
+ if (Number.isFinite(n) && n >= 0) return n;
3903
+ }
3904
+ return POLL_INTERVAL_MS;
3905
+ }
3906
+ function parseStatus(lines) {
3907
+ const raw = lines.join("\n").trim();
3908
+ try {
3909
+ const json = JSON.parse(raw);
3910
+ if (json && typeof json === "object") {
3911
+ if (typeof json.success === "boolean") {
3912
+ if (json.success) return "paid";
3913
+ const err = String(json.errorCode ?? "").toUpperCase();
3914
+ if (/UNPAID|WAIT|PENDING|PROCESS|NOTPAY/.test(err)) return "pending";
3915
+ if (/CLOSED|CANCEL|FAIL|REJECT|REFUSE|TIMEOUT|EXPIRE/.test(err)) return "rejected";
3916
+ return "pending";
3917
+ }
3918
+ if (typeof json.code !== "undefined") {
3919
+ if (Number(json.code) === 200) return "paid";
3920
+ const msg = `${json.message ?? ""}${json.reason ?? ""}`;
3921
+ if (/关闭|失败|拒绝|取消|超时|已撤销/.test(msg)) return "rejected";
3922
+ return "pending";
3923
+ }
3924
+ if (typeof json.body === "string") {
3925
+ return classifyReportText(json.body);
3926
+ }
3927
+ }
3928
+ } catch {
3929
+ }
3930
+ return classifyReportText(raw);
3931
+ }
3932
+ function classifyReportText(s) {
3933
+ const text = s.toUpperCase();
3934
+ if (/查询支付状态成功并获取资源/.test(s) || /资源响应状态[^0-9\n]{0,8}200/.test(s) || /TRADE_SUCCESS|TRADE_FINISHED|"STATUS":\s*"FULFILLED"/.test(text)) return "paid";
3935
+ if (/TRADE_CLOSED|REJECTED|REFUSE|CANCEL/.test(text) || /交易关闭|支付失败|已拒绝|已取消|已撤销|交易超时/.test(s)) return "rejected";
3936
+ if (/WAIT_BUYER_PAY|UNPAID|PENDING|WAITING/.test(text) || /交易未支付|等待付款|待支付|未支付/.test(s)) return "pending";
3937
+ return "unknown";
3938
+ }
3939
+ function defaultSleep(ms, signal) {
3940
+ return new Promise((resolve2, reject) => {
3941
+ if (signal?.aborted) return reject(new Error("aborted"));
3942
+ const t = setTimeout(resolve2, ms);
3943
+ signal?.addEventListener(
3944
+ "abort",
3945
+ () => {
3946
+ clearTimeout(t);
3947
+ reject(new Error("aborted"));
3948
+ },
3949
+ { once: true }
3950
+ );
3951
+ });
3952
+ }
3953
+ var LAUNCH = /* @__PURE__ */ Symbol("launch");
3954
+ async function pollUntil(tradeNo, resourceUrl, opts) {
3955
+ const runner = opts.runner ?? runCli;
3956
+ const sleep = opts.sleep ?? defaultSleep;
3957
+ const now = opts.now ?? Date.now;
3958
+ const maxInflight = resolveMaxInflight(opts.maxInflight);
3959
+ const launchGap = resolveLaunchGap(opts.launchIntervalMs);
3960
+ const args = [
3961
+ "402-query-payment-status",
3962
+ "-t",
3963
+ tradeNo,
3964
+ "-r",
3965
+ resourceUrl,
3966
+ ...opts.method ? ["-m", opts.method] : [],
3967
+ ...opts.data ? ["-d", opts.data] : []
3968
+ ];
3969
+ const internal = new AbortController();
3970
+ const signal = opts.signal ? AbortSignal.any([opts.signal, internal.signal]) : internal.signal;
3971
+ let tick = 0;
3972
+ let lastLaunch = -Infinity;
3973
+ const inflight2 = /* @__PURE__ */ new Set();
3974
+ const launch = () => {
3975
+ tick += 1;
3976
+ const myTick = tick;
3977
+ lastLaunch = now();
3978
+ const run = (async () => {
3979
+ const { lines } = await runner(args, {
3980
+ signal,
3981
+ step: "query-payment-status",
3982
+ flow: tradeNo
3983
+ });
3984
+ const status = parseStatus(lines);
3985
+ alipayLog.debug("poll.tick", { flow: tradeNo, tick: myTick, status });
3986
+ return { status, lines };
3987
+ })();
3988
+ const tracked = run.finally(() => {
3989
+ inflight2.delete(tracked);
3990
+ });
3991
+ inflight2.add(tracked);
3992
+ };
3993
+ try {
3994
+ for (; ; ) {
3995
+ if (opts.signal?.aborted) throw new Error("aborted");
3996
+ const expired = now() >= opts.deadline;
3997
+ if (expired && inflight2.size === 0) {
3998
+ throw new AlipayPaymentTimeoutError(
3999
+ `Payment ${tradeNo} not completed before pay_before deadline`
4000
+ );
4001
+ }
4002
+ while (!expired && inflight2.size < maxInflight && now() - lastLaunch >= launchGap) {
4003
+ launch();
4004
+ }
4005
+ const waiters = [...inflight2];
4006
+ if (!expired && inflight2.size < maxInflight) {
4007
+ const untilNext = Math.max(0, launchGap - (now() - lastLaunch));
4008
+ waiters.push(sleep(untilNext, signal).then(() => LAUNCH, () => LAUNCH));
4009
+ }
4010
+ const winner = await Promise.race(waiters);
4011
+ if (winner === LAUNCH) continue;
4012
+ if (winner.status === "paid") return { status: "paid", lines: winner.lines };
4013
+ if (winner.status === "rejected") {
4014
+ throw new AlipayPaymentRejectedError(`Payment ${tradeNo} was rejected`);
4015
+ }
4016
+ }
4017
+ } finally {
4018
+ internal.abort();
4019
+ for (const p of inflight2) p.catch(() => void 0);
4020
+ }
4021
+ }
4022
+
4023
+ // src/client/alipay/index.ts
4024
+ var TRADE_NO_RE = /^\d{32}$/;
4025
+ function resolveSessionId(explicit, envSession) {
4026
+ return explicit ?? envSession ?? (0, import_node_crypto3.randomUUID)();
4027
+ }
4028
+ function assertTradeNo(t) {
4029
+ if (!TRADE_NO_RE.test(t)) {
4030
+ throw new AlipayProtocolError(`invalid tradeNo (expect 32 digits): ${t}`);
4031
+ }
4032
+ }
4033
+ function parseTradeNo(lines) {
4034
+ for (const line of lines) {
4035
+ const labeled = line.match(/trade[_-]?no["'\s:=]+(\d{32})/i);
4036
+ if (labeled) return labeled[1];
4037
+ const bare = line.match(/\b(\d{32})\b/);
4038
+ if (bare) return bare[1];
4039
+ }
4040
+ return null;
4041
+ }
4042
+ function parsePaymentUrl(lines) {
4043
+ let paymentUrl;
4044
+ let shortenUrl;
4045
+ for (const line of lines) {
4046
+ const m = line.match(/(alipays?:\/\/\S+|https?:\/\/\S+)/i);
4047
+ if (!m) continue;
4048
+ const url = m[1].replace(/[)\]`>]+$/, "");
4049
+ if (/short|qr\.alipay|surl|\/s\//i.test(line) && !shortenUrl) shortenUrl = url;
4050
+ else if (!paymentUrl) paymentUrl = url;
4051
+ }
4052
+ if (!paymentUrl && shortenUrl) paymentUrl = shortenUrl;
4053
+ return { paymentUrl, shortenUrl };
4054
+ }
4055
+ function extractMedia(line) {
4056
+ const m = line.match(/^\s*MEDIA:\s*(.+?)\s*$/);
4057
+ return m ? m[1] : null;
4058
+ }
4059
+ function isWalletReady(lines) {
4060
+ const text = lines.join("\n").trim();
4061
+ try {
4062
+ const json = JSON.parse(text);
4063
+ if (json && typeof json.code !== "undefined") return Number(json.code) === 200;
4064
+ } catch {
4065
+ }
4066
+ return !/未开通|未开启|NOT[_\s-]*(OPEN|BOUND|SET)|NEEDS?[_\s-]*SETUP|NO[_\s-]*WALLET/i.test(text);
4067
+ }
4068
+ function extractResourceFromReport(report) {
4069
+ const label = report.search(/资源响应体/);
4070
+ const start = report.indexOf("{", label === -1 ? 0 : label);
4071
+ if (start === -1) return void 0;
4072
+ let depth = 0, inStr = false, esc = false;
4073
+ for (let i = start; i < report.length; i++) {
4074
+ const ch = report[i];
4075
+ if (inStr) {
4076
+ if (esc) esc = false;
4077
+ else if (ch === "\\") esc = true;
4078
+ else if (ch === '"') inStr = false;
4079
+ continue;
4080
+ }
4081
+ if (ch === '"') inStr = true;
4082
+ else if (ch === "{") depth++;
4083
+ else if (ch === "}" && --depth === 0) {
4084
+ const slice = report.slice(start, i + 1);
4085
+ try {
4086
+ const obj = JSON.parse(slice);
4087
+ const r = obj?.resourceResponse ?? obj?.result ?? obj?.data ?? obj?.body ?? obj;
4088
+ return typeof r === "string" ? r : JSON.stringify(r);
4089
+ } catch {
4090
+ return slice;
4091
+ }
4092
+ }
4093
+ }
4094
+ return void 0;
4095
+ }
4096
+ function extractBody(lines) {
4097
+ const text = lines.join("\n").trim();
4098
+ try {
4099
+ const json = JSON.parse(text);
4100
+ if (json && typeof json === "object") {
4101
+ if (json.resourceResponse !== void 0 && json.resourceResponse !== null) {
4102
+ const rr = json.resourceResponse;
4103
+ const r = typeof rr === "object" ? rr.result ?? rr.data ?? rr.body ?? rr : rr;
4104
+ return typeof r === "string" ? r : JSON.stringify(r);
4105
+ }
4106
+ if (typeof json.body === "string") {
4107
+ const inner = extractResourceFromReport(json.body);
4108
+ return inner ?? json.body;
4109
+ }
4110
+ const body = json.data ?? json.result ?? json.body ?? json.resource;
4111
+ if (body !== void 0) return typeof body === "string" ? body : JSON.stringify(body);
4112
+ return text;
4113
+ }
4114
+ } catch {
4115
+ }
4116
+ const idx = lines.findIndex((l) => /^\s*BODY:/.test(l));
4117
+ if (idx !== -1) {
4118
+ const first = lines[idx].replace(/^\s*BODY:\s*/, "");
4119
+ return [first, ...lines.slice(idx + 1)].join("\n").trim();
4120
+ }
4121
+ return lines.filter((l) => !/^\s*(MEDIA|STATUS|INFO):/.test(l)).join("\n").trim();
4122
+ }
4123
+ var DEFAULT_WALLET_TTL_MS = 10 * 60 * 1e3;
4124
+ function resolveWalletTtlMs() {
4125
+ const raw = process.env.MOLTSPAY_ALIPAY_WALLET_TTL_MS;
4126
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_WALLET_TTL_MS;
4127
+ const n = Number(raw);
4128
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_WALLET_TTL_MS;
4129
+ }
4130
+ var walletReadyUntil = /* @__PURE__ */ new Map();
4131
+ function resetWalletCache() {
4132
+ walletReadyUntil.clear();
4133
+ }
4134
+ var DEFAULT_INTENT_TTL_MS = 10 * 60 * 1e3;
4135
+ function resolveIntentTtlMs() {
4136
+ const raw = process.env.MOLTSPAY_ALIPAY_INTENT_TTL_MS;
4137
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_INTENT_TTL_MS;
4138
+ const n = Number(raw);
4139
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_INTENT_TTL_MS;
4140
+ }
4141
+ var intentDoneUntil = /* @__PURE__ */ new Map();
4142
+ function resetIntentCache() {
4143
+ intentDoneUntil.clear();
4144
+ }
4145
+ var AlipayClient = class {
4146
+ sessionId;
4147
+ configDir;
4148
+ framework;
4149
+ runner;
4150
+ getVersion;
4151
+ now;
4152
+ /** Only the default runner may use the process-level wallet cache. */
4153
+ walletCacheable;
4154
+ /** Only the default runner may use the process-level payment-intent cache. */
4155
+ intentCacheable;
4156
+ constructor(opts = {}) {
4157
+ this.sessionId = resolveSessionId(opts.sessionId, process.env.AIPAY_SESSION_ID);
4158
+ this.configDir = opts.configDir ?? (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
4159
+ this.framework = opts.framework ?? process.env.AIPAY_FRAMEWORK ?? "openclaw";
4160
+ this.runner = opts.runner ?? runCli;
4161
+ this.getVersion = opts.getVersion;
4162
+ this.now = opts.now ?? Date.now;
4163
+ this.walletCacheable = opts.cacheWallet ?? !opts.runner;
4164
+ this.intentCacheable = opts.cacheIntent ?? !opts.runner;
4165
+ }
4166
+ /**
4167
+ * Throws NeedsWalletSetupError unless alipay-bot reports an opened wallet.
4168
+ *
4169
+ * Skips the ~22s `check-wallet` spawn entirely when a prior call cached a
4170
+ * "ready" verdict within the TTL (see {@link DEFAULT_WALLET_TTL_MS}).
4171
+ */
4172
+ async checkWallet(signal, flow) {
4173
+ const ttlMs = resolveWalletTtlMs();
4174
+ const cacheKey = `${this.configDir}::${this.framework}`;
4175
+ const useCache = this.walletCacheable && ttlMs > 0;
4176
+ if (useCache) {
4177
+ const until = walletReadyUntil.get(cacheKey);
4178
+ if (until !== void 0 && this.now() < until) {
4179
+ alipayLog.info("wallet.cache", { flow, step: "check-wallet", hit: true, ttlMsLeft: until - this.now() });
4180
+ return;
4181
+ }
4182
+ }
4183
+ const { lines } = await this.runner(["check-wallet"], { signal, step: "check-wallet", flow });
4184
+ if (!isWalletReady(lines)) {
4185
+ if (this.walletCacheable) walletReadyUntil.delete(cacheKey);
4186
+ throw new NeedsWalletSetupError(
4187
+ "Alipay wallet not opened. Run: moltspay alipay apply (then: moltspay alipay bind)"
4188
+ );
4189
+ }
4190
+ if (useCache) {
4191
+ walletReadyUntil.set(cacheKey, this.now() + ttlMs);
4192
+ alipayLog.info("wallet.cache", { flow, step: "check-wallet", hit: false, cachedForMs: ttlMs });
4193
+ }
4194
+ }
4195
+ /** Run the full 8-step flow and resolve with the resource body. */
4196
+ async pay402(opts) {
4197
+ const { resourceUrl, requirement, signal } = opts;
4198
+ const extra = requirement.extra ?? {};
4199
+ const paymentNeededHeader = String(extra.payment_needed_header ?? "");
4200
+ if (!paymentNeededHeader) {
4201
+ throw new AlipayProtocolError("alipay requirement missing extra.payment_needed_header");
4202
+ }
4203
+ const flow = this.sessionId;
4204
+ const flowStart = this.now();
4205
+ alipayLog.info("flow.start", { flow, resource: resourceUrl });
4206
+ const media = [];
4207
+ const onLine = (line) => {
4208
+ const m = extractMedia(line);
4209
+ if (m) media.push(m);
4210
+ else opts.onLine?.(line);
4211
+ };
4212
+ const intentSummary = opts.intentSummary?.trim() || `\u652F\u4ED8 ${requirement.amount ?? ""} ${requirement.asset ?? "CNY"}`.trim();
4213
+ await timeStep("ensure-cli", flow, () => ensureCli(this.getVersion));
4214
+ const intentTtlMs = resolveIntentTtlMs();
4215
+ const intentKey = `${this.configDir}::${this.framework}`;
4216
+ const useIntentCache = this.intentCacheable && intentTtlMs > 0;
4217
+ const intentUntil = useIntentCache ? intentDoneUntil.get(intentKey) : void 0;
4218
+ if (intentUntil !== void 0 && this.now() < intentUntil) {
4219
+ alipayLog.info("intent.cache", { flow, step: "payment-intent", hit: true, ttlMsLeft: intentUntil - this.now() });
4220
+ } else {
4221
+ await this.runner(
4222
+ [
4223
+ "payment-intent",
4224
+ "--session-id",
4225
+ this.sessionId,
4226
+ "--intent-summary",
4227
+ intentSummary,
4228
+ "--framework",
4229
+ this.framework
4230
+ ],
4231
+ { onLine, signal, step: "payment-intent", flow }
4232
+ );
4233
+ if (useIntentCache) {
4234
+ intentDoneUntil.set(intentKey, this.now() + intentTtlMs);
4235
+ alipayLog.info("intent.cache", { flow, step: "payment-intent", hit: false, cachedForMs: intentTtlMs });
4236
+ }
4237
+ }
4238
+ await this.checkWallet(signal, flow);
4239
+ const reqId = String(extra.out_trade_no ?? (0, import_node_crypto3.randomUUID)());
4240
+ const dir = (0, import_path2.join)(this.configDir, "alipay");
4241
+ await (0, import_promises.mkdir)(dir, { recursive: true });
4242
+ const challengeFile = (0, import_path2.join)(dir, `402_${reqId}.txt`);
4243
+ await (0, import_promises.writeFile)(challengeFile, paymentNeededHeader, "utf-8");
4244
+ const payArgs = [
4245
+ "402-buyer-pay",
4246
+ "-f",
4247
+ challengeFile,
4248
+ "-r",
4249
+ resourceUrl,
4250
+ "-s",
4251
+ this.sessionId,
4252
+ "-i",
4253
+ intentSummary,
4254
+ "-w",
4255
+ this.framework
4256
+ ];
4257
+ if (opts.method) payArgs.push("-m", opts.method);
4258
+ if (opts.data) payArgs.push("-d", opts.data);
4259
+ const payRun = await this.runner(payArgs, { onLine, signal, step: "402-buyer-pay", flow });
4260
+ const tradeNo = parseTradeNo(payRun.lines);
4261
+ if (!tradeNo) {
4262
+ throw new AlipayProtocolError("402-buyer-pay did not return a tradeNo");
4263
+ }
4264
+ assertTradeNo(tradeNo);
4265
+ const { paymentUrl, shortenUrl } = parsePaymentUrl(payRun.lines);
4266
+ if (paymentUrl) {
4267
+ alipayLog.info("flow.pending", { flow, tradeNo, ms: this.now() - flowStart });
4268
+ opts.onPaymentPending?.({ paymentUrl, shortenUrl, tradeNo });
4269
+ }
4270
+ const pendingAt = this.now();
4271
+ const windowMs = (requirement.maxTimeoutSeconds ?? 30 * 60) * 1e3;
4272
+ const deadline = this.now() + (opts.timeoutMs ?? windowMs);
4273
+ const poll = await pollUntil(tradeNo, resourceUrl, {
4274
+ // No onLine: the status-poll output embeds the resource and must not reach
4275
+ // the log stream — the body is surfaced via the return value below (§9.3).
4276
+ deadline,
4277
+ signal,
4278
+ runner: this.runner,
4279
+ now: this.now,
4280
+ // Re-fetch the resource the same way it was paid (POST + body), else the
4281
+ // status poll defaults to GET and 404s on a POST-only `/execute`.
4282
+ method: opts.method,
4283
+ data: opts.data
4284
+ });
4285
+ alipayLog.info("flow.settled", { flow, tradeNo, ms: this.now() - pendingAt });
4286
+ const body = extractBody(poll.lines);
4287
+ void this.runner(["402-buyer-fulfillment-ack", "-t", tradeNo], {
4288
+ onLine,
4289
+ signal,
4290
+ step: "402-buyer-fulfillment-ack",
4291
+ flow
4292
+ }).catch(() => void 0);
4293
+ return { body, payment: { tradeNo, outTradeNo: reqId }, media };
4294
+ }
4295
+ };
4296
+
4297
+ // src/client/alipay/router.ts
4298
+ var ALIPAY_RAIL = "alipay";
4299
+ function railOf(req) {
4300
+ if (req.scheme === "alipay-aipay" || req.network === ALIPAY_RAIL) return ALIPAY_RAIL;
4301
+ return networkToChainName(req.network) ?? req.network;
4302
+ }
4303
+ function findRail(accepts, rail) {
4304
+ const requirement = accepts.find((r) => railOf(r) === rail);
4305
+ return requirement ? { rail, requirement } : null;
4306
+ }
4307
+ function selectRail(input) {
4308
+ const { serverAccepts, explicitRail, preference, availability } = input;
4309
+ if (!serverAccepts || serverAccepts.length === 0) {
4310
+ throw new UnsupportedRailError(explicitRail ?? "unknown", "Server offered no payment options");
4311
+ }
4312
+ if (explicitRail) {
4313
+ const hit = findRail(serverAccepts, explicitRail);
4314
+ if (!hit) {
4315
+ const offered = serverAccepts.map(railOf);
4316
+ throw new UnsupportedRailError(
4317
+ explicitRail,
4318
+ `Server doesn't accept rail '${explicitRail}'. Offered: ${offered.join(", ")}`
4319
+ );
4320
+ }
4321
+ return hit;
4322
+ }
4323
+ if (preference) {
4324
+ for (const pref of preference) {
4325
+ const hit = findRail(serverAccepts, pref);
4326
+ if (hit) return hit;
4327
+ }
4328
+ }
4329
+ if (availability?.evmReady) {
4330
+ const evm = serverAccepts.find((r) => railOf(r) !== ALIPAY_RAIL);
4331
+ if (evm) return { rail: railOf(evm), requirement: evm };
4332
+ }
4333
+ if (availability?.alipayReady) {
4334
+ const hit = findRail(serverAccepts, ALIPAY_RAIL);
4335
+ if (hit) return hit;
4336
+ }
4337
+ return { rail: railOf(serverAccepts[0]), requirement: serverAccepts[0] };
4338
+ }
4339
+
3042
4340
  // src/client/node/index.ts
3043
4341
  var DEFAULT_CONFIG = {
3044
4342
  chain: "base",
@@ -3055,9 +4353,13 @@ var MoltsPayClient = class {
3055
4353
  signer = null;
3056
4354
  todaySpending = 0;
3057
4355
  lastSpendingReset = 0;
4356
+ railPreference;
4357
+ alipaySessionId;
3058
4358
  constructor(options = {}) {
3059
- this.configDir = options.configDir || (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
4359
+ this.configDir = options.configDir || (0, import_path3.join)((0, import_os3.homedir)(), ".moltspay");
3060
4360
  this.config = this.loadConfig();
4361
+ this.railPreference = options.railPreference ?? this.config.railPreference;
4362
+ this.alipaySessionId = options.alipaySessionId;
3061
4363
  this.walletData = this.loadWallet();
3062
4364
  this.loadSpending();
3063
4365
  if (this.walletData) {
@@ -3135,6 +4437,9 @@ var MoltsPayClient = class {
3135
4437
  * @param options - Payment options (token selection)
3136
4438
  */
3137
4439
  async pay(serverUrl, service, params, options = {}) {
4440
+ if (options.rail === ALIPAY_RAIL) {
4441
+ return this.payViaAlipay(serverUrl, service, params, options);
4442
+ }
3138
4443
  if (!this.wallet || !this.walletData) {
3139
4444
  throw new Error("Client not initialized. Run: npx moltspay init");
3140
4445
  }
@@ -3326,6 +4631,76 @@ Please specify: --chain <chain_name>`
3326
4631
  console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
3327
4632
  return result.result || result;
3328
4633
  }
4634
+ /**
4635
+ * Pay for a service over the Alipay fiat rail (2.0.0).
4636
+ *
4637
+ * Unlike the crypto path this needs no EVM wallet — it shells out to
4638
+ * alipay-bot via {@link AlipayClient}. Flow: hit the resource with no
4639
+ * payment to get the 402 challenge, confirm the server actually offers the
4640
+ * alipay rail (selectRail), then run the 8-step state machine and return the
4641
+ * resource body.
4642
+ */
4643
+ async payViaAlipay(serverUrl, service, params, options) {
4644
+ const flow = this.alipaySessionId;
4645
+ let executeUrl = `${serverUrl}/execute`;
4646
+ try {
4647
+ const services = await timeStep(
4648
+ "discover-services",
4649
+ flow,
4650
+ () => this.getServices(serverUrl)
4651
+ );
4652
+ const svc = services.services?.find((s) => s.id === service);
4653
+ if (svc?.endpoint) executeUrl = `${serverUrl}${svc.endpoint}`;
4654
+ } catch {
4655
+ }
4656
+ const requestBody = options.rawData ? { service, ...params } : { service, params };
4657
+ const bodyJson = JSON.stringify(requestBody);
4658
+ const res = await timeStep(
4659
+ "challenge-402",
4660
+ flow,
4661
+ () => fetch(executeUrl, {
4662
+ method: "POST",
4663
+ headers: { "Content-Type": "application/json" },
4664
+ body: bodyJson
4665
+ })
4666
+ );
4667
+ if (res.status !== 402) {
4668
+ const data = await res.json().catch(() => ({}));
4669
+ if (res.ok && data.result) return data.result;
4670
+ throw new Error(data.error || `Expected 402, got ${res.status}`);
4671
+ }
4672
+ const header = res.headers.get(PAYMENT_REQUIRED_HEADER2);
4673
+ if (!header) throw new Error("Missing x-payment-required header on 402");
4674
+ const parsed = JSON.parse(Buffer.from(header, "base64").toString("utf-8"));
4675
+ const accepts = Array.isArray(parsed) ? parsed : parsed.accepts ?? [parsed];
4676
+ const { requirement } = selectRail({
4677
+ serverAccepts: accepts,
4678
+ explicitRail: ALIPAY_RAIL,
4679
+ preference: this.railPreference,
4680
+ availability: { evmReady: this.isInitialized }
4681
+ });
4682
+ const onLine = options.onLine ?? ((line) => process.stdout.write(line + "\n"));
4683
+ const alipay = new AlipayClient({
4684
+ sessionId: this.alipaySessionId,
4685
+ configDir: this.configDir
4686
+ });
4687
+ const result = await alipay.pay402({
4688
+ resourceUrl: executeUrl,
4689
+ requirement,
4690
+ method: "POST",
4691
+ data: bodyJson,
4692
+ onLine,
4693
+ onPaymentPending: options.onPaymentPending,
4694
+ timeoutMs: options.timeoutMs,
4695
+ signal: options.signal
4696
+ });
4697
+ try {
4698
+ const json = JSON.parse(result.body);
4699
+ return json.result ?? json;
4700
+ } catch {
4701
+ return { body: result.body, payment: result.payment, media: result.media };
4702
+ }
4703
+ }
3329
4704
  /**
3330
4705
  * Handle MPP (Machine Payments Protocol) payment flow
3331
4706
  * Called when pay() detects WWW-Authenticate header in 402 response
@@ -3666,7 +5041,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
3666
5041
  }
3667
5042
  // --- Config & Wallet Management ---
3668
5043
  loadConfig() {
3669
- const configPath = (0, import_path2.join)(this.configDir, "config.json");
5044
+ const configPath = (0, import_path3.join)(this.configDir, "config.json");
3670
5045
  if ((0, import_fs4.existsSync)(configPath)) {
3671
5046
  const content = (0, import_fs4.readFileSync)(configPath, "utf-8");
3672
5047
  return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
@@ -3675,14 +5050,14 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
3675
5050
  }
3676
5051
  saveConfig() {
3677
5052
  (0, import_fs4.mkdirSync)(this.configDir, { recursive: true });
3678
- const configPath = (0, import_path2.join)(this.configDir, "config.json");
5053
+ const configPath = (0, import_path3.join)(this.configDir, "config.json");
3679
5054
  (0, import_fs4.writeFileSync)(configPath, JSON.stringify(this.config, null, 2));
3680
5055
  }
3681
5056
  /**
3682
5057
  * Load spending data from disk
3683
5058
  */
3684
5059
  loadSpending() {
3685
- const spendingPath = (0, import_path2.join)(this.configDir, "spending.json");
5060
+ const spendingPath = (0, import_path3.join)(this.configDir, "spending.json");
3686
5061
  if ((0, import_fs4.existsSync)(spendingPath)) {
3687
5062
  try {
3688
5063
  const data = JSON.parse((0, import_fs4.readFileSync)(spendingPath, "utf-8"));
@@ -3705,7 +5080,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
3705
5080
  */
3706
5081
  saveSpending() {
3707
5082
  (0, import_fs4.mkdirSync)(this.configDir, { recursive: true });
3708
- const spendingPath = (0, import_path2.join)(this.configDir, "spending.json");
5083
+ const spendingPath = (0, import_path3.join)(this.configDir, "spending.json");
3709
5084
  const data = {
3710
5085
  date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
3711
5086
  amount: this.todaySpending,
@@ -3714,7 +5089,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
3714
5089
  (0, import_fs4.writeFileSync)(spendingPath, JSON.stringify(data, null, 2));
3715
5090
  }
3716
5091
  loadWallet() {
3717
- const walletPath = (0, import_path2.join)(this.configDir, "wallet.json");
5092
+ const walletPath = (0, import_path3.join)(this.configDir, "wallet.json");
3718
5093
  if ((0, import_fs4.existsSync)(walletPath)) {
3719
5094
  if (process.platform !== "win32") {
3720
5095
  try {
@@ -3744,7 +5119,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
3744
5119
  privateKey: wallet.privateKey,
3745
5120
  createdAt: Date.now()
3746
5121
  };
3747
- const walletPath = (0, import_path2.join)(configDir, "wallet.json");
5122
+ const walletPath = (0, import_path3.join)(configDir, "wallet.json");
3748
5123
  (0, import_fs4.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
3749
5124
  const config = {
3750
5125
  chain: options.chain,
@@ -3753,7 +5128,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
3753
5128
  maxPerDay: options.maxPerDay
3754
5129
  }
3755
5130
  };
3756
- const configPath = (0, import_path2.join)(configDir, "config.json");
5131
+ const configPath = (0, import_path3.join)(configDir, "config.json");
3757
5132
  (0, import_fs4.writeFileSync)(configPath, JSON.stringify(config, null, 2));
3758
5133
  return { address: wallet.address, configDir };
3759
5134
  }
@@ -3967,9 +5342,9 @@ var import_ethers4 = require("ethers");
3967
5342
  // src/wallet/createWallet.ts
3968
5343
  var import_ethers5 = require("ethers");
3969
5344
  var import_fs5 = require("fs");
3970
- var import_path3 = require("path");
5345
+ var import_path4 = require("path");
3971
5346
  var import_crypto = require("crypto");
3972
- var DEFAULT_STORAGE_DIR = (0, import_path3.join)(process.env.HOME || "~", ".moltspay");
5347
+ var DEFAULT_STORAGE_DIR = (0, import_path4.join)(process.env.HOME || "~", ".moltspay");
3973
5348
  var DEFAULT_STORAGE_FILE = "wallet.json";
3974
5349
  function encryptPrivateKey(privateKey, password) {
3975
5350
  const salt = (0, import_crypto.randomBytes)(16);
@@ -3992,7 +5367,7 @@ function decryptPrivateKey(encrypted, password, iv, salt) {
3992
5367
  return decrypted;
3993
5368
  }
3994
5369
  function createWallet(options = {}) {
3995
- const storagePath = options.storagePath || (0, import_path3.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
5370
+ const storagePath = options.storagePath || (0, import_path4.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
3996
5371
  if ((0, import_fs5.existsSync)(storagePath) && !options.overwrite) {
3997
5372
  try {
3998
5373
  const existing = JSON.parse((0, import_fs5.readFileSync)(storagePath, "utf8"));
@@ -4026,7 +5401,7 @@ function createWallet(options = {}) {
4026
5401
  } else {
4027
5402
  walletData.privateKey = wallet.privateKey;
4028
5403
  }
4029
- const dir = (0, import_path3.dirname)(storagePath);
5404
+ const dir = (0, import_path4.dirname)(storagePath);
4030
5405
  if (!(0, import_fs5.existsSync)(dir)) {
4031
5406
  (0, import_fs5.mkdirSync)(dir, { recursive: true });
4032
5407
  }
@@ -4045,7 +5420,7 @@ function createWallet(options = {}) {
4045
5420
  }
4046
5421
  }
4047
5422
  function loadWallet(options = {}) {
4048
- const storagePath = options.storagePath || (0, import_path3.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
5423
+ const storagePath = options.storagePath || (0, import_path4.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
4049
5424
  if (!(0, import_fs5.existsSync)(storagePath)) {
4050
5425
  return { success: false, error: "Wallet not found. Run createWallet() first." };
4051
5426
  }
@@ -4065,7 +5440,7 @@ function loadWallet(options = {}) {
4065
5440
  }
4066
5441
  }
4067
5442
  function getWalletAddress(storagePath) {
4068
- const path4 = storagePath || (0, import_path3.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
5443
+ const path4 = storagePath || (0, import_path4.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
4069
5444
  if (!(0, import_fs5.existsSync)(path4)) {
4070
5445
  return null;
4071
5446
  }
@@ -4077,7 +5452,7 @@ function getWalletAddress(storagePath) {
4077
5452
  }
4078
5453
  }
4079
5454
  function walletExists(storagePath) {
4080
- const path4 = storagePath || (0, import_path3.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
5455
+ const path4 = storagePath || (0, import_path4.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
4081
5456
  return (0, import_fs5.existsSync)(path4);
4082
5457
  }
4083
5458
 
@@ -4443,8 +5818,10 @@ var CDPWallet = class {
4443
5818
  FacilitatorRegistry,
4444
5819
  MoltsPayClient,
4445
5820
  MoltsPayServer,
5821
+ alipayLog,
4446
5822
  createRegistry,
4447
5823
  createWallet,
5824
+ getAlipayLogLevel,
4448
5825
  getCDPWalletAddress,
4449
5826
  getChain,
4450
5827
  getChainById,
@@ -4456,6 +5833,10 @@ var CDPWallet = class {
4456
5833
  listChains,
4457
5834
  loadCDPWallet,
4458
5835
  loadWallet,
5836
+ resetIntentCache,
5837
+ resetWalletCache,
5838
+ setAlipayLogLevel,
5839
+ setAlipayLogSink,
4459
5840
  verifyPayment,
4460
5841
  waitForTransaction,
4461
5842
  walletExists