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