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
@@ -398,6 +398,10 @@ var CHAINS = {
398
398
  requiresApproval: true
399
399
  }
400
400
  };
401
+ var ALIPAY_CHAIN_ID = "alipay";
402
+ function isAlipayChainId(id) {
403
+ return id === ALIPAY_CHAIN_ID;
404
+ }
401
405
 
402
406
  // src/facilitators/tempo.ts
403
407
  var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
@@ -1159,6 +1163,374 @@ var SolanaFacilitator = class extends BaseFacilitator {
1159
1163
  }
1160
1164
  };
1161
1165
 
1166
+ // src/facilitators/alipay.ts
1167
+ import crypto2 from "crypto";
1168
+
1169
+ // src/facilitators/alipay/rsa2.ts
1170
+ import crypto from "crypto";
1171
+ function rsa2Sign(data, privateKeyPem) {
1172
+ const signer = crypto.createSign("RSA-SHA256");
1173
+ signer.update(data, "utf-8");
1174
+ signer.end();
1175
+ return signer.sign(privateKeyPem, "base64");
1176
+ }
1177
+
1178
+ // src/facilitators/alipay/encoding.ts
1179
+ function base64url(input) {
1180
+ return Buffer.from(input, "utf-8").toString("base64url");
1181
+ }
1182
+ function decodeBase64UrlWithPadFix(input) {
1183
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
1184
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
1185
+ return Buffer.from(padded, "base64").toString("utf-8");
1186
+ }
1187
+ function toPem(key, kind) {
1188
+ const trimmed = key.trim();
1189
+ if (trimmed.includes("-----BEGIN")) return trimmed;
1190
+ const label = kind === "PRIVATE" ? "PRIVATE KEY" : "PUBLIC KEY";
1191
+ const body = trimmed.replace(/\s+/g, "").match(/.{1,64}/g)?.join("\n") ?? "";
1192
+ return `-----BEGIN ${label}-----
1193
+ ${body}
1194
+ -----END ${label}-----
1195
+ `;
1196
+ }
1197
+
1198
+ // src/facilitators/alipay/openapi.ts
1199
+ function formatAlipayTimestamp(d = /* @__PURE__ */ new Date()) {
1200
+ const pad = (n) => String(n).padStart(2, "0");
1201
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
1202
+ }
1203
+ function buildSigningString(params) {
1204
+ return Object.keys(params).sort().map((k) => `${k}=${params[k]}`).join("&");
1205
+ }
1206
+ function responseWrapperKey(method) {
1207
+ return `${method.replace(/\./g, "_")}_response`;
1208
+ }
1209
+ async function alipayOpenApiCall(method, bizContent, config) {
1210
+ const publicParams = {
1211
+ app_id: config.app_id,
1212
+ method,
1213
+ format: "JSON",
1214
+ charset: "utf-8",
1215
+ sign_type: config.sign_type ?? "RSA2",
1216
+ timestamp: formatAlipayTimestamp(),
1217
+ version: "1.0",
1218
+ biz_content: JSON.stringify(bizContent)
1219
+ };
1220
+ const signingString = buildSigningString(publicParams);
1221
+ const sign = rsa2Sign(signingString, config.private_key_pem);
1222
+ const body = new URLSearchParams({ ...publicParams, sign }).toString();
1223
+ const response = await fetch(config.gateway_url, {
1224
+ method: "POST",
1225
+ headers: {
1226
+ "Content-Type": "application/x-www-form-urlencoded;charset=utf-8"
1227
+ },
1228
+ body
1229
+ });
1230
+ if (!response.ok) {
1231
+ const text = await response.text().catch(() => "<unreadable>");
1232
+ throw new Error(
1233
+ `Alipay Open API HTTP ${response.status} for ${method}: ${text.slice(0, 500)}`
1234
+ );
1235
+ }
1236
+ const json = await response.json();
1237
+ const wrapperKey = responseWrapperKey(method);
1238
+ const business = json[wrapperKey];
1239
+ if (business === void 0 || business === null || typeof business !== "object") {
1240
+ throw new Error(
1241
+ `Alipay Open API response missing "${wrapperKey}" wrapper for ${method}: ${JSON.stringify(json).slice(0, 500)}`
1242
+ );
1243
+ }
1244
+ return business;
1245
+ }
1246
+
1247
+ // src/facilitators/alipay.ts
1248
+ var ALIPAY_NETWORK = "alipay";
1249
+ var ALIPAY_SCHEME = "alipay-aipay";
1250
+ var ALIPAY_GATEWAY_PROD = "https://openapi.alipay.com/gateway.do";
1251
+ var ALIPAY_AMOUNT_REGEX = /^\d+(\.\d{1,2})?$/;
1252
+ var ALIPAY_PAY_BEFORE_MS = 30 * 60 * 1e3;
1253
+ var ALIPAY_SIGNING_FIELDS = [
1254
+ "amount",
1255
+ "currency",
1256
+ "goods_name",
1257
+ "out_trade_no",
1258
+ "pay_before",
1259
+ "resource_id",
1260
+ "seller_id",
1261
+ "service_id"
1262
+ ];
1263
+ var AlipayFacilitator = class extends BaseFacilitator {
1264
+ name = "alipay";
1265
+ displayName = "Alipay AI \u6536";
1266
+ supportedNetworks = [ALIPAY_NETWORK];
1267
+ config;
1268
+ constructor(config) {
1269
+ super();
1270
+ this.config = {
1271
+ gateway_url: ALIPAY_GATEWAY_PROD,
1272
+ sign_type: "RSA2",
1273
+ ...config
1274
+ };
1275
+ }
1276
+ /**
1277
+ * Build the 402 challenge for a service: signs the 8-field payload with
1278
+ * RSA2, packages the nested `{protocol, method}` JSON as Base64URL for
1279
+ * `Payment-Needed`, and emits the parallel x402 `accepts[]` entry.
1280
+ */
1281
+ async createPaymentRequirements(opts) {
1282
+ if (!ALIPAY_AMOUNT_REGEX.test(opts.priceCny)) {
1283
+ throw new Error(
1284
+ `AlipayFacilitator.createPaymentRequirements: priceCny "${opts.priceCny}" does not match /^\\d+(\\.\\d{1,2})?$/ (unit is \u5143, not \u5206; e.g. "1.00" not "100")`
1285
+ );
1286
+ }
1287
+ const now = /* @__PURE__ */ new Date();
1288
+ const outTradeNo = opts.outTradeNo ?? generateOutTradeNo();
1289
+ const payBefore = formatPayBefore(now);
1290
+ const signedFields = {
1291
+ amount: opts.priceCny,
1292
+ currency: "CNY",
1293
+ goods_name: opts.goodsName,
1294
+ out_trade_no: outTradeNo,
1295
+ pay_before: payBefore,
1296
+ resource_id: opts.resourceId,
1297
+ seller_id: this.config.seller_id,
1298
+ service_id: opts.serviceId
1299
+ };
1300
+ const signingString = ALIPAY_SIGNING_FIELDS.map((k) => `${k}=${signedFields[k]}`).join("&");
1301
+ const seller_signature = rsa2Sign(signingString, this.config.private_key_pem);
1302
+ const challenge = {
1303
+ protocol: {
1304
+ out_trade_no: outTradeNo,
1305
+ amount: signedFields.amount,
1306
+ currency: signedFields.currency,
1307
+ resource_id: signedFields.resource_id,
1308
+ pay_before: payBefore,
1309
+ seller_signature,
1310
+ seller_sign_type: this.config.sign_type ?? "RSA2",
1311
+ seller_unique_id: this.config.seller_id
1312
+ },
1313
+ method: {
1314
+ seller_name: this.config.seller_name,
1315
+ seller_id: this.config.seller_id,
1316
+ seller_app_id: this.config.app_id,
1317
+ goods_name: signedFields.goods_name,
1318
+ seller_unique_id_key: "seller_id",
1319
+ service_id: signedFields.service_id
1320
+ }
1321
+ };
1322
+ const paymentNeededHeader = base64url(JSON.stringify(challenge));
1323
+ const x402Accepts = {
1324
+ scheme: ALIPAY_SCHEME,
1325
+ network: ALIPAY_NETWORK,
1326
+ asset: "CNY",
1327
+ amount: opts.priceCny,
1328
+ payTo: this.config.seller_id,
1329
+ maxTimeoutSeconds: ALIPAY_PAY_BEFORE_MS / 1e3,
1330
+ extra: {
1331
+ payment_needed_header: paymentNeededHeader,
1332
+ out_trade_no: outTradeNo,
1333
+ pay_before: payBefore,
1334
+ service_id: signedFields.service_id
1335
+ }
1336
+ };
1337
+ return { x402Accepts, paymentNeededHeader };
1338
+ }
1339
+ /**
1340
+ * Verify a `Payment-Proof` by calling
1341
+ * `alipay.aipay.agent.payment.verify` against the Alipay Open API.
1342
+ *
1343
+ * The proof is conveyed via `paymentPayload.payload`, which may be:
1344
+ * - a raw Base64URL string (the `Payment-Proof` header value), or
1345
+ * - an object `{ paymentProof: string }` / `{ proofHeader: string }`.
1346
+ *
1347
+ * All failure modes (malformed payload, network errors, Alipay
1348
+ * `code != 10000`) return `{ valid: false, error }`. No exception
1349
+ * escapes, regardless of client-supplied input.
1350
+ */
1351
+ async verify(paymentPayload, _requirements) {
1352
+ try {
1353
+ const proofHeader = extractProofHeader(paymentPayload.payload);
1354
+ const decoded = decodeProof(proofHeader);
1355
+ const response = await alipayOpenApiCall(
1356
+ "alipay.aipay.agent.payment.verify",
1357
+ {
1358
+ payment_proof: decoded.protocol.payment_proof,
1359
+ trade_no: decoded.protocol.trade_no,
1360
+ client_session: decoded.method.client_session
1361
+ },
1362
+ this.getOpenApiConfig()
1363
+ );
1364
+ if (response.code !== "10000") {
1365
+ return {
1366
+ valid: false,
1367
+ error: `alipay verify ${response.code}: ${response.sub_msg ?? response.msg ?? "unknown"}`,
1368
+ details: {
1369
+ code: response.code,
1370
+ sub_code: response.sub_code,
1371
+ sub_msg: response.sub_msg
1372
+ }
1373
+ };
1374
+ }
1375
+ return {
1376
+ valid: true,
1377
+ details: {
1378
+ trade_no: response.trade_no ?? decoded.protocol.trade_no,
1379
+ amount: response.amount,
1380
+ out_trade_no: response.out_trade_no,
1381
+ resource_id: response.resource_id,
1382
+ active: response.active
1383
+ }
1384
+ };
1385
+ } catch (e) {
1386
+ return {
1387
+ valid: false,
1388
+ error: e instanceof Error ? e.message : String(e)
1389
+ };
1390
+ }
1391
+ }
1392
+ /**
1393
+ * Settle by calling `alipay.aipay.agent.fulfillment.confirm` after the
1394
+ * service resource has been returned to the buyer.
1395
+ *
1396
+ * Per the design (see ALIPAY-INTEGRATION-DESIGN.md §5.1, risk row
1397
+ * "履约确认失败"), this is **fire-and-forget**: the caller (registry /
1398
+ * server) is expected to log fulfillment failures but NOT roll back
1399
+ * the already-delivered resource.
1400
+ *
1401
+ * Re-decodes `paymentPayload.payload` to extract `trade_no` (verify
1402
+ * does the same; the redundant Base64URL decode is negligible).
1403
+ */
1404
+ async settle(paymentPayload, _requirements) {
1405
+ try {
1406
+ const proofHeader = extractProofHeader(paymentPayload.payload);
1407
+ const decoded = decodeProof(proofHeader);
1408
+ const tradeNo = decoded.protocol.trade_no;
1409
+ const response = await alipayOpenApiCall(
1410
+ "alipay.aipay.agent.fulfillment.confirm",
1411
+ { trade_no: tradeNo },
1412
+ this.getOpenApiConfig()
1413
+ );
1414
+ if (response.code !== "10000") {
1415
+ return {
1416
+ success: false,
1417
+ transaction: tradeNo,
1418
+ error: `alipay fulfillment ${response.code}: ${response.sub_msg ?? response.msg ?? "unknown"}`,
1419
+ status: "fulfillment_failed"
1420
+ };
1421
+ }
1422
+ return {
1423
+ success: true,
1424
+ transaction: tradeNo,
1425
+ status: "fulfilled"
1426
+ };
1427
+ } catch (e) {
1428
+ return {
1429
+ success: false,
1430
+ error: e instanceof Error ? e.message : String(e)
1431
+ };
1432
+ }
1433
+ }
1434
+ /**
1435
+ * Validate that the configured RSA keys parse and that the Open API
1436
+ * gateway is reachable. Does NOT make a real business API call (would
1437
+ * burn quota and could appear as a legitimate verify attempt in logs).
1438
+ */
1439
+ async healthCheck() {
1440
+ const start = Date.now();
1441
+ try {
1442
+ crypto2.createPrivateKey(this.config.private_key_pem);
1443
+ } catch (e) {
1444
+ return {
1445
+ healthy: false,
1446
+ error: `merchant private_key_pem parse failed: ${e instanceof Error ? e.message : String(e)}`
1447
+ };
1448
+ }
1449
+ try {
1450
+ crypto2.createPublicKey(this.config.alipay_public_key_pem);
1451
+ } catch (e) {
1452
+ return {
1453
+ healthy: false,
1454
+ error: `alipay_public_key_pem parse failed: ${e instanceof Error ? e.message : String(e)}`
1455
+ };
1456
+ }
1457
+ const gatewayUrl = this.config.gateway_url ?? ALIPAY_GATEWAY_PROD;
1458
+ const controller = new AbortController();
1459
+ const timeout = setTimeout(() => controller.abort(), 5e3);
1460
+ const response = await fetch(gatewayUrl, {
1461
+ method: "HEAD",
1462
+ signal: controller.signal
1463
+ }).catch(() => null);
1464
+ clearTimeout(timeout);
1465
+ const latencyMs = Date.now() - start;
1466
+ if (!response) {
1467
+ return { healthy: false, error: `gateway unreachable: ${gatewayUrl}`, latencyMs };
1468
+ }
1469
+ return { healthy: true, latencyMs };
1470
+ }
1471
+ /** Bundle the facilitator config into the shape openapi.ts wants. */
1472
+ getOpenApiConfig() {
1473
+ return {
1474
+ gateway_url: this.config.gateway_url ?? ALIPAY_GATEWAY_PROD,
1475
+ app_id: this.config.app_id,
1476
+ private_key_pem: this.config.private_key_pem,
1477
+ alipay_public_key_pem: this.config.alipay_public_key_pem,
1478
+ sign_type: this.config.sign_type
1479
+ };
1480
+ }
1481
+ };
1482
+ function generateOutTradeNo() {
1483
+ return "VID" + crypto2.randomBytes(22).toString("base64url").slice(0, 29);
1484
+ }
1485
+ function formatPayBefore(now) {
1486
+ const expiry = new Date(now.getTime() + ALIPAY_PAY_BEFORE_MS);
1487
+ return expiry.toISOString().replace(/\.\d{3}Z$/, "Z");
1488
+ }
1489
+ function extractProofHeader(payload) {
1490
+ if (typeof payload === "string") {
1491
+ if (payload.length === 0) {
1492
+ throw new Error("alipay Payment-Proof is empty");
1493
+ }
1494
+ return payload;
1495
+ }
1496
+ if (payload !== null && typeof payload === "object") {
1497
+ const obj = payload;
1498
+ const candidate = obj.paymentProof ?? obj.proofHeader;
1499
+ if (typeof candidate === "string" && candidate.length > 0) {
1500
+ return candidate;
1501
+ }
1502
+ }
1503
+ throw new Error(
1504
+ "alipay payment payload must be a Base64URL string or {paymentProof: string} / {proofHeader: string}"
1505
+ );
1506
+ }
1507
+ function decodeProof(proofHeader) {
1508
+ let parsed;
1509
+ try {
1510
+ parsed = JSON.parse(decodeBase64UrlWithPadFix(proofHeader));
1511
+ } catch (e) {
1512
+ throw new Error(
1513
+ `failed to decode Payment-Proof: ${e instanceof Error ? e.message : String(e)}`
1514
+ );
1515
+ }
1516
+ if (parsed === null || typeof parsed !== "object") {
1517
+ throw new Error("decoded Payment-Proof is not an object");
1518
+ }
1519
+ const obj = parsed;
1520
+ const protocol = obj.protocol;
1521
+ const method = obj.method;
1522
+ if (!protocol || typeof protocol.payment_proof !== "string") {
1523
+ throw new Error("decoded Payment-Proof missing protocol.payment_proof");
1524
+ }
1525
+ if (typeof protocol.trade_no !== "string") {
1526
+ throw new Error("decoded Payment-Proof missing protocol.trade_no");
1527
+ }
1528
+ if (!method || typeof method.client_session !== "string") {
1529
+ throw new Error("decoded Payment-Proof missing method.client_session");
1530
+ }
1531
+ return parsed;
1532
+ }
1533
+
1162
1534
  // src/facilitators/registry.ts
