@odatano/x402 0.1.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/srv/index.js ADDED
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ /**
3
+ * @odatano/x402 — public API barrel.
4
+ *
5
+ * Cardano-x402-v2 payment library for SAP CAP applications.
6
+ *
7
+ * Three usage shapes:
8
+ *
9
+ * // 1. Express middleware (mount under a path)
10
+ * import { x402Middleware } from '@odatano/x402';
11
+ * app.use('/api/premium', x402Middleware({
12
+ * payTo: 'addr_test1...',
13
+ * network: 'cardano:preprod',
14
+ * asset: '16a55b...ddde.0014df105553444d',
15
+ * priceUnits: '1000000',
16
+ * onAccepted: async (claim) => { ... },
17
+ * }));
18
+ *
19
+ * // 2. CAP service gate (registers a before-* handler)
20
+ * import { gateService } from '@odatano/x402';
21
+ * class MyService extends cds.ApplicationService {
22
+ * async init() {
23
+ * gateService(this, {
24
+ * payTo, network, asset,
25
+ * routePricing: { Prices: '10000', getBestPrice: '10000' },
26
+ * });
27
+ * return super.init();
28
+ * }
29
+ * }
30
+ *
31
+ * // 3. Programmatic — verify a confirmed payment by tx hash
32
+ * import { verifyConfirmedPayment } from '@odatano/x402';
33
+ * const r = await verifyConfirmedPayment({
34
+ * txHash, requiredAmount, asset, payTo, network,
35
+ * });
36
+ */
37
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
38
+ if (k2 === undefined) k2 = k;
39
+ var desc = Object.getOwnPropertyDescriptor(m, k);
40
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
41
+ desc = { enumerable: true, get: function() { return m[k]; } };
42
+ }
43
+ Object.defineProperty(o, k2, desc);
44
+ }) : (function(o, m, k, k2) {
45
+ if (k2 === undefined) k2 = k;
46
+ o[k2] = m[k];
47
+ }));
48
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
49
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
50
+ }) : function(o, v) {
51
+ o["default"] = v;
52
+ });
53
+ var __importStar = (this && this.__importStar) || (function () {
54
+ var ownKeys = function(o) {
55
+ ownKeys = Object.getOwnPropertyNames || function (o) {
56
+ var ar = [];
57
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
58
+ return ar;
59
+ };
60
+ return ownKeys(o);
61
+ };
62
+ return function (mod) {
63
+ if (mod && mod.__esModule) return mod;
64
+ var result = {};
65
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
66
+ __setModuleDefault(result, mod);
67
+ return result;
68
+ };
69
+ })();
70
+ Object.defineProperty(exports, "__esModule", { value: true });
71
+ exports.bridge = exports.gateService = exports.x402Middleware = exports.buildUnsignedPaymentTx = exports.verifyConfirmedPayment = exports.checkNonceUnspent = exports.settle = exports.verifyPayment = exports.Codes = exports.X402Error = exports.networksMatch = exports.isNetwork = exports.parseNetwork = exports.buildAssetString = exports.parseAsset = exports.validatePayment = exports.decode = exports.flatRequirements = exports.buildEntry = exports.buildPaymentRequirements = void 0;
72
+ // ─── Core builders / validators (pure) ────────────────────────────────
73
+ var requirements_1 = require("./core/requirements");
74
+ Object.defineProperty(exports, "buildPaymentRequirements", { enumerable: true, get: function () { return requirements_1.buildPaymentRequirements; } });
75
+ Object.defineProperty(exports, "buildEntry", { enumerable: true, get: function () { return requirements_1.buildEntry; } });
76
+ Object.defineProperty(exports, "flatRequirements", { enumerable: true, get: function () { return requirements_1.flatRequirements; } });
77
+ var decode_1 = require("./core/decode");
78
+ Object.defineProperty(exports, "decode", { enumerable: true, get: function () { return decode_1.decode; } });
79
+ var validate_1 = require("./core/validate");
80
+ Object.defineProperty(exports, "validatePayment", { enumerable: true, get: function () { return validate_1.validatePayment; } });
81
+ // ─── Asset / network helpers ──────────────────────────────────────────
82
+ var asset_1 = require("./core/asset");
83
+ Object.defineProperty(exports, "parseAsset", { enumerable: true, get: function () { return asset_1.parseAsset; } });
84
+ Object.defineProperty(exports, "buildAssetString", { enumerable: true, get: function () { return asset_1.buildAssetString; } });
85
+ var network_1 = require("./core/network");
86
+ Object.defineProperty(exports, "parseNetwork", { enumerable: true, get: function () { return network_1.parseNetwork; } });
87
+ Object.defineProperty(exports, "isNetwork", { enumerable: true, get: function () { return network_1.isNetwork; } });
88
+ Object.defineProperty(exports, "networksMatch", { enumerable: true, get: function () { return network_1.networksMatch; } });
89
+ // ─── Errors / codes ───────────────────────────────────────────────────
90
+ var errors_1 = require("./core/errors");
91
+ Object.defineProperty(exports, "X402Error", { enumerable: true, get: function () { return errors_1.X402Error; } });
92
+ Object.defineProperty(exports, "Codes", { enumerable: true, get: function () { return errors_1.Codes; } });
93
+ // ─── Facilitator (chain-touching) ─────────────────────────────────────
94
+ var verify_1 = require("./facilitator/verify");
95
+ Object.defineProperty(exports, "verifyPayment", { enumerable: true, get: function () { return verify_1.process; } });
96
+ var settle_1 = require("./facilitator/settle");
97
+ Object.defineProperty(exports, "settle", { enumerable: true, get: function () { return settle_1.settle; } });
98
+ var nonce_1 = require("./facilitator/nonce");
99
+ Object.defineProperty(exports, "checkNonceUnspent", { enumerable: true, get: function () { return nonce_1.checkNonceUnspent; } });
100
+ // ─── Helpers ──────────────────────────────────────────────────────────
101
+ var verify_confirmed_1 = require("./helpers/verify-confirmed");
102
+ Object.defineProperty(exports, "verifyConfirmedPayment", { enumerable: true, get: function () { return verify_confirmed_1.verifyConfirmedPayment; } });
103
+ var build_unsigned_tx_1 = require("./helpers/build-unsigned-tx");
104
+ Object.defineProperty(exports, "buildUnsignedPaymentTx", { enumerable: true, get: function () { return build_unsigned_tx_1.buildUnsignedPaymentTx; } });
105
+ // ─── Middleware ───────────────────────────────────────────────────────
106
+ var express_1 = require("./middleware/express");
107
+ Object.defineProperty(exports, "x402Middleware", { enumerable: true, get: function () { return express_1.x402Middleware; } });
108
+ var cap_1 = require("./middleware/cap");
109
+ Object.defineProperty(exports, "gateService", { enumerable: true, get: function () { return cap_1.gateService; } });
110
+ // ─── Bridge (lower-level: exposed for advanced consumers) ─────────────
111
+ exports.bridge = __importStar(require("./bridge"));
@@ -0,0 +1,65 @@
1
+ /**
2
+ * CAP integration for x402 payment gating.
3
+ *
4
+ * CAP services receive requests through `srv.before(...)` / `srv.on(...)`
5
+ * handlers, not Express middleware. For OData-served entities, the 402
6
+ * needs to come from `req.reject(402, body)` — the response is built by
7
+ * CAP, not by `res.status(...)`.
8
+ *
9
+ * Usage:
10
+ *
11
+ * import { gateService } from '@odatano/x402';
12
+ *
13
+ * class MyService extends cds.ApplicationService {
14
+ * async init() {
15
+ * gateService(this, {
16
+ * payTo: '...', network: 'cardano:preprod', asset: '<policy>.<name>',
17
+ * routePricing: { Prices: '10000', getBestPrice: '10000' },
18
+ * });
19
+ * return super.init();
20
+ * }
21
+ * }
22
+ *
23
+ * The gate inspects each `req.event` (entity name OR action name) and
24
+ * matches it against `routePricing`. For unmapped events / entities,
25
+ * the request passes through unmodified.
26
+ */
27
+ import cds from '@sap/cds';
28
+ import type { AssetTransferMethod, Network, PaymentClaim } from '../core/types';
29
+ export interface X402CapOptions {
30
+ payTo: string;
31
+ network: Network | string;
32
+ asset: string;
33
+ /** Single price (applies to all gated events). */
34
+ priceUnits?: string | number | bigint;
35
+ /**
36
+ * Per-event prices. Keys are CAP event names (entity name for CRUD,
37
+ * action name for actions). Events absent here pass through.
38
+ */
39
+ routePricing?: Record<string, string | number | bigint>;
40
+ description?: string;
41
+ mimeType?: string;
42
+ assetTransferMethod?: AssetTransferMethod;
43
+ maxTimeoutSeconds?: number;
44
+ extra?: Record<string, unknown>;
45
+ settlePollBudgetMs?: number;
46
+ allowNoTtl?: boolean;
47
+ onAccepted?: (claim: PaymentClaim, req: cds.Request) => void | Promise<void>;
48
+ /**
49
+ * Optional resource URL builder. Defaults to the request's HTTP URL
50
+ * when available, falling back to `cap://<event>`. Pass a custom
51
+ * builder to embed pair / entity id in the resource string.
52
+ */
53
+ resourceUrl?: (req: cds.Request) => string;
54
+ }
55
+ /**
56
+ * Attach the x402 gate to a CAP ApplicationService. Returns the service
57
+ * (chainable) so callers can fluently wire multiple middlewares.
58
+ *
59
+ * The gate registers as `srv.before('*', ...)` which fires for every
60
+ * event on the service. We filter inside the handler based on
61
+ * `routePricing` — registering per-entity would lose actions, and
62
+ * per-event arrays don't support the `'*'` fallback we want.
63
+ */
64
+ export declare function gateService<S extends cds.Service>(srv: S, opts: X402CapOptions): S;
65
+ //# sourceMappingURL=cap.d.ts.map
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ /**
3
+ * CAP integration for x402 payment gating.
4
+ *
5
+ * CAP services receive requests through `srv.before(...)` / `srv.on(...)`
6
+ * handlers, not Express middleware. For OData-served entities, the 402
7
+ * needs to come from `req.reject(402, body)` — the response is built by
8
+ * CAP, not by `res.status(...)`.
9
+ *
10
+ * Usage:
11
+ *
12
+ * import { gateService } from '@odatano/x402';
13
+ *
14
+ * class MyService extends cds.ApplicationService {
15
+ * async init() {
16
+ * gateService(this, {
17
+ * payTo: '...', network: 'cardano:preprod', asset: '<policy>.<name>',
18
+ * routePricing: { Prices: '10000', getBestPrice: '10000' },
19
+ * });
20
+ * return super.init();
21
+ * }
22
+ * }
23
+ *
24
+ * The gate inspects each `req.event` (entity name OR action name) and
25
+ * matches it against `routePricing`. For unmapped events / entities,
26
+ * the request passes through unmodified.
27
+ */
28
+ var __importDefault = (this && this.__importDefault) || function (mod) {
29
+ return (mod && mod.__esModule) ? mod : { "default": mod };
30
+ };
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.gateService = gateService;
33
+ const cds_1 = __importDefault(require("@sap/cds"));
34
+ const requirements_1 = require("../core/requirements");
35
+ const verify_1 = require("../facilitator/verify");
36
+ const errors_1 = require("../core/errors");
37
+ const log = cds_1.default.log('x402');
38
+ /**
39
+ * Resolve the routePricing key for a CAP request.
40
+ *
41
+ * CAP fires two distinct shapes:
42
+ * - CRUD on an entity: req.event === 'READ' | 'CREATE' | 'UPDATE' | 'DELETE'
43
+ * req.target.name === '<Service>.<Entity>'
44
+ * - Action call: req.event === '<actionName>'
45
+ * req.target may be empty or the bound-entity target
46
+ *
47
+ * routePricing keys are meant to be human-readable identifiers (entity
48
+ * names or action names), so we try the entity name first (more specific)
49
+ * and fall back to the event verb. Either match wins; both miss falls
50
+ * through to opts.priceUnits or null.
51
+ */
52
+ function pickPriceUnits(req, opts) {
53
+ if (opts.routePricing) {
54
+ const event = String(req.event ?? '');
55
+ const targetName = req.target?.name ?? '';
56
+ const entitySegment = targetName.split('.').pop() ?? '';
57
+ const price = opts.routePricing[entitySegment] ?? opts.routePricing[event];
58
+ if (price != null)
59
+ return String(price);
60
+ if (opts.priceUnits != null)
61
+ return String(opts.priceUnits);
62
+ return null;
63
+ }
64
+ return opts.priceUnits != null ? String(opts.priceUnits) : null;
65
+ }
66
+ function getHeader(req, name) {
67
+ // CAP's req.http?.req exposes the underlying express request. Fall
68
+ // back to req._.req for older shapes.
69
+ const httpReq = req;
70
+ const hdrs = httpReq.http?.req?.headers ?? httpReq._?.req?.headers;
71
+ const v = hdrs?.[name];
72
+ return Array.isArray(v) ? v[0] : v;
73
+ }
74
+ function getResourceUrl(req, opts) {
75
+ if (opts.resourceUrl)
76
+ return opts.resourceUrl(req);
77
+ const httpReq = req;
78
+ return httpReq.http?.req?.originalUrl ?? httpReq.http?.req?.url ?? `cap://${req.event}`;
79
+ }
80
+ /**
81
+ * Attach the x402 gate to a CAP ApplicationService. Returns the service
82
+ * (chainable) so callers can fluently wire multiple middlewares.
83
+ *
84
+ * The gate registers as `srv.before('*', ...)` which fires for every
85
+ * event on the service. We filter inside the handler based on
86
+ * `routePricing` — registering per-entity would lose actions, and
87
+ * per-event arrays don't support the `'*'` fallback we want.
88
+ */
89
+ function gateService(srv, opts) {
90
+ if (!opts.payTo)
91
+ throw new Error('gateService: payTo is required');
92
+ if (!opts.network)
93
+ throw new Error('gateService: network is required');
94
+ if (!opts.asset)
95
+ throw new Error('gateService: asset is required');
96
+ if (opts.priceUnits == null && !opts.routePricing) {
97
+ throw new Error('gateService: priceUnits or routePricing is required');
98
+ }
99
+ srv.before('*', async function x402CapGate(req) {
100
+ const priceUnits = pickPriceUnits(req, opts);
101
+ if (priceUnits == null)
102
+ return; // unmapped → pass through
103
+ const requirementsBody = (0, requirements_1.buildPaymentRequirements)({
104
+ amount: priceUnits,
105
+ asset: opts.asset,
106
+ payTo: opts.payTo,
107
+ network: opts.network,
108
+ resource: {
109
+ url: getResourceUrl(req, opts),
110
+ description: opts.description ?? '',
111
+ mimeType: opts.mimeType ?? 'application/json',
112
+ },
113
+ ...(opts.assetTransferMethod ? { assetTransferMethod: opts.assetTransferMethod } : {}),
114
+ ...(opts.maxTimeoutSeconds !== undefined ? { maxTimeoutSeconds: opts.maxTimeoutSeconds } : {}),
115
+ ...(opts.extra ? { extra: opts.extra } : {}),
116
+ withMissingHeaderError: true,
117
+ });
118
+ const headerVal = getHeader(req, 'payment-signature');
119
+ const processArgs = {
120
+ paymentHeader: headerVal,
121
+ requirementsBody,
122
+ };
123
+ if (opts.settlePollBudgetMs !== undefined) {
124
+ processArgs.settlePollBudgetMs = opts.settlePollBudgetMs;
125
+ }
126
+ if (opts.allowNoTtl)
127
+ processArgs.allowNoTtl = true;
128
+ if (opts.onAccepted) {
129
+ processArgs.onAccepted = (claim) => opts.onAccepted(claim, req);
130
+ }
131
+ // ─── Run the pipeline. Only the orchestrator's internal errors are
132
+ // trapped here — `req.reject` MUST be called outside this catch
133
+ // because it throws synchronously, and re-catching that throw
134
+ // would translate the 402 into a 500. ─────────────────────────
135
+ let result;
136
+ try {
137
+ result = await (0, verify_1.process)(processArgs);
138
+ }
139
+ catch (err) {
140
+ log.error('x402 CAP gate internal error', err);
141
+ // `reject` throws; we DO NOT wrap this in another try/catch.
142
+ req
143
+ .reject(500, 'x402 internal error');
144
+ return;
145
+ }
146
+ // ─── Apply the result. From here on, `req.reject` is called once
147
+ // and we let its synchronous throw bubble — CAP's dispatcher
148
+ // wraps it into the OData error response.
149
+ if (result.kind === 'accepted') {
150
+ req.payment = result.payment;
151
+ const httpRes = req.http?.res;
152
+ httpRes?.setHeader('X-PAYMENT-RESPONSE', result.paymentResponseB64);
153
+ return;
154
+ }
155
+ // rejected | pending → 402 with the requirements body
156
+ const body = { ...result.requirementsBody };
157
+ const baseError = (result.requirementsBody.error ?? 'payment required').toString();
158
+ if (result.code && result.code !== errors_1.Codes.MISSING_HEADER) {
159
+ body.error = `${baseError} (${result.code}): ${result.reason ?? ''}`.trim();
160
+ }
161
+ if (result.kind === 'pending') {
162
+ body.pending = true;
163
+ if (result.txHash)
164
+ body.transaction = result.txHash;
165
+ }
166
+ req
167
+ .reject(402, JSON.stringify(body));
168
+ });
169
+ return srv;
170
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Express middleware factory for Cardano-x402 payment gating.
3
+ *
4
+ * Mount on a route or service path to gate every request beneath it.
5
+ * The `skipPaths` regex carves out paths buyers MUST be able to fetch
6
+ * without paying (e.g. OData `$metadata`, `$batch` previews).
7
+ *
8
+ * Two pricing modes:
9
+ * 1. `priceUnits` — single price for everything under this mount.
10
+ * 2. `routePricing` — { 'EntityOrActionName': 'priceUnits' }, keyed
11
+ * by the last URL segment (with OData function
12
+ * args stripped). Unmapped paths pass through.
13
+ *
14
+ * The 402 body is the canonical v2 shape (`x402Version: 2`, `accepts[]`,
15
+ * etc). When the rejection has a more specific cause than "missing
16
+ * header", we append `(code): reason` to the `error` string so clients
17
+ * can extract the code without breaking wire format.
18
+ */
19
+ import type { Request, RequestHandler } from 'express';
20
+ import type { AssetTransferMethod, PaymentClaim, Network } from '../core/types';
21
+ declare module 'express-serve-static-core' {
22
+ interface Request {
23
+ payment?: PaymentClaim;
24
+ }
25
+ }
26
+ export interface X402MiddlewareOptions {
27
+ /** Bech32 recipient. */
28
+ payTo: string;
29
+ /** v2 network identifier. */
30
+ network: Network | string;
31
+ /** v2 asset string: 'lovelace' or '<policy>.<nameHex>'. */
32
+ asset: string;
33
+ /**
34
+ * Single price for everything under this mount. Mutually exclusive
35
+ * with `routePricing` (but coexistable: routePricing wins where
36
+ * defined, falls back to priceUnits otherwise).
37
+ */
38
+ priceUnits?: string | number | bigint;
39
+ /** Per-route prices keyed by the URL's last segment. */
40
+ routePricing?: Record<string, string | number | bigint>;
41
+ /** Regex of paths that bypass payment (default: $metadata, $batch, root, /index). */
42
+ skipPaths?: RegExp;
43
+ /** Shown in `accepts[0].resource.description`. */
44
+ description?: string;
45
+ /** Override default `accepts[0].resource.mimeType` ('application/json'). */
46
+ mimeType?: string;
47
+ /** v2 `assetTransferMethod`. Default 'default'. */
48
+ assetTransferMethod?: AssetTransferMethod;
49
+ /** Buyer-side timeout hint. Default 600. */
50
+ maxTimeoutSeconds?: number;
51
+ /** Free-form extras (decimals, fingerprint, UI hints). */
52
+ extra?: Record<string, unknown>;
53
+ /** Settle poll budget (ms). Default 60_000. */
54
+ settlePollBudgetMs?: number;
55
+ /** If true, accept tx with no TTL set. Default false (spec-strict). */
56
+ allowNoTtl?: boolean;
57
+ /**
58
+ * Audit / persistence callback. Invoked exactly once per accepted
59
+ * payment, after settle confirms. Errors here are logged but never
60
+ * block serving the response.
61
+ */
62
+ onAccepted?: (claim: PaymentClaim, req: Request) => void | Promise<void>;
63
+ }
64
+ /** Build the Express middleware. */
65
+ export declare function x402Middleware(opts: X402MiddlewareOptions): RequestHandler;
66
+ //# sourceMappingURL=express.d.ts.map
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ /**
3
+ * Express middleware factory for Cardano-x402 payment gating.
4
+ *
5
+ * Mount on a route or service path to gate every request beneath it.
6
+ * The `skipPaths` regex carves out paths buyers MUST be able to fetch
7
+ * without paying (e.g. OData `$metadata`, `$batch` previews).
8
+ *
9
+ * Two pricing modes:
10
+ * 1. `priceUnits` — single price for everything under this mount.
11
+ * 2. `routePricing` — { 'EntityOrActionName': 'priceUnits' }, keyed
12
+ * by the last URL segment (with OData function
13
+ * args stripped). Unmapped paths pass through.
14
+ *
15
+ * The 402 body is the canonical v2 shape (`x402Version: 2`, `accepts[]`,
16
+ * etc). When the rejection has a more specific cause than "missing
17
+ * header", we append `(code): reason` to the `error` string so clients
18
+ * can extract the code without breaking wire format.
19
+ */
20
+ var __importDefault = (this && this.__importDefault) || function (mod) {
21
+ return (mod && mod.__esModule) ? mod : { "default": mod };
22
+ };
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.x402Middleware = x402Middleware;
25
+ const cds_1 = __importDefault(require("@sap/cds"));
26
+ const requirements_1 = require("../core/requirements");
27
+ const verify_1 = require("../facilitator/verify");
28
+ const errors_1 = require("../core/errors");
29
+ const log = cds_1.default.log('x402');
30
+ function pickPriceUnits(req, opts) {
31
+ if (opts.routePricing) {
32
+ // Last URL segment, OData function-args stripped:
33
+ // /odata/v4/price/getBestPrice(pair='ADA-USD') → getBestPrice
34
+ const segment = (req.path.split('/').pop() ?? '').split('(')[0];
35
+ const price = opts.routePricing[segment];
36
+ if (price != null)
37
+ return String(price);
38
+ if (opts.priceUnits != null)
39
+ return String(opts.priceUnits);
40
+ return null; // unmapped under routePricing = pass through
41
+ }
42
+ return opts.priceUnits != null ? String(opts.priceUnits) : null;
43
+ }
44
+ /** Build the Express middleware. */
45
+ function x402Middleware(opts) {
46
+ if (!opts.payTo)
47
+ throw new Error('x402Middleware: payTo is required');
48
+ if (!opts.network)
49
+ throw new Error('x402Middleware: network is required');
50
+ if (!opts.asset)
51
+ throw new Error('x402Middleware: asset is required');
52
+ if (opts.priceUnits == null && !opts.routePricing) {
53
+ throw new Error('x402Middleware: priceUnits or routePricing is required');
54
+ }
55
+ const skipPaths = opts.skipPaths ?? /(^\/?$|\$metadata|\$batch|^\/?\?|^\/index)/i;
56
+ return async function x402Express(req, res, next) {
57
+ try {
58
+ if (skipPaths.test(req.path))
59
+ return next();
60
+ const priceUnits = pickPriceUnits(req, opts);
61
+ if (priceUnits == null)
62
+ return next(); // unmapped path = pass through
63
+ const requirementsBody = (0, requirements_1.buildPaymentRequirements)({
64
+ amount: priceUnits,
65
+ asset: opts.asset,
66
+ payTo: opts.payTo,
67
+ network: opts.network,
68
+ resource: {
69
+ url: req.originalUrl ?? req.url,
70
+ description: opts.description ?? '',
71
+ mimeType: opts.mimeType ?? 'application/json',
72
+ },
73
+ ...(opts.assetTransferMethod ? { assetTransferMethod: opts.assetTransferMethod } : {}),
74
+ ...(opts.maxTimeoutSeconds !== undefined ? { maxTimeoutSeconds: opts.maxTimeoutSeconds } : {}),
75
+ ...(opts.extra ? { extra: opts.extra } : {}),
76
+ withMissingHeaderError: true,
77
+ });
78
+ const headerVal = req.headers['payment-signature'];
79
+ const processArgs = {
80
+ paymentHeader: headerVal,
81
+ requirementsBody,
82
+ };
83
+ if (opts.settlePollBudgetMs !== undefined) {
84
+ processArgs.settlePollBudgetMs = opts.settlePollBudgetMs;
85
+ }
86
+ if (opts.allowNoTtl)
87
+ processArgs.allowNoTtl = true;
88
+ if (opts.onAccepted) {
89
+ processArgs.onAccepted = (claim) => opts.onAccepted(claim, req);
90
+ }
91
+ const result = await (0, verify_1.process)(processArgs);
92
+ if (result.kind === 'accepted') {
93
+ res.setHeader('X-PAYMENT-RESPONSE', result.paymentResponseB64);
94
+ req.payment = result.payment;
95
+ return next();
96
+ }
97
+ // rejected | pending → 402
98
+ const body = { ...result.requirementsBody };
99
+ const baseError = (result.requirementsBody.error ?? 'payment required').toString();
100
+ if (result.code && result.code !== errors_1.Codes.MISSING_HEADER) {
101
+ body.error = `${baseError} (${result.code}): ${result.reason ?? ''}`.trim();
102
+ }
103
+ if (result.kind === 'pending') {
104
+ // Add the spec-defined "pending" markers so the buyer can poll.
105
+ body.pending = true;
106
+ if (result.txHash)
107
+ body.transaction = result.txHash;
108
+ }
109
+ res.status(402).json(body);
110
+ }
111
+ catch (err) {
112
+ log.error('x402 middleware failed', err);
113
+ next(err);
114
+ }
115
+ };
116
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * CAP plugin registration for `@odatano/x402`.
3
+ *
4
+ * Unlike `@odatano/core`, this plugin doesn't auto-serve any CDS
5
+ * entities — x402 is a library, not a service. We use the
6
+ * `cds.on('served')` hook only to initialise the bridge (warm up the
7
+ * @odatano/core connection) and the `cds.on('shutdown')` hook to clean
8
+ * up. Consumers wire middleware / gateService themselves inside their
9
+ * own service init().
10
+ *
11
+ * NEVER throws on init failure — the plugin must not crash the host
12
+ * CAP application. Errors are logged; calls into the bridge later
13
+ * will fail with `BRIDGE_UNAVAILABLE`.
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=plugin.d.ts.map
package/srv/plugin.js ADDED
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ /**
3
+ * CAP plugin registration for `@odatano/x402`.
4
+ *
5
+ * Unlike `@odatano/core`, this plugin doesn't auto-serve any CDS
6
+ * entities — x402 is a library, not a service. We use the
7
+ * `cds.on('served')` hook only to initialise the bridge (warm up the
8
+ * @odatano/core connection) and the `cds.on('shutdown')` hook to clean
9
+ * up. Consumers wire middleware / gateService themselves inside their
10
+ * own service init().
11
+ *
12
+ * NEVER throws on init failure — the plugin must not crash the host
13
+ * CAP application. Errors are logged; calls into the bridge later
14
+ * will fail with `BRIDGE_UNAVAILABLE`.
15
+ */
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ var __importDefault = (this && this.__importDefault) || function (mod) {
50
+ return (mod && mod.__esModule) ? mod : { "default": mod };
51
+ };
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ const cds_1 = __importDefault(require("@sap/cds"));
54
+ const bridge = __importStar(require("./bridge"));
55
+ const log = cds_1.default.log('x402');
56
+ cds_1.default.on('served', async () => {
57
+ try {
58
+ await bridge.init();
59
+ log.info('@odatano/x402 bridge ready');
60
+ }
61
+ catch (err) {
62
+ log.warn('@odatano/x402 bridge init failed (will retry on first request):', err?.message ?? err);
63
+ }
64
+ });
65
+ cds_1.default.on('shutdown', async () => {
66
+ try {
67
+ await bridge.shutdown();
68
+ }
69
+ catch (err) {
70
+ log.warn('@odatano/x402 bridge shutdown:', err?.message ?? err);
71
+ }
72
+ });
73
+ module.exports = {};