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
@@ -432,6 +432,10 @@ var CHAINS = {
432
432
  requiresApproval: true
433
433
  }
434
434
  };
435
+ var ALIPAY_CHAIN_ID = "alipay";
436
+ function isAlipayChainId(id) {
437
+ return id === ALIPAY_CHAIN_ID;
438
+ }
435
439
 
436
440
  // src/facilitators/tempo.ts
437
441
  var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
@@ -1183,6 +1187,374 @@ var SolanaFacilitator = class extends BaseFacilitator {
1183
1187
  }
1184
1188
  };
1185
1189
 
1190
+ // src/facilitators/alipay.ts
1191
+ var import_node_crypto2 = __toESM(require("crypto"));
1192
+
1193
+ // src/facilitators/alipay/rsa2.ts
1194
+ var import_node_crypto = __toESM(require("crypto"));
1195
+ function rsa2Sign(data, privateKeyPem) {
1196
+ const signer = import_node_crypto.default.createSign("RSA-SHA256");
1197
+ signer.update(data, "utf-8");
1198
+ signer.end();
1199
+ return signer.sign(privateKeyPem, "base64");
1200
+ }
1201
+
1202
+ // src/facilitators/alipay/encoding.ts
1203
+ function base64url(input) {
1204
+ return Buffer.from(input, "utf-8").toString("base64url");
1205
+ }
1206
+ function decodeBase64UrlWithPadFix(input) {
1207
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
1208
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
1209
+ return Buffer.from(padded, "base64").toString("utf-8");
1210
+ }
1211
+ function toPem(key, kind) {
1212
+ const trimmed = key.trim();
1213
+ if (trimmed.includes("-----BEGIN")) return trimmed;
1214
+ const label = kind === "PRIVATE" ? "PRIVATE KEY" : "PUBLIC KEY";
1215
+ const body = trimmed.replace(/\s+/g, "").match(/.{1,64}/g)?.join("\n") ?? "";
1216
+ return `-----BEGIN ${label}-----
1217
+ ${body}
1218
+ -----END ${label}-----
1219
+ `;
1220
+ }
1221
+
1222
+ // src/facilitators/alipay/openapi.ts
1223
+ function formatAlipayTimestamp(d = /* @__PURE__ */ new Date()) {
1224
+ const pad = (n) => String(n).padStart(2, "0");
1225
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
1226
+ }
1227
+ function buildSigningString(params) {
1228
+ return Object.keys(params).sort().map((k) => `${k}=${params[k]}`).join("&");
1229
+ }
1230
+ function responseWrapperKey(method) {
1231
+ return `${method.replace(/\./g, "_")}_response`;
1232
+ }
1233
+ async function alipayOpenApiCall(method, bizContent, config) {
1234
+ const publicParams = {
1235
+ app_id: config.app_id,
1236
+ method,
1237
+ format: "JSON",
1238
+ charset: "utf-8",
1239
+ sign_type: config.sign_type ?? "RSA2",
1240
+ timestamp: formatAlipayTimestamp(),
1241
+ version: "1.0",
1242
+ biz_content: JSON.stringify(bizContent)
1243
+ };
1244
+ const signingString = buildSigningString(publicParams);
1245
+ const sign = rsa2Sign(signingString, config.private_key_pem);
1246
+ const body = new URLSearchParams({ ...publicParams, sign }).toString();
1247
+ const response = await fetch(config.gateway_url, {
1248
+ method: "POST",
1249
+ headers: {
1250
+ "Content-Type": "application/x-www-form-urlencoded;charset=utf-8"
1251
+ },
1252
+ body
1253
+ });
1254
+ if (!response.ok) {
1255
+ const text = await response.text().catch(() => "<unreadable>");
1256
+ throw new Error(
1257
+ `Alipay Open API HTTP ${response.status} for ${method}: ${text.slice(0, 500)}`
1258
+ );
1259
+ }
1260
+ const json = await response.json();
1261
+ const wrapperKey = responseWrapperKey(method);
1262
+ const business = json[wrapperKey];
1263
+ if (business === void 0 || business === null || typeof business !== "object") {
1264
+ throw new Error(
1265
+ `Alipay Open API response missing "${wrapperKey}" wrapper for ${method}: ${JSON.stringify(json).slice(0, 500)}`
1266
+ );
1267
+ }
1268
+ return business;
1269
+ }
1270
+
1271
+ // src/facilitators/alipay.ts
1272
+ var ALIPAY_NETWORK = "alipay";
1273
+ var ALIPAY_SCHEME = "alipay-aipay";
1274
+ var ALIPAY_GATEWAY_PROD = "https://openapi.alipay.com/gateway.do";
1275
+ var ALIPAY_AMOUNT_REGEX = /^\d+(\.\d{1,2})?$/;
1276
+ var ALIPAY_PAY_BEFORE_MS = 30 * 60 * 1e3;
1277
+ var ALIPAY_SIGNING_FIELDS = [
1278
+ "amount",
1279
+ "currency",
1280
+ "goods_name",
1281
+ "out_trade_no",
1282
+ "pay_before",
1283
+ "resource_id",
1284
+ "seller_id",
1285
+ "service_id"
1286
+ ];
1287
+ var AlipayFacilitator = class extends BaseFacilitator {
1288
+ name = "alipay";
1289
+ displayName = "Alipay AI \u6536";
1290
+ supportedNetworks = [ALIPAY_NETWORK];
1291
+ config;
1292
+ constructor(config) {
1293
+ super();
1294
+ this.config = {
1295
+ gateway_url: ALIPAY_GATEWAY_PROD,
1296
+ sign_type: "RSA2",
1297
+ ...config
1298
+ };
1299
+ }
1300
+ /**
1301
+ * Build the 402 challenge for a service: signs the 8-field payload with
1302
+ * RSA2, packages the nested `{protocol, method}` JSON as Base64URL for
1303
+ * `Payment-Needed`, and emits the parallel x402 `accepts[]` entry.
1304
+ */
1305
+ async createPaymentRequirements(opts) {
1306
+ if (!ALIPAY_AMOUNT_REGEX.test(opts.priceCny)) {
1307
+ throw new Error(
1308
+ `AlipayFacilitator.createPaymentRequirements: priceCny "${opts.priceCny}" does not match /^\\d+(\\.\\d{1,2})?$/ (unit is \u5143, not \u5206; e.g. "1.00" not "100")`
1309
+ );
1310
+ }
1311
+ const now = /* @__PURE__ */ new Date();
1312
+ const outTradeNo = opts.outTradeNo ?? generateOutTradeNo();
1313
+ const payBefore = formatPayBefore(now);
1314
+ const signedFields = {
1315
+ amount: opts.priceCny,
1316
+ currency: "CNY",
1317
+ goods_name: opts.goodsName,
1318
+ out_trade_no: outTradeNo,
1319
+ pay_before: payBefore,
1320
+ resource_id: opts.resourceId,
1321
+ seller_id: this.config.seller_id,
1322
+ service_id: opts.serviceId
1323
+ };
1324
+ const signingString = ALIPAY_SIGNING_FIELDS.map((k) => `${k}=${signedFields[k]}`).join("&");
1325
+ const seller_signature = rsa2Sign(signingString, this.config.private_key_pem);
1326
+ const challenge = {
1327
+ protocol: {
1328
+ out_trade_no: outTradeNo,
1329
+ amount: signedFields.amount,
1330
+ currency: signedFields.currency,
1331
+ resource_id: signedFields.resource_id,
1332
+ pay_before: payBefore,
1333
+ seller_signature,
1334
+ seller_sign_type: this.config.sign_type ?? "RSA2",
1335
+ seller_unique_id: this.config.seller_id
1336
+ },
1337
+ method: {
1338
+ seller_name: this.config.seller_name,
1339
+ seller_id: this.config.seller_id,
1340
+ seller_app_id: this.config.app_id,
1341
+ goods_name: signedFields.goods_name,
1342
+ seller_unique_id_key: "seller_id",
1343
+ service_id: signedFields.service_id
1344
+ }
1345
+ };
1346
+ const paymentNeededHeader = base64url(JSON.stringify(challenge));
1347
+ const x402Accepts = {
1348
+ scheme: ALIPAY_SCHEME,
1349
+ network: ALIPAY_NETWORK,
1350
+ asset: "CNY",
1351
+ amount: opts.priceCny,
1352
+ payTo: this.config.seller_id,
1353
+ maxTimeoutSeconds: ALIPAY_PAY_BEFORE_MS / 1e3,
1354
+ extra: {
1355
+ payment_needed_header: paymentNeededHeader,
1356
+ out_trade_no: outTradeNo,
1357
+ pay_before: payBefore,
1358
+ service_id: signedFields.service_id
1359
+ }
1360
+ };
1361
+ return { x402Accepts, paymentNeededHeader };
1362
+ }
1363
+ /**
1364
+ * Verify a `Payment-Proof` by calling
1365
+ * `alipay.aipay.agent.payment.verify` against the Alipay Open API.
1366
+ *
1367
+ * The proof is conveyed via `paymentPayload.payload`, which may be:
1368
+ * - a raw Base64URL string (the `Payment-Proof` header value), or
1369
+ * - an object `{ paymentProof: string }` / `{ proofHeader: string }`.
1370
+ *
1371
+ * All failure modes (malformed payload, network errors, Alipay
1372
+ * `code != 10000`) return `{ valid: false, error }`. No exception
1373
+ * escapes, regardless of client-supplied input.
1374
+ */
1375
+ async verify(paymentPayload, _requirements) {
1376
+ try {
1377
+ const proofHeader = extractProofHeader(paymentPayload.payload);
1378
+ const decoded = decodeProof(proofHeader);
1379
+ const response = await alipayOpenApiCall(
1380
+ "alipay.aipay.agent.payment.verify",
1381
+ {
1382
+ payment_proof: decoded.protocol.payment_proof,
1383
+ trade_no: decoded.protocol.trade_no,
1384
+ client_session: decoded.method.client_session
1385
+ },
1386
+ this.getOpenApiConfig()
1387
+ );
1388
+ if (response.code !== "10000") {
1389
+ return {
1390
+ valid: false,
1391
+ error: `alipay verify ${response.code}: ${response.sub_msg ?? response.msg ?? "unknown"}`,
1392
+ details: {
1393
+ code: response.code,
1394
+ sub_code: response.sub_code,
1395
+ sub_msg: response.sub_msg
1396
+ }
1397
+ };
1398
+ }
1399
+ return {
1400
+ valid: true,
1401
+ details: {
1402
+ trade_no: response.trade_no ?? decoded.protocol.trade_no,
1403
+ amount: response.amount,
1404
+ out_trade_no: response.out_trade_no,
1405
+ resource_id: response.resource_id,
1406
+ active: response.active
1407
+ }
1408
+ };
1409
+ } catch (e) {
1410
+ return {
1411
+ valid: false,
1412
+ error: e instanceof Error ? e.message : String(e)
1413
+ };
1414
+ }
1415
+ }
1416
+ /**
1417
+ * Settle by calling `alipay.aipay.agent.fulfillment.confirm` after the
1418
+ * service resource has been returned to the buyer.
1419
+ *
1420
+ * Per the design (see ALIPAY-INTEGRATION-DESIGN.md §5.1, risk row
1421
+ * "履约确认失败"), this is **fire-and-forget**: the caller (registry /
1422
+ * server) is expected to log fulfillment failures but NOT roll back
1423
+ * the already-delivered resource.
1424
+ *
1425
+ * Re-decodes `paymentPayload.payload` to extract `trade_no` (verify
1426
+ * does the same; the redundant Base64URL decode is negligible).
1427
+ */
1428
+ async settle(paymentPayload, _requirements) {
1429
+ try {
1430
+ const proofHeader = extractProofHeader(paymentPayload.payload);
1431
+ const decoded = decodeProof(proofHeader);
1432
+ const tradeNo = decoded.protocol.trade_no;
1433
+ const response = await alipayOpenApiCall(
1434
+ "alipay.aipay.agent.fulfillment.confirm",
1435
+ { trade_no: tradeNo },
1436
+ this.getOpenApiConfig()
1437
+ );
1438
+ if (response.code !== "10000") {
1439
+ return {
1440
+ success: false,
1441
+ transaction: tradeNo,
1442
+ error: `alipay fulfillment ${response.code}: ${response.sub_msg ?? response.msg ?? "unknown"}`,
1443
+ status: "fulfillment_failed"
1444
+ };
1445
+ }
1446
+ return {
1447
+ success: true,
1448
+ transaction: tradeNo,
1449
+ status: "fulfilled"
1450
+ };
1451
+ } catch (e) {
1452
+ return {
1453
+ success: false,
1454
+ error: e instanceof Error ? e.message : String(e)
1455
+ };
1456
+ }
1457
+ }
1458
+ /**
1459
+ * Validate that the configured RSA keys parse and that the Open API
1460
+ * gateway is reachable. Does NOT make a real business API call (would
1461
+ * burn quota and could appear as a legitimate verify attempt in logs).
1462
+ */
1463
+ async healthCheck() {
1464
+ const start = Date.now();
1465
+ try {
1466
+ import_node_crypto2.default.createPrivateKey(this.config.private_key_pem);
1467
+ } catch (e) {
1468
+ return {
1469
+ healthy: false,
1470
+ error: `merchant private_key_pem parse failed: ${e instanceof Error ? e.message : String(e)}`
1471
+ };
1472
+ }
1473
+ try {
1474
+ import_node_crypto2.default.createPublicKey(this.config.alipay_public_key_pem);
1475
+ } catch (e) {
1476
+ return {
1477
+ healthy: false,
1478
+ error: `alipay_public_key_pem parse failed: ${e instanceof Error ? e.message : String(e)}`
1479
+ };
1480
+ }
1481
+ const gatewayUrl = this.config.gateway_url ?? ALIPAY_GATEWAY_PROD;
1482
+ const controller = new AbortController();
1483
+ const timeout = setTimeout(() => controller.abort(), 5e3);
1484
+ const response = await fetch(gatewayUrl, {
1485
+ method: "HEAD",
1486
+ signal: controller.signal
1487
+ }).catch(() => null);
1488
+ clearTimeout(timeout);
1489
+ const latencyMs = Date.now() - start;
1490
+ if (!response) {
1491
+ return { healthy: false, error: `gateway unreachable: ${gatewayUrl}`, latencyMs };
1492
+ }
1493
+ return { healthy: true, latencyMs };
1494
+ }
1495
+ /** Bundle the facilitator config into the shape openapi.ts wants. */
1496
+ getOpenApiConfig() {
1497
+ return {
1498
+ gateway_url: this.config.gateway_url ?? ALIPAY_GATEWAY_PROD,
1499
+ app_id: this.config.app_id,
1500
+ private_key_pem: this.config.private_key_pem,
1501
+ alipay_public_key_pem: this.config.alipay_public_key_pem,
1502
+ sign_type: this.config.sign_type
1503
+ };
1504
+ }
1505
+ };
1506
+ function generateOutTradeNo() {
1507
+ return "VID" + import_node_crypto2.default.randomBytes(22).toString("base64url").slice(0, 29);
1508
+ }
1509
+ function formatPayBefore(now) {
1510
+ const expiry = new Date(now.getTime() + ALIPAY_PAY_BEFORE_MS);
1511
+ return expiry.toISOString().replace(/\.\d{3}Z$/, "Z");
1512
+ }
1513
+ function extractProofHeader(payload) {
1514
+ if (typeof payload === "string") {
1515
+ if (payload.length === 0) {
1516
+ throw new Error("alipay Payment-Proof is empty");
1517
+ }
1518
+ return payload;
1519
+ }
1520
+ if (payload !== null && typeof payload === "object") {
1521
+ const obj = payload;
1522
+ const candidate = obj.paymentProof ?? obj.proofHeader;
1523
+ if (typeof candidate === "string" && candidate.length > 0) {
1524
+ return candidate;
1525
+ }
1526
+ }
1527
+ throw new Error(
1528
+ "alipay payment payload must be a Base64URL string or {paymentProof: string} / {proofHeader: string}"
1529
+ );
1530
+ }
1531
+ function decodeProof(proofHeader) {
1532
+ let parsed;
1533
+ try {
1534
+ parsed = JSON.parse(decodeBase64UrlWithPadFix(proofHeader));
1535
+ } catch (e) {
1536
+ throw new Error(
1537
+ `failed to decode Payment-Proof: ${e instanceof Error ? e.message : String(e)}`
1538
+ );
1539
+ }
1540
+ if (parsed === null || typeof parsed !== "object") {
1541
+ throw new Error("decoded Payment-Proof is not an object");
1542
+ }
1543
+ const obj = parsed;
1544
+ const protocol = obj.protocol;
1545
+ const method = obj.method;
1546
+ if (!protocol || typeof protocol.payment_proof !== "string") {
1547
+ throw new Error("decoded Payment-Proof missing protocol.payment_proof");
1548
+ }
1549
+ if (typeof protocol.trade_no !== "string") {
1550
+ throw new Error("decoded Payment-Proof missing protocol.trade_no");
1551
+ }
1552
+ if (!method || typeof method.client_session !== "string") {
1553
+ throw new Error("decoded Payment-Proof missing method.client_session");
1554
+ }
1555
+ return parsed;
1556
+ }
1557
+
1186
1558
  // src/facilitators/registry.ts