1163
1535
  import { Keypair as Keypair2 } from "@solana/web3.js";
1164
1536
  import bs58 from "bs58";
@@ -1183,6 +1555,7 @@ var FacilitatorRegistry = class {
1183
1555
  }
1184
1556
  return new SolanaFacilitator({ feePayerKeypair });
1185
1557
  });
1558
+ this.registerFactory("alipay", (config) => new AlipayFacilitator(config));
1186
1559
  this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
1187
1560
  }
1188
1561
  /**
@@ -1407,6 +1780,11 @@ var PAYMENT_RESPONSE_HEADER = "x-payment-response";
1407
1780
  var MPP_AUTH_HEADER = "authorization";
1408
1781
  var MPP_WWW_AUTH_HEADER = "www-authenticate";
1409
1782
  var MPP_RECEIPT_HEADER = "payment-receipt";
1783
+ var ALIPAY_PAYMENT_NEEDED_HEADER = "payment-needed";
1784
+ var ALIPAY_PAYMENT_PROOF_HEADER = "payment-proof";
1785
+ function headerSafe(value) {
1786
+ return String(value ?? "").replace(/[^\x20-\x7E]/g, (ch) => encodeURIComponent(ch)).replace(/"/g, "%22");
1787
+ }
1410
1788
  var TOKEN_ADDRESSES = {
1411
1789
  "eip155:8453": {
1412
1790
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -1542,6 +1920,8 @@ var MoltsPayServer = class {
1542
1920
  registry;
1543
1921
  networkId;
1544
1922
  useMainnet;
1923
+ /** Alipay AI 收 facilitator instance, set when `provider.alipay` is configured (2.0.0). */
1924
+ alipayFacilitator = null;
1545
1925
  constructor(servicesPath, options = {}) {
1546
1926
  loadEnvFile2();
1547
1927
  const content = readFileSync2(servicesPath, "utf-8");
@@ -1563,7 +1943,38 @@ var MoltsPayServer = class {
1563
1943
  cdp: { useMainnet: this.useMainnet }
1564
1944
  }
1565
1945
  };
