@nevermined-io/payments 1.0.8 → 1.0.10
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 +7 -14
- package/dist/a2a/paymentsClient.d.ts +2 -2
- package/dist/a2a/paymentsClient.d.ts.map +1 -1
- package/dist/a2a/paymentsClient.js +7 -7
- package/dist/a2a/paymentsClient.js.map +1 -1
- package/dist/a2a/server.d.ts.map +1 -1
- package/dist/a2a/server.js +45 -26
- package/dist/a2a/server.js.map +1 -1
- package/dist/a2a/types.d.ts +6 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/api/base-payments.d.ts +2 -1
- package/dist/api/base-payments.d.ts.map +1 -1
- package/dist/api/base-payments.js +11 -3
- package/dist/api/base-payments.js.map +1 -1
- package/dist/common/types.d.ts +13 -0
- package/dist/common/types.d.ts.map +1 -1
- package/dist/common/types.js.map +1 -1
- package/dist/environments.d.ts +7 -0
- package/dist/environments.d.ts.map +1 -1
- package/dist/environments.js +13 -0
- package/dist/environments.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/payments.d.ts.map +1 -1
- package/dist/payments.js +10 -2
- package/dist/payments.js.map +1 -1
- package/dist/x402/express/middleware.d.ts.map +1 -1
- package/dist/x402/express/middleware.js +28 -19
- package/dist/x402/express/middleware.js.map +1 -1
- package/dist/x402/facilitator-api.d.ts +5 -4
- package/dist/x402/facilitator-api.d.ts.map +1 -1
- package/dist/x402/facilitator-api.js.map +1 -1
- package/dist/x402/index.d.ts +4 -0
- package/dist/x402/index.d.ts.map +1 -1
- package/dist/x402/index.js +3 -0
- package/dist/x402/index.js.map +1 -1
- package/dist/x402/visa-facilitator-api.d.ts +150 -0
- package/dist/x402/visa-facilitator-api.d.ts.map +1 -0
- package/dist/x402/visa-facilitator-api.js +206 -0
- package/dist/x402/visa-facilitator-api.js.map +1 -0
- package/dist/x402/visa-token-api.d.ts +53 -0
- package/dist/x402/visa-token-api.d.ts.map +1 -0
- package/dist/x402/visa-token-api.js +88 -0
- package/dist/x402/visa-token-api.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../../src/x402/express/middleware.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Express middleware for Nevermined payment protection using the x402 protocol.\n *\n * This middleware provides a simple way to protect Express routes with\n * Nevermined payment verification and settlement.\n *\n * ## x402 HTTP Transport Headers\n *\n * Following the x402 spec (https://github.com/coinbase/x402/blob/main/specs/transports-v2/http.md):\n *\n * - **Client → Server**: `payment-signature` header with base64-encoded token\n * - **Server → Client (402)**: `payment-required` header with base64-encoded PaymentRequired\n * - **Server → Client (success)**: `payment-response` header with settlement receipt\n *\n * @example\n * ```typescript\n * import express from 'express'\n * import { Payments } from '@nevermined-io/payments'\n * import { paymentMiddleware } from '@nevermined-io/payments/express'\n *\n * const app = express()\n * const payments = Payments.getInstance({ nvmApiKey: '...', environment: 'testing' })\n *\n * // Protect routes with payment middleware\n * app.use(paymentMiddleware(payments, {\n * 'POST /ask': { planId: '123', credits: 1 },\n * 'POST /generate': { planId: '123', credits: 5 },\n * }))\n *\n * // Route handlers - no payment logic needed!\n * app.post('/ask', (req, res) => res.json({ answer: '...' }))\n * ```\n *\n * @example Client usage\n * ```typescript\n * const token = await payments.x402.getX402AccessToken(planId)\n *\n * const response = await fetch('/ask', {\n * method: 'POST',\n * headers: {\n * 'Content-Type': 'application/json',\n * 'payment-signature': token.accessToken, // x402 header\n * },\n * body: JSON.stringify({ query: 'Hello!' }),\n * })\n * ```\n */\n\nimport type { Request, Response, NextFunction } from 'express'\n\n/**\n * Express middleware function type.\n * Using explicit signature instead of RequestHandler to avoid type resolution issues\n * when SDK's \\@types/express version differs from consumer's.\n */\nexport type ExpressMiddleware = (req: Request, res: Response, next: NextFunction) => void\nimport type { Payments } from '../../payments.js'\nimport type { StartAgentRequest } from '../../common/types.js'\nimport {\n buildPaymentRequired,\n type X402PaymentRequired,\n type VerifyPermissionsResult,\n} from '../facilitator-api.js'\n\n/**\n * Configuration for a protected route\n */\nexport interface RouteConfig {\n /** The Nevermined plan ID that protects this route */\n planId: string\n /** Number of credits to charge for this route (default: 1) */\n credits?: number | ((req: Request, res: Response) => number | Promise<number>)\n /** Optional agent ID */\n agentId?: string\n /** Network identifier (default: 'eip155:84532' for Base Sepolia) */\n network?: string\n}\n\n/**\n * Route configuration map: \"METHOD \\/path\" -> RouteConfig\n */\nexport type RouteConfigMap = Record<string, RouteConfig>\n\n/**\n * x402 HTTP Transport header names (v2 spec)\n * @see https://github.com/coinbase/x402/blob/main/specs/transports-v2/http.md\n */\nexport const X402_HEADERS = {\n /** Client sends payment token in this header */\n PAYMENT_SIGNATURE: 'payment-signature',\n /** Server sends PaymentRequired in this header (base64-encoded) */\n PAYMENT_REQUIRED: 'payment-required',\n /** Server sends settlement receipt in this header (base64-encoded) */\n PAYMENT_RESPONSE: 'payment-response',\n} as const\n\n/**\n * Payment context attached to the request after verification.\n * Available as `req.paymentContext` in route handlers.\n */\nexport interface PaymentContext {\n /** The x402 access token */\n token: string\n /** The payment required object */\n paymentRequired: X402PaymentRequired\n /** Number of credits to settle */\n creditsToSettle: number\n /** Whether verification was successful */\n verified: boolean\n /** Agent request context for observability (from verification response) */\n agentRequest?: StartAgentRequest\n /** Agent request ID for observability tracking */\n agentRequestId?: string\n}\n\n/**\n * Options for the payment middleware\n */\nexport interface PaymentMiddlewareOptions {\n /**\n * Header name(s) to check for the x402 access token.\n * Default: 'payment-signature' (x402 v2 compliant)\n */\n tokenHeader?: string | string[]\n /** Custom error handler for payment failures */\n onPaymentError?: (error: Error, req: Request, res: Response) => void\n /** Hook called before verification */\n onBeforeVerify?: (req: Request, paymentRequired: X402PaymentRequired) => void | Promise<void>\n /**\n * Hook called after successful verification.\n * Use this to access agentRequest for observability configuration.\n */\n onAfterVerify?: (req: Request, verification: VerifyPermissionsResult) => void | Promise<void>\n /** Hook called after successful settlement */\n onAfterSettle?: (req: Request, creditsUsed: number, result: unknown) => void | Promise<void>\n}\n\n/**\n * Default header for token extraction (x402 v2 compliant)\n */\nconst DEFAULT_TOKEN_HEADERS = [X402_HEADERS.PAYMENT_SIGNATURE]\n\n/**\n * Extract the x402 access token from the request headers.\n * Checks multiple headers in priority order.\n */\nfunction extractToken(req: Request, headerNames: string | string[]): string | null {\n const headers = Array.isArray(headerNames) ? headerNames : [headerNames]\n\n for (const headerName of headers) {\n const header = req.headers[headerName.toLowerCase()]\n if (header && typeof header === 'string') {\n return header\n }\n }\n\n return null\n}\n\n/**\n * Match a request to a route config.\n * Returns the config if found, null otherwise.\n */\nfunction matchRoute(req: Request, routes: RouteConfigMap): RouteConfig | null {\n const method = req.method.toUpperCase()\n const path = req.path\n\n // Try exact match first: \"POST /ask\"\n const exactKey = `${method} ${path}`\n if (routes[exactKey]) {\n return routes[exactKey]\n }\n\n // Try pattern matching with path parameters\n for (const [routeKey, config] of Object.entries(routes)) {\n const [routeMethod, routePath] = routeKey.split(' ')\n if (routeMethod !== method) continue\n\n // Simple pattern matching: /users/:id -> /users/123\n const routeParts = routePath.split('/')\n const pathParts = path.split('/')\n\n if (routeParts.length !== pathParts.length) continue\n\n let match = true\n for (let i = 0; i < routeParts.length; i++) {\n if (routeParts[i].startsWith(':')) continue // Parameter - always matches\n if (routeParts[i] !== pathParts[i]) {\n match = false\n break\n }\n }\n\n if (match) return config\n }\n\n return null\n}\n\n/**\n * Create an Express middleware that protects routes with Nevermined payments.\n *\n * The middleware:\n * 1. Checks if the request matches a protected route\n * 2. Extracts the x402 token from headers\n * 3. Verifies the subscriber has sufficient credits\n * 4. Lets the route handler execute\n * 5. Settles (burns) the credits after successful response\n *\n * @param payments - The Payments instance\n * @param routes - Map of routes to protect: \\{ \"METHOD \\/path\": \\{ planId, credits \\} \\}\n * @param options - Optional middleware configuration\n * @returns Express middleware function\n *\n * @example\n * ```typescript\n * app.use(paymentMiddleware(payments, {\n * 'POST /ask': { planId: PLAN_ID, credits: 1 },\n * 'POST /generate': { planId: PLAN_ID, credits: 5 },\n * 'GET /status/:id': { planId: PLAN_ID, credits: 0 }, // Free but requires auth\n * }))\n * ```\n */\n/**\n * Helper to send a 402 Payment Required response with proper x402 headers.\n */\nfunction sendPaymentRequired(\n res: Response,\n paymentRequired: X402PaymentRequired,\n message: string,\n): void {\n // Base64 encode the PaymentRequired object for the header (per x402 spec)\n const paymentRequiredBase64 = Buffer.from(JSON.stringify(paymentRequired)).toString('base64')\n\n res.status(402).setHeader(X402_HEADERS.PAYMENT_REQUIRED, paymentRequiredBase64).json({\n error: 'Payment Required',\n message,\n })\n}\n\nexport function paymentMiddleware(\n payments: Payments,\n routes: RouteConfigMap,\n options: PaymentMiddlewareOptions = {},\n): ExpressMiddleware {\n const {\n tokenHeader = DEFAULT_TOKEN_HEADERS,\n onPaymentError,\n onBeforeVerify,\n onAfterVerify,\n onAfterSettle,\n } = options\n\n return (req: Request, res: Response, next: NextFunction): void => {\n // Wrap async logic to handle promises properly\n const handleRequest = async (): Promise<void> => {\n // Check if this route requires payment\n const routeConfig = matchRoute(req, routes)\n if (!routeConfig) {\n // Route not protected - pass through\n next()\n return\n }\n\n const { planId, credits = 1, agentId, network } = routeConfig\n\n // Build payment required object (needed for both error responses and verification)\n const paymentRequired = buildPaymentRequired(planId, {\n endpoint: req.originalUrl || req.url,\n agentId,\n httpVerb: req.method,\n network,\n })\n\n // Extract token from headers (x402 v2: payment-signature)\n const token = extractToken(req, tokenHeader)\n if (!token) {\n const error = new Error('Payment required: missing x402 access token')\n if (onPaymentError) {\n onPaymentError(error, req, res)\n return\n }\n sendPaymentRequired(\n res,\n paymentRequired,\n `Missing x402 payment token. Send token in ${X402_HEADERS.PAYMENT_SIGNATURE} header.`,\n )\n return\n }\n\n // Calculate credits to verify\n const creditsToVerify = typeof credits === 'function' ? await credits(req, res) : credits\n\n try {\n // Hook: before verification\n if (onBeforeVerify) {\n await onBeforeVerify(req, paymentRequired)\n }\n\n // Verify permissions\n const verification = await payments.facilitator.verifyPermissions({\n paymentRequired,\n x402AccessToken: token,\n maxAmount: BigInt(creditsToVerify),\n })\n\n if (!verification.isValid) {\n const error = new Error(verification.invalidReason || 'Payment verification failed')\n if (onPaymentError) {\n onPaymentError(error, req, res)\n return\n }\n sendPaymentRequired(\n res,\n paymentRequired,\n verification.invalidReason || 'Insufficient credits or invalid token',\n )\n return\n }\n\n // Hook: after verification (use for observability setup)\n if (onAfterVerify) {\n await onAfterVerify(req, verification)\n }\n\n // Store payment context for settlement and route handler access\n const paymentContext: PaymentContext = {\n token,\n paymentRequired,\n creditsToSettle: creditsToVerify,\n verified: true,\n agentRequest: verification.agentRequest,\n agentRequestId: verification.agentRequest?.agentRequestId || verification.agentRequestId,\n }\n\n // Attach to request for potential use by route handler\n ;(req as Request & { paymentContext?: PaymentContext }).paymentContext = paymentContext\n\n // Override res.json to settle BEFORE sending response\n // This ensures credits are burned and payment-response header is included\n const originalJson = res.json.bind(res)\n res.json = function (body: unknown) {\n // Settle credits synchronously before sending response\n // Pass agentRequestId to enable observability updates\n payments.facilitator\n .settlePermissions({\n paymentRequired,\n x402AccessToken: token,\n maxAmount: BigInt(creditsToVerify),\n agentRequestId: paymentContext.agentRequestId,\n })\n .then((settlement) => {\n // Add settlement response header (base64-encoded per x402 spec)\n const settlementBase64 = Buffer.from(JSON.stringify(settlement)).toString('base64')\n res.setHeader(X402_HEADERS.PAYMENT_RESPONSE, settlementBase64)\n\n // Hook: after settlement\n if (onAfterSettle) {\n return Promise.resolve(onAfterSettle(req, creditsToVerify, settlement)).then(\n () => settlement,\n )\n }\n return settlement\n })\n .catch((settleError) => {\n console.error('Payment settlement failed:', settleError)\n // Still send response even if settlement fails\n })\n .finally(() => {\n // Send the actual response after settlement completes\n originalJson(body)\n })\n\n // Return res for chaining (Express pattern)\n return res\n }\n\n // Continue to route handler\n next()\n } catch (error) {\n if (onPaymentError) {\n onPaymentError(error as Error, req, res)\n return\n }\n sendPaymentRequired(\n res,\n paymentRequired,\n error instanceof Error ? error.message : 'Payment verification failed',\n )\n }\n }\n\n // Execute async handler with error handling\n handleRequest().catch(next)\n }\n}\n\nexport default paymentMiddleware\n"]}
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../../src/x402/express/middleware.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Express middleware for Nevermined payment protection using the x402 protocol.\n *\n * This middleware provides a simple way to protect Express routes with\n * Nevermined payment verification and settlement.\n *\n * ## x402 HTTP Transport Headers\n *\n * Following the x402 spec (https://github.com/coinbase/x402/blob/main/specs/transports-v2/http.md):\n *\n * - **Client → Server**: `payment-signature` header with base64-encoded token\n * - **Server → Client (402)**: `payment-required` header with base64-encoded PaymentRequired\n * - **Server → Client (success)**: `payment-response` header with settlement receipt\n *\n * @example\n * ```typescript\n * import express from 'express'\n * import { Payments } from '@nevermined-io/payments'\n * import { paymentMiddleware } from '@nevermined-io/payments/express'\n *\n * const app = express()\n * const payments = Payments.getInstance({ nvmApiKey: '...', environment: 'testing' })\n *\n * // Protect routes with payment middleware\n * app.use(paymentMiddleware(payments, {\n * 'POST /ask': { planId: '123', credits: 1 },\n * 'POST /generate': { planId: '123', credits: 5 },\n * }))\n *\n * // Route handlers - no payment logic needed!\n * app.post('/ask', (req, res) => res.json({ answer: '...' }))\n * ```\n *\n * @example Client usage\n * ```typescript\n * const token = await payments.x402.getX402AccessToken(planId)\n *\n * const response = await fetch('/ask', {\n * method: 'POST',\n * headers: {\n * 'Content-Type': 'application/json',\n * 'payment-signature': token.accessToken, // x402 header\n * },\n * body: JSON.stringify({ query: 'Hello!' }),\n * })\n * ```\n */\n\nimport type { Request, Response, NextFunction } from 'express'\n\n/**\n * Express middleware function type.\n * Using explicit signature instead of RequestHandler to avoid type resolution issues\n * when SDK's \\@types/express version differs from consumer's.\n */\nexport type ExpressMiddleware = (req: Request, res: Response, next: NextFunction) => void\nimport type { Payments } from '../../payments.js'\nimport type { StartAgentRequest } from '../../common/types.js'\nimport {\n buildPaymentRequired,\n type X402PaymentRequired,\n type VerifyPermissionsResult,\n} from '../facilitator-api.js'\n\n/**\n * Configuration for a protected route\n */\nexport interface RouteConfig {\n /** The Nevermined plan ID that protects this route */\n planId: string\n /** Number of credits to charge for this route (default: 1) */\n credits?: number | ((req: Request, res: Response) => number | Promise<number>)\n /** Optional agent ID */\n agentId?: string\n /** Network identifier (default: 'eip155:84532' for Base Sepolia) */\n network?: string\n}\n\n/**\n * Route configuration map: \"METHOD \\/path\" -> RouteConfig\n */\nexport type RouteConfigMap = Record<string, RouteConfig>\n\n/**\n * x402 HTTP Transport header names (v2 spec)\n * @see https://github.com/coinbase/x402/blob/main/specs/transports-v2/http.md\n */\nexport const X402_HEADERS = {\n /** Client sends payment token in this header */\n PAYMENT_SIGNATURE: 'payment-signature',\n /** Server sends PaymentRequired in this header (base64-encoded) */\n PAYMENT_REQUIRED: 'payment-required',\n /** Server sends settlement receipt in this header (base64-encoded) */\n PAYMENT_RESPONSE: 'payment-response',\n} as const\n\n/**\n * Payment context attached to the request after verification.\n * Available as `req.paymentContext` in route handlers.\n */\nexport interface PaymentContext {\n /** The x402 access token */\n token: string\n /** The payment required object */\n paymentRequired: X402PaymentRequired\n /** Number of credits to settle */\n creditsToSettle: number\n /** Whether verification was successful */\n verified: boolean\n /** Agent request context for observability (from verification response) */\n agentRequest?: StartAgentRequest\n /** Agent request ID for observability tracking */\n agentRequestId?: string\n}\n\n/**\n * Options for the payment middleware\n */\nexport interface PaymentMiddlewareOptions {\n /**\n * Header name(s) to check for the x402 access token.\n * Default: 'payment-signature' (x402 v2 compliant)\n */\n tokenHeader?: string | string[]\n /** Custom error handler for payment failures */\n onPaymentError?: (error: Error, req: Request, res: Response) => void\n /** Hook called before verification */\n onBeforeVerify?: (req: Request, paymentRequired: X402PaymentRequired) => void | Promise<void>\n /**\n * Hook called after successful verification.\n * Use this to access agentRequest for observability configuration.\n */\n onAfterVerify?: (req: Request, verification: VerifyPermissionsResult) => void | Promise<void>\n /** Hook called after successful settlement */\n onAfterSettle?: (req: Request, creditsUsed: number, result: unknown) => void | Promise<void>\n}\n\n/**\n * Default header for token extraction (x402 v2 compliant)\n */\nconst DEFAULT_TOKEN_HEADERS = [X402_HEADERS.PAYMENT_SIGNATURE]\n\n/**\n * Extract the x402 access token from the request headers.\n * Checks multiple headers in priority order.\n */\nfunction extractToken(req: Request, headerNames: string | string[]): string | null {\n const headers = Array.isArray(headerNames) ? headerNames : [headerNames]\n\n for (const headerName of headers) {\n const header = req.headers[headerName.toLowerCase()]\n if (header && typeof header === 'string') {\n return header\n }\n }\n\n return null\n}\n\n/**\n * Match a request to a route config.\n * Returns the config if found, null otherwise.\n */\nfunction matchRoute(req: Request, routes: RouteConfigMap): RouteConfig | null {\n const method = req.method.toUpperCase()\n const path = req.path\n\n // Try exact match first: \"POST /ask\"\n const exactKey = `${method} ${path}`\n if (routes[exactKey]) {\n return routes[exactKey]\n }\n\n // Try pattern matching with path parameters\n for (const [routeKey, config] of Object.entries(routes)) {\n const [routeMethod, routePath] = routeKey.split(' ')\n if (routeMethod !== method) continue\n\n // Simple pattern matching: /users/:id -> /users/123\n const routeParts = routePath.split('/')\n const pathParts = path.split('/')\n\n if (routeParts.length !== pathParts.length) continue\n\n let match = true\n for (let i = 0; i < routeParts.length; i++) {\n if (routeParts[i].startsWith(':')) continue // Parameter - always matches\n if (routeParts[i] !== pathParts[i]) {\n match = false\n break\n }\n }\n\n if (match) return config\n }\n\n return null\n}\n\n/**\n * Create an Express middleware that protects routes with Nevermined payments.\n *\n * The middleware:\n * 1. Checks if the request matches a protected route\n * 2. Extracts the x402 token from headers\n * 3. Verifies the subscriber has sufficient credits\n * 4. Lets the route handler execute\n * 5. Settles (burns) the credits after successful response\n *\n * @param payments - The Payments instance\n * @param routes - Map of routes to protect: \\{ \"METHOD \\/path\": \\{ planId, credits \\} \\}\n * @param options - Optional middleware configuration\n * @returns Express middleware function\n *\n * @example\n * ```typescript\n * app.use(paymentMiddleware(payments, {\n * 'POST /ask': { planId: PLAN_ID, credits: 1 },\n * 'POST /generate': { planId: PLAN_ID, credits: 5 },\n * 'GET /status/:id': { planId: PLAN_ID, credits: 0 }, // Free but requires auth\n * }))\n * ```\n */\n/**\n * Helper to send a 402 Payment Required response with proper x402 headers.\n */\nfunction sendPaymentRequired(\n res: Response,\n paymentRequired: X402PaymentRequired,\n message: string,\n): void {\n // Base64 encode the PaymentRequired object for the header (per x402 spec)\n const paymentRequiredBase64 = Buffer.from(JSON.stringify(paymentRequired)).toString('base64')\n\n res.status(402).setHeader(X402_HEADERS.PAYMENT_REQUIRED, paymentRequiredBase64).json({\n error: 'Payment Required',\n message,\n })\n}\n\nexport function paymentMiddleware(\n payments: Payments,\n routes: RouteConfigMap,\n options: PaymentMiddlewareOptions = {},\n): ExpressMiddleware {\n const {\n tokenHeader = DEFAULT_TOKEN_HEADERS,\n onPaymentError,\n onBeforeVerify,\n onAfterVerify,\n onAfterSettle,\n } = options\n\n return (req: Request, res: Response, next: NextFunction): void => {\n // Wrap async logic to handle promises properly\n const handleRequest = async (): Promise<void> => {\n // Check if this route requires payment\n const routeConfig = matchRoute(req, routes)\n if (!routeConfig) {\n // Route not protected - pass through\n next()\n return\n }\n\n const { planId, credits = 1, agentId, network } = routeConfig\n\n // Build payment required object (needed for both error responses and verification)\n const paymentRequired = buildPaymentRequired(planId, {\n endpoint: req.originalUrl || req.url,\n agentId,\n httpVerb: req.method,\n network,\n })\n\n // Extract token from headers (x402 v2: payment-signature)\n const token = extractToken(req, tokenHeader)\n if (!token) {\n const error = new Error('Payment required: missing x402 access token')\n if (onPaymentError) {\n onPaymentError(error, req, res)\n return\n }\n sendPaymentRequired(\n res,\n paymentRequired,\n `Missing x402 payment token. Send token in ${X402_HEADERS.PAYMENT_SIGNATURE} header.`,\n )\n return\n }\n\n // Calculate credits to verify\n const creditsToVerify = typeof credits === 'function' ? await credits(req, res) : credits\n\n try {\n // Hook: before verification\n if (onBeforeVerify) {\n await onBeforeVerify(req, paymentRequired)\n }\n\n // Verify permissions\n const verification = await payments.facilitator.verifyPermissions({\n paymentRequired,\n x402AccessToken: token,\n maxAmount: BigInt(creditsToVerify),\n })\n\n if (!verification.isValid) {\n const error = new Error(verification.invalidReason || 'Payment verification failed')\n if (onPaymentError) {\n onPaymentError(error, req, res)\n return\n }\n sendPaymentRequired(\n res,\n paymentRequired,\n verification.invalidReason || 'Insufficient credits or invalid token',\n )\n return\n }\n\n // Hook: after verification (use for observability setup)\n if (onAfterVerify) {\n await onAfterVerify(req, verification)\n }\n\n // Store payment context for settlement and route handler access\n const paymentContext: PaymentContext = {\n token,\n paymentRequired,\n creditsToSettle: creditsToVerify,\n verified: true,\n agentRequest: verification.agentRequest,\n agentRequestId: verification.agentRequest?.agentRequestId || verification.agentRequestId,\n }\n\n // Attach to request for potential use by route handler\n ;(req as Request & { paymentContext?: PaymentContext }).paymentContext = paymentContext\n\n // Override res.json to settle BEFORE sending response\n // This ensures credits are burned and payment-response header is included\n const originalJson = res.json.bind(res)\n res.json = function (body: unknown) {\n // Re-evaluate dynamic credits now that the handler has run and\n // res.locals is populated. For fixed (numeric) credits this is a no-op.\n const settlePromise = (\n typeof credits === 'function'\n ? Promise.resolve(credits(req, res))\n : Promise.resolve(creditsToVerify)\n ).then((creditsToSettle) => {\n // Update payment context so downstream consumers see the actual value\n paymentContext.creditsToSettle = creditsToSettle\n\n // Settle credits before sending response\n // Pass agentRequestId to enable observability updates\n return payments.facilitator\n .settlePermissions({\n paymentRequired,\n x402AccessToken: token,\n maxAmount: BigInt(creditsToSettle),\n agentRequestId: paymentContext.agentRequestId,\n })\n .then((settlement) => {\n // Add settlement response header (base64-encoded per x402 spec)\n const settlementBase64 = Buffer.from(JSON.stringify(settlement)).toString('base64')\n res.setHeader(X402_HEADERS.PAYMENT_RESPONSE, settlementBase64)\n\n // Hook: after settlement\n if (onAfterSettle) {\n return Promise.resolve(onAfterSettle(req, creditsToSettle, settlement)).then(\n () => settlement,\n )\n }\n return settlement\n })\n })\n\n settlePromise\n .catch((settleError) => {\n console.error('Payment settlement failed:', settleError)\n // Still send response even if settlement fails\n })\n .finally(() => {\n // Send the actual response after settlement completes\n originalJson(body)\n })\n\n // Return res for chaining (Express pattern)\n return res\n }\n\n // Continue to route handler\n next()\n } catch (error) {\n if (onPaymentError) {\n onPaymentError(error as Error, req, res)\n return\n }\n sendPaymentRequired(\n res,\n paymentRequired,\n error instanceof Error ? error.message : 'Payment verification failed',\n )\n }\n }\n\n // Execute async handler with error handling\n handleRequest().catch(next)\n }\n}\n\nexport default paymentMiddleware\n"]}
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
*/
|
|
43
43
|
import { BasePaymentsAPI } from '../api/base-payments.js';
|
|
44
44
|
import { PaymentOptions, StartAgentRequest } from '../common/types.js';
|
|
45
|
+
import type { VisaPaymentRequired } from './visa-facilitator-api.js';
|
|
45
46
|
/**
|
|
46
47
|
* x402 Resource information
|
|
47
48
|
*/
|
|
@@ -115,8 +116,8 @@ export interface X402PaymentAccepted {
|
|
|
115
116
|
* Parameters for verifying permissions
|
|
116
117
|
*/
|
|
117
118
|
export interface VerifyPermissionsParams {
|
|
118
|
-
/** The server's 402 PaymentRequired response */
|
|
119
|
-
paymentRequired: X402PaymentRequired;
|
|
119
|
+
/** The server's 402 PaymentRequired response (NVM or Visa flavored) */
|
|
120
|
+
paymentRequired: X402PaymentRequired | VisaPaymentRequired;
|
|
120
121
|
/** The X402 access token (base64-encoded) */
|
|
121
122
|
x402AccessToken: string;
|
|
122
123
|
/** Maximum credits to verify (optional) */
|
|
@@ -144,8 +145,8 @@ export interface VerifyPermissionsResult {
|
|
|
144
145
|
* Parameters for settling permissions
|
|
145
146
|
*/
|
|
146
147
|
export interface SettlePermissionsParams {
|
|
147
|
-
/** The server's 402 PaymentRequired response */
|
|
148
|
-
paymentRequired: X402PaymentRequired;
|
|
148
|
+
/** The server's 402 PaymentRequired response (NVM or Visa flavored) */
|
|
149
|
+
paymentRequired: X402PaymentRequired | VisaPaymentRequired;
|
|
149
150
|
/** The X402 access token (base64-encoded) */
|
|
150
151
|
x402AccessToken: string;
|
|
151
152
|
/** Number of credits to burn (optional) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facilitator-api.d.ts","sourceRoot":"","sources":["../../src/x402/facilitator-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAGzD,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"facilitator-api.d.ts","sourceRoot":"","sources":["../../src/x402/facilitator-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"facilitator-api.js","sourceRoot":"","sources":["../../src/x402/facilitator-api.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The FacilitatorAPI class provides methods to verify and settle AI agent permissions using X402 access tokens.\n * This allows AI agents to act as facilitators, verifying and settling credits on behalf of subscribers.\n *\n * @example\n * ```typescript\n * import { Payments, X402PaymentRequired } from '@nevermined-io/payments'\n *\n * // Initialize the Payments instance\n * const payments = Payments.getInstance({\n * nvmApiKey: 'your-nvm-api-key',\n * environment: 'sandbox'\n * })\n *\n * // The server's 402 PaymentRequired response\n * const paymentRequired: X402PaymentRequired = buildPaymentRequired('123456789', {\n * endpoint: '/api/v1/agents/task',\n * agentId: '987654321',\n * httpVerb: 'POST'\n * })\n *\n * // Get X402 access token from subscriber (x402 v2: payment-signature header)\n * const x402Token = req.headers['payment-signature'] as string\n *\n * // Verify if subscriber has sufficient permissions/credits\n * const verification = await payments.facilitator.verifyPermissions({\n * paymentRequired,\n * x402AccessToken: x402Token,\n * maxAmount: 2n\n * })\n *\n * if (verification.isValid) {\n * // Settle (burn) the credits\n * const settlement = await payments.facilitator.settlePermissions({\n * paymentRequired,\n * x402AccessToken: x402Token,\n * maxAmount: 2n\n * })\n * console.log(`Credits redeemed: ${settlement.creditsRedeemed}`)\n * }\n * ```\n */\n\nimport { BasePaymentsAPI } from '../api/base-payments.js'\nimport { API_URL_SETTLE_PERMISSIONS, API_URL_VERIFY_PERMISSIONS } from '../api/nvm-api.js'\nimport { PaymentsError } from '../common/payments.error.js'\nimport { PaymentOptions, StartAgentRequest } from '../common/types.js'\n\n/**\n * x402 Resource information\n */\nexport interface X402Resource {\n /** The protected resource URL */\n url: string\n /** Human-readable description */\n description?: string\n /** Expected response MIME type (e.g., \"application/json\") */\n mimeType?: string\n}\n\n/**\n * x402 Scheme extra fields for nvm:erc4337\n */\nexport interface X402SchemeExtra {\n /** Scheme version (e.g., \"1\") */\n version?: string\n /** Agent identifier */\n agentId?: string\n /** HTTP method for the endpoint */\n httpVerb?: string\n}\n\n/**\n * x402 Scheme definition (nvm:erc4337)\n */\nexport interface X402Scheme {\n /** Payment scheme identifier (e.g., \"nvm:erc4337\") */\n scheme: string\n /** Blockchain network in CAIP-2 format (e.g., \"eip155:84532\") */\n network: string\n /** 256-bit plan identifier */\n planId: string\n /** Scheme-specific extra fields */\n extra?: X402SchemeExtra\n}\n\n/**\n * x402 PaymentRequired response (402 response from server)\n */\nexport interface X402PaymentRequired {\n /** x402 protocol version (always 2) */\n x402Version: number\n /** Human-readable error message */\n error?: string\n /** Protected resource information */\n resource: X402Resource\n /** Array of accepted payment schemes */\n accepts: X402Scheme[]\n /** Extensions object (empty object for nvm:erc4337) */\n extensions: Record<string, unknown>\n}\n\n/**\n * x402 PaymentAccepted response (accepted payment scheme)\n */\nexport interface X402PaymentAccepted {\n /** The x402 version */\n x402Version: number\n /** The accepted payment scheme (nvm:erc4337) */\n accepted: X402Scheme\n /** The payload of the payment accepted */\n payload: {\n signature: string\n authorization: {\n from: string\n sessionKeysProvider: string\n sessionKeys: string[]\n }\n }\n extensions: Record<string, unknown>\n}\n\n/**\n * Parameters for verifying permissions\n */\nexport interface VerifyPermissionsParams {\n /** The server's 402 PaymentRequired response */\n paymentRequired: X402PaymentRequired\n /** The X402 access token (base64-encoded) */\n x402AccessToken: string\n /** Maximum credits to verify (optional) */\n maxAmount?: bigint\n}\n\n/**\n * x402 Verify Response - per x402 facilitator spec\n * @see https://github.com/coinbase/x402/blob/main/specs/x402-specification-v2.md\n */\nexport interface VerifyPermissionsResult {\n /** Whether the payment authorization is valid */\n isValid: boolean\n /** Reason for invalidity (only present if isValid is false) */\n invalidReason?: string\n /** Address of the payer's wallet */\n payer?: string\n /** Agent request ID for observability tracking (Nevermined extension) */\n agentRequestId?: string\n /** URL pattern that matched the endpoint (Nevermined extension) */\n urlMatching?: string\n /** Agent request context for observability (Nevermined extension) */\n agentRequest?: StartAgentRequest\n}\n\n/**\n * Parameters for settling permissions\n */\nexport interface SettlePermissionsParams {\n /** The server's 402 PaymentRequired response */\n paymentRequired: X402PaymentRequired\n /** The X402 access token (base64-encoded) */\n x402AccessToken: string\n /** Number of credits to burn (optional) */\n maxAmount?: bigint\n /** Agent request ID for observability tracking. Returned by verifyPermissions. */\n agentRequestId?: string\n /** Whether this is a batch request (multiple LLM calls under one agentRequestId) */\n batch?: boolean\n /** Margin percentage (0-10) for credit calculation. Mutually exclusive with maxAmount when agentRequestId provided. */\n marginPercent?: number\n}\n\n/**\n * x402 Settle Response - per x402 facilitator spec\n * @see https://github.com/coinbase/x402/blob/main/specs/x402-specification-v2.md\n */\nexport interface SettlePermissionsResult {\n /** Whether settlement was successful */\n success: boolean\n /** Reason for settlement failure (only present if success is false) */\n errorReason?: string\n /** Address of the payer's wallet */\n payer?: string\n /** Blockchain transaction hash (empty string if settlement failed) */\n transaction: string\n /** Blockchain network identifier in CAIP-2 format */\n network: string\n /** Number of credits redeemed (Nevermined extension) */\n creditsRedeemed?: string\n /** Subscriber's remaining balance (Nevermined extension) */\n remainingBalance?: string\n /** Transaction hash of the order operation if auto top-up occurred (Nevermined extension) */\n orderTx?: string\n}\n\n/**\n * Build an X402PaymentRequired object for verify/settle operations.\n *\n * This helper simplifies the creation of payment requirement objects\n * that are needed for the facilitator API.\n *\n * @param planId - The Nevermined plan identifier (required)\n * @param options - Optional configuration with endpoint, agentId, httpVerb, network, description\n * @returns X402PaymentRequired object ready to use with verifyPermissions/settlePermissions\n *\n * @example\n * ```typescript\n * import { buildPaymentRequired } from '@nevermined-io/payments'\n *\n * const paymentRequired = buildPaymentRequired('123456789', {\n * endpoint: '/api/v1/agents/task',\n * agentId: '987654321',\n * httpVerb: 'POST'\n * })\n *\n * const result = await payments.facilitator.verifyPermissions({\n * paymentRequired,\n * x402AccessToken: token,\n * maxAmount: 2n\n * })\n * ```\n */\nexport function buildPaymentRequired(\n planId: string,\n options?: {\n endpoint?: string\n agentId?: string\n httpVerb?: string\n network?: string\n description?: string\n },\n): X402PaymentRequired {\n const { endpoint, agentId, httpVerb, network = 'eip155:84532', description } = options || {}\n\n // Build extra fields if any are provided\n const extra: X402SchemeExtra | undefined =\n agentId || httpVerb\n ? {\n ...(agentId && { agentId }),\n ...(httpVerb && { httpVerb }),\n }\n : undefined\n\n return {\n x402Version: 2,\n resource: {\n url: endpoint || '',\n ...(description && { description }),\n },\n accepts: [\n {\n scheme: 'nvm:erc4337',\n network,\n planId,\n ...(extra && { extra }),\n },\n ],\n extensions: {},\n }\n}\n\n/**\n * The FacilitatorAPI class provides methods to verify and settle AI agent permissions.\n * It enables AI agents to act as facilitators, managing credit verification and settlement\n * for subscribers using X402 access tokens.\n */\nexport class FacilitatorAPI extends BasePaymentsAPI {\n /**\n * Get a singleton instance of the FacilitatorAPI class.\n *\n * @param options - The options to initialize the payments class\n * @returns The instance of the FacilitatorAPI class\n */\n static getInstance(options: PaymentOptions): FacilitatorAPI {\n return new FacilitatorAPI(options)\n }\n\n /**\n * Verify if a subscriber has permission to use credits from a payment plan.\n * This method simulates the credit usage without actually burning credits,\n * checking if the subscriber has sufficient balance and permissions.\n *\n * The planId and subscriberAddress are extracted from the x402AccessToken.\n *\n * @param params - Verification parameters (see {@link VerifyPermissionsParams}).\n * - paymentRequired: x402 PaymentRequired from 402 response (required, for validation)\n * - x402AccessToken: X402 access token (contains planId, subscriberAddress, agentId)\n * - maxAmount: maximum credits to verify (optional, bigint)\n * @returns A promise that resolves to a verification result with 'isValid' boolean\n *\n * @throws PaymentsError if verification fails\n */\n async verifyPermissions(params: VerifyPermissionsParams): Promise<VerifyPermissionsResult> {\n const { paymentRequired, x402AccessToken, maxAmount } = params\n\n const url = new URL(API_URL_VERIFY_PERMISSIONS, this.environment.backend)\n\n const body: Record<string, unknown> = {\n paymentRequired,\n x402AccessToken,\n }\n\n if (maxAmount !== undefined) {\n body.maxAmount = maxAmount.toString()\n }\n\n const options = this.getPublicHTTPOptions('POST', body)\n\n try {\n const response = await fetch(url, options)\n if (!response.ok) {\n let errorMessage = 'Permission verification failed'\n try {\n const errorData = await response.json()\n errorMessage = errorData.message || errorMessage\n } catch {\n // Use default error message\n }\n throw PaymentsError.fromBackend(errorMessage, {\n message: errorMessage,\n code: `HTTP ${response.status}`,\n })\n }\n return await response.json()\n } catch (error) {\n if (error instanceof PaymentsError) {\n throw error\n }\n throw PaymentsError.fromBackend('Network error during permission verification', {\n message: error instanceof Error ? error.message : String(error),\n code: 'network_error',\n })\n }\n }\n\n /**\n * Settle (burn) credits from a subscriber's payment plan.\n * This method executes the actual credit consumption, burning the specified\n * number of credits from the subscriber's balance. If the subscriber doesn't\n * have enough credits, it will attempt to order more before settling.\n *\n * The planId and subscriberAddress are extracted from the x402AccessToken.\n *\n * @param params - Settlement parameters (see {@link SettlePermissionsParams}).\n * - paymentRequired: x402 PaymentRequired from 402 response (required, for validation)\n * - x402AccessToken: X402 access token (contains planId, subscriberAddress, agentId)\n * - maxAmount: number of credits to burn (optional, bigint)\n * - agentRequestId: Agent request ID for observability tracking (optional)\n * - batch: Whether this is a batch request (optional)\n * - marginPercent: Margin percentage for credit calculation (optional)\n * @returns A promise that resolves to a settlement result with transaction details\n *\n * @throws PaymentsError if settlement fails\n */\n async settlePermissions(params: SettlePermissionsParams): Promise<SettlePermissionsResult> {\n const { paymentRequired, x402AccessToken, maxAmount, agentRequestId, batch, marginPercent } =\n params\n\n const url = new URL(API_URL_SETTLE_PERMISSIONS, this.environment.backend)\n\n const body: Record<string, unknown> = {\n paymentRequired,\n x402AccessToken,\n }\n\n if (maxAmount !== undefined) {\n body.maxAmount = maxAmount.toString()\n }\n if (agentRequestId !== undefined) {\n body.agentRequestId = agentRequestId\n }\n if (batch !== undefined) {\n body.batch = batch\n }\n if (marginPercent !== undefined) {\n body.marginPercent = marginPercent\n }\n\n const options = this.getPublicHTTPOptions('POST', body)\n\n try {\n const response = await fetch(url, options)\n if (!response.ok) {\n let errorMessage = 'Permission settlement failed'\n try {\n const errorData = await response.json()\n errorMessage = errorData.message || errorMessage\n } catch {\n // Use default error message\n }\n throw PaymentsError.fromBackend(errorMessage, {\n message: errorMessage,\n code: `HTTP ${response.status}`,\n })\n }\n return await response.json()\n } catch (error) {\n if (error instanceof PaymentsError) {\n throw error\n }\n throw PaymentsError.fromBackend('Network error during permission settlement', {\n message: error instanceof Error ? error.message : String(error),\n code: 'network_error',\n })\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"facilitator-api.js","sourceRoot":"","sources":["../../src/x402/facilitator-api.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The FacilitatorAPI class provides methods to verify and settle AI agent permissions using X402 access tokens.\n * This allows AI agents to act as facilitators, verifying and settling credits on behalf of subscribers.\n *\n * @example\n * ```typescript\n * import { Payments, X402PaymentRequired } from '@nevermined-io/payments'\n *\n * // Initialize the Payments instance\n * const payments = Payments.getInstance({\n * nvmApiKey: 'your-nvm-api-key',\n * environment: 'sandbox'\n * })\n *\n * // The server's 402 PaymentRequired response\n * const paymentRequired: X402PaymentRequired = buildPaymentRequired('123456789', {\n * endpoint: '/api/v1/agents/task',\n * agentId: '987654321',\n * httpVerb: 'POST'\n * })\n *\n * // Get X402 access token from subscriber (x402 v2: payment-signature header)\n * const x402Token = req.headers['payment-signature'] as string\n *\n * // Verify if subscriber has sufficient permissions/credits\n * const verification = await payments.facilitator.verifyPermissions({\n * paymentRequired,\n * x402AccessToken: x402Token,\n * maxAmount: 2n\n * })\n *\n * if (verification.isValid) {\n * // Settle (burn) the credits\n * const settlement = await payments.facilitator.settlePermissions({\n * paymentRequired,\n * x402AccessToken: x402Token,\n * maxAmount: 2n\n * })\n * console.log(`Credits redeemed: ${settlement.creditsRedeemed}`)\n * }\n * ```\n */\n\nimport { BasePaymentsAPI } from '../api/base-payments.js'\nimport { API_URL_SETTLE_PERMISSIONS, API_URL_VERIFY_PERMISSIONS } from '../api/nvm-api.js'\nimport { PaymentsError } from '../common/payments.error.js'\nimport { PaymentOptions, StartAgentRequest } from '../common/types.js'\nimport type { VisaPaymentRequired } from './visa-facilitator-api.js'\n\n/**\n * x402 Resource information\n */\nexport interface X402Resource {\n /** The protected resource URL */\n url: string\n /** Human-readable description */\n description?: string\n /** Expected response MIME type (e.g., \"application/json\") */\n mimeType?: string\n}\n\n/**\n * x402 Scheme extra fields for nvm:erc4337\n */\nexport interface X402SchemeExtra {\n /** Scheme version (e.g., \"1\") */\n version?: string\n /** Agent identifier */\n agentId?: string\n /** HTTP method for the endpoint */\n httpVerb?: string\n}\n\n/**\n * x402 Scheme definition (nvm:erc4337)\n */\nexport interface X402Scheme {\n /** Payment scheme identifier (e.g., \"nvm:erc4337\") */\n scheme: string\n /** Blockchain network in CAIP-2 format (e.g., \"eip155:84532\") */\n network: string\n /** 256-bit plan identifier */\n planId: string\n /** Scheme-specific extra fields */\n extra?: X402SchemeExtra\n}\n\n/**\n * x402 PaymentRequired response (402 response from server)\n */\nexport interface X402PaymentRequired {\n /** x402 protocol version (always 2) */\n x402Version: number\n /** Human-readable error message */\n error?: string\n /** Protected resource information */\n resource: X402Resource\n /** Array of accepted payment schemes */\n accepts: X402Scheme[]\n /** Extensions object (empty object for nvm:erc4337) */\n extensions: Record<string, unknown>\n}\n\n/**\n * x402 PaymentAccepted response (accepted payment scheme)\n */\nexport interface X402PaymentAccepted {\n /** The x402 version */\n x402Version: number\n /** The accepted payment scheme (nvm:erc4337) */\n accepted: X402Scheme\n /** The payload of the payment accepted */\n payload: {\n signature: string\n authorization: {\n from: string\n sessionKeysProvider: string\n sessionKeys: string[]\n }\n }\n extensions: Record<string, unknown>\n}\n\n/**\n * Parameters for verifying permissions\n */\nexport interface VerifyPermissionsParams {\n /** The server's 402 PaymentRequired response (NVM or Visa flavored) */\n paymentRequired: X402PaymentRequired | VisaPaymentRequired\n /** The X402 access token (base64-encoded) */\n x402AccessToken: string\n /** Maximum credits to verify (optional) */\n maxAmount?: bigint\n}\n\n/**\n * x402 Verify Response - per x402 facilitator spec\n * @see https://github.com/coinbase/x402/blob/main/specs/x402-specification-v2.md\n */\nexport interface VerifyPermissionsResult {\n /** Whether the payment authorization is valid */\n isValid: boolean\n /** Reason for invalidity (only present if isValid is false) */\n invalidReason?: string\n /** Address of the payer's wallet */\n payer?: string\n /** Agent request ID for observability tracking (Nevermined extension) */\n agentRequestId?: string\n /** URL pattern that matched the endpoint (Nevermined extension) */\n urlMatching?: string\n /** Agent request context for observability (Nevermined extension) */\n agentRequest?: StartAgentRequest\n}\n\n/**\n * Parameters for settling permissions\n */\nexport interface SettlePermissionsParams {\n /** The server's 402 PaymentRequired response (NVM or Visa flavored) */\n paymentRequired: X402PaymentRequired | VisaPaymentRequired\n /** The X402 access token (base64-encoded) */\n x402AccessToken: string\n /** Number of credits to burn (optional) */\n maxAmount?: bigint\n /** Agent request ID for observability tracking. Returned by verifyPermissions. */\n agentRequestId?: string\n /** Whether this is a batch request (multiple LLM calls under one agentRequestId) */\n batch?: boolean\n /** Margin percentage (0-10) for credit calculation. Mutually exclusive with maxAmount when agentRequestId provided. */\n marginPercent?: number\n}\n\n/**\n * x402 Settle Response - per x402 facilitator spec\n * @see https://github.com/coinbase/x402/blob/main/specs/x402-specification-v2.md\n */\nexport interface SettlePermissionsResult {\n /** Whether settlement was successful */\n success: boolean\n /** Reason for settlement failure (only present if success is false) */\n errorReason?: string\n /** Address of the payer's wallet */\n payer?: string\n /** Blockchain transaction hash (empty string if settlement failed) */\n transaction: string\n /** Blockchain network identifier in CAIP-2 format */\n network: string\n /** Number of credits redeemed (Nevermined extension) */\n creditsRedeemed?: string\n /** Subscriber's remaining balance (Nevermined extension) */\n remainingBalance?: string\n /** Transaction hash of the order operation if auto top-up occurred (Nevermined extension) */\n orderTx?: string\n}\n\n/**\n * Build an X402PaymentRequired object for verify/settle operations.\n *\n * This helper simplifies the creation of payment requirement objects\n * that are needed for the facilitator API.\n *\n * @param planId - The Nevermined plan identifier (required)\n * @param options - Optional configuration with endpoint, agentId, httpVerb, network, description\n * @returns X402PaymentRequired object ready to use with verifyPermissions/settlePermissions\n *\n * @example\n * ```typescript\n * import { buildPaymentRequired } from '@nevermined-io/payments'\n *\n * const paymentRequired = buildPaymentRequired('123456789', {\n * endpoint: '/api/v1/agents/task',\n * agentId: '987654321',\n * httpVerb: 'POST'\n * })\n *\n * const result = await payments.facilitator.verifyPermissions({\n * paymentRequired,\n * x402AccessToken: token,\n * maxAmount: 2n\n * })\n * ```\n */\nexport function buildPaymentRequired(\n planId: string,\n options?: {\n endpoint?: string\n agentId?: string\n httpVerb?: string\n network?: string\n description?: string\n },\n): X402PaymentRequired {\n const { endpoint, agentId, httpVerb, network = 'eip155:84532', description } = options || {}\n\n // Build extra fields if any are provided\n const extra: X402SchemeExtra | undefined =\n agentId || httpVerb\n ? {\n ...(agentId && { agentId }),\n ...(httpVerb && { httpVerb }),\n }\n : undefined\n\n return {\n x402Version: 2,\n resource: {\n url: endpoint || '',\n ...(description && { description }),\n },\n accepts: [\n {\n scheme: 'nvm:erc4337',\n network,\n planId,\n ...(extra && { extra }),\n },\n ],\n extensions: {},\n }\n}\n\n/**\n * The FacilitatorAPI class provides methods to verify and settle AI agent permissions.\n * It enables AI agents to act as facilitators, managing credit verification and settlement\n * for subscribers using X402 access tokens.\n */\nexport class FacilitatorAPI extends BasePaymentsAPI {\n /**\n * Get a singleton instance of the FacilitatorAPI class.\n *\n * @param options - The options to initialize the payments class\n * @returns The instance of the FacilitatorAPI class\n */\n static getInstance(options: PaymentOptions): FacilitatorAPI {\n return new FacilitatorAPI(options)\n }\n\n /**\n * Verify if a subscriber has permission to use credits from a payment plan.\n * This method simulates the credit usage without actually burning credits,\n * checking if the subscriber has sufficient balance and permissions.\n *\n * The planId and subscriberAddress are extracted from the x402AccessToken.\n *\n * @param params - Verification parameters (see {@link VerifyPermissionsParams}).\n * - paymentRequired: x402 PaymentRequired from 402 response (required, for validation)\n * - x402AccessToken: X402 access token (contains planId, subscriberAddress, agentId)\n * - maxAmount: maximum credits to verify (optional, bigint)\n * @returns A promise that resolves to a verification result with 'isValid' boolean\n *\n * @throws PaymentsError if verification fails\n */\n async verifyPermissions(params: VerifyPermissionsParams): Promise<VerifyPermissionsResult> {\n const { paymentRequired, x402AccessToken, maxAmount } = params\n\n const url = new URL(API_URL_VERIFY_PERMISSIONS, this.environment.backend)\n\n const body: Record<string, unknown> = {\n paymentRequired,\n x402AccessToken,\n }\n\n if (maxAmount !== undefined) {\n body.maxAmount = maxAmount.toString()\n }\n\n const options = this.getPublicHTTPOptions('POST', body)\n\n try {\n const response = await fetch(url, options)\n if (!response.ok) {\n let errorMessage = 'Permission verification failed'\n try {\n const errorData = await response.json()\n errorMessage = errorData.message || errorMessage\n } catch {\n // Use default error message\n }\n throw PaymentsError.fromBackend(errorMessage, {\n message: errorMessage,\n code: `HTTP ${response.status}`,\n })\n }\n return await response.json()\n } catch (error) {\n if (error instanceof PaymentsError) {\n throw error\n }\n throw PaymentsError.fromBackend('Network error during permission verification', {\n message: error instanceof Error ? error.message : String(error),\n code: 'network_error',\n })\n }\n }\n\n /**\n * Settle (burn) credits from a subscriber's payment plan.\n * This method executes the actual credit consumption, burning the specified\n * number of credits from the subscriber's balance. If the subscriber doesn't\n * have enough credits, it will attempt to order more before settling.\n *\n * The planId and subscriberAddress are extracted from the x402AccessToken.\n *\n * @param params - Settlement parameters (see {@link SettlePermissionsParams}).\n * - paymentRequired: x402 PaymentRequired from 402 response (required, for validation)\n * - x402AccessToken: X402 access token (contains planId, subscriberAddress, agentId)\n * - maxAmount: number of credits to burn (optional, bigint)\n * - agentRequestId: Agent request ID for observability tracking (optional)\n * - batch: Whether this is a batch request (optional)\n * - marginPercent: Margin percentage for credit calculation (optional)\n * @returns A promise that resolves to a settlement result with transaction details\n *\n * @throws PaymentsError if settlement fails\n */\n async settlePermissions(params: SettlePermissionsParams): Promise<SettlePermissionsResult> {\n const { paymentRequired, x402AccessToken, maxAmount, agentRequestId, batch, marginPercent } =\n params\n\n const url = new URL(API_URL_SETTLE_PERMISSIONS, this.environment.backend)\n\n const body: Record<string, unknown> = {\n paymentRequired,\n x402AccessToken,\n }\n\n if (maxAmount !== undefined) {\n body.maxAmount = maxAmount.toString()\n }\n if (agentRequestId !== undefined) {\n body.agentRequestId = agentRequestId\n }\n if (batch !== undefined) {\n body.batch = batch\n }\n if (marginPercent !== undefined) {\n body.marginPercent = marginPercent\n }\n\n const options = this.getPublicHTTPOptions('POST', body)\n\n try {\n const response = await fetch(url, options)\n if (!response.ok) {\n let errorMessage = 'Permission settlement failed'\n try {\n const errorData = await response.json()\n errorMessage = errorData.message || errorMessage\n } catch {\n // Use default error message\n }\n throw PaymentsError.fromBackend(errorMessage, {\n message: errorMessage,\n code: `HTTP ${response.status}`,\n })\n }\n return await response.json()\n } catch (error) {\n if (error instanceof PaymentsError) {\n throw error\n }\n throw PaymentsError.fromBackend('Network error during permission settlement', {\n message: error instanceof Error ? error.message : String(error),\n code: 'network_error',\n })\n }\n }\n}\n"]}
|
package/dist/x402/index.d.ts
CHANGED
|
@@ -4,4 +4,8 @@
|
|
|
4
4
|
export { X402TokenAPI } from './token.js';
|
|
5
5
|
export { FacilitatorAPI, buildPaymentRequired } from './facilitator-api.js';
|
|
6
6
|
export type { X402Resource, X402SchemeExtra, X402Scheme, X402PaymentRequired, X402PaymentAccepted, VerifyPermissionsParams, VerifyPermissionsResult, SettlePermissionsParams, SettlePermissionsResult, } from './facilitator-api.js';
|
|
7
|
+
export { VisaFacilitatorAPI, buildVisaPaymentRequired, VISA_X402_HEADERS } from './visa-facilitator-api.js';
|
|
8
|
+
export type { VisaPaymentExtra, VisaPaymentRequirements, VisaPaymentRequired, VisaVerifyResponse, VisaSettlementResponse, } from './visa-facilitator-api.js';
|
|
9
|
+
export { VisaTokenAPI } from './visa-token-api.js';
|
|
10
|
+
export type { VisaPaymentPayloadResponse } from './visa-token-api.js';
|
|
7
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/x402/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/x402/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3E,YAAY,EAEV,YAAY,EACZ,eAAe,EACf,UAAU,EACV,mBAAmB,EACnB,mBAAmB,EAEnB,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,sBAAsB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/x402/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3E,YAAY,EAEV,YAAY,EACZ,eAAe,EACf,UAAU,EACV,mBAAmB,EACnB,mBAAmB,EAEnB,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,sBAAsB,CAAA;AAG7B,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAC3G,YAAY,EACV,gBAAgB,EAChB,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,YAAY,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAA"}
|
package/dist/x402/index.js
CHANGED
|
@@ -3,4 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { X402TokenAPI } from './token.js';
|
|
5
5
|
export { FacilitatorAPI, buildPaymentRequired } from './facilitator-api.js';
|
|
6
|
+
// Visa x402 exports
|
|
7
|
+
export { VisaFacilitatorAPI, buildVisaPaymentRequired, VISA_X402_HEADERS } from './visa-facilitator-api.js';
|
|
8
|
+
export { VisaTokenAPI } from './visa-token-api.js';
|
|
6
9
|
//# sourceMappingURL=index.js.map
|
package/dist/x402/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/x402/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA","sourcesContent":["/**\n * X402 API module for token generation and facilitator operations.\n */\n\nexport { X402TokenAPI } from './token.js'\nexport { FacilitatorAPI, buildPaymentRequired } from './facilitator-api.js'\nexport type {\n // x402 types\n X402Resource,\n X402SchemeExtra,\n X402Scheme,\n X402PaymentRequired,\n X402PaymentAccepted,\n // Facilitator params and results\n VerifyPermissionsParams,\n VerifyPermissionsResult,\n SettlePermissionsParams,\n SettlePermissionsResult,\n} from './facilitator-api.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/x402/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAe3E,oBAAoB;AACpB,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAQ3G,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA","sourcesContent":["/**\n * X402 API module for token generation and facilitator operations.\n */\n\nexport { X402TokenAPI } from './token.js'\nexport { FacilitatorAPI, buildPaymentRequired } from './facilitator-api.js'\nexport type {\n // x402 types\n X402Resource,\n X402SchemeExtra,\n X402Scheme,\n X402PaymentRequired,\n X402PaymentAccepted,\n // Facilitator params and results\n VerifyPermissionsParams,\n VerifyPermissionsResult,\n SettlePermissionsParams,\n SettlePermissionsResult,\n} from './facilitator-api.js'\n\n// Visa x402 exports\nexport { VisaFacilitatorAPI, buildVisaPaymentRequired, VISA_X402_HEADERS } from './visa-facilitator-api.js'\nexport type {\n VisaPaymentExtra,\n VisaPaymentRequirements,\n VisaPaymentRequired,\n VisaVerifyResponse,\n VisaSettlementResponse,\n} from './visa-facilitator-api.js'\nexport { VisaTokenAPI } from './visa-token-api.js'\nexport type { VisaPaymentPayloadResponse } from './visa-token-api.js'\n"]}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visa Facilitator API — verifies and settles payments via the Visa x402 backend.
|
|
3
|
+
*
|
|
4
|
+
* Unlike the base FacilitatorAPI (which sends JSON bodies to the NVM backend),
|
|
5
|
+
* the Visa flow uses the PAYMENT-SIGNATURE HTTP header to transport a base64-encoded
|
|
6
|
+
* PaymentPayload to the Visa backend's /verify and /settle endpoints.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { Payments } from '@nevermined-io/payments'
|
|
11
|
+
*
|
|
12
|
+
* const payments = Payments.getInstance({
|
|
13
|
+
* nvmApiKey: 'your-nvm-api-key',
|
|
14
|
+
* environment: 'sandbox',
|
|
15
|
+
* scheme: 'visa',
|
|
16
|
+
* })
|
|
17
|
+
*
|
|
18
|
+
* const paymentRequired = buildVisaPaymentRequired({
|
|
19
|
+
* amount: '2.00',
|
|
20
|
+
* asset: 'USD',
|
|
21
|
+
* payTo: 'merchant-id',
|
|
22
|
+
* endpoint: '/tools/random-article',
|
|
23
|
+
* })
|
|
24
|
+
*
|
|
25
|
+
* // The PAYMENT-SIGNATURE header from the client request
|
|
26
|
+
* const paymentSignature = req.headers['payment-signature'] as string
|
|
27
|
+
*
|
|
28
|
+
* const verification = await payments.facilitator.verifyPermissions({
|
|
29
|
+
* paymentRequired,
|
|
30
|
+
* x402AccessToken: paymentSignature,
|
|
31
|
+
* })
|
|
32
|
+
*
|
|
33
|
+
* if (verification.isValid) {
|
|
34
|
+
* const settlement = await payments.facilitator.settlePermissions({
|
|
35
|
+
* paymentRequired,
|
|
36
|
+
* x402AccessToken: paymentSignature,
|
|
37
|
+
* })
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
import { FacilitatorAPI } from './facilitator-api.js';
|
|
42
|
+
import type { VerifyPermissionsParams, VerifyPermissionsResult, SettlePermissionsParams, SettlePermissionsResult, X402Resource } from './facilitator-api.js';
|
|
43
|
+
import { PaymentOptions } from '../common/types.js';
|
|
44
|
+
/**
|
|
45
|
+
* Visa-specific extra fields in PaymentRequirements
|
|
46
|
+
*/
|
|
47
|
+
export interface VisaPaymentExtra {
|
|
48
|
+
/** Visa Token Service provisioned token ID */
|
|
49
|
+
vProvisionedTokenID: string;
|
|
50
|
+
/** VIC instruction ID from mandate creation */
|
|
51
|
+
instructionId: string;
|
|
52
|
+
/** Maximum number of times this payment can be used */
|
|
53
|
+
maxUsage: number;
|
|
54
|
+
/** Merchant name */
|
|
55
|
+
merchantName?: string;
|
|
56
|
+
/** Merchant URL */
|
|
57
|
+
merchantUrl?: string;
|
|
58
|
+
/** Merchant country code */
|
|
59
|
+
merchantCountryCode?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Visa payment requirements (x402 v2 with scheme: "visa")
|
|
63
|
+
*/
|
|
64
|
+
export interface VisaPaymentRequirements {
|
|
65
|
+
scheme: 'visa';
|
|
66
|
+
network: 'visa:vts';
|
|
67
|
+
amount: string;
|
|
68
|
+
asset: string;
|
|
69
|
+
payTo: string;
|
|
70
|
+
maxTimeoutSeconds: number;
|
|
71
|
+
extra: VisaPaymentExtra;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Visa-specific PaymentRequired response
|
|
75
|
+
*/
|
|
76
|
+
export interface VisaPaymentRequired {
|
|
77
|
+
x402Version: 2;
|
|
78
|
+
error?: string;
|
|
79
|
+
resource: X402Resource;
|
|
80
|
+
accepts: VisaPaymentRequirements[];
|
|
81
|
+
extensions?: Record<string, unknown>;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Visa verify response
|
|
85
|
+
*/
|
|
86
|
+
export interface VisaVerifyResponse {
|
|
87
|
+
isValid: boolean;
|
|
88
|
+
payer?: string;
|
|
89
|
+
invalidReason?: string;
|
|
90
|
+
remainingUsage?: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Visa settlement response
|
|
94
|
+
*/
|
|
95
|
+
export interface VisaSettlementResponse {
|
|
96
|
+
success: boolean;
|
|
97
|
+
transaction: string;
|
|
98
|
+
network: 'visa:vts';
|
|
99
|
+
payer?: string;
|
|
100
|
+
errorReason?: string;
|
|
101
|
+
}
|
|
102
|
+
export declare const VISA_X402_HEADERS: {
|
|
103
|
+
readonly PAYMENT_REQUIRED: "PAYMENT-REQUIRED";
|
|
104
|
+
readonly PAYMENT_SIGNATURE: "PAYMENT-SIGNATURE";
|
|
105
|
+
readonly PAYMENT_RESPONSE: "PAYMENT-RESPONSE";
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Build a Visa-flavored X402PaymentRequired object.
|
|
109
|
+
*
|
|
110
|
+
* This is the Visa counterpart of `buildPaymentRequired` (which builds NVM-flavored ones).
|
|
111
|
+
*/
|
|
112
|
+
export declare function buildVisaPaymentRequired(options: {
|
|
113
|
+
amount: string;
|
|
114
|
+
asset?: string;
|
|
115
|
+
payTo: string;
|
|
116
|
+
endpoint?: string;
|
|
117
|
+
description?: string;
|
|
118
|
+
maxTimeoutSeconds?: number;
|
|
119
|
+
merchantName?: string;
|
|
120
|
+
merchantUrl?: string;
|
|
121
|
+
merchantCountryCode?: string;
|
|
122
|
+
}): VisaPaymentRequired;
|
|
123
|
+
/**
|
|
124
|
+
* Visa Facilitator API — sends PAYMENT-SIGNATURE header to the Visa backend
|
|
125
|
+
* for verify and settle operations.
|
|
126
|
+
*/
|
|
127
|
+
export declare class VisaFacilitatorAPI extends FacilitatorAPI {
|
|
128
|
+
protected visaBackendUrl: string;
|
|
129
|
+
constructor(options: PaymentOptions);
|
|
130
|
+
static getInstance(options: PaymentOptions): VisaFacilitatorAPI;
|
|
131
|
+
/**
|
|
132
|
+
* Verify a Visa payment authorization.
|
|
133
|
+
*
|
|
134
|
+
* Sends the base64-encoded PaymentPayload as a PAYMENT-SIGNATURE header
|
|
135
|
+
* to the Visa backend's POST /verify endpoint.
|
|
136
|
+
*
|
|
137
|
+
* @param params - contains x402AccessToken, the base64-encoded PaymentPayload from the client
|
|
138
|
+
*/
|
|
139
|
+
verifyPermissions(params: VerifyPermissionsParams): Promise<VerifyPermissionsResult>;
|
|
140
|
+
/**
|
|
141
|
+
* Settle a Visa payment transaction.
|
|
142
|
+
*
|
|
143
|
+
* Sends the base64-encoded PaymentPayload as a PAYMENT-SIGNATURE header
|
|
144
|
+
* to the Visa backend's POST /settle endpoint.
|
|
145
|
+
*
|
|
146
|
+
* @param params - contains x402AccessToken, the base64-encoded PaymentPayload from the client
|
|
147
|
+
*/
|
|
148
|
+
settlePermissions(params: SettlePermissionsParams): Promise<SettlePermissionsResult>;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=visa-facilitator-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visa-facilitator-api.d.ts","sourceRoot":"","sources":["../../src/x402/visa-facilitator-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,KAAK,EACV,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,YAAY,EACb,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAOnD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,8CAA8C;IAC9C,mBAAmB,EAAE,MAAM,CAAA;IAC3B,+CAA+C;IAC/C,aAAa,EAAE,MAAM,CAAA;IACrB,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAA;IAChB,oBAAoB;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mBAAmB;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4BAA4B;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,UAAU,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,iBAAiB,EAAE,MAAM,CAAA;IACzB,KAAK,EAAE,gBAAgB,CAAA;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,CAAC,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,YAAY,CAAA;IACtB,OAAO,EAAE,uBAAuB,EAAE,CAAA;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,UAAU,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAMD,eAAO,MAAM,iBAAiB;;;;CAIpB,CAAA;AAMV;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B,GAAG,mBAAmB,CAsCtB;AAMD;;;GAGG;AACH,qBAAa,kBAAmB,SAAQ,cAAc;IACpD,SAAS,CAAC,cAAc,EAAE,MAAM,CAAA;gBAEpB,OAAO,EAAE,cAAc;WAKnB,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,kBAAkB;IAIxE;;;;;;;OAOG;IACY,iBAAiB,CAC9B,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,uBAAuB,CAAC;IA6CnC;;;;;;;OAOG;IACY,iBAAiB,CAC9B,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,uBAAuB,CAAC;CA8CpC"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visa Facilitator API — verifies and settles payments via the Visa x402 backend.
|
|
3
|
+
*
|
|
4
|
+
* Unlike the base FacilitatorAPI (which sends JSON bodies to the NVM backend),
|
|
5
|
+
* the Visa flow uses the PAYMENT-SIGNATURE HTTP header to transport a base64-encoded
|
|
6
|
+
* PaymentPayload to the Visa backend's /verify and /settle endpoints.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { Payments } from '@nevermined-io/payments'
|
|
11
|
+
*
|
|
12
|
+
* const payments = Payments.getInstance({
|
|
13
|
+
* nvmApiKey: 'your-nvm-api-key',
|
|
14
|
+
* environment: 'sandbox',
|
|
15
|
+
* scheme: 'visa',
|
|
16
|
+
* })
|
|
17
|
+
*
|
|
18
|
+
* const paymentRequired = buildVisaPaymentRequired({
|
|
19
|
+
* amount: '2.00',
|
|
20
|
+
* asset: 'USD',
|
|
21
|
+
* payTo: 'merchant-id',
|
|
22
|
+
* endpoint: '/tools/random-article',
|
|
23
|
+
* })
|
|
24
|
+
*
|
|
25
|
+
* // The PAYMENT-SIGNATURE header from the client request
|
|
26
|
+
* const paymentSignature = req.headers['payment-signature'] as string
|
|
27
|
+
*
|
|
28
|
+
* const verification = await payments.facilitator.verifyPermissions({
|
|
29
|
+
* paymentRequired,
|
|
30
|
+
* x402AccessToken: paymentSignature,
|
|
31
|
+
* })
|
|
32
|
+
*
|
|
33
|
+
* if (verification.isValid) {
|
|
34
|
+
* const settlement = await payments.facilitator.settlePermissions({
|
|
35
|
+
* paymentRequired,
|
|
36
|
+
* x402AccessToken: paymentSignature,
|
|
37
|
+
* })
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
import { FacilitatorAPI } from './facilitator-api.js';
|
|
42
|
+
import { PaymentsError } from '../common/payments.error.js';
|
|
43
|
+
import { VisaBackendUrls } from '../environments.js';
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Visa x402 header names
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
export const VISA_X402_HEADERS = {
|
|
48
|
+
PAYMENT_REQUIRED: 'PAYMENT-REQUIRED',
|
|
49
|
+
PAYMENT_SIGNATURE: 'PAYMENT-SIGNATURE',
|
|
50
|
+
PAYMENT_RESPONSE: 'PAYMENT-RESPONSE',
|
|
51
|
+
};
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Helper: build a Visa PaymentRequired
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
/**
|
|
56
|
+
* Build a Visa-flavored X402PaymentRequired object.
|
|
57
|
+
*
|
|
58
|
+
* This is the Visa counterpart of `buildPaymentRequired` (which builds NVM-flavored ones).
|
|
59
|
+
*/
|
|
60
|
+
export function buildVisaPaymentRequired(options) {
|
|
61
|
+
const { amount, asset = 'USD', payTo, endpoint = '', description, maxTimeoutSeconds = 3600, merchantName, merchantUrl, merchantCountryCode, } = options;
|
|
62
|
+
return {
|
|
63
|
+
x402Version: 2,
|
|
64
|
+
resource: {
|
|
65
|
+
url: endpoint,
|
|
66
|
+
...(description && { description }),
|
|
67
|
+
},
|
|
68
|
+
accepts: [
|
|
69
|
+
{
|
|
70
|
+
scheme: 'visa',
|
|
71
|
+
network: 'visa:vts',
|
|
72
|
+
amount,
|
|
73
|
+
asset,
|
|
74
|
+
payTo,
|
|
75
|
+
maxTimeoutSeconds,
|
|
76
|
+
extra: {
|
|
77
|
+
vProvisionedTokenID: '',
|
|
78
|
+
instructionId: '',
|
|
79
|
+
maxUsage: 1,
|
|
80
|
+
...(merchantName && { merchantName }),
|
|
81
|
+
...(merchantUrl && { merchantUrl }),
|
|
82
|
+
...(merchantCountryCode && { merchantCountryCode }),
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// VisaFacilitatorAPI
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
/**
|
|
92
|
+
* Visa Facilitator API — sends PAYMENT-SIGNATURE header to the Visa backend
|
|
93
|
+
* for verify and settle operations.
|
|
94
|
+
*/
|
|
95
|
+
export class VisaFacilitatorAPI extends FacilitatorAPI {
|
|
96
|
+
constructor(options) {
|
|
97
|
+
super(options);
|
|
98
|
+
this.visaBackendUrl = VisaBackendUrls[this.environmentName];
|
|
99
|
+
}
|
|
100
|
+
static getInstance(options) {
|
|
101
|
+
return new VisaFacilitatorAPI(options);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Verify a Visa payment authorization.
|
|
105
|
+
*
|
|
106
|
+
* Sends the base64-encoded PaymentPayload as a PAYMENT-SIGNATURE header
|
|
107
|
+
* to the Visa backend's POST /verify endpoint.
|
|
108
|
+
*
|
|
109
|
+
* @param params - contains x402AccessToken, the base64-encoded PaymentPayload from the client
|
|
110
|
+
*/
|
|
111
|
+
async verifyPermissions(params) {
|
|
112
|
+
const { x402AccessToken } = params;
|
|
113
|
+
const url = new URL('verify', this.visaBackendUrl);
|
|
114
|
+
try {
|
|
115
|
+
const response = await fetch(url, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers: {
|
|
118
|
+
[VISA_X402_HEADERS.PAYMENT_SIGNATURE]: x402AccessToken,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
let errorMessage = 'Visa payment verification failed';
|
|
123
|
+
try {
|
|
124
|
+
const errorData = await response.json();
|
|
125
|
+
errorMessage = errorData.invalidReason || errorData.message || errorMessage;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Use default error message
|
|
129
|
+
}
|
|
130
|
+
throw PaymentsError.fromBackend(errorMessage, {
|
|
131
|
+
message: errorMessage,
|
|
132
|
+
code: `HTTP ${response.status}`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const visaResult = await response.json();
|
|
136
|
+
// Map Visa response to the standard VerifyPermissionsResult shape
|
|
137
|
+
return {
|
|
138
|
+
isValid: visaResult.isValid,
|
|
139
|
+
payer: visaResult.payer,
|
|
140
|
+
invalidReason: visaResult.invalidReason,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (error instanceof PaymentsError) {
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
throw PaymentsError.fromBackend('Network error during Visa payment verification', {
|
|
148
|
+
message: error instanceof Error ? error.message : String(error),
|
|
149
|
+
code: 'network_error',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Settle a Visa payment transaction.
|
|
155
|
+
*
|
|
156
|
+
* Sends the base64-encoded PaymentPayload as a PAYMENT-SIGNATURE header
|
|
157
|
+
* to the Visa backend's POST /settle endpoint.
|
|
158
|
+
*
|
|
159
|
+
* @param params - contains x402AccessToken, the base64-encoded PaymentPayload from the client
|
|
160
|
+
*/
|
|
161
|
+
async settlePermissions(params) {
|
|
162
|
+
const { x402AccessToken } = params;
|
|
163
|
+
const url = new URL('settle', this.visaBackendUrl);
|
|
164
|
+
try {
|
|
165
|
+
const response = await fetch(url, {
|
|
166
|
+
method: 'POST',
|
|
167
|
+
headers: {
|
|
168
|
+
[VISA_X402_HEADERS.PAYMENT_SIGNATURE]: x402AccessToken,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
let errorMessage = 'Visa payment settlement failed';
|
|
173
|
+
try {
|
|
174
|
+
const errorData = await response.json();
|
|
175
|
+
errorMessage = errorData.errorReason || errorData.message || errorMessage;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// Use default error message
|
|
179
|
+
}
|
|
180
|
+
throw PaymentsError.fromBackend(errorMessage, {
|
|
181
|
+
message: errorMessage,
|
|
182
|
+
code: `HTTP ${response.status}`,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
const visaResult = await response.json();
|
|
186
|
+
// Map Visa response to the standard SettlePermissionsResult shape
|
|
187
|
+
return {
|
|
188
|
+
success: visaResult.success,
|
|
189
|
+
transaction: visaResult.transaction,
|
|
190
|
+
network: visaResult.network,
|
|
191
|
+
payer: visaResult.payer,
|
|
192
|
+
errorReason: visaResult.errorReason,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
if (error instanceof PaymentsError) {
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
throw PaymentsError.fromBackend('Network error during Visa payment settlement', {
|
|
200
|
+
message: error instanceof Error ? error.message : String(error),
|
|
201
|
+
code: 'network_error',
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=visa-facilitator-api.js.map
|