1187
1559
  var import_web33 = require("@solana/web3.js");
1188
1560
  var import_bs58 = __toESM(require("bs58"));
@@ -1207,6 +1579,7 @@ var FacilitatorRegistry = class {
1207
1579
  }
1208
1580
  return new SolanaFacilitator({ feePayerKeypair });
1209
1581
  });
1582
+ this.registerFactory("alipay", (config) => new AlipayFacilitator(config));
1210
1583
  this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
1211
1584
  }
1212
1585
  /**
@@ -1431,6 +1804,11 @@ var PAYMENT_RESPONSE_HEADER = "x-payment-response";
1431
1804
  var MPP_AUTH_HEADER = "authorization";
1432
1805
  var MPP_WWW_AUTH_HEADER = "www-authenticate";
1433
1806
  var MPP_RECEIPT_HEADER = "payment-receipt";
1807
+ var ALIPAY_PAYMENT_NEEDED_HEADER = "payment-needed";
1808
+ var ALIPAY_PAYMENT_PROOF_HEADER = "payment-proof";
1809
+ function headerSafe(value) {
1810
+ return String(value ?? "").replace(/[^\x20-\x7E]/g, (ch) => encodeURIComponent(ch)).replace(/"/g, "%22");
1811
+ }
1434
1812
  var TOKEN_ADDRESSES = {
1435
1813
  "eip155:8453": {
1436
1814
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -1566,6 +1944,8 @@ var MoltsPayServer = class {
1566
1944
  registry;
1567
1945
  networkId;
1568
1946
  useMainnet;
1947
+ /** Alipay AI 收 facilitator instance, set when `provider.alipay` is configured (2.0.0). */
1948
+ alipayFacilitator = null;
1569
1949
  constructor(servicesPath, options = {}) {
1570
1950
  loadEnvFile2();
1571
1951
  const content = (0, import_fs2.readFileSync)(servicesPath, "utf-8");
@@ -1587,7 +1967,38 @@ var MoltsPayServer = class {
1587
1967
  cdp: { useMainnet: this.useMainnet }
1588
1968
  }
1589
1969
  };