1946
+ const providerAlipay = this.manifest.provider.alipay;
1947
+ if (providerAlipay) {
1948
+ try {
1949
+ const baseDir = path2.dirname(servicesPath);
1950
+ const resolvePem = (p, kind) => toPem(readFileSync2(path2.isAbsolute(p) ? p : path2.resolve(baseDir, p), "utf-8"), kind);
1951
+ const alipayFacilitatorConfig = {
1952
+ seller_id: providerAlipay.seller_id,
1953
+ app_id: providerAlipay.app_id,
1954
+ seller_name: providerAlipay.seller_name,
1955
+ service_id_default: providerAlipay.service_id_default,
1956
+ private_key_pem: resolvePem(providerAlipay.private_key_path, "PRIVATE"),
1957
+ alipay_public_key_pem: resolvePem(providerAlipay.alipay_public_key_path, "PUBLIC"),
1958
+ gateway_url: providerAlipay.gateway_url,
1959
+ sign_type: providerAlipay.sign_type
1960
+ };
1961
+ facilitatorConfig.config = {
1962
+ ...facilitatorConfig.config,
1963
+ alipay: alipayFacilitatorConfig
1964
+ };
1965
+ facilitatorConfig.fallback = facilitatorConfig.fallback || [];
1966
+ if (facilitatorConfig.primary !== "alipay" && !facilitatorConfig.fallback.includes("alipay")) {
1967
+ facilitatorConfig.fallback.push("alipay");
1968
+ }
1969
+ } catch (err) {
1970
+ throw new Error(`[MoltsPay] Alipay rail configured but key load failed: ${err.message}`);
1971
+ }
1972
+ }
1566
1973
  this.registry = new FacilitatorRegistry(facilitatorConfig);
