moltspay 1.5.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 +143 -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 +1941 -204
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/cli/index.mjs +1938 -201
  15. package/dist/cli/index.mjs.map +1 -1
  16. package/dist/client/index.d.mts +46 -0
  17. package/dist/client/index.d.ts +46 -0
  18. package/dist/client/index.js +1059 -118
  19. package/dist/client/index.js.map +1 -1
  20. package/dist/client/index.mjs +1055 -116
  21. package/dist/client/index.mjs.map +1 -1
  22. package/dist/client/web/index.d.mts +434 -0
  23. package/dist/client/web/index.mjs +1300 -0
  24. package/dist/client/web/index.mjs.map +1 -0
  25. package/dist/facilitators/index.d.mts +185 -6
  26. package/dist/facilitators/index.d.ts +185 -6
  27. package/dist/facilitators/index.js +497 -13
  28. package/dist/facilitators/index.js.map +1 -1
  29. package/dist/facilitators/index.mjs +492 -13
  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 +1865 -172
  34. package/dist/index.js.map +1 -1
  35. package/dist/index.mjs +1854 -167
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/mcp/index.js +1062 -123
  38. package/dist/mcp/index.js.map +1 -1
  39. package/dist/mcp/index.mjs +1059 -120
  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 +137 -2
  44. package/dist/server/index.d.ts +137 -2
  45. package/dist/server/index.js +757 -30
  46. package/dist/server/index.js.map +1 -1
  47. package/dist/server/index.mjs +757 -30
  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 +15 -2
  54. package/schemas/moltspay.services.schema.json +100 -6
  55. package/scripts/postinstall.js +91 -0
@@ -263,6 +263,9 @@ var CDPFacilitator = class extends BaseFacilitator {
263
263
  }
264
264
  };
265
265
 
266
+ // src/facilitators/tempo.ts
267
+ var import_ethers = require("ethers");
268
+
266
269
  // src/chains/index.ts
267
270
  var CHAINS = {
268
271
  // ============ Mainnet ============
@@ -429,18 +432,45 @@ var CHAINS = {
429
432
  requiresApproval: true
430
433
  }
431
434
  };
435
+ var ALIPAY_CHAIN_ID = "alipay";
436
+ function isAlipayChainId(id) {
437
+ return id === ALIPAY_CHAIN_ID;
438
+ }
432
439
 
433
440
  // src/facilitators/tempo.ts
434
441
  var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