1970
+ const providerAlipay = this.manifest.provider.alipay;
1971
+ if (providerAlipay) {
1972
+ try {
1973
+ const baseDir = path2.dirname(servicesPath);
1974
+ const resolvePem = (p, kind) => toPem((0, import_fs2.readFileSync)(path2.isAbsolute(p) ? p : path2.resolve(baseDir, p), "utf-8"), kind);
1975
+ const alipayFacilitatorConfig = {
1976
+ seller_id: providerAlipay.seller_id,
1977
+ app_id: providerAlipay.app_id,
1978
+ seller_name: providerAlipay.seller_name,
1979
+ service_id_default: providerAlipay.service_id_default,
1980
+ private_key_pem: resolvePem(providerAlipay.private_key_path, "PRIVATE"),
1981
+ alipay_public_key_pem: resolvePem(providerAlipay.alipay_public_key_path, "PUBLIC"),
1982
+ gateway_url: providerAlipay.gateway_url,
1983
+ sign_type: providerAlipay.sign_type
1984
+ };
1985
+ facilitatorConfig.config = {
1986
+ ...facilitatorConfig.config,
1987
+ alipay: alipayFacilitatorConfig
1988
+ };
1989
+ facilitatorConfig.fallback = facilitatorConfig.fallback || [];
1990
+ if (facilitatorConfig.primary !== "alipay" && !facilitatorConfig.fallback.includes("alipay")) {
1991
+ facilitatorConfig.fallback.push("alipay");
1992
+ }
1993
+ } catch (err) {
1994
+ throw new Error(`[MoltsPay] Alipay rail configured but key load failed: ${err.message}`);
1995
+ }
1996
+ }
1590
1997
  this.registry = new FacilitatorRegistry(facilitatorConfig);
