mppx 0.8.12 → 0.8.14
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/CHANGELOG.md +34 -0
- package/README.md +16 -1
- package/dist/Errors.d.ts +10 -0
- package/dist/Errors.d.ts.map +1 -1
- package/dist/Errors.js +11 -2
- package/dist/Errors.js.map +1 -1
- package/dist/Method.d.ts +105 -3
- package/dist/Method.d.ts.map +1 -1
- package/dist/Method.js +102 -2
- package/dist/Method.js.map +1 -1
- package/dist/cli/account.d.ts +88 -0
- package/dist/cli/account.d.ts.map +1 -1
- package/dist/cli/account.js +39 -9
- package/dist/cli/account.js.map +1 -1
- package/dist/cli/cli.d.ts +2 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +59 -2
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/sessions/Manager.d.ts +31 -0
- package/dist/cli/sessions/Manager.d.ts.map +1 -0
- package/dist/cli/sessions/Manager.js +56 -0
- package/dist/cli/sessions/Manager.js.map +1 -0
- package/dist/cli/sessions/commands.d.ts +36 -0
- package/dist/cli/sessions/commands.d.ts.map +1 -0
- package/dist/cli/sessions/commands.js +407 -0
- package/dist/cli/sessions/commands.js.map +1 -0
- package/dist/cli/sessions/request.d.ts +35 -0
- package/dist/cli/sessions/request.d.ts.map +1 -0
- package/dist/cli/sessions/request.js +271 -0
- package/dist/cli/sessions/request.js.map +1 -0
- package/dist/cli/sessions/store.d.ts +134 -0
- package/dist/cli/sessions/store.d.ts.map +1 -0
- package/dist/cli/sessions/store.js +723 -0
- package/dist/cli/sessions/store.js.map +1 -0
- package/dist/cli/utils.d.ts +2 -0
- package/dist/cli/utils.d.ts.map +1 -1
- package/dist/cli/utils.js +12 -0
- package/dist/cli/utils.js.map +1 -1
- package/dist/cli/validate/index.d.ts.map +1 -1
- package/dist/cli/validate/index.js +12 -6
- package/dist/cli/validate/index.js.map +1 -1
- package/dist/cli/validate/messages.d.ts +2 -0
- package/dist/cli/validate/messages.d.ts.map +1 -0
- package/dist/cli/validate/messages.js +8 -0
- package/dist/cli/validate/messages.js.map +1 -0
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +12 -2
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/client/internal/MethodChallenge.d.ts +17 -0
- package/dist/client/internal/MethodChallenge.d.ts.map +1 -0
- package/dist/client/internal/MethodChallenge.js +11 -0
- package/dist/client/internal/MethodChallenge.js.map +1 -0
- package/dist/client/node.d.ts +29 -0
- package/dist/client/node.d.ts.map +1 -0
- package/dist/client/node.js +177 -0
- package/dist/client/node.js.map +1 -0
- package/dist/internal/types.d.ts +24 -0
- package/dist/internal/types.d.ts.map +1 -1
- package/dist/server/Mppx.d.ts +23 -4
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +91 -82
- package/dist/server/Mppx.js.map +1 -1
- package/dist/stripe/server/Charge.d.ts +8 -4
- package/dist/stripe/server/Charge.d.ts.map +1 -1
- package/dist/stripe/server/Charge.js.map +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/stripe/server/internal/html.gen.js.map +1 -1
- package/dist/tempo/client/Methods.d.ts +2 -1
- package/dist/tempo/client/Methods.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.d.ts +1 -0
- package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.js +7 -2
- package/dist/tempo/internal/fee-payer.js.map +1 -1
- package/dist/tempo/internal/types.d.ts +10 -1
- package/dist/tempo/internal/types.d.ts.map +1 -1
- package/dist/tempo/legacy/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/legacy/client/ChannelOps.js +2 -1
- package/dist/tempo/legacy/client/ChannelOps.js.map +1 -1
- package/dist/tempo/legacy/client/Session.d.ts +2 -2
- package/dist/tempo/legacy/server/Session.d.ts +3 -3
- package/dist/tempo/legacy/server/Session.d.ts.map +1 -1
- package/dist/tempo/legacy/server/Session.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts +40 -78
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +289 -209
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +13 -23
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Methods.js +6 -2
- package/dist/tempo/server/Methods.js.map +1 -1
- package/dist/tempo/server/Relay.d.ts +48 -0
- package/dist/tempo/server/Relay.d.ts.map +1 -0
- package/dist/tempo/server/Relay.js +177 -0
- package/dist/tempo/server/Relay.js.map +1 -0
- package/dist/tempo/server/SponsorBudget.d.ts +59 -0
- package/dist/tempo/server/SponsorBudget.d.ts.map +1 -0
- package/dist/tempo/server/SponsorBudget.js +144 -0
- package/dist/tempo/server/SponsorBudget.js.map +1 -0
- package/dist/tempo/server/Subscription.d.ts +3 -2
- package/dist/tempo/server/Subscription.d.ts.map +1 -1
- package/dist/tempo/server/Subscription.js.map +1 -1
- package/dist/tempo/server/index.d.ts +1 -1
- package/dist/tempo/server/index.d.ts.map +1 -1
- package/dist/tempo/server/index.js.map +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/html.gen.js.map +1 -1
- package/dist/tempo/session/Snapshot.d.ts +9 -0
- package/dist/tempo/session/Snapshot.d.ts.map +1 -1
- package/dist/tempo/session/Snapshot.js +6 -0
- package/dist/tempo/session/Snapshot.js.map +1 -1
- package/dist/tempo/session/client/ChannelOps.d.ts +7 -1
- package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/session/client/ChannelOps.js +17 -3
- package/dist/tempo/session/client/ChannelOps.js.map +1 -1
- package/dist/tempo/session/client/CredentialState.d.ts +26 -2
- package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
- package/dist/tempo/session/client/CredentialState.js +99 -16
- package/dist/tempo/session/client/CredentialState.js.map +1 -1
- package/dist/tempo/session/client/Runtime.d.ts +8 -0
- package/dist/tempo/session/client/Runtime.d.ts.map +1 -1
- package/dist/tempo/session/client/Runtime.js +17 -0
- package/dist/tempo/session/client/Runtime.js.map +1 -1
- package/dist/tempo/session/client/Session.d.ts +16 -6
- package/dist/tempo/session/client/Session.d.ts.map +1 -1
- package/dist/tempo/session/client/Session.js +93 -36
- package/dist/tempo/session/client/Session.js.map +1 -1
- package/dist/tempo/session/client/SessionManager.d.ts +10 -3
- package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/session/client/SessionManager.js +176 -50
- package/dist/tempo/session/client/SessionManager.js.map +1 -1
- package/dist/tempo/session/client/Transports.d.ts +18 -7
- package/dist/tempo/session/client/Transports.d.ts.map +1 -1
- package/dist/tempo/session/client/Transports.js +81 -33
- package/dist/tempo/session/client/Transports.js.map +1 -1
- package/dist/tempo/session/client/internal/SessionManager.d.ts +19 -0
- package/dist/tempo/session/client/internal/SessionManager.d.ts.map +1 -0
- package/dist/tempo/session/client/internal/SessionManager.js +13 -0
- package/dist/tempo/session/client/internal/SessionManager.js.map +1 -0
- package/dist/tempo/session/precompile/Chain.d.ts.map +1 -1
- package/dist/tempo/session/precompile/Chain.js +109 -12
- package/dist/tempo/session/precompile/Chain.js.map +1 -1
- package/dist/tempo/session/precompile/Channel.d.ts +2 -0
- package/dist/tempo/session/precompile/Channel.d.ts.map +1 -1
- package/dist/tempo/session/precompile/Channel.js +4 -0
- package/dist/tempo/session/precompile/Channel.js.map +1 -1
- package/dist/tempo/session/server/CredentialVerification.d.ts +3 -0
- package/dist/tempo/session/server/CredentialVerification.d.ts.map +1 -1
- package/dist/tempo/session/server/CredentialVerification.js +14 -0
- package/dist/tempo/session/server/CredentialVerification.js.map +1 -1
- package/dist/tempo/session/server/MeteredStream.d.ts +7 -1
- package/dist/tempo/session/server/MeteredStream.d.ts.map +1 -1
- package/dist/tempo/session/server/MeteredStream.js +5 -4
- package/dist/tempo/session/server/MeteredStream.js.map +1 -1
- package/dist/tempo/session/server/RequestState.d.ts +5 -1
- package/dist/tempo/session/server/RequestState.d.ts.map +1 -1
- package/dist/tempo/session/server/RequestState.js +13 -5
- package/dist/tempo/session/server/RequestState.js.map +1 -1
- package/dist/tempo/session/server/Session.d.ts +21 -6
- package/dist/tempo/session/server/Session.d.ts.map +1 -1
- package/dist/tempo/session/server/Session.js +4 -1
- package/dist/tempo/session/server/Session.js.map +1 -1
- package/dist/tempo/session/server/Settlement.d.ts +20 -0
- package/dist/tempo/session/server/Settlement.d.ts.map +1 -1
- package/dist/tempo/session/server/Settlement.js +20 -0
- package/dist/tempo/session/server/Settlement.js.map +1 -1
- package/dist/tempo/session/server/Ws.d.ts +2 -0
- package/dist/tempo/session/server/Ws.d.ts.map +1 -1
- package/dist/tempo/session/server/Ws.js.map +1 -1
- package/dist/tempo/session/server/index.d.ts +1 -1
- package/dist/tempo/session/server/index.d.ts.map +1 -1
- package/dist/tempo/subscription/KeyAuthorization.d.ts +21 -21
- package/dist/validation/core.d.ts.map +1 -1
- package/dist/validation/core.js +6 -2
- package/dist/validation/core.js.map +1 -1
- package/dist/viem/Client.d.ts.map +1 -1
- package/dist/viem/Client.js +24 -17
- package/dist/viem/Client.js.map +1 -1
- package/package.json +6 -1
- package/src/Errors.test.ts +23 -0
- package/src/Errors.ts +21 -2
- package/src/Method.test.ts +102 -1
- package/src/Method.ts +241 -5
- package/src/cli/account.ts +45 -10
- package/src/cli/cli.test.ts +165 -70
- package/src/cli/cli.ts +67 -2
- package/src/cli/mcp.test.ts +11 -0
- package/src/cli/sessions/Manager.test.ts +249 -0
- package/src/cli/sessions/Manager.ts +93 -0
- package/src/cli/sessions/commands.ts +444 -0
- package/src/cli/sessions/request.test.ts +51 -0
- package/src/cli/sessions/request.ts +353 -0
- package/src/cli/sessions/store.test.ts +581 -0
- package/src/cli/sessions/store.ts +940 -0
- package/src/cli/utils.test.ts +23 -0
- package/src/cli/utils.ts +10 -0
- package/src/cli/validate/index.ts +12 -14
- package/src/cli/validate/messages.ts +7 -0
- package/src/cli/validate.test.ts +38 -5
- package/src/client/Mppx.test-d.ts +3 -3
- package/src/client/internal/Fetch.ts +16 -7
- package/src/client/internal/MethodChallenge.ts +30 -0
- package/src/client/node.test.ts +115 -0
- package/src/client/node.ts +247 -0
- package/src/internal/types.test-d.ts +21 -0
- package/src/internal/types.ts +33 -0
- package/src/server/Mppx.test-d.ts +44 -0
- package/src/server/Mppx.test.ts +205 -0
- package/src/server/Mppx.ts +167 -103
- package/src/stripe/Methods.test.ts +10 -0
- package/src/stripe/server/Charge.test-d.ts +66 -0
- package/src/stripe/server/Charge.ts +8 -5
- package/src/stripe/server/internal/html.gen.ts +1 -1
- package/src/tempo/PublicExports.test-d.ts +20 -0
- package/src/tempo/internal/fee-payer.ts +7 -3
- package/src/tempo/internal/types.ts +13 -2
- package/src/tempo/legacy/client/ChannelOps.test.ts +7 -0
- package/src/tempo/legacy/client/ChannelOps.ts +2 -1
- package/src/tempo/legacy/server/Defaults.test-d.ts +9 -0
- package/src/tempo/legacy/server/Session.ts +6 -5
- package/src/tempo/server/Charge.test.ts +337 -63
- package/src/tempo/server/Charge.ts +440 -270
- package/src/tempo/server/Methods.ts +16 -4
- package/src/tempo/server/Relay.test.ts +523 -0
- package/src/tempo/server/Relay.ts +288 -0
- package/src/tempo/server/SponsorBudget.test.ts +125 -0
- package/src/tempo/server/SponsorBudget.ts +213 -0
- package/src/tempo/server/Subscription.ts +6 -4
- package/src/tempo/server/index.ts +5 -1
- package/src/tempo/server/internal/html.gen.ts +1 -1
- package/src/tempo/session/README.md +201 -0
- package/src/tempo/session/Snapshot.ts +21 -0
- package/src/tempo/session/client/ChannelOps.test.ts +55 -0
- package/src/tempo/session/client/ChannelOps.ts +21 -3
- package/src/tempo/session/client/CredentialState.test.ts +2 -2
- package/src/tempo/session/client/CredentialState.ts +159 -16
- package/src/tempo/session/client/Runtime.test.ts +12 -0
- package/src/tempo/session/client/Runtime.ts +22 -0
- package/src/tempo/session/client/Session.test.ts +238 -5
- package/src/tempo/session/client/Session.ts +130 -41
- package/src/tempo/session/client/SessionManager.test.ts +239 -8
- package/src/tempo/session/client/SessionManager.ts +204 -54
- package/src/tempo/session/client/Transports.test.ts +181 -8
- package/src/tempo/session/client/Transports.ts +127 -45
- package/src/tempo/session/client/internal/SessionManager.ts +36 -0
- package/src/tempo/session/precompile/Chain.integration.test.ts +19 -1
- package/src/tempo/session/precompile/Chain.test.ts +223 -10
- package/src/tempo/session/precompile/Chain.ts +119 -12
- package/src/tempo/session/precompile/Channel.test.ts +9 -0
- package/src/tempo/session/precompile/Channel.ts +5 -0
- package/src/tempo/session/server/CredentialVerification.ts +18 -1
- package/src/tempo/session/server/MeteredStream.ts +12 -5
- package/src/tempo/session/server/RequestState.test.ts +47 -4
- package/src/tempo/session/server/RequestState.ts +15 -8
- package/src/tempo/session/server/Session.test.ts +461 -0
- package/src/tempo/session/server/Session.ts +31 -8
- package/src/tempo/session/server/Settlement.ts +45 -0
- package/src/tempo/session/server/Ws.test.ts +113 -0
- package/src/tempo/session/server/Ws.ts +2 -0
- package/src/tempo/session/server/index.ts +2 -0
- package/src/validation/core.ts +7 -1
- package/src/viem/Client.test.ts +44 -1
- package/src/viem/Client.ts +31 -17
package/src/server/Mppx.ts
CHANGED
|
@@ -9,7 +9,7 @@ import * as Expires from '../Expires.js'
|
|
|
9
9
|
import * as AcceptPayment from '../internal/AcceptPayment.js'
|
|
10
10
|
import * as Env from '../internal/env.js'
|
|
11
11
|
import type { MaybePromise } from '../internal/types.js'
|
|
12
|
-
import
|
|
12
|
+
import * as Method from '../Method.js'
|
|
13
13
|
import * as PaymentRequest from '../PaymentRequest.js'
|
|
14
14
|
import type * as Receipt from '../Receipt.js'
|
|
15
15
|
import * as x402_Header from '../x402/Header.js'
|
|
@@ -248,14 +248,18 @@ export type Mppx<
|
|
|
248
248
|
/**
|
|
249
249
|
* Verify a credential string or object end-to-end: deserialize,
|
|
250
250
|
* HMAC-check, match to a registered method, validate payload schema,
|
|
251
|
-
* check expiry, and call the method's verify function.
|
|
251
|
+
* check expiry, and call the method's broadcast or legacy verify function.
|
|
252
252
|
*
|
|
253
|
-
* Method verification can
|
|
253
|
+
* Method verification can broadcast payments and persist state. For example,
|
|
254
254
|
* subscription credentials may activate or renew a subscription. Failed
|
|
255
255
|
* standalone verification emits `payment.failed` once a credential challenge
|
|
256
256
|
* can be parsed; strings that cannot be deserialized have no challenge
|
|
257
257
|
* context to report.
|
|
258
258
|
*
|
|
259
|
+
* @deprecated Use `broadcastCredential()` instead. This legacy name maps
|
|
260
|
+
* to the same mutating operation and does not provide validation-only
|
|
261
|
+
* semantics.
|
|
262
|
+
*
|
|
259
263
|
* @example
|
|
260
264
|
* ```ts
|
|
261
265
|
* const receipt = await mppx.verifyCredential('eyJjaGFsbGVuZ2...')
|
|
@@ -267,6 +271,27 @@ export type Mppx<
|
|
|
267
271
|
credential: string | Credential.Credential,
|
|
268
272
|
options?: VerifyCredentialOptions | undefined,
|
|
269
273
|
): Promise<Receipt.Receipt>
|
|
274
|
+
/**
|
|
275
|
+
* Validate a credential without consuming or broadcasting it.
|
|
276
|
+
*
|
|
277
|
+
* This is an advisory pre-check. Methods that support it must not write
|
|
278
|
+
* replay state, sign fee-payer transactions, broadcast, or otherwise
|
|
279
|
+
* mutate payment state. Use `broadcastCredential()` when accepting payment.
|
|
280
|
+
*/
|
|
281
|
+
validateCredential(
|
|
282
|
+
credential: string | Credential.Credential,
|
|
283
|
+
options?: VerifyCredentialOptions | undefined,
|
|
284
|
+
): Promise<Method.Validation>
|
|
285
|
+
/**
|
|
286
|
+
* Re-validates and broadcasts a credential, returning a receipt.
|
|
287
|
+
*
|
|
288
|
+
* `verifyCredential()` is retained as a backwards-compatible alias for
|
|
289
|
+
* this mutating path.
|
|
290
|
+
*/
|
|
291
|
+
broadcastCredential(
|
|
292
|
+
credential: string | Credential.Credential,
|
|
293
|
+
options?: VerifyCredentialOptions | undefined,
|
|
294
|
+
): Promise<Receipt.Receipt>
|
|
270
295
|
}
|
|
271
296
|
|
|
272
297
|
/** Extracts the transport override from a method, if any. */
|
|
@@ -292,7 +317,9 @@ const reservedMppxKeyValues = [
|
|
|
292
317
|
'onPaymentFailed',
|
|
293
318
|
'onPaymentSuccess',
|
|
294
319
|
'realm',
|
|
320
|
+
'broadcastCredential',
|
|
295
321
|
'transport',
|
|
322
|
+
'validateCredential',
|
|
296
323
|
'verifyCredential',
|
|
297
324
|
] as const
|
|
298
325
|
|
|
@@ -457,9 +484,11 @@ export function create<
|
|
|
457
484
|
preflight: mi.preflight as never,
|
|
458
485
|
request: mi.request as never,
|
|
459
486
|
respond: mi.respond as never,
|
|
487
|
+
broadcast: mi.broadcast as never,
|
|
460
488
|
secretKey,
|
|
461
489
|
stableBinding: mi.stableBinding as never,
|
|
462
490
|
transport: (mi.transport ?? transport) as never,
|
|
491
|
+
validate: mi.validate as never,
|
|
463
492
|
verify: mi.verify as never,
|
|
464
493
|
})
|
|
465
494
|
const wireKey = `${mi.name}/${mi.intent}`
|
|
@@ -496,125 +525,118 @@ export function create<
|
|
|
496
525
|
})
|
|
497
526
|
}
|
|
498
527
|
|
|
499
|
-
|
|
500
|
-
async function verifyCredentialFn(
|
|
528
|
+
async function prepareStandaloneCredential(
|
|
501
529
|
input: string | Credential.Credential,
|
|
502
|
-
options
|
|
503
|
-
|
|
530
|
+
options: VerifyCredentialOptions | undefined,
|
|
531
|
+
parameters: { emitFailures?: boolean | undefined; requireValidate?: boolean | undefined } = {},
|
|
532
|
+
) {
|
|
504
533
|
const credential = hydrateCredentialMeta(
|
|
505
534
|
typeof input === 'string' ? Credential.deserialize(input) : input,
|
|
506
535
|
)
|
|
507
536
|
|
|
508
|
-
|
|
509
|
-
// when multiple handlers intentionally share the same wire identity.
|
|
537
|
+
const emitFailures = parameters.emitFailures === true
|
|
510
538
|
const { method: credMethod, intent: credIntent } = credential.challenge
|
|
511
539
|
const methodCandidates = (methods as readonly Method.AnyServer[]).filter(
|
|
512
540
|
(m) => m.name === credMethod && m.intent === credIntent,
|
|
513
541
|
)
|
|
514
|
-
const mi =
|
|
542
|
+
const mi = Method.selectServerMethod(methodCandidates, credential.challenge)
|
|
515
543
|
const eventMethod =
|
|
516
544
|
mi ?? ({ intent: credIntent, name: credMethod } satisfies ServerMethodDescriptor)
|
|
517
545
|
|
|
518
|
-
const emitStandalonePaymentFailed = async (
|
|
546
|
+
const emitStandalonePaymentFailed = async (failure: {
|
|
519
547
|
challenge: Challenge.Challenge
|
|
520
548
|
credential: Credential.Credential | null
|
|
521
549
|
error: Errors.PaymentError
|
|
522
550
|
request: Record<string, unknown>
|
|
523
551
|
submittedChallenge?: Challenge.Challenge | undefined
|
|
524
552
|
}) => {
|
|
553
|
+
if (!emitFailures) return
|
|
525
554
|
await serverEvents.emit(
|
|
526
555
|
'payment.failed',
|
|
527
556
|
createPaymentFailedContext({
|
|
528
557
|
capturedRequest: options?.capturedRequest,
|
|
529
|
-
challenge:
|
|
530
|
-
credential:
|
|
531
|
-
error:
|
|
558
|
+
challenge: failure.challenge,
|
|
559
|
+
credential: failure.credential,
|
|
560
|
+
error: failure.error,
|
|
532
561
|
method: eventMethod,
|
|
533
|
-
request:
|
|
534
|
-
submittedChallenge:
|
|
562
|
+
request: failure.request,
|
|
563
|
+
submittedChallenge: failure.submittedChallenge,
|
|
535
564
|
}) as never,
|
|
536
565
|
)
|
|
537
566
|
}
|
|
538
567
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
568
|
+
const fail = async (
|
|
569
|
+
error: Errors.PaymentError,
|
|
570
|
+
failure: {
|
|
571
|
+
credential?: Credential.Credential | null | undefined
|
|
572
|
+
request?: Record<string, unknown> | undefined
|
|
573
|
+
submittedChallenge?: Challenge.Challenge | undefined
|
|
574
|
+
} = {},
|
|
575
|
+
thrown: unknown = error,
|
|
576
|
+
): Promise<never> => {
|
|
544
577
|
await emitStandalonePaymentFailed({
|
|
545
578
|
challenge: credential.challenge,
|
|
546
|
-
credential,
|
|
579
|
+
credential: failure.credential ?? credential,
|
|
547
580
|
error,
|
|
548
|
-
request: credential.challenge.request as Record<string, unknown
|
|
549
|
-
submittedChallenge: credential.challenge,
|
|
581
|
+
request: failure.request ?? (credential.challenge.request as Record<string, unknown>),
|
|
582
|
+
submittedChallenge: failure.submittedChallenge ?? credential.challenge,
|
|
550
583
|
})
|
|
551
|
-
throw
|
|
584
|
+
throw thrown
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (!mi) {
|
|
588
|
+
await fail(
|
|
589
|
+
new Errors.InvalidChallengeError({
|
|
590
|
+
id: credential.challenge.id,
|
|
591
|
+
reason: `no registered method for ${credMethod}/${credIntent}`,
|
|
592
|
+
}),
|
|
593
|
+
)
|
|
552
594
|
}
|
|
553
595
|
|
|
554
|
-
|
|
596
|
+
if (parameters.requireValidate && !mi.validate)
|
|
597
|
+
await fail(
|
|
598
|
+
new Errors.VerificationFailedError({
|
|
599
|
+
details: { intent: credIntent, method: credMethod },
|
|
600
|
+
reason: `${credMethod}/${credIntent} does not support non-mutating credential validation`,
|
|
601
|
+
}),
|
|
602
|
+
)
|
|
603
|
+
|
|
555
604
|
if (!Challenge.verify(credential.challenge, { secretKey: secretKey! })) {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
credential,
|
|
563
|
-
error,
|
|
564
|
-
request: credential.challenge.request as Record<string, unknown>,
|
|
565
|
-
submittedChallenge: credential.challenge,
|
|
566
|
-
})
|
|
567
|
-
throw error
|
|
605
|
+
await fail(
|
|
606
|
+
new Errors.InvalidChallengeError({
|
|
607
|
+
id: credential.challenge.id,
|
|
608
|
+
reason: 'challenge was not issued by this server',
|
|
609
|
+
}),
|
|
610
|
+
)
|
|
568
611
|
}
|
|
569
612
|
|
|
570
|
-
// Expiry check
|
|
571
613
|
try {
|
|
572
614
|
Expires.assert(credential.challenge.expires, credential.challenge.id)
|
|
573
615
|
} catch (e) {
|
|
574
|
-
if (e instanceof Errors.PaymentError)
|
|
575
|
-
await emitStandalonePaymentFailed({
|
|
576
|
-
challenge: credential.challenge,
|
|
577
|
-
credential,
|
|
578
|
-
error: e,
|
|
579
|
-
request: credential.challenge.request as Record<string, unknown>,
|
|
580
|
-
submittedChallenge: credential.challenge,
|
|
581
|
-
})
|
|
616
|
+
if (e instanceof Errors.PaymentError) await fail(e)
|
|
582
617
|
throw e
|
|
583
618
|
}
|
|
584
619
|
|
|
585
|
-
|
|
586
|
-
let parsedCredential: Credential.Credential
|
|
620
|
+
let parsedCredential!: Credential.Credential
|
|
587
621
|
try {
|
|
588
622
|
parsedCredential = withParsedCredentialPayload(
|
|
589
623
|
credential,
|
|
590
624
|
mi.schema.credential.payload.parse(credential.payload),
|
|
591
625
|
)
|
|
592
626
|
} catch (e) {
|
|
593
|
-
await
|
|
594
|
-
challenge: credential.challenge,
|
|
595
|
-
credential,
|
|
596
|
-
error: new Errors.InvalidPayloadError(),
|
|
597
|
-
request: credential.challenge.request as Record<string, unknown>,
|
|
598
|
-
submittedChallenge: credential.challenge,
|
|
599
|
-
})
|
|
600
|
-
throw e
|
|
627
|
+
await fail(new Errors.InvalidPayloadError(), {}, e)
|
|
601
628
|
}
|
|
602
629
|
|
|
603
630
|
const expectedMeta = Scope.merge({ meta: options?.meta, scope: options?.scope })
|
|
604
631
|
|
|
605
632
|
if (options?.scope !== undefined && Scope.read(credential.challenge.meta) !== options.scope) {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
error,
|
|
614
|
-
request: credential.challenge.request as Record<string, unknown>,
|
|
615
|
-
submittedChallenge: credential.challenge,
|
|
616
|
-
})
|
|
617
|
-
throw error
|
|
633
|
+
await fail(
|
|
634
|
+
new Errors.InvalidChallengeError({
|
|
635
|
+
id: credential.challenge.id,
|
|
636
|
+
reason: "credential scope does not match this route's requirements",
|
|
637
|
+
}),
|
|
638
|
+
{ credential: parsedCredential },
|
|
639
|
+
)
|
|
618
640
|
}
|
|
619
641
|
|
|
620
642
|
const shouldValidateRoute =
|
|
@@ -660,12 +682,9 @@ export function create<
|
|
|
660
682
|
: (credential.challenge.request as z.input<typeof mi.schema.request>)
|
|
661
683
|
} catch (e) {
|
|
662
684
|
if (e instanceof Errors.PaymentError)
|
|
663
|
-
await
|
|
664
|
-
challenge: credential.challenge,
|
|
685
|
+
await fail(e, {
|
|
665
686
|
credential: parsedCredential,
|
|
666
|
-
error: e,
|
|
667
687
|
request: credential.challenge.request as Record<string, unknown>,
|
|
668
|
-
submittedChallenge: credential.challenge,
|
|
669
688
|
})
|
|
670
689
|
throw e
|
|
671
690
|
}
|
|
@@ -679,17 +698,72 @@ export function create<
|
|
|
679
698
|
} as Method.VerifiedChallengeEnvelope)
|
|
680
699
|
: undefined
|
|
681
700
|
|
|
701
|
+
return {
|
|
702
|
+
credential,
|
|
703
|
+
envelope,
|
|
704
|
+
eventMethod,
|
|
705
|
+
method: mi,
|
|
706
|
+
parsedCredential,
|
|
707
|
+
parsedRequest,
|
|
708
|
+
request,
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
async function validateCredentialFn(
|
|
713
|
+
input: string | Credential.Credential,
|
|
714
|
+
options?: VerifyCredentialOptions,
|
|
715
|
+
): Promise<Method.Validation> {
|
|
716
|
+
const prepared = await prepareStandaloneCredential(input, options, { requireValidate: true })
|
|
717
|
+
return prepared.method.validate!({
|
|
718
|
+
credential: prepared.parsedCredential,
|
|
719
|
+
envelope: prepared.envelope,
|
|
720
|
+
request: prepared.request,
|
|
721
|
+
} as never)
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// broadcastCredential: single-call end-to-end validation and broadcast
|
|
725
|
+
async function broadcastCredentialFn(
|
|
726
|
+
input: string | Credential.Credential,
|
|
727
|
+
options?: VerifyCredentialOptions,
|
|
728
|
+
): Promise<Receipt.Receipt> {
|
|
729
|
+
const prepared = await prepareStandaloneCredential(input, options, { emitFailures: true })
|
|
730
|
+
const { method: mi, parsedCredential, parsedRequest, request, envelope } = prepared
|
|
731
|
+
|
|
732
|
+
const emitStandalonePaymentFailed = async (parameters: {
|
|
733
|
+
challenge: Challenge.Challenge
|
|
734
|
+
credential: Credential.Credential | null
|
|
735
|
+
error: Errors.PaymentError
|
|
736
|
+
request: Record<string, unknown>
|
|
737
|
+
submittedChallenge?: Challenge.Challenge | undefined
|
|
738
|
+
}) => {
|
|
739
|
+
await serverEvents.emit(
|
|
740
|
+
'payment.failed',
|
|
741
|
+
createPaymentFailedContext({
|
|
742
|
+
capturedRequest: options?.capturedRequest,
|
|
743
|
+
challenge: parameters.challenge,
|
|
744
|
+
credential: parameters.credential,
|
|
745
|
+
error: parameters.error,
|
|
746
|
+
method: prepared.eventMethod,
|
|
747
|
+
request: parameters.request,
|
|
748
|
+
submittedChallenge: parameters.submittedChallenge,
|
|
749
|
+
}) as never,
|
|
750
|
+
)
|
|
751
|
+
}
|
|
752
|
+
|
|
682
753
|
let receipt: Receipt.Receipt
|
|
683
754
|
try {
|
|
684
|
-
|
|
755
|
+
if (mi.broadcast && mi.validate)
|
|
756
|
+
await mi.validate({ credential: parsedCredential, envelope, request } as never)
|
|
757
|
+
const broadcast = mi.broadcast ?? mi.verify
|
|
758
|
+
receipt = await broadcast({ credential: parsedCredential, envelope, request } as never)
|
|
685
759
|
} catch (e) {
|
|
686
760
|
const error = e instanceof Errors.PaymentError ? e : new Errors.VerificationFailedError()
|
|
687
761
|
await emitStandalonePaymentFailed({
|
|
688
|
-
challenge: credential.challenge,
|
|
762
|
+
challenge: prepared.credential.challenge,
|
|
689
763
|
credential: parsedCredential,
|
|
690
764
|
error,
|
|
691
765
|
request: parsedRequest,
|
|
692
|
-
submittedChallenge: credential.challenge,
|
|
766
|
+
submittedChallenge: prepared.credential.challenge,
|
|
693
767
|
})
|
|
694
768
|
throw e
|
|
695
769
|
}
|
|
@@ -698,7 +772,7 @@ export function create<
|
|
|
698
772
|
'payment.success',
|
|
699
773
|
createPaymentSuccessContext({
|
|
700
774
|
capturedRequest: options?.capturedRequest,
|
|
701
|
-
challenge: credential.challenge,
|
|
775
|
+
challenge: prepared.credential.challenge,
|
|
702
776
|
credential: parsedCredential,
|
|
703
777
|
envelope,
|
|
704
778
|
method: mi,
|
|
@@ -710,6 +784,8 @@ export function create<
|
|
|
710
784
|
return receipt
|
|
711
785
|
}
|
|
712
786
|
|
|
787
|
+
const verifyCredentialFn = broadcastCredentialFn
|
|
788
|
+
|
|
713
789
|
function composeFn(
|
|
714
790
|
...entries: readonly [
|
|
715
791
|
Method.AnyServer | AnyMethodFnWithMethod | string,
|
|
@@ -760,7 +836,9 @@ export function create<
|
|
|
760
836
|
onPaymentFailed,
|
|
761
837
|
onPaymentSuccess,
|
|
762
838
|
realm: realm as string | undefined,
|
|
839
|
+
broadcastCredential: broadcastCredentialFn,
|
|
763
840
|
transport,
|
|
841
|
+
validateCredential: validateCredentialFn,
|
|
764
842
|
verifyCredential: verifyCredentialFn,
|
|
765
843
|
...handlers,
|
|
766
844
|
} as never
|
|
@@ -809,8 +887,10 @@ function createMethodFn(parameters: createMethodFn.Parameters): createMethodFn.R
|
|
|
809
887
|
realm,
|
|
810
888
|
respond,
|
|
811
889
|
secretKey,
|
|
890
|
+
broadcast,
|
|
812
891
|
stableBinding,
|
|
813
892
|
transport,
|
|
893
|
+
validate,
|
|
814
894
|
verify,
|
|
815
895
|
} = parameters
|
|
816
896
|
|
|
@@ -1256,7 +1336,14 @@ function createMethodFn(parameters: createMethodFn.Parameters): createMethodFn.R
|
|
|
1256
1336
|
// If verification fails, re-issue the challenge so the client can retry.
|
|
1257
1337
|
let receiptData: Receipt.Receipt
|
|
1258
1338
|
try {
|
|
1259
|
-
|
|
1339
|
+
if (broadcast && validate)
|
|
1340
|
+
await validate({ credential: parsedCredential, envelope, request } as never)
|
|
1341
|
+
const broadcastCredential = broadcast ?? verify
|
|
1342
|
+
receiptData = await broadcastCredential({
|
|
1343
|
+
credential: parsedCredential,
|
|
1344
|
+
envelope,
|
|
1345
|
+
request,
|
|
1346
|
+
} as never)
|
|
1260
1347
|
} catch (e) {
|
|
1261
1348
|
if (!(e instanceof Errors.PaymentError))
|
|
1262
1349
|
console.error('mppx: internal verification error', e)
|
|
@@ -1375,9 +1462,11 @@ declare namespace createMethodFn {
|
|
|
1375
1462
|
realm: string | undefined
|
|
1376
1463
|
request?: Method.RequestFn<method>
|
|
1377
1464
|
respond?: Method.RespondFn<method>
|
|
1465
|
+
broadcast?: Method.BroadcastFn<method>
|
|
1378
1466
|
secretKey: string
|
|
1379
1467
|
stableBinding?: Method.StableBindingFn<method>
|
|
1380
1468
|
transport: transport
|
|
1469
|
+
validate?: Method.ValidateFn<method>
|
|
1381
1470
|
verify: Method.VerifyFn<method>
|
|
1382
1471
|
}
|
|
1383
1472
|
|
|
@@ -1843,31 +1932,6 @@ type PinnedRequestBindingField = (typeof pinnedRequestBindingFields)[number]
|
|
|
1843
1932
|
type PinnedChallengeField = 'method' | 'intent' | 'realm' | 'opaque' | PinnedRequestBindingField
|
|
1844
1933
|
type StableBinding = Record<string, unknown>
|
|
1845
1934
|
|
|
1846
|
-
function selectVerificationMethod(
|
|
1847
|
-
methods: readonly Method.AnyServer[],
|
|
1848
|
-
challenge: Challenge.Challenge,
|
|
1849
|
-
): Method.AnyServer | undefined {
|
|
1850
|
-
if (methods.length <= 1) return methods[0]
|
|
1851
|
-
if (
|
|
1852
|
-
challenge.method !== Constants.Methods.tempo ||
|
|
1853
|
-
challenge.intent !== Constants.Intents.session
|
|
1854
|
-
)
|
|
1855
|
-
return methods[0]
|
|
1856
|
-
|
|
1857
|
-
const sessionProtocolMarker = Constants.getMethodDetail(
|
|
1858
|
-
challenge.request.methodDetails,
|
|
1859
|
-
Constants.MethodDetailKeys.sessionProtocol,
|
|
1860
|
-
)
|
|
1861
|
-
if (
|
|
1862
|
-
sessionProtocolMarker === undefined ||
|
|
1863
|
-
sessionProtocolMarker === Constants.SessionProtocols.v1
|
|
1864
|
-
)
|
|
1865
|
-
return methods.find((method) => method.alias === 'sessionLegacy') ?? methods[0]
|
|
1866
|
-
if (sessionProtocolMarker === Constants.SessionProtocols.v2)
|
|
1867
|
-
return methods.find((method) => method.alias === undefined) ?? methods[0]
|
|
1868
|
-
return undefined
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
1935
|
function getChallengeBindingMismatch(
|
|
1872
1936
|
expectedChallenge: Challenge.Challenge,
|
|
1873
1937
|
actualChallenge: Challenge.Challenge,
|
|
@@ -35,6 +35,16 @@ describe('charge', () => {
|
|
|
35
35
|
expect(result.success).toBe(false)
|
|
36
36
|
})
|
|
37
37
|
|
|
38
|
+
test('schema: requires decimals when no server default supplies it', () => {
|
|
39
|
+
const result = Methods.charge.schema.request.safeParse({
|
|
40
|
+
amount: '1',
|
|
41
|
+
currency: 'usd',
|
|
42
|
+
networkId: 'profile_123',
|
|
43
|
+
paymentMethodTypes: ['card'],
|
|
44
|
+
})
|
|
45
|
+
expect(result.success).toBe(false)
|
|
46
|
+
})
|
|
47
|
+
|
|
38
48
|
test('schema: validates spt payload', () => {
|
|
39
49
|
const result = Methods.charge.schema.credential.payload.safeParse({
|
|
40
50
|
spt: 'spt_test_123',
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Mppx, stripe } from 'mppx/server'
|
|
2
|
+
import { test } from 'vp/test'
|
|
3
|
+
|
|
4
|
+
import type { StripeClient } from '../internal/types.js'
|
|
5
|
+
import type { charge as StripeCharge } from './Charge.js'
|
|
6
|
+
|
|
7
|
+
const client = {} as StripeClient
|
|
8
|
+
const secretKey = 'test-secret-key-test-secret-key-32'
|
|
9
|
+
|
|
10
|
+
test('requires request fields not configured as Stripe defaults', () => {
|
|
11
|
+
const server = Mppx.create({
|
|
12
|
+
methods: [
|
|
13
|
+
stripe.charge({
|
|
14
|
+
client,
|
|
15
|
+
networkId: 'internal',
|
|
16
|
+
paymentMethodTypes: ['card'],
|
|
17
|
+
}),
|
|
18
|
+
],
|
|
19
|
+
realm: 'api.example.com',
|
|
20
|
+
secretKey,
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// @ts-expect-error decimals is required unless the factory supplied it.
|
|
24
|
+
void server.charge({ amount: '1', currency: 'usd' })
|
|
25
|
+
void server.charge({ amount: '1', currency: 'usd', decimals: 2 })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('allows request fields configured as Stripe defaults', () => {
|
|
29
|
+
const server = Mppx.create({
|
|
30
|
+
methods: [
|
|
31
|
+
stripe.charge({
|
|
32
|
+
client,
|
|
33
|
+
decimals: 2,
|
|
34
|
+
networkId: 'internal',
|
|
35
|
+
paymentMethodTypes: ['card'],
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
realm: 'api.example.com',
|
|
39
|
+
secretKey,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
void server.charge({ amount: '1', currency: 'usd' })
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('does not treat optional properties on widened config as defaults', () => {
|
|
46
|
+
const parameters: StripeCharge.Parameters = {
|
|
47
|
+
client,
|
|
48
|
+
networkId: 'internal',
|
|
49
|
+
paymentMethodTypes: ['card'],
|
|
50
|
+
}
|
|
51
|
+
const server = Mppx.create({
|
|
52
|
+
methods: [stripe.charge(parameters)],
|
|
53
|
+
realm: 'api.example.com',
|
|
54
|
+
secretKey,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// @ts-expect-error a widened optional decimals property is not a guaranteed default.
|
|
58
|
+
void server.charge({ amount: '1', currency: 'usd' })
|
|
59
|
+
void server.charge({
|
|
60
|
+
amount: '1',
|
|
61
|
+
currency: 'usd',
|
|
62
|
+
decimals: 2,
|
|
63
|
+
networkId: 'internal',
|
|
64
|
+
paymentMethodTypes: ['card'],
|
|
65
|
+
})
|
|
66
|
+
})
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
VerificationFailedError,
|
|
7
7
|
} from '../../Errors.js'
|
|
8
8
|
import * as Expires from '../../Expires.js'
|
|
9
|
-
import type { LooseOmit, MaybePromise, OneOf } from '../../internal/types.js'
|
|
9
|
+
import type { ConfiguredDefaults, LooseOmit, MaybePromise, OneOf } from '../../internal/types.js'
|
|
10
10
|
import * as Method from '../../Method.js'
|
|
11
11
|
import type * as Html from '../../server/internal/html/config.ts'
|
|
12
12
|
import type * as z from '../../zod.js'
|
|
@@ -207,10 +207,13 @@ export declare namespace charge {
|
|
|
207
207
|
}
|
|
208
208
|
>
|
|
209
209
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Captures only configuration values that are known to exist at runtime.
|
|
212
|
+
*
|
|
213
|
+
* The returned handler requires any remaining request fields. In particular,
|
|
214
|
+
* `decimals` remains required when it was not configured on `stripe.charge()`.
|
|
215
|
+
*/
|
|
216
|
+
type DeriveDefaults<parameters extends Parameters> = ConfiguredDefaults<parameters, Defaults>
|
|
214
217
|
|
|
215
218
|
/**
|
|
216
219
|
* Server-side Stripe Connect settlement parameters.
|