@nevermined-io/payments 1.4.1 → 1.5.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.
@@ -1,4 +1,36 @@
1
1
  export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
2
+ /**
3
+ * Safe JSON parse for fetch responses. Reads the body once and tolerates
4
+ * non-JSON payloads (e.g. NGINX HTML 5xx gateway pages) by returning a
5
+ * `{ message }` shell that downstream error code paths can still consume.
6
+ *
7
+ * Prevents the failure mode tracked in #1727: callers doing
8
+ * `throw PaymentsError.fromBackend('...', await response.json())` would
9
+ * otherwise let a SyntaxError from `.json()` escape and surface as an
10
+ * `unhandledRejection` that can take the host Node process down.
11
+ */
12
+ export async function safeParseJson(response) {
13
+ const contentType = response.headers.get('content-type') ?? '';
14
+ // Always consume the body so the underlying socket can be released, even
15
+ // when we decide we can't parse it as JSON.
16
+ const text = await response.text().catch(() => '');
17
+ if (!text)
18
+ return {};
19
+ if (!contentType.toLowerCase().includes('json')) {
20
+ // Truncate to keep the payload bounded — a full HTML gateway page is
21
+ // noise in an error log.
22
+ const snippet = text.length > 200 ? `${text.slice(0, 200)}…` : text;
23
+ return { message: `Non-JSON response (${contentType || 'no content-type'}): ${snippet}` };
24
+ }
25
+ try {
26
+ return JSON.parse(text);
27
+ }
28
+ catch (cause) {
29
+ return {
30
+ message: `Malformed JSON response: ${cause instanceof Error ? cause.message : String(cause)}`,
31
+ };
32
+ }
33
+ }
2
34
  export const jsonReplacer = (_key, value) => {
3
35
  return typeof value === 'bigint' ? value.toString() : value;
4
36
  };
