@nevermined-io/payments 1.9.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/base-payments.d.ts +11 -0
- package/dist/api/base-payments.d.ts.map +1 -1
- package/dist/api/base-payments.js +28 -3
- package/dist/api/base-payments.js.map +1 -1
- package/dist/api/nvm-api.d.ts +4 -0
- package/dist/api/nvm-api.d.ts.map +1 -1
- package/dist/api/nvm-api.js +7 -0
- package/dist/api/nvm-api.js.map +1 -1
- package/dist/api/organizations-api/organizations-api.d.ts +19 -1
- package/dist/api/organizations-api/organizations-api.d.ts.map +1 -1
- package/dist/api/organizations-api/organizations-api.js +49 -0
- package/dist/api/organizations-api/organizations-api.js.map +1 -1
- package/dist/api/organizations-api/types.d.ts +27 -0
- package/dist/api/organizations-api/types.d.ts.map +1 -1
- package/dist/api/organizations-api/types.js.map +1 -1
- package/dist/common/api-version.d.ts +1 -1
- package/dist/common/api-version.js +1 -1
- package/dist/common/api-version.js.map +1 -1
- package/dist/common/types.d.ts +9 -4
- package/dist/common/types.d.ts.map +1 -1
- package/dist/common/types.js.map +1 -1
- package/dist/environments.d.ts +12 -0
- package/dist/environments.d.ts.map +1 -1
- package/dist/environments.js +31 -0
- package/dist/environments.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/core/server-manager.d.ts.map +1 -1
- package/dist/mcp/core/server-manager.js +3 -0
- package/dist/mcp/core/server-manager.js.map +1 -1
- package/dist/mcp/http/mcp-handler.d.ts +7 -0
- package/dist/mcp/http/mcp-handler.d.ts.map +1 -1
- package/dist/mcp/http/mcp-handler.js +8 -1
- package/dist/mcp/http/mcp-handler.js.map +1 -1
- package/dist/mcp/http/oauth-metadata.d.ts.map +1 -1
- package/dist/mcp/http/oauth-metadata.js +31 -6
- package/dist/mcp/http/oauth-metadata.js.map +1 -1
- package/dist/mcp/http/oauth-router.d.ts +17 -16
- package/dist/mcp/http/oauth-router.d.ts.map +1 -1
- package/dist/mcp/http/oauth-router.js +56 -28
- package/dist/mcp/http/oauth-router.js.map +1 -1
- package/dist/mpp/codec.d.ts +92 -0
- package/dist/mpp/codec.d.ts.map +1 -0
- package/dist/mpp/codec.js +405 -0
- package/dist/mpp/codec.js.map +1 -0
- package/dist/mpp/errors.d.ts +165 -0
- package/dist/mpp/errors.d.ts.map +1 -0
- package/dist/mpp/errors.js +177 -0
- package/dist/mpp/errors.js.map +1 -0
- package/dist/mpp/fetch.d.ts +196 -0
- package/dist/mpp/fetch.d.ts.map +1 -0
- package/dist/mpp/fetch.js +493 -0
- package/dist/mpp/fetch.js.map +1 -0
- package/dist/mpp/index.d.ts +11 -0
- package/dist/mpp/index.d.ts.map +1 -0
- package/dist/mpp/index.js +20 -0
- package/dist/mpp/index.js.map +1 -0
- package/dist/mpp/mpp-api.d.ts +153 -0
- package/dist/mpp/mpp-api.d.ts.map +1 -0
- package/dist/mpp/mpp-api.js +298 -0
- package/dist/mpp/mpp-api.js.map +1 -0
- package/dist/mpp/types.d.ts +43 -0
- package/dist/mpp/types.d.ts.map +1 -0
- package/dist/mpp/types.js +9 -0
- package/dist/mpp/types.js.map +1 -0
- package/dist/payments.d.ts +13 -0
- package/dist/payments.d.ts.map +1 -1
- package/dist/payments.js +3 -0
- package/dist/payments.js.map +1 -1
- package/dist/x402/delegation-api.d.ts +3 -1
- package/dist/x402/delegation-api.d.ts.map +1 -1
- package/dist/x402/delegation-api.js.map +1 -1
- package/dist/x402/express/index.d.ts +2 -0
- package/dist/x402/express/index.d.ts.map +1 -1
- package/dist/x402/express/index.js +2 -0
- package/dist/x402/express/index.js.map +1 -1
- package/dist/x402/express/middleware.d.ts +33 -1
- package/dist/x402/express/middleware.d.ts.map +1 -1
- package/dist/x402/express/middleware.js +831 -1
- package/dist/x402/express/middleware.js.map +1 -1
- package/dist/x402/express/mpp-support.d.ts +58 -0
- package/dist/x402/express/mpp-support.d.ts.map +1 -0
- package/dist/x402/express/mpp-support.js +67 -0
- package/dist/x402/express/mpp-support.js.map +1 -0
- package/dist/x402/express/raw-body.d.ts +24 -0
- package/dist/x402/express/raw-body.d.ts.map +1 -0
- package/dist/x402/express/raw-body.js +34 -0
- package/dist/x402/express/raw-body.js.map +1 -0
- package/dist/x402/token-request.d.ts +15 -0
- package/dist/x402/token-request.d.ts.map +1 -0
- package/dist/x402/token-request.js +66 -0
- package/dist/x402/token-request.js.map +1 -0
- package/dist/x402/token.d.ts.map +1 -1
- package/dist/x402/token.js +7 -51
- package/dist/x402/token.js.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed errors for the MPP surface.
|
|
3
|
+
*
|
|
4
|
+
* The backend deliberately collapses every rejection reason into one code so
|
|
5
|
+
* the endpoint cannot be used as a forgery oracle. The SDK mirrors that: it
|
|
6
|
+
* does not try to reconstruct why a credential was refused.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Reads the spend accounting off any error raised by `payments.mpp.fetch`.
|
|
10
|
+
*
|
|
11
|
+
* Exported so a caller never has to hand-cast: the field is declared on
|
|
12
|
+
* {@link MppError} but is also attached to the {@link PaymentsError} a
|
|
13
|
+
* caller-constraint guard throws on the re-challenge turn, and those two do
|
|
14
|
+
* not share a base class.
|
|
15
|
+
*
|
|
16
|
+
* A report is attached **only** when at least one credential was presented, so
|
|
17
|
+
* a truthy result always means money may have left, and `undefined` always
|
|
18
|
+
* means nothing was spent. That is what makes `if (mppSpendOf(err))` a usable
|
|
19
|
+
* test rather than one that fires on plain argument validation too.
|
|
20
|
+
*/
|
|
21
|
+
export function mppSpendOf(error) {
|
|
22
|
+
if (typeof error !== 'object' || error === null)
|
|
23
|
+
return undefined;
|
|
24
|
+
const spend = error.spend;
|
|
25
|
+
if (typeof spend !== 'object' || spend === null)
|
|
26
|
+
return undefined;
|
|
27
|
+
return typeof spend.credentialsPresented === 'number'
|
|
28
|
+
? spend
|
|
29
|
+
: undefined;
|
|
30
|
+
}
|
|
31
|
+
export class MppError extends Error {
|
|
32
|
+
constructor(message, code) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.code = code;
|
|
35
|
+
this.name = 'MppError';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** `BCK.MPP.0002` — the deployment has no MPP secret, so MPP routes are off. */
|
|
39
|
+
export class MppNotConfiguredError extends MppError {
|
|
40
|
+
constructor(message = 'MPP is not configured on this environment') {
|
|
41
|
+
super(message, 'BCK.MPP.0002');
|
|
42
|
+
this.name = 'MppNotConfiguredError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** `BCK.MPP.0003` — the credential was refused (replay, forgery, plan, balance). */
|
|
46
|
+
export class MppCredentialRejectedError extends MppError {
|
|
47
|
+
constructor(message = 'MPP credential rejected') {
|
|
48
|
+
super(message, 'BCK.MPP.0003');
|
|
49
|
+
this.name = 'MppCredentialRejectedError';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** `BCK.MPP.0004` — the challenge expired. Fetch a fresh one; do not retry blindly. */
|
|
53
|
+
export class MppChallengeExpiredError extends MppError {
|
|
54
|
+
constructor(message = 'MPP challenge expired') {
|
|
55
|
+
super(message, 'BCK.MPP.0004');
|
|
56
|
+
this.name = 'MppChallengeExpiredError';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** `BCK.MPP.0005` — the body sent does not match the digest sealed in the challenge. */
|
|
60
|
+
export class MppBodyDigestMismatchError extends MppError {
|
|
61
|
+
constructor(message = 'MPP body digest mismatch') {
|
|
62
|
+
super(message, 'BCK.MPP.0005');
|
|
63
|
+
this.name = 'MppBodyDigestMismatchError';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Stable `code` on {@link MppSettlementOutcomeUnknownError}. Not a backend
|
|
68
|
+
* code — no `BCK.MPP.*` prefix, so it can never collide with one.
|
|
69
|
+
*/
|
|
70
|
+
export const MPP_SETTLEMENT_OUTCOME_UNKNOWN_CODE = 'settlement_outcome_unknown';
|
|
71
|
+
/**
|
|
72
|
+
* Raised when settlement's outcome cannot be determined: `settleCredential`'s
|
|
73
|
+
* own outbound deadline firing before any response, or a 2xx whose body could
|
|
74
|
+
* not be read. Settlement is the one MPP call that burns, so in both cases the
|
|
75
|
+
* burn may have already happened even though the caller received nothing
|
|
76
|
+
* usable. Collapsing that into the same `network_error` `MppError` used for
|
|
77
|
+
* "nothing happened" failures (connection refused, DNS failure, a hung
|
|
78
|
+
* challenge/verify call) would let a real burn get logged and treated exactly
|
|
79
|
+
* like one that never occurred — silently corrupting the seller's own
|
|
80
|
+
* accounting on the call that is not safe to shrug off.
|
|
81
|
+
*/
|
|
82
|
+
export class MppSettlementOutcomeUnknownError extends MppError {
|
|
83
|
+
constructor(message = 'MPP settlement outcome unknown: the request timed out before the backend responded, so the credits may or may not have been burned') {
|
|
84
|
+
// No backend issues this code — the condition is detected here — but it
|
|
85
|
+
// still carries one, following the same convention as the other
|
|
86
|
+
// SDK-invented codes (`network_error`, `http_${status}`). `code` is the
|
|
87
|
+
// only DATA-level discriminant on this hierarchy, and this is the branch
|
|
88
|
+
// whose whole point is that misclassifying it corrupts the seller's
|
|
89
|
+
// accounting. Two copies of this package in one dependency tree make
|
|
90
|
+
// `instanceof` false for a genuinely-MPP error, and the check would
|
|
91
|
+
// degrade silently to the "nothing happened" path — which the
|
|
92
|
+
// integration guide tells sellers to rely on. Same across a process or
|
|
93
|
+
// serialization boundary.
|
|
94
|
+
super(message, MPP_SETTLEMENT_OUTCOME_UNKNOWN_CODE);
|
|
95
|
+
this.name = 'MppSettlementOutcomeUnknownError';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Stable `code` on {@link MppSpendOutcomeUnknownError}. Not a backend code, for
|
|
100
|
+
* the same reason as {@link MPP_SETTLEMENT_OUTCOME_UNKNOWN_CODE}.
|
|
101
|
+
*/
|
|
102
|
+
export const MPP_SPEND_OUTCOME_UNKNOWN_CODE = 'spend_outcome_unknown';
|
|
103
|
+
/**
|
|
104
|
+
* The buyer-side mirror of {@link MppSettlementOutcomeUnknownError}: raised
|
|
105
|
+
* when the retry that carries a credential fails at the transport level — a
|
|
106
|
+
* disconnect, a DNS failure, or the caller's own `AbortSignal` firing between
|
|
107
|
+
* the mint and the retry (`init.signal` is re-sent with it).
|
|
108
|
+
*
|
|
109
|
+
* The credential is on the wire by then, so the seller may already have
|
|
110
|
+
* verified and burned it. Left as the raw `TypeError` that `fetch` rejects
|
|
111
|
+
* with, the failure would escape the `catch (e) { if (e instanceof MppError) }`
|
|
112
|
+
* pattern this module and `markdown/mpp-integration.md` both prescribe, and
|
|
113
|
+
* carry no accounting at all: spent, invisible, unrecoverable. Wrapped, it
|
|
114
|
+
* reaches the documented handler with {@link MppSpendReport} attached and the
|
|
115
|
+
* original error preserved on `cause`.
|
|
116
|
+
*
|
|
117
|
+
* A transport failure BEFORE any credential exists is not this error — it stays
|
|
118
|
+
* exactly as `fetch` threw it, because nothing was spent and wrapping it would
|
|
119
|
+
* only obscure a plain network fault.
|
|
120
|
+
*/
|
|
121
|
+
export class MppSpendOutcomeUnknownError extends MppError {
|
|
122
|
+
constructor(message, cause, spend) {
|
|
123
|
+
super(message, MPP_SPEND_OUTCOME_UNKNOWN_CODE);
|
|
124
|
+
this.name = 'MppSpendOutcomeUnknownError';
|
|
125
|
+
this.cause = cause;
|
|
126
|
+
this.spend = spend;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* `BCK.MPP.*` codes a buyer can retry by minting a fresh credential against
|
|
131
|
+
* the NEW challenge the same 402 carries alongside them — as opposed to
|
|
132
|
+
* `BCK.MPP.0003`, which means the credential itself was refused and paying
|
|
133
|
+
* again cannot help.
|
|
134
|
+
*
|
|
135
|
+
* `0004` (expired) is obviously retryable: fetch a fresh challenge, pay it.
|
|
136
|
+
* `0005` (body digest mismatch) is less obvious but equally retryable: the
|
|
137
|
+
* fresh challenge is sealed to the digest of the request that just arrived,
|
|
138
|
+
* so a credential minted against it — presented with the SAME body — would
|
|
139
|
+
* succeed. A buyer gate that only excepts `0004` from a bare
|
|
140
|
+
* `code.startsWith('BCK.MPP.')` check wrongly treats `0005` as terminal and
|
|
141
|
+
* gives up on a request that would have worked on the next attempt.
|
|
142
|
+
*
|
|
143
|
+
* Grouped here, once, so a buyer checks {@link isRetryableMppCode} instead of
|
|
144
|
+
* hardcoding the exception list — and so a future retryable code only needs
|
|
145
|
+
* to be added to this one set.
|
|
146
|
+
*
|
|
147
|
+
* Exported so the buyer's own tests derive their table from this set instead of
|
|
148
|
+
* repeating its members, which is what makes "a code added here is honoured by
|
|
149
|
+
* `payments.mpp.fetch`" an enforced property rather than a convention. It is
|
|
150
|
+
* deliberately NOT re-exported from `./index.js`: a mutable-looking membership
|
|
151
|
+
* list is not something to make public API.
|
|
152
|
+
*/
|
|
153
|
+
export const RETRYABLE_BCK_MPP_CODES = new Set([
|
|
154
|
+
'BCK.MPP.0004',
|
|
155
|
+
'BCK.MPP.0005',
|
|
156
|
+
]);
|
|
157
|
+
/** Whether a `BCK.MPP.*` code means "mint a fresh credential and try again"
|
|
158
|
+
* rather than "this credential was refused; a new one changes nothing". */
|
|
159
|
+
export function isRetryableMppCode(code) {
|
|
160
|
+
return code !== undefined && RETRYABLE_BCK_MPP_CODES.has(code);
|
|
161
|
+
}
|
|
162
|
+
/** Maps a backend error payload onto the typed error hierarchy. */
|
|
163
|
+
export function toMppError(code, message) {
|
|
164
|
+
switch (code) {
|
|
165
|
+
case 'BCK.MPP.0002':
|
|
166
|
+
return new MppNotConfiguredError(message);
|
|
167
|
+
case 'BCK.MPP.0003':
|
|
168
|
+
return new MppCredentialRejectedError(message);
|
|
169
|
+
case 'BCK.MPP.0004':
|
|
170
|
+
return new MppChallengeExpiredError(message);
|
|
171
|
+
case 'BCK.MPP.0005':
|
|
172
|
+
return new MppBodyDigestMismatchError(message);
|
|
173
|
+
default:
|
|
174
|
+
return new MppError(message, code);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/mpp/errors.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Typed errors for the MPP surface.\n *\n * The backend deliberately collapses every rejection reason into one code so\n * the endpoint cannot be used as a forgery oracle. The SDK mirrors that: it\n * does not try to reconstruct why a credential was refused.\n */\n\n/**\n * What a buyer-side failure reports about money already committed.\n *\n * `payments.mpp.fetch` returns this accounting on its success path\n * (`credentialsPresented` / `creditsPresented` on `MppFetchResult`). It is\n * repeated on the ERROR path because that is where a caller needs it most: the\n * credential and its access token are function-local and gone once the helper\n * throws, so an error without these numbers leaves the caller unable to tell\n * whether money left — the one outcome a buyer helper must never produce.\n *\n * Read it with {@link mppSpendOf} rather than casting: it rides on\n * `PaymentsError` too (a `maxCredits` or `planId` guard can fire on the\n * re-challenge turn, after a credential has already been presented).\n */\nexport interface MppSpendReport {\n /** How many credentials were minted and sent before the failure (0, 1 or 2). */\n credentialsPresented: number\n /**\n * Total credits named by the challenges those credentials were minted\n * against, as a decimal string. Present whenever `credentialsPresented > 0`.\n */\n creditsPresented?: string\n /** `id` of the challenge the last credential was minted against, so the caller can correlate it with the seller's side. */\n challengeId?: string\n}\n\n/**\n * Reads the spend accounting off any error raised by `payments.mpp.fetch`.\n *\n * Exported so a caller never has to hand-cast: the field is declared on\n * {@link MppError} but is also attached to the {@link PaymentsError} a\n * caller-constraint guard throws on the re-challenge turn, and those two do\n * not share a base class.\n *\n * A report is attached **only** when at least one credential was presented, so\n * a truthy result always means money may have left, and `undefined` always\n * means nothing was spent. That is what makes `if (mppSpendOf(err))` a usable\n * test rather than one that fires on plain argument validation too.\n */\nexport function mppSpendOf(error: unknown): MppSpendReport | undefined {\n if (typeof error !== 'object' || error === null) return undefined\n const spend = (error as { spend?: unknown }).spend\n if (typeof spend !== 'object' || spend === null) return undefined\n return typeof (spend as MppSpendReport).credentialsPresented === 'number'\n ? (spend as MppSpendReport)\n : undefined\n}\n\nexport class MppError extends Error {\n /**\n * Spend accounting, set when this error was raised after a credential had\n * been minted and presented. Absent means nothing was spent. Prefer\n * {@link mppSpendOf}, which also reads it off a `PaymentsError`.\n */\n spend?: MppSpendReport\n\n constructor(\n message: string,\n readonly code?: string,\n ) {\n super(message)\n this.name = 'MppError'\n }\n}\n\n/** `BCK.MPP.0002` — the deployment has no MPP secret, so MPP routes are off. */\nexport class MppNotConfiguredError extends MppError {\n constructor(message = 'MPP is not configured on this environment') {\n super(message, 'BCK.MPP.0002')\n this.name = 'MppNotConfiguredError'\n }\n}\n\n/** `BCK.MPP.0003` — the credential was refused (replay, forgery, plan, balance). */\nexport class MppCredentialRejectedError extends MppError {\n constructor(message = 'MPP credential rejected') {\n super(message, 'BCK.MPP.0003')\n this.name = 'MppCredentialRejectedError'\n }\n}\n\n/** `BCK.MPP.0004` — the challenge expired. Fetch a fresh one; do not retry blindly. */\nexport class MppChallengeExpiredError extends MppError {\n constructor(message = 'MPP challenge expired') {\n super(message, 'BCK.MPP.0004')\n this.name = 'MppChallengeExpiredError'\n }\n}\n\n/** `BCK.MPP.0005` — the body sent does not match the digest sealed in the challenge. */\nexport class MppBodyDigestMismatchError extends MppError {\n constructor(message = 'MPP body digest mismatch') {\n super(message, 'BCK.MPP.0005')\n this.name = 'MppBodyDigestMismatchError'\n }\n}\n\n/**\n * Stable `code` on {@link MppSettlementOutcomeUnknownError}. Not a backend\n * code — no `BCK.MPP.*` prefix, so it can never collide with one.\n */\nexport const MPP_SETTLEMENT_OUTCOME_UNKNOWN_CODE = 'settlement_outcome_unknown'\n\n/**\n * Raised when settlement's outcome cannot be determined: `settleCredential`'s\n * own outbound deadline firing before any response, or a 2xx whose body could\n * not be read. Settlement is the one MPP call that burns, so in both cases the\n * burn may have already happened even though the caller received nothing\n * usable. Collapsing that into the same `network_error` `MppError` used for\n * \"nothing happened\" failures (connection refused, DNS failure, a hung\n * challenge/verify call) would let a real burn get logged and treated exactly\n * like one that never occurred — silently corrupting the seller's own\n * accounting on the call that is not safe to shrug off.\n */\nexport class MppSettlementOutcomeUnknownError extends MppError {\n constructor(\n message = 'MPP settlement outcome unknown: the request timed out before the backend responded, so the credits may or may not have been burned',\n ) {\n // No backend issues this code — the condition is detected here — but it\n // still carries one, following the same convention as the other\n // SDK-invented codes (`network_error`, `http_${status}`). `code` is the\n // only DATA-level discriminant on this hierarchy, and this is the branch\n // whose whole point is that misclassifying it corrupts the seller's\n // accounting. Two copies of this package in one dependency tree make\n // `instanceof` false for a genuinely-MPP error, and the check would\n // degrade silently to the \"nothing happened\" path — which the\n // integration guide tells sellers to rely on. Same across a process or\n // serialization boundary.\n super(message, MPP_SETTLEMENT_OUTCOME_UNKNOWN_CODE)\n this.name = 'MppSettlementOutcomeUnknownError'\n }\n}\n\n/**\n * Stable `code` on {@link MppSpendOutcomeUnknownError}. Not a backend code, for\n * the same reason as {@link MPP_SETTLEMENT_OUTCOME_UNKNOWN_CODE}.\n */\nexport const MPP_SPEND_OUTCOME_UNKNOWN_CODE = 'spend_outcome_unknown'\n\n/**\n * The buyer-side mirror of {@link MppSettlementOutcomeUnknownError}: raised\n * when the retry that carries a credential fails at the transport level — a\n * disconnect, a DNS failure, or the caller's own `AbortSignal` firing between\n * the mint and the retry (`init.signal` is re-sent with it).\n *\n * The credential is on the wire by then, so the seller may already have\n * verified and burned it. Left as the raw `TypeError` that `fetch` rejects\n * with, the failure would escape the `catch (e) { if (e instanceof MppError) }`\n * pattern this module and `markdown/mpp-integration.md` both prescribe, and\n * carry no accounting at all: spent, invisible, unrecoverable. Wrapped, it\n * reaches the documented handler with {@link MppSpendReport} attached and the\n * original error preserved on `cause`.\n *\n * A transport failure BEFORE any credential exists is not this error — it stays\n * exactly as `fetch` threw it, because nothing was spent and wrapping it would\n * only obscure a plain network fault.\n */\nexport class MppSpendOutcomeUnknownError extends MppError {\n /** The original error `fetch` (or the mint) rejected with. */\n readonly cause?: unknown\n\n constructor(message: string, cause?: unknown, spend?: MppSpendReport) {\n super(message, MPP_SPEND_OUTCOME_UNKNOWN_CODE)\n this.name = 'MppSpendOutcomeUnknownError'\n this.cause = cause\n this.spend = spend\n }\n}\n\n/**\n * What `paymentMiddleware`'s `onAfterSettle` hook receives as its third\n * argument when settlement raised {@link MppSettlementOutcomeUnknownError}.\n * That parameter's declared type is `unknown` (shared with the x402 hook of\n * the same name — out of scope to narrow here), so nothing stops a consumer\n * from casting straight to `MppSettleResult` and silently reading\n * `undefined` for `creditsRedeemed` on this branch. Exporting this shape\n * gives a consumer something concrete to check for instead — e.g.\n * `if ((result as MppSettlementOutcomeUnknown).outcome === 'unknown')` —\n * and documents, at the type level, that the branch exists at all.\n */\nexport interface MppSettlementOutcomeUnknown {\n outcome: 'unknown'\n reason: string\n}\n\n/**\n * `BCK.MPP.*` codes a buyer can retry by minting a fresh credential against\n * the NEW challenge the same 402 carries alongside them — as opposed to\n * `BCK.MPP.0003`, which means the credential itself was refused and paying\n * again cannot help.\n *\n * `0004` (expired) is obviously retryable: fetch a fresh challenge, pay it.\n * `0005` (body digest mismatch) is less obvious but equally retryable: the\n * fresh challenge is sealed to the digest of the request that just arrived,\n * so a credential minted against it — presented with the SAME body — would\n * succeed. A buyer gate that only excepts `0004` from a bare\n * `code.startsWith('BCK.MPP.')` check wrongly treats `0005` as terminal and\n * gives up on a request that would have worked on the next attempt.\n *\n * Grouped here, once, so a buyer checks {@link isRetryableMppCode} instead of\n * hardcoding the exception list — and so a future retryable code only needs\n * to be added to this one set.\n *\n * Exported so the buyer's own tests derive their table from this set instead of\n * repeating its members, which is what makes \"a code added here is honoured by\n * `payments.mpp.fetch`\" an enforced property rather than a convention. It is\n * deliberately NOT re-exported from `./index.js`: a mutable-looking membership\n * list is not something to make public API.\n */\nexport const RETRYABLE_BCK_MPP_CODES: ReadonlySet<string> = new Set([\n 'BCK.MPP.0004',\n 'BCK.MPP.0005',\n])\n\n/** Whether a `BCK.MPP.*` code means \"mint a fresh credential and try again\"\n * rather than \"this credential was refused; a new one changes nothing\". */\nexport function isRetryableMppCode(code: string | undefined): boolean {\n return code !== undefined && RETRYABLE_BCK_MPP_CODES.has(code)\n}\n\n/** Maps a backend error payload onto the typed error hierarchy. */\nexport function toMppError(code: string | undefined, message: string): MppError {\n switch (code) {\n case 'BCK.MPP.0002':\n return new MppNotConfiguredError(message)\n case 'BCK.MPP.0003':\n return new MppCredentialRejectedError(message)\n case 'BCK.MPP.0004':\n return new MppChallengeExpiredError(message)\n case 'BCK.MPP.0005':\n return new MppBodyDigestMismatchError(message)\n default:\n return new MppError(message, code)\n }\n}\n"]}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The buyer half of MPP: pay a challenged endpoint with an existing Nevermined
|
|
3
|
+
* delegation.
|
|
4
|
+
*
|
|
5
|
+
* The buyer learns nothing about MPP beyond calling this instead of `fetch`.
|
|
6
|
+
* The plan comes out of the challenge, the credential is built from the
|
|
7
|
+
* challenge plus an MPP-domain access token, and the request is retried once.
|
|
8
|
+
*
|
|
9
|
+
* Two error families are used deliberately:
|
|
10
|
+
* - {@link PaymentsError} (`code: 'validation'`) for guards this call refuses
|
|
11
|
+
* to even attempt — a bad argument, a challenge that violates a
|
|
12
|
+
* caller-supplied constraint (`planId`, `maxCredits`), or a body that
|
|
13
|
+
* cannot be replayed. None of these mean a payment failed; nothing was ever
|
|
14
|
+
* attempted.
|
|
15
|
+
* - {@link MppError} (and its typed subclasses) for what the wire actually
|
|
16
|
+
* said: a rejected credential, a malformed challenge, an MPP-disabled
|
|
17
|
+
* environment. A caller branching on `instanceof MppError` to mean
|
|
18
|
+
* "the payment failed" gets exactly that, and no more.
|
|
19
|
+
*/
|
|
20
|
+
import type { DelegationConfig, X402TokenOptions } from '../common/types.js';
|
|
21
|
+
import type { MppReceipt } from './types.js';
|
|
22
|
+
/**
|
|
23
|
+
* Options for {@link mppFetch} / `MppAPI.fetch`.
|
|
24
|
+
*
|
|
25
|
+
* @experimental The MPP buyer surface may change in a minor release.
|
|
26
|
+
*
|
|
27
|
+
* The request's `init.body`, if any, must be replayable **if the endpoint may
|
|
28
|
+
* challenge the request**: a `ReadableStream` body throws a typed
|
|
29
|
+
* {@link PaymentsError} once a 402 challenge actually requires a retry, since
|
|
30
|
+
* the stream cannot be resent. A request that is never challenged sends a
|
|
31
|
+
* stream body exactly once, exactly like plain `fetch`.
|
|
32
|
+
*
|
|
33
|
+
* This helper mints `nvm:erc4337` access tokens only in this release — a
|
|
34
|
+
* buyer holding an `nvm:card-delegation` delegation cannot use it yet.
|
|
35
|
+
*/
|
|
36
|
+
export interface MppFetchOptions {
|
|
37
|
+
/** The delegation that backs the payment — the same one x402 uses. */
|
|
38
|
+
delegationConfig: DelegationConfig;
|
|
39
|
+
/**
|
|
40
|
+
* Overrides the agent id the seller's challenge names — the minted token is
|
|
41
|
+
* addressed to this agent id instead of whatever the challenge carries.
|
|
42
|
+
* Unlike `planId`, this is not a guard: a mismatch is not checked or
|
|
43
|
+
* refused, it simply replaces what the seller asked for. Leave unset to
|
|
44
|
+
* honor the challenge as issued.
|
|
45
|
+
*/
|
|
46
|
+
agentId?: string;
|
|
47
|
+
/** Fail before minting if the challenge names a different plan than this. */
|
|
48
|
+
planId?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Budget for the WHOLE call, not for one challenge: the helper refuses to
|
|
51
|
+
* mint whenever the credits named so far plus the credits this challenge
|
|
52
|
+
* asks for would exceed it. A seller unilaterally names the price, and a
|
|
53
|
+
* re-challenge names it again — a per-turn cap would therefore bound each
|
|
54
|
+
* turn at `maxCredits` and the call at twice it, which is not what "cap"
|
|
55
|
+
* reads as. `creditsPresented` on the result is the same running total, so
|
|
56
|
+
* the two always speak about the same number.
|
|
57
|
+
*
|
|
58
|
+
* Must be a non-negative integer (a decimal string, a safe integer, or a
|
|
59
|
+
* `bigint`); anything else is refused with a {@link PaymentsError} at entry,
|
|
60
|
+
* before the first request, rather than mid-flight on the first 402.
|
|
61
|
+
*/
|
|
62
|
+
maxCredits?: string | number | bigint;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* What {@link mppFetch} / `MppAPI.fetch` resolves to.
|
|
66
|
+
*
|
|
67
|
+
* @experimental Fields may be added or change meaning in a minor release —
|
|
68
|
+
* `settled` and `creditsPresented` both did during review.
|
|
69
|
+
*/
|
|
70
|
+
export interface MppFetchResult {
|
|
71
|
+
/** The final response — the paid one when a payment happened. */
|
|
72
|
+
response: Response;
|
|
73
|
+
/**
|
|
74
|
+
* The decoded `Payment-Receipt`, when the server returned one and it
|
|
75
|
+
* decoded cleanly. A malformed receipt never throws — it leaves this
|
|
76
|
+
* absent (with a console warning) rather than destroying a response the
|
|
77
|
+
* caller already paid for.
|
|
78
|
+
*/
|
|
79
|
+
receipt?: MppReceipt;
|
|
80
|
+
/**
|
|
81
|
+
* Whether the endpoint returned settlement evidence: a `Payment-Receipt` that
|
|
82
|
+
* decoded cleanly and does not state failure outright. Never derived from the
|
|
83
|
+
* HTTP status.
|
|
84
|
+
*
|
|
85
|
+
* `receipt.status` is read asymmetrically, on purpose. Success is NOT
|
|
86
|
+
* recognized — `'success'` is the only value with agreement behind it, so
|
|
87
|
+
* treating an unrecognized `'ok'`/`'completed'` as failure would report an
|
|
88
|
+
* unpaid call that was in fact paid. An explicit negative (`'failed'`,
|
|
89
|
+
* `'failure'`, `'declined'`, `'error'`) is a different thing from an unknown
|
|
90
|
+
* vocabulary, and is excluded: `paid: true` on a receipt that says the
|
|
91
|
+
* settlement failed is wrong in the one direction this field must never be
|
|
92
|
+
* wrong. Anything else stays `settled: true` with `receipt` on the result, so
|
|
93
|
+
* a caller paying third parties can judge the value itself.
|
|
94
|
+
*
|
|
95
|
+
* This SDK's own seller attaches a receipt only when settlement succeeded, so
|
|
96
|
+
* the distinction is third-party exposure rather than in-ecosystem.
|
|
97
|
+
*/
|
|
98
|
+
settled: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* How many credentials were minted and presented to the endpoint during
|
|
101
|
+
* this call (0, 1 or 2). This is NOT the same as `settled`:
|
|
102
|
+
* `credentialsPresented > 0` with `settled: false` means the seller may
|
|
103
|
+
* already have burned credits for a credential whose fate is unknown to
|
|
104
|
+
* the caller — treat that as "do not blindly retry", not as "nothing
|
|
105
|
+
* happened".
|
|
106
|
+
*/
|
|
107
|
+
credentialsPresented: number;
|
|
108
|
+
/**
|
|
109
|
+
* TOTAL credits named by every challenge a credential was minted against
|
|
110
|
+
* during this call, as a decimal string — summed, not the last turn's
|
|
111
|
+
* amount, since a re-challenge is free to name a different price and the
|
|
112
|
+
* caller is accounting for the call. Present whenever
|
|
113
|
+
* `credentialsPresented > 0`.
|
|
114
|
+
*
|
|
115
|
+
* It is an **upper bound** on what may have burned, never a lower one. A
|
|
116
|
+
* seller that answers a retryable code while replaying the identical
|
|
117
|
+
* challenge id gets a second credential minted against that same challenge
|
|
118
|
+
* (a code decides alone — see {@link mppFetch}), and against a seller that
|
|
119
|
+
* keys single-use on the challenge id, as this SDK's middleware does, that
|
|
120
|
+
* second credential is refused as a replay and burns nothing. The count is
|
|
121
|
+
* deliberately not lowered for it: this field answers "what could have left",
|
|
122
|
+
* and guessing which of a remote's credentials it honoured would answer a
|
|
123
|
+
* question the buyer cannot see.
|
|
124
|
+
*/
|
|
125
|
+
creditsPresented?: string;
|
|
126
|
+
/**
|
|
127
|
+
* Whether a credential was presented AND the final response looks
|
|
128
|
+
* successful: `response.ok && settled`. A 2xx with no receipt (a
|
|
129
|
+
* settlement that silently failed) and a non-2xx with a receipt (settle-
|
|
130
|
+
* then-error) are both `paid: false` — check `settled` and
|
|
131
|
+
* `credentialsPresented` for the honest picture in either case.
|
|
132
|
+
*
|
|
133
|
+
* `ok: true, paid: false, credentialsPresented: 1` is a ROUTINE outcome, not
|
|
134
|
+
* an exotic one: a seller whose handler streams or flushes has already sent
|
|
135
|
+
* its headers when settlement runs, so `Payment-Receipt` cannot be attached
|
|
136
|
+
* to a response that is already on the wire (this SDK's own middleware says
|
|
137
|
+
* so where it settles detached). The credits were burned. Never read this
|
|
138
|
+
* combination as "the payment did not happen" and retry.
|
|
139
|
+
*/
|
|
140
|
+
paid: boolean;
|
|
141
|
+
}
|
|
142
|
+
/** Mints an MPP access token for a plan. Supplied by `MppAPI`. */
|
|
143
|
+
export type MppTokenMinter = (planId: string, agentId?: string, tokenOptions?: X402TokenOptions) => Promise<{
|
|
144
|
+
accessToken: string;
|
|
145
|
+
}>;
|
|
146
|
+
/**
|
|
147
|
+
* Performs the request, paying an MPP challenge if one comes back.
|
|
148
|
+
*
|
|
149
|
+
* At most one re-challenge cycle is followed: a seller that keeps challenging
|
|
150
|
+
* a freshly paid credential is not going to be satisfied by looping, and a
|
|
151
|
+
* loop would burn a credential per turn.
|
|
152
|
+
*
|
|
153
|
+
* The default on a retry-turn 402 is to STOP, not to pay again. **A code, when
|
|
154
|
+
* present, decides alone**: one {@link isRetryableMppCode} accepts
|
|
155
|
+
* (`BCK.MPP.0004` expired, `BCK.MPP.0005` body-digest mismatch) is retried,
|
|
156
|
+
* every other code is terminal — including a non-`BCK.MPP.*` one, e.g. the
|
|
157
|
+
* `network_error`/`http_500`-shaped code `MppAPI.post` can synthesize and this
|
|
158
|
+
* repo's own seller forwards. The challenge id is not consulted on that path,
|
|
159
|
+
* so a retryable code replaying the identical id does re-mint; `maxCredits`,
|
|
160
|
+
* not id-freshness, is what bounds what that can cost.
|
|
161
|
+
*
|
|
162
|
+
* **With no code**, freshness is the whole signal: a challenge whose `id`
|
|
163
|
+
* differs from the one just presented is a real re-challenge and is retried
|
|
164
|
+
* once, while the identical id replayed, an unparseable challenge or an
|
|
165
|
+
* unreadable body are terminal — a credential already proven invalid is never
|
|
166
|
+
* paid for twice.
|
|
167
|
+
*
|
|
168
|
+
* ### What throws and what comes back as a 402
|
|
169
|
+
*
|
|
170
|
+
* Only a rejection the remote NAMED throws. Three dead ends RETURN the 402
|
|
171
|
+
* instead, because the response is evidence the caller may need and throwing
|
|
172
|
+
* would discard it:
|
|
173
|
+
*
|
|
174
|
+
* 1. A 402 with no USABLE `Payment` challenge (either turn): no
|
|
175
|
+
* `WWW-Authenticate`, another scheme, or a `Payment` challenge missing a
|
|
176
|
+
* required param — {@link parseChallengeHeader} yields nothing for all
|
|
177
|
+
* three, so a seller that announced `Payment` and then sent it malformed
|
|
178
|
+
* lands here too, not only one that does not speak MPP.
|
|
179
|
+
* `credentialsPresented: 0`.
|
|
180
|
+
* 2. A retry-turn 402 that IS retryable but carries no challenge to retry
|
|
181
|
+
* against, so the next turn finds nothing to mint for.
|
|
182
|
+
* 3. The one re-challenge cycle spent: two credentials presented and the
|
|
183
|
+
* seller still answering 402.
|
|
184
|
+
*
|
|
185
|
+
* In cases 2 and 3 a credential WAS presented and may have been burned, so the
|
|
186
|
+
* result is `paid: false` with a non-zero `credentialsPresented` and the last
|
|
187
|
+
* 402 as `response`. Checking `response.ok` is therefore not optional — a
|
|
188
|
+
* resolved promise does not mean the request was paid for.
|
|
189
|
+
*
|
|
190
|
+
* Every throw raised after a credential was presented carries the same
|
|
191
|
+
* accounting as {@link MppSpendReport}, readable with `mppSpendOf(error)`,
|
|
192
|
+
* including a transport failure on the credential-bearing retry (wrapped as
|
|
193
|
+
* {@link MppSpendOutcomeUnknownError} so `instanceof MppError` catches it).
|
|
194
|
+
*/
|
|
195
|
+
export declare function mppFetch(mintToken: MppTokenMinter, input: string | URL, init: RequestInit | undefined, options: MppFetchOptions): Promise<MppFetchResult>;
|
|
196
|
+
//# sourceMappingURL=fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/mpp/fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAI5E,OAAO,KAAK,EAAgB,UAAU,EAAE,MAAM,YAAY,CAAA;AAE1D;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,gBAAgB,EAAE,gBAAgB,CAAA;IAClC;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;CACtC;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,QAAQ,EAAE,QAAQ,CAAA;IAClB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,UAAU,CAAA;IACpB;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,EAAE,OAAO,CAAA;IAChB;;;;;;;OAOG;IACH,oBAAoB,EAAE,MAAM,CAAA;IAC5B;;;;;;;;;;;;;;;;OAgBG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;;;;;;OAaG;IACH,IAAI,EAAE,OAAO,CAAA;CACd;AAED,kEAAkE;AAClE,MAAM,MAAM,cAAc,GAAG,CAC3B,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,gBAAgB,KAC5B,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAuPrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,cAAc,EACzB,KAAK,EAAE,MAAM,GAAG,GAAG,EACnB,IAAI,EAAE,WAAW,GAAG,SAAS,EAC7B,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,cAAc,CAAC,CAoKzB"}
|