1974
+ if (providerAlipay) {
1975
+ this.alipayFacilitator = this.registry.get("alipay");
1976
+ console.log(`[MoltsPay] Alipay AI \u6536 rail enabled (seller ${providerAlipay.seller_id})`);
1977
+ }
1567
1978
  const primaryFacilitator = this.registry.get(facilitatorConfig.primary);
1568
1979
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
1569
1980
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
@@ -1699,10 +2110,10 @@ var MoltsPayServer = class {
1699
2110
  writeCorsHeaders(res, origin) {
1700
2111
  res.setHeader("Access-Control-Allow-Origin", origin);
1701
2112
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
1702
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
2113
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization, Payment-Proof");
1703
2114
  res.setHeader(
1704
2115
  "Access-Control-Expose-Headers",
1705
- "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt"
2116
+ "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt, Payment-Needed"
1706
2117
  );
1707
2118
  }
1708
2119
  /**
@@ -1729,7 +2140,8 @@ var MoltsPayServer = class {
1729
2140
  if (url.pathname === "/execute" && req.method === "POST") {
1730
2141
  const body = await this.readBody(req);
1731
2142
  const paymentHeader = req.headers[PAYMENT_HEADER];
1732
- return await this.handleExecute(body, paymentHeader, res);
2143
+ const proofHeader = req.headers[ALIPAY_PAYMENT_PROOF_HEADER];
2144
+ return await this.handleExecute(body, paymentHeader, res, proofHeader);
1733
2145
  }
1734
2146
  if (url.pathname === "/proxy" && req.method === "POST") {
1735
2147
  const clientIP = req.headers["x-real-ip"] || req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket.remoteAddress || "";
@@ -1747,7 +2159,8 @@ var MoltsPayServer = class {
1747
2159
  const body = req.method === "POST" ? await this.readBody(req) : {};
1748
2160
  const authHeader = req.headers[MPP_AUTH_HEADER];
1749
2161
  const x402Header = req.headers[PAYMENT_HEADER];
1750
- return await this.handleMPPRequest(skill, body, authHeader, x402Header, res);
2162
+ const proofHeader = req.headers[ALIPAY_PAYMENT_PROOF_HEADER];
2163
+ return await this.handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader);
1751
2164
  }
1752
2165
  this.sendJson(res, 404, { error: "Not found" });
1753
2166
  } catch (err) {
@@ -1844,7 +2257,7 @@ var MoltsPayServer = class {
1844
2257
  /**
1845
2258
  * POST /execute - Execute service with x402 payment
1846
2259
  */
1847
- async handleExecute(body, paymentHeader, res) {
2260
+ async handleExecute(body, paymentHeader, res, proofHeader) {
1848
2261
  const { service, params } = body;
1849
2262
  if (!service) {
1850
2263
  return this.sendJson(res, 400, { error: "Missing service" });
@@ -1858,6 +2271,15 @@ var MoltsPayServer = class {
1858
2271
  return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
1859
2272
  }
1860
2273
  }
2274
+ if (proofHeader) {
2275
+ const alipayPayment = {
2276
+ x402Version: X402_VERSION2,
2277
+ scheme: ALIPAY_SCHEME,
2278
+ network: ALIPAY_NETWORK,
2279
+ payload: proofHeader
2280
+ };
2281
+ return this.handleAlipayExecute(skill, params || {}, alipayPayment, res);
2282
+ }
1861
2283
  if (!paymentHeader) {
1862
2284
  return this.sendPaymentRequired(skill.config, res);
1863
2285
  }
@@ -1868,6 +2290,11 @@ var MoltsPayServer = class {
1868
2290
  } catch {
1869
2291
  return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
1870
2292
  }
2293
+ const payScheme = payment.accepted?.scheme || payment.scheme;
2294
+ const payNetwork = payment.accepted?.network || payment.network;
2295
+ if (payScheme === ALIPAY_SCHEME || (payNetwork ? isAlipayChainId(payNetwork) : false)) {
2296
+ return this.handleAlipayExecute(skill, params || {}, payment, res);
2297
+ }
1871
2298
  const validation = this.validatePayment(payment, skill.config);
1872
2299
  if (!validation.valid) {
1873
2300
  return this.sendJson(res, 402, { error: validation.error });
@@ -1960,13 +2387,111 @@ var MoltsPayServer = class {
1960
2387
  payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
1961
2388
  }, responseHeaders);
1962
2389
  }
2390
+ /**
2391
+ * Execute a service paid via the Alipay AI 收 fiat rail (2.0.0).
2392
+ *
2393
+ * Differs from the EVM/SVM path: no token detection, no EIP-3009/permit
2394
+ * validation. Verify hits the Alipay Open API (`payment.verify`). Settlement
2395
+ * (`fulfillment.confirm`) is FIRE-AND-FORGET per ALIPAY-INTEGRATION-DESIGN
2396
+ * §5.1: a confirm failure is logged but does NOT fail the already-delivered
2397
+ * response (the buyer's payment proof was already verified).
2398
+ */
2399
+ async handleAlipayExecute(skill, params, payment, res) {
2400
+ if (!this.alipayFacilitator) {
2401
+ return this.sendJson(res, 402, { error: "Alipay rail not configured on this server" });
2402
+ }
2403
+ const requirements = {
2404
+ scheme: ALIPAY_SCHEME,
2405
+ network: ALIPAY_NETWORK,
2406
+ asset: "CNY",
2407
+ amount: skill.config.alipay?.price_cny || "0",
2408
+ payTo: this.manifest.provider.alipay?.seller_id || "",
2409
+ maxTimeoutSeconds: 1800
2410
+ };
2411
+ console.log(`[MoltsPay] Verifying Alipay payment...`);
2412
+ const verifyResult = await this.registry.verify(payment, requirements);
2413
+ if (!verifyResult.valid) {
2414
+ return this.sendJson(res, 402, {
2415
+ error: `Payment verification failed: ${verifyResult.error}`,
2416
+ facilitator: verifyResult.facilitator
2417
+ });
2418
+ }
2419
+ console.log(`[MoltsPay] Alipay payment verified by ${verifyResult.facilitator}`);
2420
+ const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
2421
+ console.log(`[MoltsPay] Executing skill: ${skill.id} (timeout: ${timeoutSeconds}s)`);
2422
+ let result;
2423
+ try {
2424
+ result = await Promise.race([
2425
+ skill.handler(params),
2426
+ new Promise(
2427
+ (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
2428
+ )
2429
+ ]);
2430
+ } catch (err) {
2431
+ console.error("[MoltsPay] Skill execution failed:", err.message);
2432
+ return this.sendJson(res, 500, {
2433
+ error: "Service execution failed",
2434
+ message: err.message
2435
+ });
2436
+ }
2437
+ let settlement;
2438
+ try {
2439
+ settlement = await this.registry.settle(payment, requirements);
2440
+ if (settlement.success) {
2441
+ console.log(`[MoltsPay] Alipay fulfillment confirmed: ${settlement.transaction}`);
2442
+ } else {
2443
+ console.error(`[MoltsPay] Alipay fulfillment confirm failed (non-fatal): ${settlement.error}`);
2444
+ }
2445
+ } catch (err) {
2446
+ console.error(`[MoltsPay] Alipay fulfillment confirm threw (non-fatal): ${err.message}`);
2447
+ settlement = { success: false, error: err.message, facilitator: "alipay" };
2448
+ }
2449
+ const responseHeaders = {};
2450
+ if (settlement.success) {
2451
+ responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(JSON.stringify({
2452
+ success: true,
2453
+ transaction: settlement.transaction,
2454
+ network: ALIPAY_NETWORK,
2455
+ facilitator: settlement.facilitator
2456
+ })).toString("base64");
2457
+ }
2458
+ this.sendJson(res, 200, {
2459
+ success: true,
2460
+ result,
2461
+ payment: settlement.success ? { transaction: settlement.transaction, status: "fulfilled", facilitator: settlement.facilitator } : { status: "delivered_unconfirmed", error: settlement.error }
2462
+ }, responseHeaders);
2463
+ }
2464
+ /**
2465
+ * Build the Alipay 402 challenge for a service, or null when the alipay rail
2466
+ * isn't configured for this server or this service. Returns the x402
2467
+ * `accepts[]` entry plus the Base64URL `Payment-Needed` header value so the
2468
+ * 402 responders can dual-emit both the x402 and legacy alipay-bot formats.
2469
+ */
2470
+ async buildAlipayChallenge(config) {
2471
+ if (!this.alipayFacilitator || !config.alipay) return null;
2472
+ try {
2473
+ const req = await this.alipayFacilitator.createPaymentRequirements({
2474
+ serviceId: config.alipay.service_id || this.manifest.provider.alipay.service_id_default,
2475
+ priceCny: config.alipay.price_cny,
2476
+ goodsName: config.alipay.goods_name,
2477
+ resourceId: `/execute?service=${config.id}`
2478
+ });
2479
+ return { accepts: req.x402Accepts, paymentNeededHeader: req.paymentNeededHeader };
2480
+ } catch (err) {
2481
+ console.error(`[MoltsPay] Alipay challenge build failed for ${config.id}: ${err.message}`);
2482
+ return null;
2483
+ }
2484
+ }
1963
2485
  /**
1964
2486
  * Handle MPP (Machine Payments Protocol) request
1965
2487
  * Supports both x402 and MPP protocols on service endpoints
1966
2488
  */
1967
- async handleMPPRequest(skill, body, authHeader, x402Header, res) {
2489
+ async handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader) {
1968
2490
  const config = skill.config;
1969
2491
  const params = body || {};
2492
+ if (proofHeader) {
2493
+ return await this.handleExecute({ service: config.id, params }, void 0, res, proofHeader);
2494
+ }
1970
2495
  if (x402Header) {
1971
2496
  return await this.handleExecute({ service: config.id, params }, x402Header, res);
1972
2497
  }
@@ -2072,7 +2597,7 @@ var MoltsPayServer = class {
2072
2597
  /**
2073
2598
  * Return 402 with both x402 and MPP payment requirements
2074
2599
  */
2075
- sendMPPPaymentRequired(config, res) {
2600
+ async sendMPPPaymentRequired(config, res) {
2076
2601
  const acceptedTokens = getAcceptedCurrencies(config);
2077
2602
  const providerChains = this.getProviderChains();
2078
2603
  const accepts = [];
@@ -2083,6 +2608,10 @@ var MoltsPayServer = class {
2083
2608
  }
2084
2609
  }
2085
2610
  }
2611
+ const alipayChallenge = await this.buildAlipayChallenge(config);
2612
+ if (alipayChallenge) {
2613
+ accepts.push(alipayChallenge.accepts);
2614
+ }
2086
2615
  const x402PaymentRequired = {
2087
2616
  x402Version: X402_VERSION2,
2088
2617
  accepts,
@@ -2110,7 +2639,7 @@ var MoltsPayServer = class {
2110
2639
  };
2111
2640
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
2112
2641
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
2113
- mppWwwAuth = `Payment id="${challengeId}", realm="${this.manifest.provider.name}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
2642
+ mppWwwAuth = `Payment id="${challengeId}", realm="${headerSafe(this.manifest.provider.name)}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${headerSafe(config.name)}", expires="${expiresAt}"`;
2114
2643
  }
2115
2644
  const headers = {
2116
2645
  "Content-Type": "application/problem+json",
@@ -2119,6 +2648,9 @@ var MoltsPayServer = class {
2119
2648
  if (mppWwwAuth) {
2120
2649
  headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
2121
2650
  }
2651
+ if (alipayChallenge) {
2652
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
2653
+ }
2122
2654
  res.writeHead(402, headers);
2123
2655
  res.end(JSON.stringify({
2124
2656
  type: "https://paymentauth.org/problems/payment-required",
@@ -2145,7 +2677,7 @@ var MoltsPayServer = class {
2145
2677
  * Return 402 with x402 payment requirements (v2 format)
2146
2678
  * Includes requirements for all chains and all accepted currencies
2147
2679
  */
2148
- sendPaymentRequired(config, res) {
2680
+ async sendPaymentRequired(config, res) {
2149
2681
  const acceptedTokens = getAcceptedCurrencies(config);
2150
2682
  const providerChains = this.getProviderChains();
2151
2683
  const accepts = [];
@@ -2156,6 +2688,10 @@ var MoltsPayServer = class {
2156
2688
  }
2157
2689
  }
2158
2690
  }
2691
+ const alipayChallenge = await this.buildAlipayChallenge(config);
2692
+ if (alipayChallenge) {
2693
+ accepts.push(alipayChallenge.accepts);
2694
+ }
2159
2695
  const acceptedChains = providerChains.map((c) => {
2160
2696
  if (c.network === "eip155:8453") return "base";
2161
2697
  if (c.network === "eip155:137") return "polygon";
@@ -2173,10 +2709,14 @@ var MoltsPayServer = class {
2173
2709
  }
2174
2710
  };
2175
2711
  const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
2176
- res.writeHead(402, {
2712
+ const headers = {
2177
2713
  "Content-Type": "application/json",
2178
2714
  [PAYMENT_REQUIRED_HEADER]: encoded
2179
- });
2715
+ };
2716
+ if (alipayChallenge) {
2717
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
2718
+ }
2719
+ res.writeHead(402, headers);
2180
2720
  res.end(JSON.stringify({
2181
2721
  error: "Payment required",
2182
2722
  message: `Service requires $${config.price} ${config.currency}`,
@@ -2283,12 +2823,12 @@ var MoltsPayServer = class {
2283
2823
  return accepted.includes(token);
2284
2824
  }
2285
2825
  async readBody(req) {
2286
- return new Promise((resolve, reject) => {
2826
+ return new Promise((resolve2, reject) => {
2287
2827
  let body = "";
2288
2828
  req.on("data", (chunk) => body += chunk);
2289
2829
  req.on("end", () => {
2290
2830
  try {
2291
- resolve(body ? JSON.parse(body) : {});
2831
+ resolve2(body ? JSON.parse(body) : {});
2292
2832
  } catch {
2293
2833
  reject(new Error("Invalid JSON"));
2294
2834
  }
@@ -2546,7 +3086,7 @@ var MoltsPayServer = class {
2546
3086
  };
2547
3087
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
2548
3088
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
2549
- const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
3089
+ const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${headerSafe(config.name)}", expires="${expiresAt}"`;
2550
3090
  res.writeHead(402, {
2551
3091
  "Content-Type": "application/problem+json",
2552
3092
  [MPP_WWW_AUTH_HEADER]: wwwAuth