442
+ var TIP20_PERMIT_ABI = [
443
+ "function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
444
+ "function transferFrom(address from, address to, uint256 value) returns (bool)"
445
+ ];
435
446
  var TempoFacilitator = class extends BaseFacilitator {
436
447
  name = "tempo";
437
448
  displayName = "Tempo Testnet";
438
449
  supportedNetworks = ["eip155:42431"];
439
450
  // Tempo Moderato
440
451
  rpcUrl;
452
+ settlerWallet = null;
441
453
  constructor() {
442
454
  super();
443
455
  this.rpcUrl = CHAINS.tempo_moderato.rpc;
456
+ const settlerKey = process.env.TEMPO_SETTLER_KEY;
457
+ if (settlerKey) {
458
+ try {
459
+ const provider = new import_ethers.ethers.JsonRpcProvider(this.rpcUrl);
460
+ this.settlerWallet = new import_ethers.ethers.Wallet(settlerKey, provider);
461
+ } catch (err) {
462
+ console.warn("[TempoFacilitator] Invalid TEMPO_SETTLER_KEY, permit settlement disabled:", err);
463
+ this.settlerWallet = null;
464
+ }
465
+ }
466
+ }
467
+ /**
468
+ * Settler EOA address advertised to clients via `X-Payment-Required.extra.tempoSpender`.
469
+ * Web Client uses this as the `spender` field in the signed EIP-2612 Permit.
470
+ * Returns null if no TEMPO_SETTLER_KEY is configured — permit settlement unavailable.
471
+ */
472
+ getSpenderAddress() {
473
+ return this.settlerWallet?.address ?? null;
444
474
  }
445
475
  async healthCheck() {
446
476
  const start = Date.now();
@@ -466,6 +496,44 @@ var TempoFacilitator = class extends BaseFacilitator {
466
496
  }
467
497
  }
468
498
  async verify(paymentPayload, requirements) {
499
+ const inner = paymentPayload.payload;
500
+ if (inner && "permit" in inner && inner.permit) {
501
+ return this.verifyPermit(inner, requirements);
502
+ }
503
+ return this.verifyTxHash(paymentPayload, requirements);
504
+ }
505
+ /**
506
+ * Structural validation of an EIP-2612 permit payload. Does NOT submit
507
+ * anything on-chain — actual submission happens in settlePermit().
508
+ */
509
+ async verifyPermit(payload, requirements) {
510
+ if (!this.settlerWallet) {
511
+ return { valid: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
512
+ }
513
+ const p = payload.permit;
514
+ if (!p || !p.owner || !p.spender || !p.value || !p.deadline) {
515
+ return { valid: false, error: "Invalid permit payload: missing fields" };
516
+ }
517
+ if (p.spender.toLowerCase() !== this.settlerWallet.address.toLowerCase()) {
518
+ return {
519
+ valid: false,
520
+ error: `Permit spender ${p.spender} does not match configured settler ${this.settlerWallet.address}`
521
+ };
522
+ }
523
+ const deadline = BigInt(p.deadline);
524
+ const now = BigInt(Math.floor(Date.now() / 1e3));
525
+ if (deadline <= now) {
526
+ return { valid: false, error: "Permit deadline has expired" };
527
+ }
528
+ if (BigInt(p.value) < BigInt(requirements.amount || "0")) {
529
+ return {
530
+ valid: false,
531
+ error: `Permit value ${p.value} is less than required ${requirements.amount}`
532
+ };
533
+ }
534
+ return { valid: true, details: { scheme: "permit", owner: p.owner } };
535
+ }
536
+ async verifyTxHash(paymentPayload, requirements) {
469
537
  try {
470
538
  const tempoPayload = paymentPayload.payload;
471
539
  if (!tempoPayload?.txHash) {
@@ -523,7 +591,11 @@ var TempoFacilitator = class extends BaseFacilitator {
523
591
  }
524
592
  }
525
593
  async settle(paymentPayload, requirements) {
526
- const verifyResult = await this.verify(paymentPayload, requirements);
594
+ const inner = paymentPayload.payload;
595
+ if (inner && "permit" in inner && inner.permit) {
596
+ return this.settlePermit(inner, requirements);
597
+ }
598
+ const verifyResult = await this.verifyTxHash(paymentPayload, requirements);
527
599
  if (!verifyResult.valid) {
528
600
  return { success: false, error: verifyResult.error };
529
601
  }
@@ -534,6 +606,52 @@ var TempoFacilitator = class extends BaseFacilitator {
534
606
  status: "settled"
535
607
  };
536
608
  }
609
+ /**
610
+ * EIP-2612 permit settlement path. Submits two transactions on Tempo:
611
+ * 1. pathUSD.permit(owner, spender=settler, value, deadline, v, r, s)
612
+ * 2. pathUSD.transferFrom(owner, payTo, value)
613
+ *
614
+ * The settler EOA pays Tempo gas (via the TIP-20 `feeToken` mechanism — no
615
+ * native tTEMPO required; any held TIP-20 token balance covers fees).
616
+ */
617
+ async settlePermit(payload, requirements) {
618
+ if (!this.settlerWallet) {
619
+ return { success: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
620
+ }
621
+ if (!requirements.asset || !requirements.payTo) {
622
+ return { success: false, error: "Missing asset or payTo in requirements" };
623
+ }
624
+ const verifyResult = await this.verifyPermit(payload, requirements);
625
+ if (!verifyResult.valid) {
626
+ return { success: false, error: verifyResult.error };
627
+ }
628
+ const token = new import_ethers.ethers.Contract(requirements.asset, TIP20_PERMIT_ABI, this.settlerWallet);
629
+ const p = payload.permit;
630
+ try {
631
+ const permitTx = await token.permit(
632
+ p.owner,
633
+ p.spender,
634
+ p.value,
635
+ p.deadline,
636
+ p.v,
637
+ p.r,
638
+ p.s
639
+ );
640
+ await permitTx.wait();
641
+ const transferTx = await token.transferFrom(p.owner, requirements.payTo, p.value);
642
+ await transferTx.wait();
643
+ return {
644
+ success: true,
645
+ transaction: transferTx.hash,
646
+ status: "settled"
647
+ };
648
+ } catch (err) {
649
+ return {
650
+ success: false,
651
+ error: `Tempo permit settlement failed: ${err.message}`
652
+ };
653
+ }
654
+ }
537
655
  async getTransactionReceipt(txHash) {
538
656
  const response = await fetch(this.rpcUrl, {
539
657
  method: "POST",
@@ -776,12 +894,12 @@ var BNBFacilitator = class extends BaseFacilitator {
776
894
  return this.spenderAddress;
777
895
  }
778
896
  async getServerAddress() {
779
- const { ethers } = await import("ethers");
780
- const wallet = new ethers.Wallet(this.serverPrivateKey);
897
+ const { ethers: ethers2 } = await import("ethers");
898
+ const wallet = new ethers2.Wallet(this.serverPrivateKey);
781
899
  return wallet.address;
782
900
  }
783
901
  async recoverIntentSigner(intent, chainId) {
784
- const { ethers } = await import("ethers");
902
+ const { ethers: ethers2 } = await import("ethers");
785
903
  const domain = {
786
904
  ...EIP712_DOMAIN,
787
905
  chainId
@@ -795,7 +913,7 @@ var BNBFacilitator = class extends BaseFacilitator {
795
913
  nonce: intent.nonce,
796
914
  deadline: intent.deadline
797
915
  };
798
- const recoveredAddress = ethers.verifyTypedData(
916
+ const recoveredAddress = ethers2.verifyTypedData(
799
917
  domain,
800
918
  INTENT_TYPES,
801
919
  message,
@@ -839,10 +957,10 @@ var BNBFacilitator = class extends BaseFacilitator {
839
957
  return result.result || "0x0";
840
958
  }
841
959
  async executeTransferFrom(from, to, amount, token, rpcUrl) {
842
- const { ethers } = await import("ethers");
843
- const provider = new ethers.JsonRpcProvider(rpcUrl);
844
- const wallet = new ethers.Wallet(this.serverPrivateKey, provider);
845
- const tokenContract = new ethers.Contract(token, [
960
+ const { ethers: ethers2 } = await import("ethers");
961
+ const provider = new ethers2.JsonRpcProvider(rpcUrl);
962
+ const wallet = new ethers2.Wallet(this.serverPrivateKey, provider);
963
+ const tokenContract = new ethers2.Contract(token, [
846
964
  "function transferFrom(address from, address to, uint256 amount) returns (bool)"
847
965
  ], wallet);
848
966
  const tx = await tokenContract.transferFrom(from, to, amount);
@@ -1069,6 +1187,374 @@ var SolanaFacilitator = class extends BaseFacilitator {
1069
1187
  }
1070
1188
  };
1071
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
+
1072
1558
  // src/facilitators/registry.ts
1073
1559
  var import_web33 = require("@solana/web3.js");
1074
1560
  var import_bs58 = __toESM(require("bs58"));
@@ -1093,6 +1579,7 @@ var FacilitatorRegistry = class {
1093
1579
  }
1094
1580
  return new SolanaFacilitator({ feePayerKeypair });
1095
1581
  });
1582
+ this.registerFactory("alipay", (config) => new AlipayFacilitator(config));
1096
1583
  this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
1097
1584
  }
1098
1585
  /**
@@ -1317,6 +1804,11 @@ var PAYMENT_RESPONSE_HEADER = "x-payment-response";
1317
1804
  var MPP_AUTH_HEADER = "authorization";
1318
1805
  var MPP_WWW_AUTH_HEADER = "www-authenticate";
1319
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
+ }
1320
1812
  var TOKEN_ADDRESSES = {
1321
1813
  "eip155:8453": {
1322
1814
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -1389,9 +1881,13 @@ var TOKEN_DOMAINS = {
1389
1881
  USDT: { name: "(PoS) Tether USD", version: "2" }
1390
1882
  },
1391
1883
  // Tempo Moderato testnet - TIP-20 stablecoins
1884
+ // Domain names verified against on-chain DOMAIN_SEPARATOR values on 2026-04-21.
1885
+ // See docs/TEMPO-WEB-SUPPORT.md Section 2 and test/server/tempo-domain.test.ts.
1886
+ // All 4 Tempo TIP-20 tokens (pathUSD / AlphaUSD / BetaUSD / ThetaUSD) use
1887
+ // the token symbol with first letter capitalized + version "1".
1392
1888
  "eip155:42431": {
1393
- USDC: { name: "pathUSD", version: "1" },
1394
- USDT: { name: "alphaUSD", version: "1" }
1889
+ USDC: { name: "PathUSD", version: "1" },
1890
+ USDT: { name: "AlphaUSD", version: "1" }
1395
1891
  },
1396
1892
  // BNB Smart Chain mainnet
1397
1893
  "eip155:56": {
@@ -1448,6 +1944,8 @@ var MoltsPayServer = class {
1448
1944
  registry;
1449
1945
  networkId;
1450
1946
  useMainnet;
1947
+ /** Alipay AI 收 facilitator instance, set when `provider.alipay` is configured (2.0.0). */
1948
+ alipayFacilitator = null;
1451
1949
  constructor(servicesPath, options = {}) {
1452
1950
  loadEnvFile2();
1453
1951
  const content = (0, import_fs2.readFileSync)(servicesPath, "utf-8");
@@ -1469,7 +1967,38 @@ var MoltsPayServer = class {
1469
1967
  cdp: { useMainnet: this.useMainnet }
1470
1968
  }
1471
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
+ }
1472
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
+ }
1473
2002
  const primaryFacilitator = this.registry.get(facilitatorConfig.primary);
1474
2003
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
1475
2004
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
@@ -1559,14 +2088,63 @@ var MoltsPayServer = class {
1559
2088
  console.log(` GET /health - Health check (incl. facilitators)`);
1560
2089
  });
1561
2090
  }
2091
+ /**
2092
+ * Apply CORS response headers according to the `cors` option.
2093
+ *
2094
+ * Default (`cors` unset or `true`): `Access-Control-Allow-Origin: *`. Matches 1.5.x behavior
2095
+ * and works for every browser client whose origin does not need to send cookies.
2096
+ *
2097
+ * `cors: false`: emit no CORS headers. Same-origin only.
2098
+ * `cors: string[]`: origin allowlist — echo the origin back iff it matches.
2099
+ * `cors: CorsOptions`: full control (allowlist + credentials + maxAge).
2100
+ *
2101
+ * The required-for-Web response headers are always exposed when CORS is active:
2102
+ * `X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt`.
2103
+ */
2104
+ applyCorsHeaders(req, res) {
2105
+ const cors = this.options.cors;
2106
+ if (cors === false) {
2107
+ return;
2108
+ }
2109
+ const requestOrigin = req.headers.origin ?? "*";
2110
+ if (cors === void 0 || cors === true) {
2111
+ this.writeCorsHeaders(res, "*");
2112
+ return;
2113
+ }
2114
+ if (Array.isArray(cors)) {
2115
+ if (cors.includes(requestOrigin)) {
2116
+ this.writeCorsHeaders(res, requestOrigin);
2117
+ res.setHeader("Vary", "Origin");
2118
+ }
2119
+ return;
2120
+ }
2121
+ const opt = cors;
2122
+ const isAllowed = typeof opt.origins === "function" ? opt.origins(requestOrigin) : opt.origins.includes(requestOrigin);
2123
+ if (!isAllowed) {
2124
+ return;
2125
+ }
2126
+ this.writeCorsHeaders(res, requestOrigin);
2127
+ res.setHeader("Vary", "Origin");
2128
+ if (opt.credentials) {
2129
+ res.setHeader("Access-Control-Allow-Credentials", "true");
2130
+ }
2131
+ const maxAge = opt.maxAge ?? 600;
2132
+ res.setHeader("Access-Control-Max-Age", String(maxAge));
2133
+ }
2134
+ writeCorsHeaders(res, origin) {
2135
+ res.setHeader("Access-Control-Allow-Origin", origin);
2136
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
2137
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization, Payment-Proof");
2138
+ res.setHeader(
2139
+ "Access-Control-Expose-Headers",
2140
+ "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt, Payment-Needed"
2141
+ );
2142
+ }
1562
2143
  /**
1563
2144
  * Handle incoming request
1564
2145
  */
1565
2146
  async handleRequest(req, res) {
1566
- res.setHeader("Access-Control-Allow-Origin", "*");
1567
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
1568
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
1569
- res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt");
2147
+ this.applyCorsHeaders(req, res);
1570
2148
  if (req.method === "OPTIONS") {
1571
2149
  res.writeHead(204);
1572
2150
  res.end();
@@ -1586,7 +2164,8 @@ var MoltsPayServer = class {
1586
2164
  if (url.pathname === "/execute" && req.method === "POST") {
1587
2165
  const body = await this.readBody(req);
1588
2166
  const paymentHeader = req.headers[PAYMENT_HEADER];
1589
- 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);
1590
2169
  }
1591
2170
  if (url.pathname === "/proxy" && req.method === "POST") {
1592
2171
  const clientIP = req.headers["x-real-ip"] || req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket.remoteAddress || "";
@@ -1604,7 +2183,8 @@ var MoltsPayServer = class {
1604
2183
  const body = req.method === "POST" ? await this.readBody(req) : {};
1605
2184
  const authHeader = req.headers[MPP_AUTH_HEADER];
1606
2185
  const x402Header = req.headers[PAYMENT_HEADER];
1607
- 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);
1608
2188
  }
1609
2189
  this.sendJson(res, 404, { error: "Not found" });
1610
2190
  } catch (err) {
@@ -1701,7 +2281,7 @@ var MoltsPayServer = class {
1701
2281
  /**
1702
2282
  * POST /execute - Execute service with x402 payment
1703
2283
  */
1704
- async handleExecute(body, paymentHeader, res) {
2284
+ async handleExecute(body, paymentHeader, res, proofHeader) {
1705
2285
  const { service, params } = body;
1706
2286
  if (!service) {
1707
2287
  return this.sendJson(res, 400, { error: "Missing service" });
@@ -1715,6 +2295,15 @@ var MoltsPayServer = class {
1715
2295
  return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
1716
2296
  }
1717
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
+ }
1718
2307
  if (!paymentHeader) {
1719
2308
  return this.sendPaymentRequired(skill.config, res);
1720
2309
  }
@@ -1725,6 +2314,11 @@ var MoltsPayServer = class {
1725
2314
  } catch {
1726
2315
  return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
1727
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
+ }
1728
2322
  const validation = this.validatePayment(payment, skill.config);
1729
2323
  if (!validation.valid) {
1730
2324
  return this.sendJson(res, 402, { error: validation.error });
@@ -1789,6 +2383,14 @@ var MoltsPayServer = class {
1789
2383
  console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1790
2384
  } catch (err) {
1791
2385
  console.error("[MoltsPay] Settlement failed:", err.message);
2386
+ settlement = { success: false, error: err.message, facilitator: "none" };
2387
+ }
2388
+ if (!settlement?.success) {
2389
+ return this.sendJson(res, 402, {
2390
+ error: "Payment settlement failed",
2391
+ message: settlement?.error || "Settlement returned no success state",
2392
+ facilitator: settlement?.facilitator
2393
+ });
1792
2394
  }
1793
2395
  }
1794
2396
  const responseHeaders = {};
@@ -1809,13 +2411,111 @@ var MoltsPayServer = class {
1809
2411
  payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
1810
2412
  }, responseHeaders);
1811
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
+ }
1812
2509
  /**
1813
2510
  * Handle MPP (Machine Payments Protocol) request
1814
2511
  * Supports both x402 and MPP protocols on service endpoints
1815
2512
  */
1816
- async handleMPPRequest(skill, body, authHeader, x402Header, res) {
2513
+ async handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader) {
1817
2514
  const config = skill.config;
1818
2515
  const params = body || {};
2516
+ if (proofHeader) {
2517
+ return await this.handleExecute({ service: config.id, params }, void 0, res, proofHeader);
2518
+ }
1819
2519
  if (x402Header) {
1820
2520
  return await this.handleExecute({ service: config.id, params }, x402Header, res);
1821
2521
  }
@@ -1921,7 +2621,7 @@ var MoltsPayServer = class {
1921
2621
  /**
1922
2622
  * Return 402 with both x402 and MPP payment requirements
1923
2623
  */
1924
- sendMPPPaymentRequired(config, res) {
2624
+ async sendMPPPaymentRequired(config, res) {
1925
2625
  const acceptedTokens = getAcceptedCurrencies(config);
1926
2626
  const providerChains = this.getProviderChains();
1927
2627
  const accepts = [];
@@ -1932,6 +2632,10 @@ var MoltsPayServer = class {
1932
2632
  }
1933
2633
  }
1934
2634
  }
2635
+ const alipayChallenge = await this.buildAlipayChallenge(config);
2636
+ if (alipayChallenge) {
2637
+ accepts.push(alipayChallenge.accepts);
2638
+ }
1935
2639
  const x402PaymentRequired = {
1936
2640
  x402Version: X402_VERSION2,
1937
2641
  accepts,
@@ -1959,7 +2663,7 @@ var MoltsPayServer = class {
1959
2663
  };
1960
2664
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
1961
2665
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
1962
- 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}"`;
1963
2667
  }
1964
2668
  const headers = {
1965
2669
  "Content-Type": "application/problem+json",
@@ -1968,6 +2672,9 @@ var MoltsPayServer = class {
1968
2672
  if (mppWwwAuth) {
1969
2673
  headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
1970
2674
  }
2675
+ if (alipayChallenge) {
2676
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
2677
+ }
1971
2678
  res.writeHead(402, headers);
1972
2679
  res.end(JSON.stringify({
1973
2680
  type: "https://paymentauth.org/problems/payment-required",
@@ -1994,7 +2701,7 @@ var MoltsPayServer = class {
1994
2701
  * Return 402 with x402 payment requirements (v2 format)
1995
2702
  * Includes requirements for all chains and all accepted currencies
1996
2703
  */
1997
- sendPaymentRequired(config, res) {
2704
+ async sendPaymentRequired(config, res) {
1998
2705
  const acceptedTokens = getAcceptedCurrencies(config);
1999
2706
  const providerChains = this.getProviderChains();
2000
2707
  const accepts = [];
@@ -2005,6 +2712,10 @@ var MoltsPayServer = class {
2005
2712
  }
2006
2713
  }
2007
2714
  }
2715
+ const alipayChallenge = await this.buildAlipayChallenge(config);
2716
+ if (alipayChallenge) {
2717
+ accepts.push(alipayChallenge.accepts);
2718
+ }
2008
2719
  const acceptedChains = providerChains.map((c) => {
2009
2720
  if (c.network === "eip155:8453") return "base";
2010
2721
  if (c.network === "eip155:137") return "polygon";
@@ -2022,10 +2733,14 @@ var MoltsPayServer = class {
2022
2733
  }
2023
2734
  };
2024
2735
  const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
2025
- res.writeHead(402, {
2736
+ const headers = {
2026
2737
  "Content-Type": "application/json",
2027
2738
  [PAYMENT_REQUIRED_HEADER]: encoded
2028
- });
2739
+ };
2740
+ if (alipayChallenge) {
2741
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
2742
+ }
2743
+ res.writeHead(402, headers);
2029
2744
  res.end(JSON.stringify({
2030
2745
  error: "Payment required",
2031
2746
  message: `Service requires $${config.price} ${config.currency}`,
@@ -2043,7 +2758,7 @@ var MoltsPayServer = class {
2043
2758
  }
2044
2759
  const scheme = payment.accepted?.scheme || payment.scheme;
2045
2760
  const network = payment.accepted?.network || payment.network || this.networkId;
2046
- if (scheme !== "exact") {
2761
+ if (scheme !== "exact" && scheme !== "permit") {
2047
2762
  return { valid: false, error: `Unsupported scheme: ${scheme}` };
2048
2763
  }
2049
2764
  if (!this.isNetworkAccepted(network)) {
@@ -2065,8 +2780,10 @@ var MoltsPayServer = class {
2065
2780
  const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
2066
2781
  const tokenAddress = tokenAddresses[selectedToken];
2067
2782
  const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
2783
+ const isTempo = selectedNetwork === "eip155:42431";
2784
+ const scheme = isTempo ? "permit" : "exact";
2068
2785
  const requirements = {
2069
- scheme: "exact",
2786
+ scheme,
2070
2787
  network: selectedNetwork,
2071
2788
  asset: tokenAddress,
2072
2789
  amount: amountInUnits,
@@ -2094,6 +2811,16 @@ var MoltsPayServer = class {
2094
2811
  };
2095
2812
  }
2096
2813
  }
2814
+ if (isTempo) {
2815
+ const tempoFacilitator = this.registry.get("tempo");
2816
+ const tempoSpender = tempoFacilitator?.getSpenderAddress?.();
2817
+ if (tempoSpender) {
2818
+ requirements.extra = {
2819
+ ...requirements.extra || {},
2820
+ tempoSpender
2821
+ };
2822
+ }
2823
+ }
2097
2824
  return requirements;
2098
2825
  }
2099
2826
  /**
@@ -2120,12 +2847,12 @@ var MoltsPayServer = class {
2120
2847
  return accepted.includes(token);
2121
2848
  }
2122
2849
  async readBody(req) {
2123
- return new Promise((resolve, reject) => {
2850
+ return new Promise((resolve2, reject) => {
2124
2851
  let body = "";
2125
2852
  req.on("data", (chunk) => body += chunk);
2126
2853
  req.on("end", () => {
2127
2854
  try {
2128
- resolve(body ? JSON.parse(body) : {});
2855
+ resolve2(body ? JSON.parse(body) : {});
2129
2856
  } catch {
2130
2857
  reject(new Error("Invalid JSON"));
2131
2858
  }
@@ -2228,7 +2955,7 @@ var MoltsPayServer = class {
2228
2955
  }
2229
2956
  const scheme = payment.accepted?.scheme || payment.scheme;
2230
2957
  const network = payment.accepted?.network || payment.network;
2231
- if (scheme !== "exact") {
2958
+ if (scheme !== "exact" && scheme !== "permit") {
2232
2959
  return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
2233
2960
  }
2234
2961
  const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
@@ -2383,7 +3110,7 @@ var MoltsPayServer = class {
2383
3110
  };
2384
3111
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
2385
3112
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
2386
- 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}"`;
2387
3114
  res.writeHead(402, {
2388
3115
  "Content-Type": "application/problem+json",
2389
3116
  [MPP_WWW_AUTH_HEADER]: wwwAuth