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
@@ -229,6 +229,9 @@ var CDPFacilitator = class extends BaseFacilitator {
229
229
  }
230
230
  };
231
231
 
232
+ // src/facilitators/tempo.ts
233
+ import { ethers } from "ethers";
234
+
232
235
  // src/chains/index.ts
233
236
  var CHAINS = {
234
237
  // ============ Mainnet ============
@@ -395,18 +398,45 @@ var CHAINS = {
395
398
  requiresApproval: true
396
399
  }
397
400
  };
401
+ var ALIPAY_CHAIN_ID = "alipay";
402
+ function isAlipayChainId(id) {
403
+ return id === ALIPAY_CHAIN_ID;
404
+ }
398
405
 
399
406
  // src/facilitators/tempo.ts
400
407
  var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
408
+ var TIP20_PERMIT_ABI = [
409
+ "function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
410
+ "function transferFrom(address from, address to, uint256 value) returns (bool)"
411
+ ];
401
412
  var TempoFacilitator = class extends BaseFacilitator {
402
413
  name = "tempo";
403
414
  displayName = "Tempo Testnet";
404
415
  supportedNetworks = ["eip155:42431"];
405
416
  // Tempo Moderato
406
417
  rpcUrl;
418
+ settlerWallet = null;
407
419
  constructor() {
408
420
  super();
409
421
  this.rpcUrl = CHAINS.tempo_moderato.rpc;
422
+ const settlerKey = process.env.TEMPO_SETTLER_KEY;
423
+ if (settlerKey) {
424
+ try {
425
+ const provider = new ethers.JsonRpcProvider(this.rpcUrl);
426
+ this.settlerWallet = new ethers.Wallet(settlerKey, provider);
427
+ } catch (err) {
428
+ console.warn("[TempoFacilitator] Invalid TEMPO_SETTLER_KEY, permit settlement disabled:", err);
429
+ this.settlerWallet = null;
430
+ }
431
+ }
432
+ }
433
+ /**
434
+ * Settler EOA address advertised to clients via `X-Payment-Required.extra.tempoSpender`.
435
+ * Web Client uses this as the `spender` field in the signed EIP-2612 Permit.
436
+ * Returns null if no TEMPO_SETTLER_KEY is configured — permit settlement unavailable.
437
+ */
438
+ getSpenderAddress() {
439
+ return this.settlerWallet?.address ?? null;
410
440
  }
411
441
  async healthCheck() {
412
442
  const start = Date.now();
@@ -432,6 +462,44 @@ var TempoFacilitator = class extends BaseFacilitator {
432
462
  }
433
463
  }
434
464
  async verify(paymentPayload, requirements) {
465
+ const inner = paymentPayload.payload;
466
+ if (inner && "permit" in inner && inner.permit) {
467
+ return this.verifyPermit(inner, requirements);
468
+ }
469
+ return this.verifyTxHash(paymentPayload, requirements);
470
+ }
471
+ /**
472
+ * Structural validation of an EIP-2612 permit payload. Does NOT submit
473
+ * anything on-chain — actual submission happens in settlePermit().
474
+ */
475
+ async verifyPermit(payload, requirements) {
476
+ if (!this.settlerWallet) {
477
+ return { valid: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
478
+ }
479
+ const p = payload.permit;
480
+ if (!p || !p.owner || !p.spender || !p.value || !p.deadline) {
481
+ return { valid: false, error: "Invalid permit payload: missing fields" };
482
+ }
483
+ if (p.spender.toLowerCase() !== this.settlerWallet.address.toLowerCase()) {
484
+ return {
485
+ valid: false,
486
+ error: `Permit spender ${p.spender} does not match configured settler ${this.settlerWallet.address}`
487
+ };
488
+ }
489
+ const deadline = BigInt(p.deadline);
490
+ const now = BigInt(Math.floor(Date.now() / 1e3));
491
+ if (deadline <= now) {
492
+ return { valid: false, error: "Permit deadline has expired" };
493
+ }
494
+ if (BigInt(p.value) < BigInt(requirements.amount || "0")) {
495
+ return {
496
+ valid: false,
497
+ error: `Permit value ${p.value} is less than required ${requirements.amount}`
498
+ };
499
+ }
500
+ return { valid: true, details: { scheme: "permit", owner: p.owner } };
501
+ }
502
+ async verifyTxHash(paymentPayload, requirements) {
435
503
  try {
436
504
  const tempoPayload = paymentPayload.payload;
437
505
  if (!tempoPayload?.txHash) {
@@ -489,7 +557,11 @@ var TempoFacilitator = class extends BaseFacilitator {
489
557
  }
490
558
  }
491
559
  async settle(paymentPayload, requirements) {
492
- const verifyResult = await this.verify(paymentPayload, requirements);
560
+ const inner = paymentPayload.payload;
561
+ if (inner && "permit" in inner && inner.permit) {
562
+ return this.settlePermit(inner, requirements);
563
+ }
564
+ const verifyResult = await this.verifyTxHash(paymentPayload, requirements);
493
565
  if (!verifyResult.valid) {
494
566
  return { success: false, error: verifyResult.error };
495
567
  }
@@ -500,6 +572,52 @@ var TempoFacilitator = class extends BaseFacilitator {
500
572
  status: "settled"
501
573
  };
502
574
  }
575
+ /**
576
+ * EIP-2612 permit settlement path. Submits two transactions on Tempo:
577
+ * 1. pathUSD.permit(owner, spender=settler, value, deadline, v, r, s)
578
+ * 2. pathUSD.transferFrom(owner, payTo, value)
579
+ *
580
+ * The settler EOA pays Tempo gas (via the TIP-20 `feeToken` mechanism — no
581
+ * native tTEMPO required; any held TIP-20 token balance covers fees).
582
+ */
583
+ async settlePermit(payload, requirements) {
584
+ if (!this.settlerWallet) {
585
+ return { success: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
586
+ }
587
+ if (!requirements.asset || !requirements.payTo) {
588
+ return { success: false, error: "Missing asset or payTo in requirements" };
589
+ }
590
+ const verifyResult = await this.verifyPermit(payload, requirements);
591
+ if (!verifyResult.valid) {
592
+ return { success: false, error: verifyResult.error };
593
+ }
594
+ const token = new ethers.Contract(requirements.asset, TIP20_PERMIT_ABI, this.settlerWallet);
595
+ const p = payload.permit;
596
+ try {
597
+ const permitTx = await token.permit(
598
+ p.owner,
599
+ p.spender,
600
+ p.value,
601
+ p.deadline,
602
+ p.v,
603
+ p.r,
604
+ p.s
605
+ );
606
+ await permitTx.wait();
607
+ const transferTx = await token.transferFrom(p.owner, requirements.payTo, p.value);
608
+ await transferTx.wait();
609
+ return {
610
+ success: true,
611
+ transaction: transferTx.hash,
612
+ status: "settled"
613
+ };
614
+ } catch (err) {
615
+ return {
616
+ success: false,
617
+ error: `Tempo permit settlement failed: ${err.message}`
618
+ };
619
+ }
620
+ }
503
621
  async getTransactionReceipt(txHash) {
504
622
  const response = await fetch(this.rpcUrl, {
505
623
  method: "POST",
@@ -742,12 +860,12 @@ var BNBFacilitator = class extends BaseFacilitator {
742
860
  return this.spenderAddress;
743
861
  }
744
862
  async getServerAddress() {
745
- const { ethers } = await import("ethers");
746
- const wallet = new ethers.Wallet(this.serverPrivateKey);
863
+ const { ethers: ethers2 } = await import("ethers");
864
+ const wallet = new ethers2.Wallet(this.serverPrivateKey);
747
865
  return wallet.address;
748
866
  }
749
867
  async recoverIntentSigner(intent, chainId) {
750
- const { ethers } = await import("ethers");
868
+ const { ethers: ethers2 } = await import("ethers");
751
869
  const domain = {
752
870
  ...EIP712_DOMAIN,
753
871
  chainId
@@ -761,7 +879,7 @@ var BNBFacilitator = class extends BaseFacilitator {
761
879
  nonce: intent.nonce,
762
880
  deadline: intent.deadline
763
881
  };
764
- const recoveredAddress = ethers.verifyTypedData(
882
+ const recoveredAddress = ethers2.verifyTypedData(
765
883
  domain,
766
884
  INTENT_TYPES,
767
885
  message,
@@ -805,10 +923,10 @@ var BNBFacilitator = class extends BaseFacilitator {
805
923
  return result.result || "0x0";
806
924
  }
807
925
  async executeTransferFrom(from, to, amount, token, rpcUrl) {
808
- const { ethers } = await import("ethers");
809
- const provider = new ethers.JsonRpcProvider(rpcUrl);
810
- const wallet = new ethers.Wallet(this.serverPrivateKey, provider);
811
- const tokenContract = new ethers.Contract(token, [
926
+ const { ethers: ethers2 } = await import("ethers");
927
+ const provider = new ethers2.JsonRpcProvider(rpcUrl);
928
+ const wallet = new ethers2.Wallet(this.serverPrivateKey, provider);
929
+ const tokenContract = new ethers2.Contract(token, [
812
930
  "function transferFrom(address from, address to, uint256 amount) returns (bool)"
813
931
  ], wallet);
814
932
  const tx = await tokenContract.transferFrom(from, to, amount);
@@ -1045,6 +1163,374 @@ var SolanaFacilitator = class extends BaseFacilitator {
1045
1163
  }
1046
1164
  };
1047
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
+
1048
1534
  // src/facilitators/registry.ts
1049
1535
  import { Keypair as Keypair2 } from "@solana/web3.js";
1050
1536
  import bs58 from "bs58";
@@ -1069,6 +1555,7 @@ var FacilitatorRegistry = class {
1069
1555
  }
1070
1556
  return new SolanaFacilitator({ feePayerKeypair });
1071
1557
  });
1558
+ this.registerFactory("alipay", (config) => new AlipayFacilitator(config));
1072
1559
  this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
1073
1560
  }
1074
1561
  /**
@@ -1293,6 +1780,11 @@ var PAYMENT_RESPONSE_HEADER = "x-payment-response";
1293
1780
  var MPP_AUTH_HEADER = "authorization";
1294
1781
  var MPP_WWW_AUTH_HEADER = "www-authenticate";
1295
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
+ }
1296
1788
  var TOKEN_ADDRESSES = {
1297
1789
  "eip155:8453": {
1298
1790
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -1365,9 +1857,13 @@ var TOKEN_DOMAINS = {
1365
1857
  USDT: { name: "(PoS) Tether USD", version: "2" }
1366
1858
  },
1367
1859
  // Tempo Moderato testnet - TIP-20 stablecoins
1860
+ // Domain names verified against on-chain DOMAIN_SEPARATOR values on 2026-04-21.
1861
+ // See docs/TEMPO-WEB-SUPPORT.md Section 2 and test/server/tempo-domain.test.ts.
1862
+ // All 4 Tempo TIP-20 tokens (pathUSD / AlphaUSD / BetaUSD / ThetaUSD) use
1863
+ // the token symbol with first letter capitalized + version "1".
1368
1864
  "eip155:42431": {
1369
- USDC: { name: "pathUSD", version: "1" },
1370
- USDT: { name: "alphaUSD", version: "1" }
1865
+ USDC: { name: "PathUSD", version: "1" },
1866
+ USDT: { name: "AlphaUSD", version: "1" }
1371
1867
  },
1372
1868
  // BNB Smart Chain mainnet
1373
1869
  "eip155:56": {
@@ -1424,6 +1920,8 @@ var MoltsPayServer = class {
1424
1920
  registry;
1425
1921
  networkId;
1426
1922
  useMainnet;
1923
+ /** Alipay AI 收 facilitator instance, set when `provider.alipay` is configured (2.0.0). */
1924
+ alipayFacilitator = null;
1427
1925
  constructor(servicesPath, options = {}) {
1428
1926
  loadEnvFile2();
1429
1927
  const content = readFileSync2(servicesPath, "utf-8");
@@ -1445,7 +1943,38 @@ var MoltsPayServer = class {
1445
1943
  cdp: { useMainnet: this.useMainnet }
1446
1944
  }
1447
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
+ }
1448
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
+ }
1449
1978
  const primaryFacilitator = this.registry.get(facilitatorConfig.primary);
1450
1979
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
1451
1980
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
@@ -1535,14 +2064,63 @@ var MoltsPayServer = class {
1535
2064
  console.log(` GET /health - Health check (incl. facilitators)`);
1536
2065
  });
1537
2066
  }
2067
+ /**
2068
+ * Apply CORS response headers according to the `cors` option.
2069
+ *
2070
+ * Default (`cors` unset or `true`): `Access-Control-Allow-Origin: *`. Matches 1.5.x behavior
2071
+ * and works for every browser client whose origin does not need to send cookies.
2072
+ *
2073
+ * `cors: false`: emit no CORS headers. Same-origin only.
2074
+ * `cors: string[]`: origin allowlist — echo the origin back iff it matches.
2075
+ * `cors: CorsOptions`: full control (allowlist + credentials + maxAge).
2076
+ *
2077
+ * The required-for-Web response headers are always exposed when CORS is active:
2078
+ * `X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt`.
2079
+ */
2080
+ applyCorsHeaders(req, res) {
2081
+ const cors = this.options.cors;
2082
+ if (cors === false) {
2083
+ return;
2084
+ }
2085
+ const requestOrigin = req.headers.origin ?? "*";
2086
+ if (cors === void 0 || cors === true) {
2087
+ this.writeCorsHeaders(res, "*");
2088
+ return;
2089
+ }
2090
+ if (Array.isArray(cors)) {
2091
+ if (cors.includes(requestOrigin)) {
2092
+ this.writeCorsHeaders(res, requestOrigin);
2093
+ res.setHeader("Vary", "Origin");
2094
+ }
2095
+ return;
2096
+ }
2097
+ const opt = cors;
2098
+ const isAllowed = typeof opt.origins === "function" ? opt.origins(requestOrigin) : opt.origins.includes(requestOrigin);
2099
+ if (!isAllowed) {
2100
+ return;
2101
+ }
2102
+ this.writeCorsHeaders(res, requestOrigin);
2103
+ res.setHeader("Vary", "Origin");
2104
+ if (opt.credentials) {
2105
+ res.setHeader("Access-Control-Allow-Credentials", "true");
2106
+ }
2107
+ const maxAge = opt.maxAge ?? 600;
2108
+ res.setHeader("Access-Control-Max-Age", String(maxAge));
2109
+ }
2110
+ writeCorsHeaders(res, origin) {
2111
+ res.setHeader("Access-Control-Allow-Origin", origin);
2112
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
2113
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization, Payment-Proof");
2114
+ res.setHeader(
2115
+ "Access-Control-Expose-Headers",
2116
+ "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt, Payment-Needed"
2117
+ );
2118
+ }
1538
2119
  /**
1539
2120
  * Handle incoming request
1540
2121
  */
1541
2122
  async handleRequest(req, res) {
1542
- res.setHeader("Access-Control-Allow-Origin", "*");
1543
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
1544
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
1545
- res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt");
2123
+ this.applyCorsHeaders(req, res);
1546
2124
  if (req.method === "OPTIONS") {
1547
2125
  res.writeHead(204);
1548
2126
  res.end();
@@ -1562,7 +2140,8 @@ var MoltsPayServer = class {
1562
2140
  if (url.pathname === "/execute" && req.method === "POST") {
1563
2141
  const body = await this.readBody(req);
1564
2142
  const paymentHeader = req.headers[PAYMENT_HEADER];
1565
- 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);
1566
2145
  }
1567
2146
  if (url.pathname === "/proxy" && req.method === "POST") {
1568
2147
  const clientIP = req.headers["x-real-ip"] || req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket.remoteAddress || "";
@@ -1580,7 +2159,8 @@ var MoltsPayServer = class {
1580
2159
  const body = req.method === "POST" ? await this.readBody(req) : {};
1581
2160
  const authHeader = req.headers[MPP_AUTH_HEADER];
1582
2161
  const x402Header = req.headers[PAYMENT_HEADER];
1583
- 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);
1584
2164
  }
1585
2165
  this.sendJson(res, 404, { error: "Not found" });
1586
2166
  } catch (err) {
@@ -1677,7 +2257,7 @@ var MoltsPayServer = class {
1677
2257
  /**
1678
2258
  * POST /execute - Execute service with x402 payment
1679
2259
  */
1680
- async handleExecute(body, paymentHeader, res) {
2260
+ async handleExecute(body, paymentHeader, res, proofHeader) {
1681
2261
  const { service, params } = body;
1682
2262
  if (!service) {
1683
2263
  return this.sendJson(res, 400, { error: "Missing service" });
@@ -1691,6 +2271,15 @@ var MoltsPayServer = class {
1691
2271
  return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
1692
2272
  }
1693
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
+ }
1694
2283
  if (!paymentHeader) {
1695
2284
  return this.sendPaymentRequired(skill.config, res);
1696
2285
  }
@@ -1701,6 +2290,11 @@ var MoltsPayServer = class {
1701
2290
  } catch {
1702
2291
  return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
1703
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
+ }
1704
2298
  const validation = this.validatePayment(payment, skill.config);
1705
2299
  if (!validation.valid) {
1706
2300
  return this.sendJson(res, 402, { error: validation.error });
@@ -1765,6 +2359,14 @@ var MoltsPayServer = class {
1765
2359
  console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
1766
2360
  } catch (err) {
1767
2361
  console.error("[MoltsPay] Settlement failed:", err.message);
2362
+ settlement = { success: false, error: err.message, facilitator: "none" };
2363
+ }
2364
+ if (!settlement?.success) {
2365
+ return this.sendJson(res, 402, {
2366
+ error: "Payment settlement failed",
2367
+ message: settlement?.error || "Settlement returned no success state",
2368
+ facilitator: settlement?.facilitator
2369
+ });
1768
2370
  }
1769
2371
  }
1770
2372
  const responseHeaders = {};
@@ -1785,13 +2387,111 @@ var MoltsPayServer = class {
1785
2387
  payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
1786
2388
  }, responseHeaders);
1787
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
+ }
1788
2485
  /**
1789
2486
  * Handle MPP (Machine Payments Protocol) request
1790
2487
  * Supports both x402 and MPP protocols on service endpoints
1791
2488
  */
1792
- async handleMPPRequest(skill, body, authHeader, x402Header, res) {
2489
+ async handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader) {
1793
2490
  const config = skill.config;
1794
2491
  const params = body || {};
2492
+ if (proofHeader) {
2493
+ return await this.handleExecute({ service: config.id, params }, void 0, res, proofHeader);
2494
+ }
1795
2495
  if (x402Header) {
1796
2496
  return await this.handleExecute({ service: config.id, params }, x402Header, res);
1797
2497
  }
@@ -1897,7 +2597,7 @@ var MoltsPayServer = class {
1897
2597
  /**
1898
2598
  * Return 402 with both x402 and MPP payment requirements
1899
2599
  */
1900
- sendMPPPaymentRequired(config, res) {
2600
+ async sendMPPPaymentRequired(config, res) {
1901
2601
  const acceptedTokens = getAcceptedCurrencies(config);
1902
2602
  const providerChains = this.getProviderChains();
1903
2603
  const accepts = [];
@@ -1908,6 +2608,10 @@ var MoltsPayServer = class {
1908
2608
  }
1909
2609
  }
1910
2610
  }
2611
+ const alipayChallenge = await this.buildAlipayChallenge(config);
2612
+ if (alipayChallenge) {
2613
+ accepts.push(alipayChallenge.accepts);
2614
+ }
1911
2615
  const x402PaymentRequired = {
1912
2616
  x402Version: X402_VERSION2,
1913
2617
  accepts,
@@ -1935,7 +2639,7 @@ var MoltsPayServer = class {
1935
2639
  };
1936
2640
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
1937
2641
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
1938
- 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}"`;
1939
2643
  }
1940
2644
  const headers = {
1941
2645
  "Content-Type": "application/problem+json",
@@ -1944,6 +2648,9 @@ var MoltsPayServer = class {
1944
2648
  if (mppWwwAuth) {
1945
2649
  headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
1946
2650
  }
2651
+ if (alipayChallenge) {
2652
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
2653
+ }
1947
2654
  res.writeHead(402, headers);
1948
2655
  res.end(JSON.stringify({
1949
2656
  type: "https://paymentauth.org/problems/payment-required",
@@ -1970,7 +2677,7 @@ var MoltsPayServer = class {
1970
2677
  * Return 402 with x402 payment requirements (v2 format)
1971
2678
  * Includes requirements for all chains and all accepted currencies
1972
2679
  */
1973
- sendPaymentRequired(config, res) {
2680
+ async sendPaymentRequired(config, res) {
1974
2681
  const acceptedTokens = getAcceptedCurrencies(config);
1975
2682
  const providerChains = this.getProviderChains();
1976
2683
  const accepts = [];
@@ -1981,6 +2688,10 @@ var MoltsPayServer = class {
1981
2688
  }
1982
2689
  }
1983
2690
  }
2691
+ const alipayChallenge = await this.buildAlipayChallenge(config);
2692
+ if (alipayChallenge) {
2693
+ accepts.push(alipayChallenge.accepts);
2694
+ }
1984
2695
  const acceptedChains = providerChains.map((c) => {
1985
2696
  if (c.network === "eip155:8453") return "base";
1986
2697
  if (c.network === "eip155:137") return "polygon";
@@ -1998,10 +2709,14 @@ var MoltsPayServer = class {
1998
2709
  }
1999
2710
  };
2000
2711
  const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
2001
- res.writeHead(402, {
2712
+ const headers = {
2002
2713
  "Content-Type": "application/json",
2003
2714
  [PAYMENT_REQUIRED_HEADER]: encoded
2004
- });
2715
+ };
2716
+ if (alipayChallenge) {
2717
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
2718
+ }
2719
+ res.writeHead(402, headers);
2005
2720
  res.end(JSON.stringify({
2006
2721
  error: "Payment required",
2007
2722
  message: `Service requires $${config.price} ${config.currency}`,
@@ -2019,7 +2734,7 @@ var MoltsPayServer = class {
2019
2734
  }
2020
2735
  const scheme = payment.accepted?.scheme || payment.scheme;
2021
2736
  const network = payment.accepted?.network || payment.network || this.networkId;
2022
- if (scheme !== "exact") {
2737
+ if (scheme !== "exact" && scheme !== "permit") {
2023
2738
  return { valid: false, error: `Unsupported scheme: ${scheme}` };
2024
2739
  }
2025
2740
  if (!this.isNetworkAccepted(network)) {
@@ -2041,8 +2756,10 @@ var MoltsPayServer = class {
2041
2756
  const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
2042
2757
  const tokenAddress = tokenAddresses[selectedToken];
2043
2758
  const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
2759
+ const isTempo = selectedNetwork === "eip155:42431";
2760
+ const scheme = isTempo ? "permit" : "exact";
2044
2761
  const requirements = {
2045
- scheme: "exact",
2762
+ scheme,
2046
2763
  network: selectedNetwork,
2047
2764
  asset: tokenAddress,
2048
2765
  amount: amountInUnits,
@@ -2070,6 +2787,16 @@ var MoltsPayServer = class {
2070
2787
  };
2071
2788
  }
2072
2789
  }
2790
+ if (isTempo) {
2791
+ const tempoFacilitator = this.registry.get("tempo");
2792
+ const tempoSpender = tempoFacilitator?.getSpenderAddress?.();
2793
+ if (tempoSpender) {
2794
+ requirements.extra = {
2795
+ ...requirements.extra || {},
2796
+ tempoSpender
2797
+ };
2798
+ }
2799
+ }
2073
2800
  return requirements;
2074
2801
  }
2075
2802
  /**
@@ -2096,12 +2823,12 @@ var MoltsPayServer = class {
2096
2823
  return accepted.includes(token);
2097
2824
  }
2098
2825
  async readBody(req) {
2099
- return new Promise((resolve, reject) => {
2826
+ return new Promise((resolve2, reject) => {
2100
2827
  let body = "";
2101
2828
  req.on("data", (chunk) => body += chunk);
2102
2829
  req.on("end", () => {
2103
2830
  try {
2104
- resolve(body ? JSON.parse(body) : {});
2831
+ resolve2(body ? JSON.parse(body) : {});
2105
2832
  } catch {
2106
2833
  reject(new Error("Invalid JSON"));
2107
2834
  }
@@ -2204,7 +2931,7 @@ var MoltsPayServer = class {
2204
2931
  }
2205
2932
  const scheme = payment.accepted?.scheme || payment.scheme;
2206
2933
  const network = payment.accepted?.network || payment.network;
2207
- if (scheme !== "exact") {
2934
+ if (scheme !== "exact" && scheme !== "permit") {
2208
2935
  return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
2209
2936
  }
2210
2937
  const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
@@ -2359,7 +3086,7 @@ var MoltsPayServer = class {
2359
3086
  };
2360
3087
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
2361
3088
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
2362
- 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}"`;
2363
3090
  res.writeHead(402, {
2364
3091
  "Content-Type": "application/problem+json",
2365
3092
  [MPP_WWW_AUTH_HEADER]: wwwAuth