1998
+ if (providerAlipay) {
1999
+ this.alipayFacilitator = this.registry.get("alipay");
2000
+ console.log(`[MoltsPay] Alipay AI \u6536 rail enabled (seller ${providerAlipay.seller_id})`);
2001
+ }
1591
2002
  const primaryFacilitator = this.registry.get(facilitatorConfig.primary);
1592
2003
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
1593
2004
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
@@ -1723,10 +2134,10 @@ var MoltsPayServer = class {
1723
2134
  writeCorsHeaders(res, origin) {
1724
2135
  res.setHeader("Access-Control-Allow-Origin", origin);
1725
2136
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
1726
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
2137
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization, Payment-Proof");
1727
2138
  res.setHeader(
1728
2139
  "Access-Control-Expose-Headers",
1729
- "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt"
2140
+ "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt, Payment-Needed"
1730
2141
  );
1731
2142
  }
1732
2143
  /**
@@ -1753,7 +2164,8 @@ var MoltsPayServer = class {
1753
2164
  if (url.pathname === "/execute" && req.method === "POST") {
1754
2165
  const body = await this.readBody(req);
1755
2166
  const paymentHeader = req.headers[PAYMENT_HEADER];
1756
- return await this.handleExecute(body, paymentHeader, res);
2167
+ const proofHeader = req.headers[ALIPAY_PAYMENT_PROOF_HEADER];
2168
+ return await this.handleExecute(body, paymentHeader, res, proofHeader);
1757
2169
  }
1758
2170
  if (url.pathname === "/proxy" && req.method === "POST") {
1759
2171
  const clientIP = req.headers["x-real-ip"] || req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket.remoteAddress || "";
@@ -1771,7 +2183,8 @@ var MoltsPayServer = class {
1771
2183
  const body = req.method === "POST" ? await this.readBody(req) : {};
1772
2184
  const authHeader = req.headers[MPP_AUTH_HEADER];
1773
2185
  const x402Header = req.headers[PAYMENT_HEADER];
1774
- return await this.handleMPPRequest(skill, body, authHeader, x402Header, res);
2186
+ const proofHeader = req.headers[ALIPAY_PAYMENT_PROOF_HEADER];
2187
+ return await this.handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader);
1775
2188
  }
1776
2189
  this.sendJson(res, 404, { error: "Not found" });
1777
2190
  } catch (err) {
@@ -1868,7 +2281,7 @@ var MoltsPayServer = class {
1868
2281
  /**
1869
2282
  * POST /execute - Execute service with x402 payment
1870
2283
  */
1871
- async handleExecute(body, paymentHeader, res) {
2284
+ async handleExecute(body, paymentHeader, res, proofHeader) {
1872
2285
  const { service, params } = body;
1873
2286
  if (!service) {
1874
2287
  return this.sendJson(res, 400, { error: "Missing service" });
@@ -1882,6 +2295,15 @@ var MoltsPayServer = class {
1882
2295
  return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
1883
2296
  }
1884
2297
  }
2298
+ if (proofHeader) {
2299
+ const alipayPayment = {
2300
+ x402Version: X402_VERSION2,
2301
+ scheme: ALIPAY_SCHEME,
2302
+ network: ALIPAY_NETWORK,
2303
+ payload: proofHeader
2304
+ };
2305
+ return this.handleAlipayExecute(skill, params || {}, alipayPayment, res);
2306
+ }
1885
2307
  if (!paymentHeader) {
1886
2308
  return this.sendPaymentRequired(skill.config, res);
1887
2309
  }
@@ -1892,6 +2314,11 @@ var MoltsPayServer = class {
1892
2314
  } catch {
1893
2315
  return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
1894
2316
  }
2317
+ const payScheme = payment.accepted?.scheme || payment.scheme;
2318
+ const payNetwork = payment.accepted?.network || payment.network;
2319
+ if (payScheme === ALIPAY_SCHEME || (payNetwork ? isAlipayChainId(payNetwork) : false)) {
2320
+ return this.handleAlipayExecute(skill, params || {}, payment, res);
2321
+ }
1895
2322
  const validation = this.validatePayment(payment, skill.config);
1896
2323
  if (!validation.valid) {
1897
2324
  return this.sendJson(res, 402, { error: validation.error });
@@ -1984,13 +2411,111 @@ var MoltsPayServer = class {
1984
2411
  payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
1985
2412
  }, responseHeaders);
1986
2413
  }
2414
+ /**
2415
+ * Execute a service paid via the Alipay AI 收 fiat rail (2.0.0).
2416
+ *
2417
+ * Differs from the EVM/SVM path: no token detection, no EIP-3009/permit
2418
+ * validation. Verify hits the Alipay Open API (`payment.verify`). Settlement
2419
+ * (`fulfillment.confirm`) is FIRE-AND-FORGET per ALIPAY-INTEGRATION-DESIGN
2420
+ * §5.1: a confirm failure is logged but does NOT fail the already-delivered
2421
+ * response (the buyer's payment proof was already verified).
2422
+ */
2423
+ async handleAlipayExecute(skill, params, payment, res) {
2424
+ if (!this.alipayFacilitator) {
2425
+ return this.sendJson(res, 402, { error: "Alipay rail not configured on this server" });
2426
+ }
2427
+ const requirements = {
2428
+ scheme: ALIPAY_SCHEME,
2429
+ network: ALIPAY_NETWORK,
2430
+ asset: "CNY",
2431
+ amount: skill.config.alipay?.price_cny || "0",
2432
+ payTo: this.manifest.provider.alipay?.seller_id || "",
2433
+ maxTimeoutSeconds: 1800
2434
+ };
2435
+ console.log(`[MoltsPay] Verifying Alipay payment...`);
2436
+ const verifyResult = await this.registry.verify(payment, requirements);
2437
+ if (!verifyResult.valid) {
2438
+ return this.sendJson(res, 402, {
2439
+ error: `Payment verification failed: ${verifyResult.error}`,
2440
+ facilitator: verifyResult.facilitator
2441
+ });
2442
+ }
2443
+ console.log(`[MoltsPay] Alipay payment verified by ${verifyResult.facilitator}`);
2444
+ const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
2445
+ console.log(`[MoltsPay] Executing skill: ${skill.id} (timeout: ${timeoutSeconds}s)`);
2446
+ let result;
2447
+ try {
2448
+ result = await Promise.race([
2449
+ skill.handler(params),
2450
+ new Promise(
2451
+ (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
2452
+ )
2453
+ ]);
2454
+ } catch (err) {
2455
+ console.error("[MoltsPay] Skill execution failed:", err.message);
2456
+ return this.sendJson(res, 500, {
2457
+ error: "Service execution failed",
2458
+ message: err.message
2459
+ });
2460
+ }
2461
+ let settlement;
2462
+ try {
2463
+ settlement = await this.registry.settle(payment, requirements);
2464
+ if (settlement.success) {
2465
+ console.log(`[MoltsPay] Alipay fulfillment confirmed: ${settlement.transaction}`);
2466
+ } else {
2467
+ console.error(`[MoltsPay] Alipay fulfillment confirm failed (non-fatal): ${settlement.error}`);
2468
+ }
2469
+ } catch (err) {
2470
+ console.error(`[MoltsPay] Alipay fulfillment confirm threw (non-fatal): ${err.message}`);
2471
+ settlement = { success: false, error: err.message, facilitator: "alipay" };
2472
+ }
2473
+ const responseHeaders = {};
2474
+ if (settlement.success) {
2475
+ responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(JSON.stringify({
2476
+ success: true,
2477
+ transaction: settlement.transaction,
2478
+ network: ALIPAY_NETWORK,
2479
+ facilitator: settlement.facilitator
2480
+ })).toString("base64");
2481
+ }
2482
+ this.sendJson(res, 200, {
2483
+ success: true,
2484
+ result,
2485
+ payment: settlement.success ? { transaction: settlement.transaction, status: "fulfilled", facilitator: settlement.facilitator } : { status: "delivered_unconfirmed", error: settlement.error }
2486
+ }, responseHeaders);
2487
+ }
2488
+ /**
2489
+ * Build the Alipay 402 challenge for a service, or null when the alipay rail
2490
+ * isn't configured for this server or this service. Returns the x402
2491
+ * `accepts[]` entry plus the Base64URL `Payment-Needed` header value so the
2492
+ * 402 responders can dual-emit both the x402 and legacy alipay-bot formats.
2493
+ */
2494
+ async buildAlipayChallenge(config) {
2495
+ if (!this.alipayFacilitator || !config.alipay) return null;
2496
+ try {
2497
+ const req = await this.alipayFacilitator.createPaymentRequirements({
2498
+ serviceId: config.alipay.service_id || this.manifest.provider.alipay.service_id_default,
2499
+ priceCny: config.alipay.price_cny,
2500
+ goodsName: config.alipay.goods_name,
2501
+ resourceId: `/execute?service=${config.id}`
2502
+ });
2503
+ return { accepts: req.x402Accepts, paymentNeededHeader: req.paymentNeededHeader };
2504
+ } catch (err) {
2505
+ console.error(`[MoltsPay] Alipay challenge build failed for ${config.id}: ${err.message}`);
2506
+ return null;
2507
+ }
2508
+ }
1987
2509
  /**
1988
2510
  * Handle MPP (Machine Payments Protocol) request
1989
2511
  * Supports both x402 and MPP protocols on service endpoints
1990
2512
  */
1991
- async handleMPPRequest(skill, body, authHeader, x402Header, res) {
2513
+ async handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader) {
1992
2514
  const config = skill.config;
1993
2515
  const params = body || {};
2516
+ if (proofHeader) {
2517
+ return await this.handleExecute({ service: config.id, params }, void 0, res, proofHeader);
2518
+ }
1994
2519
  if (x402Header) {
1995
2520
  return await this.handleExecute({ service: config.id, params }, x402Header, res);
1996
2521
  }
@@ -2096,7 +2621,7 @@ var MoltsPayServer = class {
2096
2621
  /**
2097
2622
  * Return 402 with both x402 and MPP payment requirements
2098
2623
  */
2099
- sendMPPPaymentRequired(config, res) {
2624
+ async sendMPPPaymentRequired(config, res) {
2100
2625
  const acceptedTokens = getAcceptedCurrencies(config);
2101
2626
  const providerChains = this.getProviderChains();
2102
2627
  const accepts = [];
@@ -2107,6 +2632,10 @@ var MoltsPayServer = class {
2107
2632
  }
2108
2633
  }
2109
2634
  }
2635
+ const alipayChallenge = await this.buildAlipayChallenge(config);
2636
+ if (alipayChallenge) {
2637
+ accepts.push(alipayChallenge.accepts);
2638
+ }
2110
2639
  const x402PaymentRequired = {
2111
2640
  x402Version: X402_VERSION2,
2112
2641
  accepts,
@@ -2134,7 +2663,7 @@ var MoltsPayServer = class {
2134
2663
  };
2135
2664
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
2136
2665
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
2137
- mppWwwAuth = `Payment id="${challengeId}", realm="${this.manifest.provider.name}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
2666
+ mppWwwAuth = `Payment id="${challengeId}", realm="${headerSafe(this.manifest.provider.name)}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${headerSafe(config.name)}", expires="${expiresAt}"`;
2138
2667
  }
2139
2668
  const headers = {
2140
2669
  "Content-Type": "application/problem+json",
@@ -2143,6 +2672,9 @@ var MoltsPayServer = class {
2143
2672
  if (mppWwwAuth) {
2144
2673
  headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
2145
2674
  }
2675
+ if (alipayChallenge) {
2676
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
2677
+ }
2146
2678
  res.writeHead(402, headers);
2147
2679
  res.end(JSON.stringify({
2148
2680
  type: "https://paymentauth.org/problems/payment-required",
@@ -2169,7 +2701,7 @@ var MoltsPayServer = class {
2169
2701
  * Return 402 with x402 payment requirements (v2 format)
2170
2702
  * Includes requirements for all chains and all accepted currencies
2171
2703
  */
2172
- sendPaymentRequired(config, res) {
2704
+ async sendPaymentRequired(config, res) {
2173
2705
  const acceptedTokens = getAcceptedCurrencies(config);
2174
2706
  const providerChains = this.getProviderChains();
2175
2707
  const accepts = [];
@@ -2180,6 +2712,10 @@ var MoltsPayServer = class {
2180
2712
  }
2181
2713
  }
2182
2714
  }
2715
+ const alipayChallenge = await this.buildAlipayChallenge(config);
2716
+ if (alipayChallenge) {
2717
+ accepts.push(alipayChallenge.accepts);
2718
+ }
2183
2719
  const acceptedChains = providerChains.map((c) => {
2184
2720
  if (c.network === "eip155:8453") return "base";
2185
2721
  if (c.network === "eip155:137") return "polygon";
@@ -2197,10 +2733,14 @@ var MoltsPayServer = class {
2197
2733
  }
2198
2734
  };
2199
2735
  const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
2200
- res.writeHead(402, {
2736
+ const headers = {
2201
2737
  "Content-Type": "application/json",
2202
2738
  [PAYMENT_REQUIRED_HEADER]: encoded
2203
- });
2739
+ };
2740
+ if (alipayChallenge) {
2741
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
2742
+ }
2743
+ res.writeHead(402, headers);
2204
2744
  res.end(JSON.stringify({
2205
2745
  error: "Payment required",
2206
2746
  message: `Service requires $${config.price} ${config.currency}`,
@@ -2307,12 +2847,12 @@ var MoltsPayServer = class {
2307
2847
  return accepted.includes(token);
2308
2848
  }
2309
2849
  async readBody(req) {
2310
- return new Promise((resolve, reject) => {
2850
+ return new Promise((resolve2, reject) => {
2311
2851
  let body = "";
2312
2852
  req.on("data", (chunk) => body += chunk);
2313
2853
  req.on("end", () => {
2314
2854
  try {
2315
- resolve(body ? JSON.parse(body) : {});
2855
+ resolve2(body ? JSON.parse(body) : {});
2316
2856
  } catch {
2317
2857
  reject(new Error("Invalid JSON"));
2318
2858
  }
@@ -2570,7 +3110,7 @@ var MoltsPayServer = class {
2570
3110
  };
2571
3111
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
2572
3112
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
2573
- const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
3113
+ const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${headerSafe(config.name)}", expires="${expiresAt}"`;
2574
3114
  res.writeHead(402, {
2575
3115
  "Content-Type": "application/problem+json",
2576
3116
  [MPP_WWW_AUTH_HEADER]: wwwAuth