@@ -1 +1 @@
1
- {"version":3,"file":"helper.js","sourceRoot":"","sources":["../../src/common/helper.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AAEtF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAS,EAAE,KAA8B,EAAE,EAAE;IACxE,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;AAC7D,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,SAAqB,EAAU,EAAE;IAC3E,IAAI,WAAW,GAAG,EAAE,CAAA;IACpB,SAAS,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;QAC1B,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;QAC5C,WAAW,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,MAAM,CAAA;IACzC,CAAC,CAAC,CAAA;IACF,OAAO,WAAW,CAAA;AACpB,CAAC,CAAA","sourcesContent":["import { Endpoint } from './types.js'\n\nexport const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))\n\nexport const jsonReplacer = (_key: any, value: { toString: () => any }) => {\n return typeof value === 'bigint' ? value.toString() : value\n}\n\nexport const getServiceHostFromEndpoints = (endpoints: Endpoint[]): string => {\n let serviceHost = ''\n endpoints.some((endpoint) => {\n const _endpoint = Object.values(endpoint)[0]\n serviceHost = new URL(_endpoint).origin\n })\n return serviceHost\n}\n"]}
1
+ {"version":3,"file":"helper.js","sourceRoot":"","sources":["../../src/common/helper.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AAEtF;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAkB;IACpD,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAA;IAC9D,yEAAyE;IACzE,4CAA4C;IAC5C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;IAClD,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IACpB,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAChD,qEAAqE;QACrE,yBAAyB;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;QACnE,OAAO,EAAE,OAAO,EAAE,sBAAsB,WAAW,IAAI,iBAAiB,MAAM,OAAO,EAAE,EAAE,CAAA;IAC3F,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,4BAA4B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;SAC9F,CAAA;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAS,EAAE,KAA8B,EAAE,EAAE;IACxE,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;AAC7D,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,SAAqB,EAAU,EAAE;IAC3E,IAAI,WAAW,GAAG,EAAE,CAAA;IACpB,SAAS,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;QAC1B,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;QAC5C,WAAW,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,MAAM,CAAA;IACzC,CAAC,CAAC,CAAA;IACF,OAAO,WAAW,CAAA;AACpB,CAAC,CAAA","sourcesContent":["import { Endpoint } from './types.js'\n\nexport const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))\n\n/**\n * Safe JSON parse for fetch responses. Reads the body once and tolerates\n * non-JSON payloads (e.g. NGINX HTML 5xx gateway pages) by returning a\n * `{ message }` shell that downstream error code paths can still consume.\n *\n * Prevents the failure mode tracked in #1727: callers doing\n * `throw PaymentsError.fromBackend('...', await response.json())` would\n * otherwise let a SyntaxError from `.json()` escape and surface as an\n * `unhandledRejection` that can take the host Node process down.\n */\nexport async function safeParseJson(response: Response): Promise<unknown> {\n const contentType = response.headers.get('content-type') ?? ''\n // Always consume the body so the underlying socket can be released, even\n // when we decide we can't parse it as JSON.\n const text = await response.text().catch(() => '')\n if (!text) return {}\n if (!contentType.toLowerCase().includes('json')) {\n // Truncate to keep the payload bounded — a full HTML gateway page is\n // noise in an error log.\n const snippet = text.length > 200 ? `${text.slice(0, 200)}…` : text\n return { message: `Non-JSON response (${contentType || 'no content-type'}): ${snippet}` }\n }\n try {\n return JSON.parse(text)\n } catch (cause) {\n return {\n message: `Malformed JSON response: ${cause instanceof Error ? cause.message : String(cause)}`,\n }\n }\n}\n\nexport const jsonReplacer = (_key: any, value: { toString: () => any }) => {\n return typeof value === 'bigint' ? value.toString() : value\n}\n\nexport const getServiceHostFromEndpoints = (endpoints: Endpoint[]): string => {\n let serviceHost = ''\n endpoints.some((endpoint) => {\n const _endpoint = Object.values(endpoint)[0]\n serviceHost = new URL(_endpoint).origin\n })\n return serviceHost\n}\n"]}
@@ -1,5 +1,13 @@
1
1
  export interface EnvironmentInfo {
2
2
  frontend: string;
3
+ /**
4
+ * Base URL of the standalone, Privy-free embed app (the `embed.<tier>`
5
+ * origin that serves the chromeless `/cards/*` and `/checkout/*` pages).
6
+ * Formed by prepending `embed.` to the webapp host. The CLI redirect-mode
7
+ * card flows open `${embed}/cards/setup` here — the old webapp
8
+ * `/embed/cards/*` routes were removed in the #1787 cutover.
9
+ */
10
+ embed: string;
3
11
  backend: string;
4
12
  proxy: string;
5
13
  heliconeUrl: string;
@@ -1 +1 @@
1
- {"version":3,"file":"environments.d.ts","sourceRoot":"","sources":["../src/environments.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,eAAO,MAAM,WAAW,+CAA+C,CAAA;AAEvE,MAAM,MAAM,eAAe,GAAG,iBAAiB,GAAG,cAAc,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAA;AAEhG;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,eAAe,EAAE,eAAe,CA2CjE,CAAA"}
1
+ {"version":3,"file":"environments.d.ts","sourceRoot":"","sources":["../src/environments.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,eAAO,MAAM,WAAW,+CAA+C,CAAA;AAEvE,MAAM,MAAM,eAAe,GAAG,iBAAiB,GAAG,cAAc,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAA;AAEhG;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,eAAe,EAAE,eAAe,CAqDjE,CAAA"}
@@ -8,12 +8,14 @@ export const Environments = {
8
8
  */
9
9
  staging_sandbox: {
10
10
  frontend: 'https://nevermined.dev',
11
+ embed: 'https://embed.nevermined.dev',
11
12
  backend: 'https://api.sandbox.nevermined.dev/',
12
13
  proxy: 'https://proxy.sandbox.nevermined.dev',
13
14
  heliconeUrl: 'https://helicone.nevermined.dev',
14
15
  },
15
16
  staging_live: {
16
17
  frontend: 'https://nevermined.dev',
18
+ embed: 'https://embed.nevermined.dev',
17
19
  backend: 'https://api.live.nevermined.dev/',
18
20
  proxy: 'https://proxy.live.nevermined.dev',
19
21
  heliconeUrl: 'https://helicone.nevermined.dev',
@@ -23,6 +25,7 @@ export const Environments = {
23
25
  */
24
26
  sandbox: {
25
27
  frontend: 'https://nevermined.app',
28
+ embed: 'https://embed.nevermined.app',
26
29
  backend: 'https://api.sandbox.nevermined.app/',
27
30
  proxy: 'https://proxy.sandbox.nevermined.app',
28
31
  heliconeUrl: 'https://helicone.nevermined.dev',
@@ -32,6 +35,7 @@ export const Environments = {
32
35
  */
33
36
  live: {
34
37
  frontend: 'https://nevermined.app',
38
+ embed: 'https://embed.nevermined.app',
35
39
  backend: 'https://api.live.nevermined.app/',
36
40
  proxy: 'https://proxy.live.nevermined.app',
37
41
  heliconeUrl: 'https://helicone.nevermined.dev',
@@ -41,6 +45,12 @@ export const Environments = {
41
45
  */
42
46
  custom: {
43
47
  frontend: process.env.NVM_FRONTEND_URL || 'http://localhost:4200',
48
+ // No fallback to NVM_FRONTEND_URL: the webapp host no longer serves
49
+ // the card pages post-#1787 cutover, so silently reusing it would
50
+ // reintroduce a dead-route footgun. The embed app runs on its own
51
+ // port (4250, matching nvm-monorepo#1824); set NVM_EMBED_URL to
52
+ // override.
53
+ embed: process.env.NVM_EMBED_URL || 'http://localhost:4250',
44
54
  backend: process.env.NVM_BACKEND_URL || 'http://localhost:3001',
45
55
  proxy: process.env.NVM_PROXY_URL || 'https://localhost:443',
46
56
  heliconeUrl: process.env.HELICONE_URL || 'http://localhost:8585',
@@ -1 +1 @@
1
- {"version":3,"file":"environments.js","sourceRoot":"","sources":["../src/environments.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,WAAW,GAAG,4CAA4C,CAAA;AAIvE;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAA6C;IACpE;;OAEG;IACH,eAAe,EAAE;QACf,QAAQ,EAAE,wBAAwB;QAClC,OAAO,EAAE,qCAAqC;QAC9C,KAAK,EAAE,sCAAsC;QAC7C,WAAW,EAAE,iCAAiC;KAC/C;IACD,YAAY,EAAE;QACZ,QAAQ,EAAE,wBAAwB;QAClC,OAAO,EAAE,kCAAkC;QAC3C,KAAK,EAAE,mCAAmC;QAC1C,WAAW,EAAE,iCAAiC;KAC/C;IACD;;OAEG;IACH,OAAO,EAAE;QACP,QAAQ,EAAE,wBAAwB;QAClC,OAAO,EAAE,qCAAqC;QAC9C,KAAK,EAAE,sCAAsC;QAC7C,WAAW,EAAE,iCAAiC;KAC/C;IACD;;OAEG;IACH,IAAI,EAAE;QACJ,QAAQ,EAAE,wBAAwB;QAClC,OAAO,EAAE,kCAAkC;QAC3C,KAAK,EAAE,mCAAmC;QAC1C,WAAW,EAAE,iCAAiC;KAC/C;IACD;;OAEG;IACH,MAAM,EAAE;QACN,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,uBAAuB;QACjE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,uBAAuB;QAC/D,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,uBAAuB;QAC3D,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,uBAAuB;KACjE;CACF,CAAA","sourcesContent":["export interface EnvironmentInfo {\n frontend: string\n backend: string\n proxy: string\n heliconeUrl: string\n}\n\nexport const ZeroAddress = '0x0000000000000000000000000000000000000000'\n\nexport type EnvironmentName = 'staging_sandbox' | 'staging_live' | 'sandbox' | 'live' | 'custom'\n\n/**\n * Represents the different environments and their corresponding URLs.\n */\nexport const Environments: Record<EnvironmentName, EnvironmentInfo> = {\n /**\n * The staging environment URLs.\n */\n staging_sandbox: {\n frontend: 'https://nevermined.dev',\n backend: 'https://api.sandbox.nevermined.dev/',\n proxy: 'https://proxy.sandbox.nevermined.dev',\n heliconeUrl: 'https://helicone.nevermined.dev',\n },\n staging_live: {\n frontend: 'https://nevermined.dev',\n backend: 'https://api.live.nevermined.dev/',\n proxy: 'https://proxy.live.nevermined.dev',\n heliconeUrl: 'https://helicone.nevermined.dev',\n },\n /**\n * The Sandbox environment URLs.\n */\n sandbox: {\n frontend: 'https://nevermined.app',\n backend: 'https://api.sandbox.nevermined.app/',\n proxy: 'https://proxy.sandbox.nevermined.app',\n heliconeUrl: 'https://helicone.nevermined.dev',\n },\n /**\n * The Live environment URLs.\n */\n live: {\n frontend: 'https://nevermined.app',\n backend: 'https://api.live.nevermined.app/',\n proxy: 'https://proxy.live.nevermined.app',\n heliconeUrl: 'https://helicone.nevermined.dev',\n },\n /**\n * A custom environment URLs.\n */\n custom: {\n frontend: process.env.NVM_FRONTEND_URL || 'http://localhost:4200',\n backend: process.env.NVM_BACKEND_URL || 'http://localhost:3001',\n proxy: process.env.NVM_PROXY_URL || 'https://localhost:443',\n heliconeUrl: process.env.HELICONE_URL || 'http://localhost:8585',\n },\n}\n"]}
1
+ {"version":3,"file":"environments.js","sourceRoot":"","sources":["../src/environments.ts"],"names":[],"mappings":"AAeA,MAAM,CAAC,MAAM,WAAW,GAAG,4CAA4C,CAAA;AAIvE;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAA6C;IACpE;;OAEG;IACH,eAAe,EAAE;QACf,QAAQ,EAAE,wBAAwB;QAClC,KAAK,EAAE,8BAA8B;QACrC,OAAO,EAAE,qCAAqC;QAC9C,KAAK,EAAE,sCAAsC;QAC7C,WAAW,EAAE,iCAAiC;KAC/C;IACD,YAAY,EAAE;QACZ,QAAQ,EAAE,wBAAwB;QAClC,KAAK,EAAE,8BAA8B;QACrC,OAAO,EAAE,kCAAkC;QAC3C,KAAK,EAAE,mCAAmC;QAC1C,WAAW,EAAE,iCAAiC;KAC/C;IACD;;OAEG;IACH,OAAO,EAAE;QACP,QAAQ,EAAE,wBAAwB;QAClC,KAAK,EAAE,8BAA8B;QACrC,OAAO,EAAE,qCAAqC;QAC9C,KAAK,EAAE,sCAAsC;QAC7C,WAAW,EAAE,iCAAiC;KAC/C;IACD;;OAEG;IACH,IAAI,EAAE;QACJ,QAAQ,EAAE,wBAAwB;QAClC,KAAK,EAAE,8BAA8B;QACrC,OAAO,EAAE,kCAAkC;QAC3C,KAAK,EAAE,mCAAmC;QAC1C,WAAW,EAAE,iCAAiC;KAC/C;IACD;;OAEG;IACH,MAAM,EAAE;QACN,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,uBAAuB;QACjE,oEAAoE;QACpE,kEAAkE;QAClE,kEAAkE;QAClE,gEAAgE;QAChE,YAAY;QACZ,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,uBAAuB;QAC3D,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,uBAAuB;QAC/D,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,uBAAuB;QAC3D,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,uBAAuB;KACjE;CACF,CAAA","sourcesContent":["export interface EnvironmentInfo {\n frontend: string\n /**\n * Base URL of the standalone, Privy-free embed app (the `embed.<tier>`\n * origin that serves the chromeless `/cards/*` and `/checkout/*` pages).\n * Formed by prepending `embed.` to the webapp host. The CLI redirect-mode\n * card flows open `${embed}/cards/setup` here — the old webapp\n * `/embed/cards/*` routes were removed in the #1787 cutover.\n */\n embed: string\n backend: string\n proxy: string\n heliconeUrl: string\n}\n\nexport const ZeroAddress = '0x0000000000000000000000000000000000000000'\n\nexport type EnvironmentName = 'staging_sandbox' | 'staging_live' | 'sandbox' | 'live' | 'custom'\n\n/**\n * Represents the different environments and their corresponding URLs.\n */\nexport const Environments: Record<EnvironmentName, EnvironmentInfo> = {\n /**\n * The staging environment URLs.\n */\n staging_sandbox: {\n frontend: 'https://nevermined.dev',\n embed: 'https://embed.nevermined.dev',\n backend: 'https://api.sandbox.nevermined.dev/',\n proxy: 'https://proxy.sandbox.nevermined.dev',\n heliconeUrl: 'https://helicone.nevermined.dev',\n },\n staging_live: {\n frontend: 'https://nevermined.dev',\n embed: 'https://embed.nevermined.dev',\n backend: 'https://api.live.nevermined.dev/',\n proxy: 'https://proxy.live.nevermined.dev',\n heliconeUrl: 'https://helicone.nevermined.dev',\n },\n /**\n * The Sandbox environment URLs.\n */\n sandbox: {\n frontend: 'https://nevermined.app',\n embed: 'https://embed.nevermined.app',\n backend: 'https://api.sandbox.nevermined.app/',\n proxy: 'https://proxy.sandbox.nevermined.app',\n heliconeUrl: 'https://helicone.nevermined.dev',\n },\n /**\n * The Live environment URLs.\n */\n live: {\n frontend: 'https://nevermined.app',\n embed: 'https://embed.nevermined.app',\n backend: 'https://api.live.nevermined.app/',\n proxy: 'https://proxy.live.nevermined.app',\n heliconeUrl: 'https://helicone.nevermined.dev',\n },\n /**\n * A custom environment URLs.\n */\n custom: {\n frontend: process.env.NVM_FRONTEND_URL || 'http://localhost:4200',\n // No fallback to NVM_FRONTEND_URL: the webapp host no longer serves\n // the card pages post-#1787 cutover, so silently reusing it would\n // reintroduce a dead-route footgun. The embed app runs on its own\n // port (4250, matching nvm-monorepo#1824); set NVM_EMBED_URL to\n // override.\n embed: process.env.NVM_EMBED_URL || 'http://localhost:4250',\n backend: process.env.NVM_BACKEND_URL || 'http://localhost:3001',\n proxy: process.env.NVM_PROXY_URL || 'https://localhost:443',\n heliconeUrl: process.env.HELICONE_URL || 'http://localhost:8585',\n },\n}\n"]}
package/dist/plans.d.ts CHANGED
@@ -3,6 +3,30 @@ export declare const ONE_DAY_DURATION = 86400n;
3
3
  export declare const ONE_WEEK_DURATION = 604800n;
4
4
  export declare const ONE_MONTH_DURATION = 2629746n;
5
5
  export declare const ONE_YEAR_DURATION = 31557600n;
6
+ /**
7
+ * Builds a price configuration for fiat-denominated plans (Stripe / Braintree).
8
+ *
9
+ * `amount` is in **6-decimal units** (the USDC convention used across the
10
+ * Nevermined protocol — NOT cents). To charge $2.00, pass `2_000_000n`;
11
+ * `200n` would be read as $0.0002 and rejected by the backend.
12
+ *
13
+ * Minimum charge enforced server-side is **$1.00** (`1_000_000n`) — fiat
14
+ * processor fixed fees make smaller amounts uneconomic. Passing below the
15
+ * minimum surfaces as `BCK.PROTOCOL.0047`.
16
+ *
17
+ * @param amount - Amount in 6-decimal units (e.g. `2_000_000n` for $2.00)
18
+ * @param receiver - Wallet address that will receive the settled funds
19
+ * @param currency - ISO currency code (defaults to `USD`)
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * // Charge $9.99 in USD
24
+ * getFiatPriceConfig(9_990_000n, sellerWallet)
25
+ *
26
+ * // Charge €29.00 in EUR
27
+ * getFiatPriceConfig(29_000_000n, sellerWallet, Currency.EUR)
28
+ * ```
29
+ */
6
30
  export declare const getFiatPriceConfig: (amount: bigint, receiver: Address, currency?: Currency | string) => PlanPriceConfig;
7
31
  export declare const getCryptoPriceConfig: (amount: bigint, receiver: Address, tokenAddress?: Address) => PlanPriceConfig;
8
32
  export declare const getERC20PriceConfig: (amount: bigint, tokenAddress: Address, receiver: Address) => PlanPriceConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../src/plans.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,QAAQ,EAER,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EACnB,MAAM,mBAAmB,CAAA;AAI1B,eAAO,MAAM,gBAAgB,SAAU,CAAA;AACvC,eAAO,MAAM,iBAAiB,UAAW,CAAA;AACzC,eAAO,MAAM,kBAAkB,WAAa,CAAA;AAC5C,eAAO,MAAM,iBAAiB,YAAc,CAAA;AAE5C,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,MAAM,EACd,UAAU,OAAO,EACjB,WAAU,QAAQ,GAAG,MAAqB,KACzC,eAcF,CAAA;AAED,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,EACd,UAAU,OAAO,EACjB,eAAc,OAAqB,KAClC,eAaF,CAAA;AAED,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,MAAM,EACd,cAAc,OAAO,EACrB,UAAU,OAAO,KAChB,eAEF,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,MAAM,EACd,UAAU,OAAO,EACjB,cAAa,OAA4B,KACxC,eAKF,CAAA;AAED,eAAO,MAAM,kBAAkB,QAAO,eAWrC,CAAA;AAED,eAAO,MAAM,yBAAyB,GAAI,QAAQ,MAAM,EAAE,UAAU,OAAO,KAAG,eAE7E,CAAA;AAED,eAAO,MAAM,0BAA0B,GAAI,gBAAgB,MAAM,KAAG,iBAUnE,CAAA;AAED,eAAO,MAAM,6BAA6B,QAAO,iBAEhD,CAAA;AAED,eAAO,MAAM,qBAAqB,GAChC,gBAAgB,MAAM,EACtB,0BAAsB,KACrB,iBAUF,CAAA;AAED,eAAO,MAAM,uBAAuB,GAClC,gBAAgB,MAAM,EACtB,6BAAyB,EACzB,6BAAyB,KACxB,iBAUF,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,eAAe,iBAAiB,EAChC,gBAAgB,kBAAkB,KACjC,iBAKF,CAAA;AAED,eAAO,MAAM,gBAAgB,GAC3B,eAAe,iBAAiB,EAChC,uBAAoB,KACnB,iBAKF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GACnC,QAAQ,MAAM,EACd,UAAU,OAAO,EACjB,eAAc,OAAqB,EACnC,kBAAkB,OAAO,KACxB,eAoBF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,QAAO,iBAU7C,CAAA"}
1
+ {"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../src/plans.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,QAAQ,EAER,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EACnB,MAAM,mBAAmB,CAAA;AAI1B,eAAO,MAAM,gBAAgB,SAAU,CAAA;AACvC,eAAO,MAAM,iBAAiB,UAAW,CAAA;AACzC,eAAO,MAAM,kBAAkB,WAAa,CAAA;AAC5C,eAAO,MAAM,iBAAiB,YAAc,CAAA;AAE5C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,MAAM,EACd,UAAU,OAAO,EACjB,WAAU,QAAQ,GAAG,MAAqB,KACzC,eAcF,CAAA;AAED,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,EACd,UAAU,OAAO,EACjB,eAAc,OAAqB,KAClC,eAaF,CAAA;AAED,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,MAAM,EACd,cAAc,OAAO,EACrB,UAAU,OAAO,KAChB,eAEF,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,MAAM,EACd,UAAU,OAAO,EACjB,cAAa,OAA4B,KACxC,eAKF,CAAA;AAED,eAAO,MAAM,kBAAkB,QAAO,eAWrC,CAAA;AAED,eAAO,MAAM,yBAAyB,GAAI,QAAQ,MAAM,EAAE,UAAU,OAAO,KAAG,eAE7E,CAAA;AAED,eAAO,MAAM,0BAA0B,GAAI,gBAAgB,MAAM,KAAG,iBAUnE,CAAA;AAED,eAAO,MAAM,6BAA6B,QAAO,iBAEhD,CAAA;AAED,eAAO,MAAM,qBAAqB,GAChC,gBAAgB,MAAM,EACtB,0BAAsB,KACrB,iBAUF,CAAA;AAED,eAAO,MAAM,uBAAuB,GAClC,gBAAgB,MAAM,EACtB,6BAAyB,EACzB,6BAAyB,KACxB,iBAUF,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,eAAe,iBAAiB,EAChC,gBAAgB,kBAAkB,KACjC,iBAKF,CAAA;AAED,eAAO,MAAM,gBAAgB,GAC3B,eAAe,iBAAiB,EAChC,uBAAoB,KACnB,iBAKF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GACnC,QAAQ,MAAM,EACd,UAAU,OAAO,EACjB,eAAc,OAAqB,EACnC,kBAAkB,OAAO,KACxB,eAoBF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,QAAO,iBAU7C,CAAA"}
package/dist/plans.js CHANGED
@@ -5,6 +5,30 @@ export const ONE_DAY_DURATION = 86400n; // 24 * 60 * 60 seconds
5
5
  export const ONE_WEEK_DURATION = 604800n; // 7 * 24 * 60 * 60 seconds
6
6
  export const ONE_MONTH_DURATION = 2629746n; // (365.25 days/year ÷ 12 months/year) × 24 × 60 × 60 ≈ 2,629,746 seconds
7
7
  export const ONE_YEAR_DURATION = 31557600n; // 365.25 * 24 * 60 * 60 seconds
8
+ /**
9
+ * Builds a price configuration for fiat-denominated plans (Stripe / Braintree).
10
+ *
11
+ * `amount` is in **6-decimal units** (the USDC convention used across the
12
+ * Nevermined protocol — NOT cents). To charge $2.00, pass `2_000_000n`;
13
+ * `200n` would be read as $0.0002 and rejected by the backend.
14
+ *
15
+ * Minimum charge enforced server-side is **$1.00** (`1_000_000n`) — fiat
16
+ * processor fixed fees make smaller amounts uneconomic. Passing below the
17
+ * minimum surfaces as `BCK.PROTOCOL.0047`.
18
+ *
19
+ * @param amount - Amount in 6-decimal units (e.g. `2_000_000n` for $2.00)
20
+ * @param receiver - Wallet address that will receive the settled funds
21
+ * @param currency - ISO currency code (defaults to `USD`)
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * // Charge $9.99 in USD
26
+ * getFiatPriceConfig(9_990_000n, sellerWallet)
27
+ *
28
+ * // Charge €29.00 in EUR
29
+ * getFiatPriceConfig(29_000_000n, sellerWallet, Currency.EUR)
30
+ * ```
31
+ */
8
32
  export const getFiatPriceConfig = (amount, receiver, currency = Currency.USD) => {
9
33
  if (!isEthereumAddress(receiver))
10
34
  throw new Error(`Receiver address ${receiver} is not a valid Ethereum address`);
package/dist/plans.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"plans.js","sourceRoot":"","sources":["../src/plans.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Address,\n Currency,\n EURC_TOKEN_ADDRESS,\n PlanCreditsConfig,\n PlanPriceConfig,\n PlanRedemptionType,\n} from './common/types.js'\nimport { ZeroAddress } from './environments.js'\nimport { isEthereumAddress } from './utils.js'\n\nexport const ONE_DAY_DURATION = 86_400n // 24 * 60 * 60 seconds\nexport const ONE_WEEK_DURATION = 604_800n // 7 * 24 * 60 * 60 seconds\nexport const ONE_MONTH_DURATION = 2_629_746n // (365.25 days/year ÷ 12 months/year) × 24 × 60 × 60 ≈ 2,629,746 seconds\nexport const ONE_YEAR_DURATION = 31_557_600n // 365.25 * 24 * 60 * 60 seconds\n\nexport const getFiatPriceConfig = (\n amount: bigint,\n receiver: Address,\n currency: Currency | string = Currency.USD,\n): PlanPriceConfig => {\n if (!isEthereumAddress(receiver))\n throw new Error(`Receiver address ${receiver} is not a valid Ethereum address`)\n return {\n tokenAddress: ZeroAddress,\n amounts: [amount],\n receivers: [receiver],\n contractAddress: ZeroAddress,\n feeController: ZeroAddress,\n externalPriceAddress: ZeroAddress,\n templateAddress: ZeroAddress,\n isCrypto: false,\n currency,\n }\n}\n\nexport const getCryptoPriceConfig = (\n amount: bigint,\n receiver: Address,\n tokenAddress: Address = ZeroAddress,\n): PlanPriceConfig => {\n if (!isEthereumAddress(receiver))\n throw new Error(`Receiver address ${receiver} is not a valid Ethereum address`)\n return {\n tokenAddress,\n amounts: [amount],\n receivers: [receiver],\n contractAddress: ZeroAddress,\n feeController: ZeroAddress,\n externalPriceAddress: ZeroAddress,\n templateAddress: ZeroAddress,\n isCrypto: true,\n }\n}\n\nexport const getERC20PriceConfig = (\n amount: bigint,\n tokenAddress: Address,\n receiver: Address,\n): PlanPriceConfig => {\n return getCryptoPriceConfig(amount, receiver, tokenAddress)\n}\n\n/**\n * Builds a price configuration for EURC (Euro stablecoin) payments.\n *\n * EURC uses 6 decimal places. To charge €29.00, pass `29_000_000n`.\n *\n * @param amount - Amount in the token's smallest unit (6 decimals for EURC).\n * @param receiver - Wallet address that will receive the payment.\n * @param eurcAddress - Optional EURC token address. Defaults to Base Mainnet EURC.\n * @returns The PlanPriceConfig representing an EURC price.\n */\nexport const getEURCPriceConfig = (\n amount: bigint,\n receiver: Address,\n eurcAddress: Address = EURC_TOKEN_ADDRESS,\n): PlanPriceConfig => {\n return {\n ...getERC20PriceConfig(amount, eurcAddress, receiver),\n currency: Currency.EURC,\n }\n}\n\nexport const getFreePriceConfig = (): PlanPriceConfig => {\n return {\n tokenAddress: ZeroAddress,\n amounts: [],\n receivers: [],\n contractAddress: ZeroAddress,\n feeController: ZeroAddress,\n externalPriceAddress: ZeroAddress,\n templateAddress: ZeroAddress,\n isCrypto: true,\n }\n}\n\nexport const getNativeTokenPriceConfig = (amount: bigint, receiver: Address): PlanPriceConfig => {\n return getCryptoPriceConfig(amount, receiver, ZeroAddress)\n}\n\nexport const getExpirableDurationConfig = (durationOfPlan: bigint): PlanCreditsConfig => {\n return {\n isRedemptionAmountFixed: false,\n redemptionType: PlanRedemptionType.ONLY_SUBSCRIBER,\n onchainMirror: false,\n durationSecs: durationOfPlan,\n amount: 1n,\n minAmount: 1n,\n maxAmount: 1n,\n }\n}\n\nexport const getNonExpirableDurationConfig = (): PlanCreditsConfig => {\n return getExpirableDurationConfig(0n)\n}\n\nexport const getFixedCreditsConfig = (\n creditsGranted: bigint,\n creditsPerRequest = 1n,\n): PlanCreditsConfig => {\n return {\n isRedemptionAmountFixed: true,\n redemptionType: PlanRedemptionType.ONLY_SUBSCRIBER,\n onchainMirror: false,\n durationSecs: 0n,\n amount: creditsGranted,\n minAmount: creditsPerRequest,\n maxAmount: creditsPerRequest,\n }\n}\n\nexport const getDynamicCreditsConfig = (\n creditsGranted: bigint,\n minCreditsPerRequest = 1n,\n maxCreditsPerRequest = 1n,\n): PlanCreditsConfig => {\n return {\n isRedemptionAmountFixed: false,\n redemptionType: PlanRedemptionType.ONLY_SUBSCRIBER,\n onchainMirror: false,\n durationSecs: 0n,\n amount: creditsGranted,\n minAmount: minCreditsPerRequest,\n maxAmount: maxCreditsPerRequest,\n }\n}\n\nexport const setRedemptionType = (\n creditsConfig: PlanCreditsConfig,\n redemptionType: PlanRedemptionType,\n): PlanCreditsConfig => {\n return {\n ...creditsConfig,\n redemptionType,\n }\n}\n\nexport const setOnchainMirror = (\n creditsConfig: PlanCreditsConfig,\n onchainMirror = true,\n): PlanCreditsConfig => {\n return {\n ...creditsConfig,\n onchainMirror,\n }\n}\n\n/**\n * Build a pay-as-you-go price configuration.\n *\n * For pay-as-you-go plans, the template address must come from the API deployment info.\n */\nexport const getPayAsYouGoPriceConfig = (\n amount: bigint,\n receiver: Address,\n tokenAddress: Address = ZeroAddress,\n templateAddress?: Address,\n): PlanPriceConfig => {\n if (!isEthereumAddress(receiver))\n throw new Error(`Receiver address ${receiver} is not a valid Ethereum address`)\n\n if (!templateAddress) {\n throw new Error(\n 'templateAddress is required. Use ContractsAPI.getPayAsYouGoTemplateAddress() or Payments.plans.getPayAsYouGoPriceConfig()',\n )\n }\n\n return {\n tokenAddress,\n amounts: [amount],\n receivers: [receiver],\n contractAddress: ZeroAddress,\n feeController: ZeroAddress,\n externalPriceAddress: ZeroAddress,\n templateAddress,\n isCrypto: true,\n }\n}\n\n/**\n * Build a pay-as-you-go credits configuration.\n *\n * Credits are not minted upfront; these values are required for validation only.\n */\nexport const getPayAsYouGoCreditsConfig = (): PlanCreditsConfig => {\n return {\n isRedemptionAmountFixed: false,\n redemptionType: PlanRedemptionType.ONLY_SUBSCRIBER,\n onchainMirror: false,\n durationSecs: 0n,\n amount: 1n,\n minAmount: 1n,\n maxAmount: 1n,\n }\n}\n"]}
1
+ {"version":3,"file":"plans.js","sourceRoot":"","sources":["../src/plans.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Address,\n Currency,\n EURC_TOKEN_ADDRESS,\n PlanCreditsConfig,\n PlanPriceConfig,\n PlanRedemptionType,\n} from './common/types.js'\nimport { ZeroAddress } from './environments.js'\nimport { isEthereumAddress } from './utils.js'\n\nexport const ONE_DAY_DURATION = 86_400n // 24 * 60 * 60 seconds\nexport const ONE_WEEK_DURATION = 604_800n // 7 * 24 * 60 * 60 seconds\nexport const ONE_MONTH_DURATION = 2_629_746n // (365.25 days/year ÷ 12 months/year) × 24 × 60 × 60 ≈ 2,629,746 seconds\nexport const ONE_YEAR_DURATION = 31_557_600n // 365.25 * 24 * 60 * 60 seconds\n\n/**\n * Builds a price configuration for fiat-denominated plans (Stripe / Braintree).\n *\n * `amount` is in **6-decimal units** (the USDC convention used across the\n * Nevermined protocol — NOT cents). To charge $2.00, pass `2_000_000n`;\n * `200n` would be read as $0.0002 and rejected by the backend.\n *\n * Minimum charge enforced server-side is **$1.00** (`1_000_000n`) — fiat\n * processor fixed fees make smaller amounts uneconomic. Passing below the\n * minimum surfaces as `BCK.PROTOCOL.0047`.\n *\n * @param amount - Amount in 6-decimal units (e.g. `2_000_000n` for $2.00)\n * @param receiver - Wallet address that will receive the settled funds\n * @param currency - ISO currency code (defaults to `USD`)\n *\n * @example\n * ```ts\n * // Charge $9.99 in USD\n * getFiatPriceConfig(9_990_000n, sellerWallet)\n *\n * // Charge €29.00 in EUR\n * getFiatPriceConfig(29_000_000n, sellerWallet, Currency.EUR)\n * ```\n */\nexport const getFiatPriceConfig = (\n amount: bigint,\n receiver: Address,\n currency: Currency | string = Currency.USD,\n): PlanPriceConfig => {\n if (!isEthereumAddress(receiver))\n throw new Error(`Receiver address ${receiver} is not a valid Ethereum address`)\n return {\n tokenAddress: ZeroAddress,\n amounts: [amount],\n receivers: [receiver],\n contractAddress: ZeroAddress,\n feeController: ZeroAddress,\n externalPriceAddress: ZeroAddress,\n templateAddress: ZeroAddress,\n isCrypto: false,\n currency,\n }\n}\n\nexport const getCryptoPriceConfig = (\n amount: bigint,\n receiver: Address,\n tokenAddress: Address = ZeroAddress,\n): PlanPriceConfig => {\n if (!isEthereumAddress(receiver))\n throw new Error(`Receiver address ${receiver} is not a valid Ethereum address`)\n return {\n tokenAddress,\n amounts: [amount],\n receivers: [receiver],\n contractAddress: ZeroAddress,\n feeController: ZeroAddress,\n externalPriceAddress: ZeroAddress,\n templateAddress: ZeroAddress,\n isCrypto: true,\n }\n}\n\nexport const getERC20PriceConfig = (\n amount: bigint,\n tokenAddress: Address,\n receiver: Address,\n): PlanPriceConfig => {\n return getCryptoPriceConfig(amount, receiver, tokenAddress)\n}\n\n/**\n * Builds a price configuration for EURC (Euro stablecoin) payments.\n *\n * EURC uses 6 decimal places. To charge €29.00, pass `29_000_000n`.\n *\n * @param amount - Amount in the token's smallest unit (6 decimals for EURC).\n * @param receiver - Wallet address that will receive the payment.\n * @param eurcAddress - Optional EURC token address. Defaults to Base Mainnet EURC.\n * @returns The PlanPriceConfig representing an EURC price.\n */\nexport const getEURCPriceConfig = (\n amount: bigint,\n receiver: Address,\n eurcAddress: Address = EURC_TOKEN_ADDRESS,\n): PlanPriceConfig => {\n return {\n ...getERC20PriceConfig(amount, eurcAddress, receiver),\n currency: Currency.EURC,\n }\n}\n\nexport const getFreePriceConfig = (): PlanPriceConfig => {\n return {\n tokenAddress: ZeroAddress,\n amounts: [],\n receivers: [],\n contractAddress: ZeroAddress,\n feeController: ZeroAddress,\n externalPriceAddress: ZeroAddress,\n templateAddress: ZeroAddress,\n isCrypto: true,\n }\n}\n\nexport const getNativeTokenPriceConfig = (amount: bigint, receiver: Address): PlanPriceConfig => {\n return getCryptoPriceConfig(amount, receiver, ZeroAddress)\n}\n\nexport const getExpirableDurationConfig = (durationOfPlan: bigint): PlanCreditsConfig => {\n return {\n isRedemptionAmountFixed: false,\n redemptionType: PlanRedemptionType.ONLY_SUBSCRIBER,\n onchainMirror: false,\n durationSecs: durationOfPlan,\n amount: 1n,\n minAmount: 1n,\n maxAmount: 1n,\n }\n}\n\nexport const getNonExpirableDurationConfig = (): PlanCreditsConfig => {\n return getExpirableDurationConfig(0n)\n}\n\nexport const getFixedCreditsConfig = (\n creditsGranted: bigint,\n creditsPerRequest = 1n,\n): PlanCreditsConfig => {\n return {\n isRedemptionAmountFixed: true,\n redemptionType: PlanRedemptionType.ONLY_SUBSCRIBER,\n onchainMirror: false,\n durationSecs: 0n,\n amount: creditsGranted,\n minAmount: creditsPerRequest,\n maxAmount: creditsPerRequest,\n }\n}\n\nexport const getDynamicCreditsConfig = (\n creditsGranted: bigint,\n minCreditsPerRequest = 1n,\n maxCreditsPerRequest = 1n,\n): PlanCreditsConfig => {\n return {\n isRedemptionAmountFixed: false,\n redemptionType: PlanRedemptionType.ONLY_SUBSCRIBER,\n onchainMirror: false,\n durationSecs: 0n,\n amount: creditsGranted,\n minAmount: minCreditsPerRequest,\n maxAmount: maxCreditsPerRequest,\n }\n}\n\nexport const setRedemptionType = (\n creditsConfig: PlanCreditsConfig,\n redemptionType: PlanRedemptionType,\n): PlanCreditsConfig => {\n return {\n ...creditsConfig,\n redemptionType,\n }\n}\n\nexport const setOnchainMirror = (\n creditsConfig: PlanCreditsConfig,\n onchainMirror = true,\n): PlanCreditsConfig => {\n return {\n ...creditsConfig,\n onchainMirror,\n }\n}\n\n/**\n * Build a pay-as-you-go price configuration.\n *\n * For pay-as-you-go plans, the template address must come from the API deployment info.\n */\nexport const getPayAsYouGoPriceConfig = (\n amount: bigint,\n receiver: Address,\n tokenAddress: Address = ZeroAddress,\n templateAddress?: Address,\n): PlanPriceConfig => {\n if (!isEthereumAddress(receiver))\n throw new Error(`Receiver address ${receiver} is not a valid Ethereum address`)\n\n if (!templateAddress) {\n throw new Error(\n 'templateAddress is required. Use ContractsAPI.getPayAsYouGoTemplateAddress() or Payments.plans.getPayAsYouGoPriceConfig()',\n )\n }\n\n return {\n tokenAddress,\n amounts: [amount],\n receivers: [receiver],\n contractAddress: ZeroAddress,\n feeController: ZeroAddress,\n externalPriceAddress: ZeroAddress,\n templateAddress,\n isCrypto: true,\n }\n}\n\n/**\n * Build a pay-as-you-go credits configuration.\n *\n * Credits are not minted upfront; these values are required for validation only.\n */\nexport const getPayAsYouGoCreditsConfig = (): PlanCreditsConfig => {\n return {\n isRedemptionAmountFixed: false,\n redemptionType: PlanRedemptionType.ONLY_SUBSCRIBER,\n onchainMirror: false,\n durationSecs: 0n,\n amount: 1n,\n minAmount: 1n,\n maxAmount: 1n,\n }\n}\n"]}
@@ -100,6 +100,13 @@ export interface UpdatePaymentMethodDto {
100
100
  export interface ListOptions {
101
101
  /** When true, return only items accessible to the requesting API key */
102
102
  accessible?: boolean;
103
+ /**
104
+ * Restrict the result to payment methods backed by this provider
105
+ * (e.g. `'stripe'`). Server-side filter for
106
+ * {@link DelegationAPI.listPaymentMethods}; omit to return methods from every
107
+ * provider (default). Has no effect on {@link DelegationAPI.listDelegations}.
108
+ */
109
+ provider?: DelegationProvider;
103
110
  }
104
111
  /**
105
112
  * API for managing payment methods and delegations (card and crypto).
@@ -109,6 +116,8 @@ export declare class DelegationAPI extends BasePaymentsAPI {
109
116
  /**
110
117
  * List the user's enrolled payment methods for card delegation.
111
118
  * When `accessible: true`, only cards accessible to the requesting API key are returned.
119
+ * When `provider` is set, only methods backed by that provider are returned
120
+ * (server-side filter); omit it to return methods from every provider.
112
121
  */
113
122
  listPaymentMethods(options?: ListOptions): Promise<PaymentMethodSummary[]>;
114
123
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"delegation-api.d.ts","sourceRoot":"","sources":["../../src/x402/delegation-api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"delegation-api.d.ts","sourceRoot":"","sources":["../../src/x402/delegation-api.ts"],"names":[],"mappings":"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"}
@@ -16,11 +16,15 @@ export class DelegationAPI extends BasePaymentsAPI {
16
16
  /**
17
17
  * List the user's enrolled payment methods for card delegation.
18
18
  * When `accessible: true`, only cards accessible to the requesting API key are returned.
19
+ * When `provider` is set, only methods backed by that provider are returned
20
+ * (server-side filter); omit it to return methods from every provider.
19
21
  */
20
22
  async listPaymentMethods(options) {
21
23
  const url = new URL('/api/v1/payment-methods', this.environment.backend);
22
24
  if (options?.accessible)
23
25
  url.searchParams.set('accessible', 'true');
26
+ if (options?.provider)
27
+ url.searchParams.set('provider', options.provider);
24
28
  return this.fetchJSON(url, 'GET', 'list payment methods');
25
29
  }
26
30
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"delegation-api.js","sourceRoot":"","sources":["../../src/x402/delegation-api.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Delegation API for managing payment delegations (crypto and card schemes).\n *\n * Provides access to the user's enrolled payment methods and delegations\n * for use with the nvm:erc4337 and nvm:card-delegation x402 schemes.\n */\n\nimport { BasePaymentsAPI } from '../api/base-payments.js'\nimport { PaymentsError } from '../common/payments.error.js'\nimport {\n CreateDelegationPayload,\n CreateDelegationResponse,\n PaymentOptions,\n} from '../common/types.js'\n\n/**\n * Card-delegation providers exposed by the SDK. Use this when you want to\n * restrict to card-shape entries only (e.g., filtering the heterogeneous\n * list returned by {@link DelegationAPI.listPaymentMethods}).\n */\nexport type CardProvider = 'stripe' | 'braintree' | 'visa'\n\n/**\n * All delegation providers, including the crypto path. Matches the\n * server-side union and aligns with {@link CreateDelegationPayload.provider}.\n */\nexport type DelegationProvider = CardProvider | 'erc4337'\n\n/**\n * Summary of a user's enrolled payment method.\n *\n * The list returned by {@link DelegationAPI.listPaymentMethods} is\n * heterogeneous: it includes enrolled cards (`provider` in\n * `stripe` / `braintree` / `visa`) AND, when the user has a smart account\n * configured, an entry for the user's ERC-4337 wallet\n * (`provider: 'erc4337'`, `type: 'crypto_wallet'`, `brand: 'ethereum'`).\n * Filter on `provider` when callers only want one shape.\n */\nexport interface PaymentMethodSummary {\n /** Payment method ID (Stripe 'pm_...', Braintree vault token, Visa Agentic\n * token id, or — for the erc4337 entry — the smart-account address) */\n id: string\n /** Payment method type ('card' | 'crypto_wallet' | 'paypal' | …) */\n type: string\n /** Card brand (e.g., 'visa', 'mastercard'), 'ethereum' for the erc4337\n * entry, or payment method type ('paypal', 'venmo') */\n brand: string\n /** Last 4 digits (cards), trailing 4 chars of the wallet address\n * (erc4337), or email/username (PayPal/Venmo) */\n last4: string\n /** Expiration month (0 for non-card methods) */\n expMonth: number\n /** Expiration year (0 for non-card methods) */\n expYear: number\n /** Human-readable alias, if set */\n alias?: string | null\n /** One of 'stripe' | 'braintree' | 'visa' | 'erc4337' */\n provider?: DelegationProvider\n /** Current status ('Active' or 'Revoked') */\n status?: string\n /** NVM API Key IDs allowed to use this payment method, or null if unrestricted */\n allowedApiKeyIds?: string[] | null\n}\n\n/**\n * Summary of a delegation (card or crypto spending).\n */\nexport interface DelegationSummary {\n delegationId: string\n provider: string\n providerPaymentMethodId: string\n status: string\n spendingLimitCents: string\n amountSpentCents: string\n remainingBudgetCents: string\n currency: string\n transactionCount: number\n expiresAt: string\n createdAt: string\n apiKeyId: string | null\n}\n\n/**\n * Paginated list of delegations returned by the API.\n */\nexport interface DelegationListResponse {\n delegations: DelegationSummary[]\n totalResults: number\n page: number\n offset: number\n}\n\n/**\n * Summary of an agent's purchasing power via card delegations.\n */\nexport interface PurchasingPower {\n cards: PaymentMethodSummary[]\n delegations: DelegationSummary[]\n totalRemainingBudgetCents: number\n currency: string\n}\n\n/**\n * DTO for updating a payment method's alias and allowed API keys.\n */\nexport interface UpdatePaymentMethodDto {\n alias?: string\n allowedApiKeyIds?: string[] | null\n}\n\n/**\n * Options for listing payment methods or delegations.\n */\nexport interface ListOptions {\n /** When true, return only items accessible to the requesting API key */\n accessible?: boolean\n}\n\n/**\n * API for managing payment methods and delegations (card and crypto).\n */\nexport class DelegationAPI extends BasePaymentsAPI {\n static getInstance(options: PaymentOptions): DelegationAPI {\n return new DelegationAPI(options)\n }\n\n /**\n * List the user's enrolled payment methods for card delegation.\n * When `accessible: true`, only cards accessible to the requesting API key are returned.\n */\n async listPaymentMethods(options?: ListOptions): Promise<PaymentMethodSummary[]> {\n const url = new URL('/api/v1/payment-methods', this.environment.backend)\n if (options?.accessible) url.searchParams.set('accessible', 'true')\n return this.fetchJSON(url, 'GET', 'list payment methods')\n }\n\n /**\n * List the user's existing delegations.\n * When `accessible: true`, only usable delegations (Active, non-expired, with budget) are returned.\n */\n async listDelegations(options?: ListOptions): Promise<DelegationListResponse> {\n const url = new URL('/api/v1/delegation', this.environment.backend)\n if (options?.accessible) url.searchParams.set('accessible', 'true')\n return this.fetchJSON(url, 'GET', 'list delegations')\n }\n\n /**\n * Get the agent's purchasing power — accessible cards, active delegations,\n * and combined remaining budget.\n */\n async getPurchasingPower(): Promise<PurchasingPower> {\n const accessible = { accessible: true } satisfies ListOptions\n const [cards, { delegations }] = await Promise.all([\n this.listPaymentMethods(accessible),\n this.listDelegations(accessible),\n ])\n\n const totalRemainingBudgetCents = delegations.reduce(\n (sum, d) => sum + (parseInt(d.remainingBudgetCents, 10) || 0),\n 0,\n )\n\n return {\n cards,\n delegations,\n totalRemainingBudgetCents,\n currency: delegations[0]?.currency ?? 'usd',\n }\n }\n\n /**\n * Create a new delegation for any supported provider (stripe, braintree,\n * visa, or erc4337).\n *\n * Note: Visa delegations require a per-delegation device-binding ceremony\n * (FIDO/passkey + assuranceData) that must be performed in the browser\n * via the Nevermined webapp. The SDK can list and consume an already-\n * created Visa delegation but cannot create one programmatically.\n *\n * @param payload - The delegation creation parameters\n * @returns The created delegation ID (and token for card delegations)\n */\n async createDelegation(payload: CreateDelegationPayload): Promise<CreateDelegationResponse> {\n const url = new URL('/api/v1/delegation/create', this.environment.backend)\n return this.fetchJSON(url, 'POST', 'create delegation', payload)\n }\n\n /**\n * Update a payment method's alias and/or allowed API keys.\n */\n async updatePaymentMethod(\n paymentMethodId: string,\n dto: UpdatePaymentMethodDto,\n ): Promise<PaymentMethodSummary> {\n const url = new URL(`/api/v1/payment-methods/${paymentMethodId}`, this.environment.backend)\n return this.fetchJSON(url, 'PATCH', 'update payment method', dto)\n }\n\n // --- Private helpers ---\n\n private async fetchJSON<T>(url: URL, method: string, action: string, body?: unknown): Promise<T> {\n const options = this.getBackendHTTPOptions(method, body)\n try {\n const response = await fetch(url, options)\n if (!response.ok) {\n let msg = `Failed to ${action}`\n let code = `http_${response.status}`\n try {\n const err = await response.json()\n if (err.message) msg = err.message\n if (err.code) code = err.code\n if (err.hint) msg = `${msg} — ${err.hint}`\n } catch {\n // use default\n }\n throw new PaymentsError(`${msg} (HTTP ${response.status})`, code)\n }\n return await response.json()\n } catch (error) {\n if (error instanceof PaymentsError) throw error\n throw PaymentsError.internal(\n `Network error while ${action}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n}\n"]}
1
+ {"version":3,"file":"delegation-api.js","sourceRoot":"","sources":["../../src/x402/delegation-api.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Delegation API for managing payment delegations (crypto and card schemes).\n *\n * Provides access to the user's enrolled payment methods and delegations\n * for use with the nvm:erc4337 and nvm:card-delegation x402 schemes.\n */\n\nimport { BasePaymentsAPI } from '../api/base-payments.js'\nimport { PaymentsError } from '../common/payments.error.js'\nimport {\n CreateDelegationPayload,\n CreateDelegationResponse,\n PaymentOptions,\n} from '../common/types.js'\n\n/**\n * Card-delegation providers exposed by the SDK. Use this when you want to\n * restrict to card-shape entries only (e.g., filtering the heterogeneous\n * list returned by {@link DelegationAPI.listPaymentMethods}).\n */\nexport type CardProvider = 'stripe' | 'braintree' | 'visa'\n\n/**\n * All delegation providers, including the crypto path. Matches the\n * server-side union and aligns with {@link CreateDelegationPayload.provider}.\n */\nexport type DelegationProvider = CardProvider | 'erc4337'\n\n/**\n * Summary of a user's enrolled payment method.\n *\n * The list returned by {@link DelegationAPI.listPaymentMethods} is\n * heterogeneous: it includes enrolled cards (`provider` in\n * `stripe` / `braintree` / `visa`) AND, when the user has a smart account\n * configured, an entry for the user's ERC-4337 wallet\n * (`provider: 'erc4337'`, `type: 'crypto_wallet'`, `brand: 'ethereum'`).\n * Filter on `provider` when callers only want one shape.\n */\nexport interface PaymentMethodSummary {\n /** Payment method ID (Stripe 'pm_...', Braintree vault token, Visa Agentic\n * token id, or — for the erc4337 entry — the smart-account address) */\n id: string\n /** Payment method type ('card' | 'crypto_wallet' | 'paypal' | …) */\n type: string\n /** Card brand (e.g., 'visa', 'mastercard'), 'ethereum' for the erc4337\n * entry, or payment method type ('paypal', 'venmo') */\n brand: string\n /** Last 4 digits (cards), trailing 4 chars of the wallet address\n * (erc4337), or email/username (PayPal/Venmo) */\n last4: string\n /** Expiration month (0 for non-card methods) */\n expMonth: number\n /** Expiration year (0 for non-card methods) */\n expYear: number\n /** Human-readable alias, if set */\n alias?: string | null\n /** One of 'stripe' | 'braintree' | 'visa' | 'erc4337' */\n provider?: DelegationProvider\n /** Current status ('Active' or 'Revoked') */\n status?: string\n /** NVM API Key IDs allowed to use this payment method, or null if unrestricted */\n allowedApiKeyIds?: string[] | null\n}\n\n/**\n * Summary of a delegation (card or crypto spending).\n */\nexport interface DelegationSummary {\n delegationId: string\n provider: string\n providerPaymentMethodId: string\n status: string\n spendingLimitCents: string\n amountSpentCents: string\n remainingBudgetCents: string\n currency: string\n transactionCount: number\n expiresAt: string\n createdAt: string\n apiKeyId: string | null\n}\n\n/**\n * Paginated list of delegations returned by the API.\n */\nexport interface DelegationListResponse {\n delegations: DelegationSummary[]\n totalResults: number\n page: number\n offset: number\n}\n\n/**\n * Summary of an agent's purchasing power via card delegations.\n */\nexport interface PurchasingPower {\n cards: PaymentMethodSummary[]\n delegations: DelegationSummary[]\n totalRemainingBudgetCents: number\n currency: string\n}\n\n/**\n * DTO for updating a payment method's alias and allowed API keys.\n */\nexport interface UpdatePaymentMethodDto {\n alias?: string\n allowedApiKeyIds?: string[] | null\n}\n\n/**\n * Options for listing payment methods or delegations.\n */\nexport interface ListOptions {\n /** When true, return only items accessible to the requesting API key */\n accessible?: boolean\n /**\n * Restrict the result to payment methods backed by this provider\n * (e.g. `'stripe'`). Server-side filter for\n * {@link DelegationAPI.listPaymentMethods}; omit to return methods from every\n * provider (default). Has no effect on {@link DelegationAPI.listDelegations}.\n */\n provider?: DelegationProvider\n}\n\n/**\n * API for managing payment methods and delegations (card and crypto).\n */\nexport class DelegationAPI extends BasePaymentsAPI {\n static getInstance(options: PaymentOptions): DelegationAPI {\n return new DelegationAPI(options)\n }\n\n /**\n * List the user's enrolled payment methods for card delegation.\n * When `accessible: true`, only cards accessible to the requesting API key are returned.\n * When `provider` is set, only methods backed by that provider are returned\n * (server-side filter); omit it to return methods from every provider.\n */\n async listPaymentMethods(options?: ListOptions): Promise<PaymentMethodSummary[]> {\n const url = new URL('/api/v1/payment-methods', this.environment.backend)\n if (options?.accessible) url.searchParams.set('accessible', 'true')\n if (options?.provider) url.searchParams.set('provider', options.provider)\n return this.fetchJSON(url, 'GET', 'list payment methods')\n }\n\n /**\n * List the user's existing delegations.\n * When `accessible: true`, only usable delegations (Active, non-expired, with budget) are returned.\n */\n async listDelegations(options?: ListOptions): Promise<DelegationListResponse> {\n const url = new URL('/api/v1/delegation', this.environment.backend)\n if (options?.accessible) url.searchParams.set('accessible', 'true')\n return this.fetchJSON(url, 'GET', 'list delegations')\n }\n\n /**\n * Get the agent's purchasing power — accessible cards, active delegations,\n * and combined remaining budget.\n */\n async getPurchasingPower(): Promise<PurchasingPower> {\n const accessible = { accessible: true } satisfies ListOptions\n const [cards, { delegations }] = await Promise.all([\n this.listPaymentMethods(accessible),\n this.listDelegations(accessible),\n ])\n\n const totalRemainingBudgetCents = delegations.reduce(\n (sum, d) => sum + (parseInt(d.remainingBudgetCents, 10) || 0),\n 0,\n )\n\n return {\n cards,\n delegations,\n totalRemainingBudgetCents,\n currency: delegations[0]?.currency ?? 'usd',\n }\n }\n\n /**\n * Create a new delegation for any supported provider (stripe, braintree,\n * visa, or erc4337).\n *\n * Note: Visa delegations require a per-delegation device-binding ceremony\n * (FIDO/passkey + assuranceData) that must be performed in the browser\n * via the Nevermined webapp. The SDK can list and consume an already-\n * created Visa delegation but cannot create one programmatically.\n *\n * @param payload - The delegation creation parameters\n * @returns The created delegation ID (and token for card delegations)\n */\n async createDelegation(payload: CreateDelegationPayload): Promise<CreateDelegationResponse> {\n const url = new URL('/api/v1/delegation/create', this.environment.backend)\n return this.fetchJSON(url, 'POST', 'create delegation', payload)\n }\n\n /**\n * Update a payment method's alias and/or allowed API keys.\n */\n async updatePaymentMethod(\n paymentMethodId: string,\n dto: UpdatePaymentMethodDto,\n ): Promise<PaymentMethodSummary> {\n const url = new URL(`/api/v1/payment-methods/${paymentMethodId}`, this.environment.backend)\n return this.fetchJSON(url, 'PATCH', 'update payment method', dto)\n }\n\n // --- Private helpers ---\n\n private async fetchJSON<T>(url: URL, method: string, action: string, body?: unknown): Promise<T> {\n const options = this.getBackendHTTPOptions(method, body)\n try {\n const response = await fetch(url, options)\n if (!response.ok) {\n let msg = `Failed to ${action}`\n let code = `http_${response.status}`\n try {\n const err = await response.json()\n if (err.message) msg = err.message\n if (err.code) code = err.code\n if (err.hint) msg = `${msg} — ${err.hint}`\n } catch {\n // use default\n }\n throw new PaymentsError(`${msg} (HTTP ${response.status})`, code)\n }\n return await response.json()\n } catch (error) {\n if (error instanceof PaymentsError) throw error\n throw PaymentsError.internal(\n `Network error while ${action}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/x402/express/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE9D;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAA;AACzF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAC9E,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC7B,MAAM,uBAAuB,CAAA;AAE9B;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAA;IACd,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;IAC9E,wBAAwB;IACxB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,yEAAyE;IACzE,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;AAExD;;;GAGG;AACH,eAAO,MAAM,YAAY;IACvB,gDAAgD;;IAEhD,mEAAmE;;IAEnE,sEAAsE;;CAE9D,CAAA;AAEV;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,kCAAkC;IAClC,eAAe,EAAE,mBAAmB,CAAA;IACpC,kCAAkC;IAClC,eAAe,EAAE,MAAM,CAAA;IACvB,0CAA0C;IAC1C,QAAQ,EAAE,OAAO,CAAA;IACjB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,iBAAiB,CAAA;IAChC,kDAAkD;IAClD,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC/B,gDAAgD;IAChD,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAA;IACpE,sCAAsC;IACtC,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,eAAe,EAAE,mBAAmB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7F;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7F,8CAA8C;IAC9C,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7F;AAyGD,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,wBAA6B,GACrC,iBAAiB,CAoLnB;AAED,eAAe,iBAAiB,CAAA"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/x402/express/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE9D;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAA;AACzF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAC9E,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC7B,MAAM,uBAAuB,CAAA;AAE9B;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAA;IACd,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;IAC9E,wBAAwB;IACxB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,yEAAyE;IACzE,MAAM,CAAC,EAAE,cAAc,CAAA;IACvB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;AAExD;;;GAGG;AACH,eAAO,MAAM,YAAY;IACvB,gDAAgD;;IAEhD,mEAAmE;;IAEnE,sEAAsE;;CAE9D,CAAA;AAEV;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,kCAAkC;IAClC,eAAe,EAAE,mBAAmB,CAAA;IACpC,kCAAkC;IAClC,eAAe,EAAE,MAAM,CAAA;IACvB,0CAA0C;IAC1C,QAAQ,EAAE,OAAO,CAAA;IACjB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,iBAAiB,CAAA;IAChC,kDAAkD;IAClD,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC/B,gDAAgD;IAChD,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAA;IACpE,sCAAsC;IACtC,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,eAAe,EAAE,mBAAmB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7F;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7F,8CAA8C;IAC9C,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7F;AAyGD,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,wBAA6B,GACrC,iBAAiB,CAiNnB;AAED,eAAe,iBAAiB,CAAA"}
@@ -221,19 +221,19 @@ export function paymentMiddleware(payments, routes, options = {}) {
221
221
  agentRequestId: verification.agentRequest?.agentRequestId || verification.agentRequestId,
222
222
  };
223
223
  req.paymentContext = paymentContext;
224
- // Override res.json to settle BEFORE sending response
225
- // This ensures credits are burned and payment-response header is included
226
- const originalJson = res.json.bind(res);
227
- res.json = function (body) {
228
- // Re-evaluate dynamic credits now that the handler has run and
229
- // res.locals is populated. For fixed (numeric) credits this is a no-op.
230
- const settlePromise = (typeof credits === 'function'
224
+ // Wrap res.end so settlement runs no matter how the handler responds
225
+ // (res.json, res.send, res.sendFile, res.end, res.pipe res.end).
226
+ // Previously only res.json was intercepted, so any other response
227
+ // method would deliver the resource without burning credits and
228
+ // without emitting the payment-response receipt header (#1728).
229
+ const originalEnd = res.end.bind(res);
230
+ let settlementStarted = false;
231
+ const runSettlement = () => {
232
+ return (typeof credits === 'function'
231
233
  ? Promise.resolve(credits(req, res))
232
- : Promise.resolve(creditsToVerify)).then((creditsToSettle) => {
233
- // Update payment context so downstream consumers see the actual value
234
+ : Promise.resolve(creditsToVerify))
235
+ .then((creditsToSettle) => {
234
236
  paymentContext.creditsToSettle = creditsToSettle;
235
- // Settle credits before sending response
236
- // Pass agentRequestId to enable observability updates
237
237
  return payments.facilitator
238
238
  .settlePermissions({
239
239
  paymentRequired,
@@ -242,26 +242,49 @@ export function paymentMiddleware(payments, routes, options = {}) {
242
242
  agentRequestId: paymentContext.agentRequestId,
243
243
  })
244
244
  .then((settlement) => {
245
- // Add settlement response header (base64-encoded per x402 spec)
246
- const settlementBase64 = Buffer.from(JSON.stringify(settlement)).toString('base64');
247
- res.setHeader(X402_HEADERS.PAYMENT_RESPONSE, settlementBase64);
248
- // Hook: after settlement
245
+ // Only attach the receipt header if headers haven't flushed
246
+ // yet streaming responses fire writeHead on the first
247
+ // chunk and may have already sent them by the time we land
248
+ // here.
249
+ if (!res.headersSent) {
250
+ const settlementBase64 = Buffer.from(JSON.stringify(settlement)).toString('base64');
251
+ res.setHeader(X402_HEADERS.PAYMENT_RESPONSE, settlementBase64);
252
+ }
253
+ else {
254
+ console.warn('[paymentMiddleware] headers already flushed; payment-response receipt not attached');
255
+ }
249
256
  if (onAfterSettle) {
250
- return Promise.resolve(onAfterSettle(req, creditsToSettle, settlement)).then(() => settlement);
257
+ return Promise.resolve(onAfterSettle(req, creditsToSettle, settlement)).then(() => undefined);
251
258
  }
252
- return settlement;
259
+ return undefined;
253
260
  });
254
- });
255
- settlePromise
261
+ })
256
262
  .catch((settleError) => {
257
263
  console.error('Payment settlement failed:', settleError);
258
- // Still send response even if settlement fails
259
- })
260
- .finally(() => {
261
- // Send the actual response after settlement completes
262
- originalJson(body);
263
264
  });
264
- // Return res for chaining (Express pattern)
265
+ };
266
+ res.end = function (...args) {
267
+ // Only bill on 2xx success. Skipping 3xx avoids charging when the
268
+ // handler redirects (e.g. `res.redirect(...)`), 304 Not Modified,
269
+ // etc. Skipping 4xx/5xx avoids charging when the handler signals
270
+ // failure — including `sendPaymentRequired`'s 402 which lands here.
271
+ const isSuccess = res.statusCode >= 200 && res.statusCode < 300;
272
+ if (settlementStarted || !isSuccess) {
273
+ return originalEnd(...args);
274
+ }
275
+ settlementStarted = true;
276
+ // If the handler streamed before calling end, headers were already
277
+ // flushed. Settle anyway (so we still charge the card) but accept
278
+ // we cannot inject the receipt header.
279
+ if (res.headersSent) {
280
+ void runSettlement();
281
+ return originalEnd(...args);
282
+ }
283
+ // Buffered response path: defer the real `end` until settlement
284
+ // finishes so the receipt header makes it into the same response.
285
+ runSettlement().finally(() => {
286
+ originalEnd(...args);
287
+ });
265
288
  return res;
266
289
  };
267
290
  // Continue to route handler
@@ -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, X402SchemeType } from '../../common/types.js'\nimport {\n buildPaymentRequired,\n resolveNetwork,\n resolveScheme,\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: auto-derived from scheme) */\n network?: string\n /** x402 scheme override (auto-detected from plan metadata if omitted) */\n scheme?: X402SchemeType\n /** Human-readable description of the protected resource */\n description?: string\n /** Expected response MIME type (e.g., \"application/json\") */\n mimeType?: 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 {\n planId,\n credits = 1,\n agentId,\n network,\n scheme: explicitScheme,\n description,\n mimeType,\n } = routeConfig\n\n // Resolve scheme and network from plan metadata (cached) or explicit overrides\n const scheme = await resolveScheme(payments, planId, explicitScheme)\n const resolvedNetwork = await resolveNetwork(payments, planId, network)\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: resolvedNetwork,\n description,\n mimeType,\n scheme,\n environment: payments.getEnvironmentName(),\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"]}
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, X402SchemeType } from '../../common/types.js'\nimport {\n buildPaymentRequired,\n resolveNetwork,\n resolveScheme,\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: auto-derived from scheme) */\n network?: string\n /** x402 scheme override (auto-detected from plan metadata if omitted) */\n scheme?: X402SchemeType\n /** Human-readable description of the protected resource */\n description?: string\n /** Expected response MIME type (e.g., \"application/json\") */\n mimeType?: 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 {\n planId,\n credits = 1,\n agentId,\n network,\n scheme: explicitScheme,\n description,\n mimeType,\n } = routeConfig\n\n // Resolve scheme and network from plan metadata (cached) or explicit overrides\n const scheme = await resolveScheme(payments, planId, explicitScheme)\n const resolvedNetwork = await resolveNetwork(payments, planId, network)\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: resolvedNetwork,\n description,\n mimeType,\n scheme,\n environment: payments.getEnvironmentName(),\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 // Wrap res.end so settlement runs no matter how the handler responds\n // (res.json, res.send, res.sendFile, res.end, res.pipe → res.end).\n // Previously only res.json was intercepted, so any other response\n // method would deliver the resource without burning credits and\n // without emitting the payment-response receipt header (#1728).\n const originalEnd = res.end.bind(res) as (...args: Parameters<Response['end']>) => Response\n let settlementStarted = false\n\n const runSettlement = (): Promise<void> => {\n return (\n typeof credits === 'function'\n ? Promise.resolve(credits(req, res))\n : Promise.resolve(creditsToVerify)\n )\n .then((creditsToSettle) => {\n paymentContext.creditsToSettle = creditsToSettle\n return payments.facilitator\n .settlePermissions({\n paymentRequired,\n x402AccessToken: token,\n maxAmount: BigInt(creditsToSettle),\n agentRequestId: paymentContext.agentRequestId,\n })\n .then((settlement) => {\n // Only attach the receipt header if headers haven't flushed\n // yet — streaming responses fire writeHead on the first\n // chunk and may have already sent them by the time we land\n // here.\n if (!res.headersSent) {\n const settlementBase64 = Buffer.from(JSON.stringify(settlement)).toString(\n 'base64',\n )\n res.setHeader(X402_HEADERS.PAYMENT_RESPONSE, settlementBase64)\n } else {\n console.warn(\n '[paymentMiddleware] headers already flushed; payment-response receipt not attached',\n )\n }\n if (onAfterSettle) {\n return Promise.resolve(onAfterSettle(req, creditsToSettle, settlement)).then(\n () => undefined,\n )\n }\n return undefined\n })\n })\n .catch((settleError) => {\n console.error('Payment settlement failed:', settleError)\n })\n }\n\n ;(res as unknown as { end: Response['end'] }).end = function (\n this: Response,\n ...args: Parameters<Response['end']>\n ): Response {\n // Only bill on 2xx success. Skipping 3xx avoids charging when the\n // handler redirects (e.g. `res.redirect(...)`), 304 Not Modified,\n // etc. Skipping 4xx/5xx avoids charging when the handler signals\n // failure — including `sendPaymentRequired`'s 402 which lands here.\n const isSuccess = res.statusCode >= 200 && res.statusCode < 300\n if (settlementStarted || !isSuccess) {\n return originalEnd(...args)\n }\n settlementStarted = true\n\n // If the handler streamed before calling end, headers were already\n // flushed. Settle anyway (so we still charge the card) but accept\n // we cannot inject the receipt header.\n if (res.headersSent) {\n void runSettlement()\n return originalEnd(...args)\n }\n\n // Buffered response path: defer the real `end` until settlement\n // finishes so the receipt header makes it into the same response.\n runSettlement().finally(() => {\n originalEnd(...args)\n })\n return res\n } as Response['end']\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"]}