@bsv/payment-express-middleware
Version:
BSV Blockchain service monetization express middleware
125 lines • 5.16 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPaymentMiddleware = createPaymentMiddleware;
const sdk_1 = require("@bsv/sdk");
const PAYMENT_VERSION = '1.0';
/**
* Creates middleware that enforces BSV payment for HTTP requests.
*
* NOTE: This middleware should run after the authentication middleware so that `req.auth` is available.
*
* @param options - Configuration for the payment middleware
* @param options.wallet - A wallet instance capable of submitting direct transactions.
* @param options.calculateRequestPrice - A function returning the price for the request in satoshis.
*
* @returns Express middleware that requires payment if `calculateRequestPrice` > 0.
*/
function createPaymentMiddleware(options) {
const { calculateRequestPrice = () => 100, // Default to 100 satoshis if no price calculator is provided
wallet } = options;
if (typeof calculateRequestPrice !== 'function') {
throw new Error('The calculateRequestPrice option must be a function.');
}
if (wallet === undefined || typeof wallet !== 'object') {
throw new Error('A valid wallet instance must be supplied to the payment middleware.');
}
return async (req, res, next) => {
var _a, _b;
if (req.auth === undefined || typeof req.auth.identityKey !== 'string') {
return res.status(500).json({
status: 'error',
code: 'ERR_SERVER_MISCONFIGURED',
description: 'The payment middleware must be executed after the Auth middleware.'
});
}
let requestPrice;
try {
requestPrice = await calculateRequestPrice(req);
}
catch (err) {
console.error(err);
return res.status(500).json({
status: 'error',
code: 'ERR_PAYMENT_INTERNAL',
description: 'An internal error occurred while determining the payment required for this request.'
});
}
// If no payment is required, proceed immediately.
if (requestPrice === 0) {
req.payment = { satoshisPaid: 0 };
return next();
}
const bsvPaymentHeader = req.headers['x-bsv-payment'];
if (bsvPaymentHeader === undefined) {
const derivationPrefix = await (0, sdk_1.createNonce)(wallet);
return res.status(402)
.set({
'x-bsv-payment-version': PAYMENT_VERSION,
'x-bsv-payment-satoshis-required': String(requestPrice),
'x-bsv-payment-derivation-prefix': derivationPrefix
})
.json({
status: 'error',
code: 'ERR_PAYMENT_REQUIRED',
satoshisRequired: requestPrice,
description: 'A BSV payment is required to complete this request. Provide the X-BSV-Payment header.'
});
}
let paymentData;
try {
paymentData = JSON.parse(String(bsvPaymentHeader));
try {
const valid = await (0, sdk_1.verifyNonce)(paymentData.derivationPrefix, wallet);
if (!valid) {
throw new Error('ERR_INVALID_DERIVATION_PREFIX');
}
}
catch {
return res.status(400).json({
status: 'error',
code: 'ERR_INVALID_DERIVATION_PREFIX',
description: 'The X-BSV-Payment-Derivation-Prefix header is not valid.',
});
}
}
catch (err) {
return res.status(400).json({
status: 'error',
code: 'ERR_MALFORMED_PAYMENT',
description: 'The X-BSV-Payment header is not valid JSON.'
});
}
try {
const { accepted } = await wallet.internalizeAction({
tx: sdk_1.Utils.toArray(paymentData.transaction, 'base64'),
outputs: [{
paymentRemittance: {
derivationPrefix: paymentData.derivationPrefix,
derivationSuffix: paymentData.derivationSuffix,
senderIdentityKey: req.auth.identityKey
},
outputIndex: 0,
protocol: 'wallet payment'
}],
description: 'Payment for request'
});
req.payment = {
satoshisPaid: requestPrice,
accepted,
tx: paymentData.transaction
};
res.set({
'x-bsv-payment-satoshis-paid': String(requestPrice)
});
next();
}
catch (err) {
return res.status(400).json({
status: 'error',
code: (_a = err.code) !== null && _a !== void 0 ? _a : 'ERR_PAYMENT_FAILED',
description: (_b = err.message) !== null && _b !== void 0 ? _b : 'Payment failed.'
});
}
};
}
//# sourceMappingURL=index.js.map