mppx 0.9.0 → 0.9.1

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/Errors.d.ts +10 -0
  3. package/dist/Errors.js +12 -0
  4. package/dist/Mcp.d.ts +9 -1
  5. package/dist/Mcp.js +18 -0
  6. package/dist/Method.d.ts +7 -0
  7. package/dist/cli/cli.js +134 -134
  8. package/dist/cli/plugins/index.js +12 -12
  9. package/dist/client/internal/protocols/Mcp.js +2 -1
  10. package/dist/mcp/client/McpClient.js +22 -10
  11. package/dist/mcp/server/Transport.d.ts +2 -2
  12. package/dist/mcp/server/Transport.js +11 -4
  13. package/dist/server/Mppx.d.ts +6 -0
  14. package/dist/server/Mppx.js +75 -6
  15. package/dist/server/Transport.js +8 -15
  16. package/dist/stripe/Methods.d.ts +6 -0
  17. package/dist/stripe/Methods.js +3 -1
  18. package/dist/stripe/client/Charge.d.ts +6 -0
  19. package/dist/stripe/client/Methods.d.ts +6 -0
  20. package/dist/stripe/internal/payment-intent.d.ts +7 -0
  21. package/dist/stripe/internal/payment-intent.js +6 -0
  22. package/dist/stripe/server/Charge.d.ts +6 -0
  23. package/dist/stripe/server/Charge.js +8 -3
  24. package/dist/stripe/server/Methods.js +62 -7
  25. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  26. package/dist/stripe/server/internal/html.gen.js +1 -1
  27. package/dist/tempo/Tokens.d.ts +13 -0
  28. package/dist/tempo/Tokens.js +13 -0
  29. package/dist/tempo/client/Charge.d.ts +1 -1
  30. package/dist/tempo/client/Charge.js +17 -0
  31. package/dist/tempo/index.d.ts +1 -0
  32. package/dist/tempo/index.js +1 -0
  33. package/dist/tempo/internal/fee-payer.d.ts +1 -1
  34. package/dist/tempo/internal/fee-payer.js +2 -12
  35. package/dist/tempo/internal/fee-token.d.ts +2 -0
  36. package/dist/tempo/internal/fee-token.js +7 -0
  37. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  38. package/dist/tempo/server/internal/html.gen.js +1 -1
  39. package/dist/tempo/session/client/SessionManager.js +1 -2
  40. package/dist/tempo/session/server/Session.js +5 -2
  41. package/dist/x402/mcp.js +4 -3
  42. package/package.json +1 -1
@@ -77,7 +77,8 @@ export function paymentRequiredData(message) {
77
77
  if (!message)
78
78
  return undefined;
79
79
  if ('error' in message) {
80
- if (message.error?.code !== Mcp.paymentRequiredCode)
80
+ if (message.error?.code !== Mcp.paymentRequiredCode &&
81
+ message.error?.code !== Mcp.paymentVerificationFailedCode)
81
82
  return undefined;
82
83
  return paymentRequiredDataFromValue(message.error.data);
83
84
  }
@@ -5,6 +5,7 @@ import * as AcceptPayment from '../../internal/AcceptPayment.js';
5
5
  import * as core_Mcp from '../../Mcp.js';
6
6
  import * as z from '../../zod.js';
7
7
  const MPPX_MCP_CLIENT_WRAPPER = Symbol.for('mppx.mcp.client.wrapper');
8
+ const maxPaymentAttempts = 3;
8
9
  /**
9
10
  * Adds automatic payment handling to an MCP SDK client.
10
11
  *
@@ -80,7 +81,8 @@ export function isPaymentRequiredError(error) {
80
81
  return false;
81
82
  if (!('code' in error) || !('message' in error))
82
83
  return false;
83
- if (error.code !== core_Mcp.paymentRequiredCode)
84
+ const code = error.code;
85
+ if (code !== core_Mcp.paymentRequiredCode && code !== core_Mcp.paymentVerificationFailedCode)
84
86
  return false;
85
87
  return isPaymentRequiredData(error.data);
86
88
  }
@@ -98,7 +100,7 @@ async function createCredential(challenge, config) {
98
100
  function createPaymentAwareCallTool(callTool, config) {
99
101
  const methods = config.methods.flat();
100
102
  const paymentPreferences = AcceptPayment.resolve(methods, config.paymentPreferences);
101
- const retryWithPayment = async (params, call, paymentRequired, cause) => {
103
+ const retryWithPayment = async (params, call, paymentRequired, cause, attemptsRemaining = maxPaymentAttempts) => {
102
104
  const challenges = paymentRequired.challenges;
103
105
  const candidates = AcceptPayment.selectChallengeCandidates(challenges, methods, paymentPreferences.entries);
104
106
  const orderedCandidates = config.orderChallenges
@@ -122,14 +124,24 @@ function createPaymentAwareCallTool(callTool, config) {
122
124
  methods,
123
125
  });
124
126
  const parsed = Credential.deserialize(credential);
125
- const retryResult = await callTool({
126
- ...params,
127
- _meta: {
128
- ...params._meta,
129
- [core_Mcp.credentialMetaKey]: parsed,
130
- },
131
- }, call.resultSchema, call.requestOptions);
132
- return withReceipt(retryResult);
127
+ try {
128
+ const retryResult = await callTool({
129
+ ...params,
130
+ _meta: {
131
+ ...params._meta,
132
+ [core_Mcp.credentialMetaKey]: parsed,
133
+ },
134
+ }, call.resultSchema, call.requestOptions);
135
+ const nextPaymentRequired = getPaymentRequiredMeta(retryResult);
136
+ if (nextPaymentRequired && attemptsRemaining > 1)
137
+ return retryWithPayment(params, call, nextPaymentRequired, retryResult, attemptsRemaining - 1);
138
+ return withReceipt(retryResult);
139
+ }
140
+ catch (error) {
141
+ if (!isPaymentRequiredError(error) || attemptsRemaining <= 1)
142
+ throw error;
143
+ return retryWithPayment(params, call, error.data, error, attemptsRemaining - 1);
144
+ }
133
145
  };
134
146
  return async (params, call) => {
135
147
  try {
@@ -1,5 +1,5 @@
1
1
  import type { CallToolResult, McpError } from '@modelcontextprotocol/sdk/types.js';
2
- import type * as Credential from '../../Credential.js';
2
+ import * as Credential from '../../Credential.js';
3
3
  import * as core_Mcp from '../../Mcp.js';
4
4
  import * as Transport from '../../server/Transport.js';
5
5
  /**
@@ -18,7 +18,7 @@ export type McpSdk = Transport.Transport<Extra, McpError, CallToolResult>;
18
18
  * MCP SDK transport for server-side payment handling with `@modelcontextprotocol/sdk`.
19
19
  *
20
20
  * - Reads credentials from `_meta["org.paymentauth/credential"]`
21
- * - Issues challenges as `McpError` with code `-32042` and challenge in `error.data`
21
+ * - Maps payment errors to their specification-defined JSON-RPC codes
22
22
  * - Attaches receipts via `_meta["org.paymentauth/receipt"]` on tool results
23
23
  *
24
24
  * @example
@@ -1,3 +1,4 @@
1
+ import * as Credential from '../../Credential.js';
1
2
  import * as core_Mcp from '../../Mcp.js';
2
3
  import * as Transport from '../../server/Transport.js';
3
4
  import * as McpCredential from '../internal/Credential.js';
@@ -5,7 +6,7 @@ import * as McpCredential from '../internal/Credential.js';
5
6
  * MCP SDK transport for server-side payment handling with `@modelcontextprotocol/sdk`.
6
7
  *
7
8
  * - Reads credentials from `_meta["org.paymentauth/credential"]`
8
- * - Issues challenges as `McpError` with code `-32042` and challenge in `error.data`
9
+ * - Maps payment errors to their specification-defined JSON-RPC codes
9
10
  * - Attaches receipts via `_meta["org.paymentauth/receipt"]` on tool results
10
11
  *
11
12
  * @example
@@ -41,7 +42,13 @@ export function mcpSdk() {
41
42
  };
42
43
  },
43
44
  getCredential(extra) {
44
- return McpCredential.parse(extra._meta?.[core_Mcp.credentialMetaKey])?.value ?? null;
45
+ const value = extra._meta?.[core_Mcp.credentialMetaKey];
46
+ if (value === undefined)
47
+ return null;
48
+ const parsed = McpCredential.parse(value);
49
+ if (!parsed)
50
+ throw new Credential.InvalidCredentialEncodingError();
51
+ return parsed.value;
45
52
  },
46
53
  async respondChallenge({ challenge, error }) {
47
54
  if (!McpErrorClass) {
@@ -55,8 +62,8 @@ export function mcpSdk() {
55
62
  throw err;
56
63
  }
57
64
  }
58
- return new McpErrorClass(core_Mcp.paymentRequiredCode, error?.message ?? 'Payment Required', {
59
- httpStatus: 402,
65
+ return new McpErrorClass(core_Mcp.errorCode(error), error?.message ?? 'Payment Required', {
66
+ httpStatus: error?.status ?? 402,
60
67
  challenges: [challenge],
61
68
  ...(error && { problem: error.toProblemDetails(challenge.id) }),
62
69
  });
@@ -99,7 +99,13 @@ export type PaymentSuccessContext<method extends Method.Method = Method.Method,
99
99
  input?: Transport.InputOf<transport> | undefined;
100
100
  method: ServerMethodDescriptor<method>;
101
101
  receipt: Receipt.Receipt;
102
+ /** Canonical request represented by the challenge. */
102
103
  request: z.output<method['schema']['request']>;
104
+ /**
105
+ * Resolved method input before request-schema output transforms. Absent during
106
+ * standalone credential verification when no route options are supplied.
107
+ */
108
+ requestInput?: z.input<method['schema']['request']> | undefined;
103
109
  }>;
104
110
  /** Options for standalone credential verification. */
105
111
  export type VerifyCredentialOptions = {
@@ -73,6 +73,7 @@ export function create(config) {
73
73
  input: ctx.input,
74
74
  receipt: ctx.receipt,
75
75
  request: ctx.request,
76
+ ...(ctx.requestInput !== undefined && { requestInput: ctx.requestInput }),
76
77
  });
77
78
  }
78
79
  }));
@@ -280,6 +281,7 @@ export function create(config) {
280
281
  parsedCredential,
281
282
  parsedRequest,
282
283
  request,
284
+ requestInput: shouldValidateRoute ? request : undefined,
283
285
  };
284
286
  }
285
287
  async function validateCredentialFn(input, options) {
@@ -293,7 +295,7 @@ export function create(config) {
293
295
  // broadcastCredential: single-call end-to-end validation and broadcast
294
296
  async function broadcastCredentialFn(input, options) {
295
297
  const prepared = await prepareStandaloneCredential(input, options, { emitFailures: true });
296
- const { method: mi, parsedCredential, parsedRequest, request, envelope } = prepared;
298
+ const { method: mi, parsedCredential, parsedRequest, request, requestInput, envelope, } = prepared;
297
299
  const emitStandalonePaymentFailed = async (parameters) => {
298
300
  await serverEvents.emit('payment.failed', createPaymentFailedContext({
299
301
  capturedRequest: options?.capturedRequest,
@@ -313,7 +315,7 @@ export function create(config) {
313
315
  receipt = await broadcast({ credential: parsedCredential, envelope, request });
314
316
  }
315
317
  catch (e) {
316
- const error = e instanceof Errors.PaymentError ? e : new Errors.VerificationFailedError();
318
+ const error = e instanceof Errors.PaymentError ? e : new Errors.InternalPaymentError();
317
319
  await emitStandalonePaymentFailed({
318
320
  challenge: prepared.credential.challenge,
319
321
  credential: parsedCredential,
@@ -331,6 +333,7 @@ export function create(config) {
331
333
  method: mi,
332
334
  receipt,
333
335
  request: parsedRequest,
336
+ ...(requestInput !== undefined && { requestInput }),
334
337
  }));
335
338
  return receipt;
336
339
  }
@@ -548,7 +551,11 @@ function createMethodFn(parameters) {
548
551
  // Credential was provided but malformed
549
552
  if (credentialError) {
550
553
  const reason = getSafeCredentialReason(credentialError);
551
- const error = new Errors.MalformedCredentialError(reason ? { reason } : {});
554
+ const error = credentialError instanceof Errors.PaymentError
555
+ ? credentialError
556
+ : reason
557
+ ? new Errors.MalformedCredentialError({ reason })
558
+ : new Errors.InternalPaymentError();
552
559
  await emitPaymentFailed({
553
560
  challenge,
554
561
  credential: null,
@@ -610,6 +617,7 @@ function createMethodFn(parameters) {
610
617
  method,
611
618
  receipt: authorized.receipt,
612
619
  request: parsedRequest,
620
+ requestInput: request,
613
621
  }));
614
622
  return success(authorized.receipt, {
615
623
  managementResponse: authorized.response,
@@ -619,7 +627,7 @@ function createMethodFn(parameters) {
619
627
  catch (e) {
620
628
  if (!(e instanceof Errors.PaymentError))
621
629
  console.error('mppx: internal authorization error', e);
622
- const error = e instanceof Errors.PaymentError ? e : new Errors.VerificationFailedError();
630
+ const error = e instanceof Errors.PaymentError ? e : new Errors.InternalPaymentError();
623
631
  await emitPaymentFailed({
624
632
  challenge,
625
633
  credential: null,
@@ -792,7 +800,7 @@ function createMethodFn(parameters) {
792
800
  catch (e) {
793
801
  if (!(e instanceof Errors.PaymentError))
794
802
  console.error('mppx: internal verification error', e);
795
- const error = e instanceof Errors.PaymentError ? e : new Errors.VerificationFailedError();
803
+ const error = e instanceof Errors.PaymentError ? e : new Errors.InternalPaymentError();
796
804
  await emitPaymentFailed({
797
805
  challenge,
798
806
  credential: parsedCredential,
@@ -832,6 +840,7 @@ function createMethodFn(parameters) {
832
840
  method,
833
841
  receipt: receiptData,
834
842
  request: parsedRequest,
843
+ requestInput: request,
835
844
  }));
836
845
  return success(receiptData, {
837
846
  challengeId: credential.challenge.id,
@@ -991,6 +1000,9 @@ function createPaymentSuccessContext(parameters) {
991
1000
  method: snapshotMethod(parameters.method),
992
1001
  receipt: snapshotValue(parameters.receipt),
993
1002
  request: snapshotValue(parameters.request),
1003
+ ...(parameters.requestInput !== undefined && {
1004
+ requestInput: snapshotValue(parameters.requestInput),
1005
+ }),
994
1006
  });
995
1007
  }
996
1008
  function snapshotMethod(method) {
@@ -1035,8 +1047,65 @@ function snapshotValue(value) {
1035
1047
  return freezeSnapshot(structuredClone(value));
1036
1048
  }
1037
1049
  catch {
1038
- return freezeSnapshot(value);
1050
+ return freezeSnapshot(cloneSnapshotFallback(value));
1051
+ }
1052
+ }
1053
+ /**
1054
+ * Copies object containers when `structuredClone` rejects callable values.
1055
+ * Functions remain callable references, while their containing request data is
1056
+ * detached so snapshot freezing and nested writes cannot affect hook-owned
1057
+ * objects.
1058
+ */
1059
+ function cloneSnapshotFallback(value, seen = new WeakMap()) {
1060
+ if (!value || typeof value !== 'object')
1061
+ return value;
1062
+ const existing = seen.get(value);
1063
+ if (existing)
1064
+ return existing;
1065
+ if (value instanceof Date)
1066
+ return new Date(value);
1067
+ if (value instanceof RegExp)
1068
+ return new RegExp(value.source, value.flags);
1069
+ if (value instanceof URL)
1070
+ return new URL(value);
1071
+ if (value instanceof Headers)
1072
+ return new Headers(value);
1073
+ if (value instanceof ArrayBuffer)
1074
+ return value.slice(0);
1075
+ if (ArrayBuffer.isView(value)) {
1076
+ const buffer = new Uint8Array(value.buffer, value.byteOffset, value.byteLength).slice().buffer;
1077
+ if (value instanceof DataView)
1078
+ return new DataView(buffer);
1079
+ const Constructor = value.constructor;
1080
+ return new Constructor(buffer);
1081
+ }
1082
+ if (value instanceof Map) {
1083
+ const snapshot = new Map();
1084
+ seen.set(value, snapshot);
1085
+ for (const [key, entry] of value)
1086
+ snapshot.set(cloneSnapshotFallback(key, seen), cloneSnapshotFallback(entry, seen));
1087
+ return snapshot;
1088
+ }
1089
+ if (value instanceof Set) {
1090
+ const snapshot = new Set();
1091
+ seen.set(value, snapshot);
1092
+ for (const entry of value)
1093
+ snapshot.add(cloneSnapshotFallback(entry, seen));
1094
+ return snapshot;
1095
+ }
1096
+ const snapshot = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
1097
+ seen.set(value, snapshot);
1098
+ for (const key of Reflect.ownKeys(value)) {
1099
+ if (Array.isArray(value) && key === 'length')
1100
+ continue;
1101
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
1102
+ if (!descriptor)
1103
+ continue;
1104
+ if ('value' in descriptor)
1105
+ descriptor.value = cloneSnapshotFallback(descriptor.value, seen);
1106
+ Object.defineProperty(snapshot, key, descriptor);
1039
1107
  }
1108
+ return snapshot;
1040
1109
  }
1041
1110
  function snapshotInputProperty(input) {
1042
1111
  if (input === undefined)
@@ -1,8 +1,8 @@
1
1
  import * as Challenge from '../Challenge.js';
2
2
  import * as Constants from '../Constants.js';
3
3
  import * as Credential from '../Credential.js';
4
- import * as Errors from '../Errors.js';
5
4
  import * as core_Mcp from '../Mcp.js';
5
+ import * as McpCredential from '../mcp/internal/Credential.js';
6
6
  import * as Receipt from '../Receipt.js';
7
7
  import * as Html from './internal/html/config.js';
8
8
  import { serviceWorker } from './internal/html/serviceWorker.gen.js';
@@ -147,17 +147,20 @@ export function mcp() {
147
147
  },
148
148
  getCredential(request) {
149
149
  const meta = request.params?._meta;
150
- const credential = meta?.[core_Mcp.credentialMetaKey];
151
- if (!credential)
150
+ const value = meta?.[core_Mcp.credentialMetaKey];
151
+ if (value === undefined)
152
152
  return null;
153
- return credential;
153
+ const parsed = McpCredential.parse(value);
154
+ if (!parsed)
155
+ throw new Credential.InvalidCredentialEncodingError();
156
+ return parsed.value;
154
157
  },
155
158
  respondChallenge({ challenge, input, error }) {
156
159
  return {
157
160
  jsonrpc: '2.0',
158
161
  id: input.id,
159
162
  error: {
160
- code: mcpErrorCode(error),
163
+ code: core_Mcp.errorCode(error),
161
164
  message: error?.message ?? 'Payment Required',
162
165
  data: {
163
166
  httpStatus: error?.status ?? 402,
@@ -187,16 +190,6 @@ export function mcp() {
187
190
  },
188
191
  });
189
192
  }
190
- /** @internal */
191
- function mcpErrorCode(error) {
192
- if (!error)
193
- return core_Mcp.paymentRequiredCode;
194
- if (error instanceof Errors.MalformedCredentialError)
195
- return -32602;
196
- if (error instanceof Errors.PaymentRequiredError)
197
- return core_Mcp.paymentRequiredCode;
198
- return core_Mcp.paymentVerificationFailedCode;
199
- }
200
193
  export function safeUrl(url) {
201
194
  try {
202
195
  if (url instanceof URL)
@@ -22,6 +22,9 @@ export declare const charge: {
22
22
  externalId: z.ZodMiniOptional<z.ZodMiniString<string>>;
23
23
  metadata: z.ZodMiniOptional<z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniString<string>>>;
24
24
  networkId: z.ZodMiniString<string>;
25
+ paymentIntentOptions: z.ZodMiniOptional<z.ZodMiniObject<{
26
+ metadata: z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniString<string>>;
27
+ }, z.core.$strip>>;
25
28
  paymentMethodTypes: z.ZodMiniArray<z.ZodMiniString<string>>;
26
29
  recipient: z.ZodMiniOptional<z.ZodMiniString<string>>;
27
30
  }, z.core.$strip>, z.ZodMiniTransform<{
@@ -43,6 +46,9 @@ export declare const charge: {
43
46
  externalId?: string | undefined;
44
47
  metadata?: Record<string, string> | undefined;
45
48
  networkId: string;
49
+ paymentIntentOptions?: {
50
+ metadata: Record<string, string>;
51
+ } | undefined;
46
52
  paymentMethodTypes: string[];
47
53
  recipient?: string | undefined;
48
54
  }>>;
@@ -1,6 +1,7 @@
1
1
  import { parseUnits } from 'viem';
2
2
  import * as Method from '../Method.js';
3
3
  import * as z from '../zod.js';
4
+ import * as PaymentIntent from './internal/payment-intent.js';
4
5
  /**
5
6
  * Stripe charge intent for one-time payments via Shared Payment Tokens (SPTs).
6
7
  *
@@ -24,9 +25,10 @@ export const charge = Method.from({
24
25
  externalId: z.optional(z.string()),
25
26
  metadata: z.optional(z.record(z.string(), z.string())),
26
27
  networkId: z.string(),
28
+ paymentIntentOptions: z.optional(PaymentIntent.Schema),
27
29
  paymentMethodTypes: z.array(z.string()).check(z.minLength(1)),
28
30
  recipient: z.optional(z.string()),
29
- }), z.transform(({ amount, decimals, metadata, networkId, paymentMethodTypes, ...rest }) => ({
31
+ }), z.transform(({ amount, decimals, metadata, networkId, paymentIntentOptions: _, paymentMethodTypes, ...rest }) => ({
30
32
  ...rest,
31
33
  amount: parseUnits(amount, decimals).toString(),
32
34
  methodDetails: {
@@ -55,6 +55,9 @@ export declare function charge(parameters: charge.Parameters): Method.Client<{
55
55
  externalId: z.ZodMiniOptional<z.ZodMiniString<string>>;
56
56
  metadata: z.ZodMiniOptional<z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniString<string>>>;
57
57
  networkId: z.ZodMiniString<string>;
58
+ paymentIntentOptions: z.ZodMiniOptional<z.ZodMiniObject<{
59
+ metadata: z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniString<string>>;
60
+ }, z.core.$strip>>;
58
61
  paymentMethodTypes: z.ZodMiniArray<z.ZodMiniString<string>>;
59
62
  recipient: z.ZodMiniOptional<z.ZodMiniString<string>>;
60
63
  }, z.core.$strip>, z.ZodMiniTransform<{
@@ -76,6 +79,9 @@ export declare function charge(parameters: charge.Parameters): Method.Client<{
76
79
  externalId?: string | undefined;
77
80
  metadata?: Record<string, string> | undefined;
78
81
  networkId: string;
82
+ paymentIntentOptions?: {
83
+ metadata: Record<string, string>;
84
+ } | undefined;
79
85
  paymentMethodTypes: string[];
80
86
  recipient?: string | undefined;
81
87
  }>>;
@@ -42,6 +42,9 @@ export declare function stripe(parameters: stripe.Parameters): readonly [import(
42
42
  externalId: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
43
43
  metadata: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniRecord<import("zod/mini").ZodMiniString<string>, import("zod/mini").ZodMiniString<string>>>;
44
44
  networkId: import("zod/mini").ZodMiniString<string>;
45
+ paymentIntentOptions: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniObject<{
46
+ metadata: import("zod/mini").ZodMiniRecord<import("zod/mini").ZodMiniString<string>, import("zod/mini").ZodMiniString<string>>;
47
+ }, import("zod/v4/core").$strip>>;
45
48
  paymentMethodTypes: import("zod/mini").ZodMiniArray<import("zod/mini").ZodMiniString<string>>;
46
49
  recipient: import("zod/mini").ZodMiniOptional<import("zod/mini").ZodMiniString<string>>;
47
50
  }, import("zod/v4/core").$strip>, import("zod/mini").ZodMiniTransform<{
@@ -63,6 +66,9 @@ export declare function stripe(parameters: stripe.Parameters): readonly [import(
63
66
  externalId?: string | undefined;
64
67
  metadata?: Record<string, string> | undefined;
65
68
  networkId: string;
69
+ paymentIntentOptions?: {
70
+ metadata: Record<string, string>;
71
+ } | undefined;
66
72
  paymentMethodTypes: string[];
67
73
  recipient?: string | undefined;
68
74
  }>>;
@@ -0,0 +1,7 @@
1
+ import * as z from '../../zod.js';
2
+ /** Stripe PaymentIntent options accepted only by server-side method input. */
3
+ export declare const Schema: z.ZodMiniObject<{
4
+ metadata: z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniString<string>>;
5
+ }, z.core.$strip>;
6
+ export type Options = z.infer<typeof Schema>;
7
+ //# sourceMappingURL=payment-intent.d.ts.map
@@ -0,0 +1,6 @@
1
+ import * as z from '../../zod.js';
2
+ /** Stripe PaymentIntent options accepted only by server-side method input. */
3
+ export const Schema = z.object({
4
+ metadata: z.record(z.string(), z.string()),
5
+ });
6
+ //# sourceMappingURL=payment-intent.js.map
@@ -49,6 +49,9 @@ export declare function charge<const parameters extends charge.Parameters>(param
49
49
  externalId: z.ZodMiniOptional<z.ZodMiniString<string>>;
50
50
  metadata: z.ZodMiniOptional<z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniString<string>>>;
51
51
  networkId: z.ZodMiniString<string>;
52
+ paymentIntentOptions: z.ZodMiniOptional<z.ZodMiniObject<{
53
+ metadata: z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniString<string>>;
54
+ }, z.core.$strip>>;
52
55
  paymentMethodTypes: z.ZodMiniArray<z.ZodMiniString<string>>;
53
56
  recipient: z.ZodMiniOptional<z.ZodMiniString<string>>;
54
57
  }, z.core.$strip>, z.ZodMiniTransform<{
@@ -70,6 +73,9 @@ export declare function charge<const parameters extends charge.Parameters>(param
70
73
  externalId?: string | undefined;
71
74
  metadata?: Record<string, string> | undefined;
72
75
  networkId: string;
76
+ paymentIntentOptions?: {
77
+ metadata: Record<string, string>;
78
+ } | undefined;
73
79
  paymentMethodTypes: string[];
74
80
  recipient?: string | undefined;
75
81
  }>>;
@@ -117,13 +117,14 @@ export function charge(parameters) {
117
117
  onPaymentSuccess,
118
118
  async verify({ credential, envelope, request }) {
119
119
  const { challenge } = credential;
120
+ const { paymentIntentOptions, ...methodRequest } = request;
120
121
  const resolvedRequest = (() => {
121
- const parsed = Methods.charge.schema.request.safeParse(request);
122
+ const parsed = Methods.charge.schema.request.safeParse(methodRequest);
122
123
  if (parsed.success)
123
124
  return parsed.data;
124
125
  // verifyCredential() passes the HMAC-bound challenge request, which is
125
126
  // already in canonical output form and should not be transformed again.
126
- return request;
127
+ return methodRequest;
127
128
  })();
128
129
  Expires.assert(challenge.expires, challenge.id);
129
130
  const parsed = Methods.charge.schema.credential.payload.safeParse(credential.payload);
@@ -138,7 +139,11 @@ export function charge(parameters) {
138
139
  });
139
140
  }
140
141
  const userMetadata = resolvedRequest.methodDetails?.metadata;
141
- const resolvedMetadata = { ...buildAnalytics({ credential }), ...userMetadata };
142
+ const resolvedMetadata = {
143
+ ...buildAnalytics({ credential }),
144
+ ...userMetadata,
145
+ ...paymentIntentOptions?.metadata,
146
+ };
142
147
  const settlement = validateConnectSettlement({
143
148
  amount: resolvedRequest.amount,
144
149
  settlement: typeof connect === 'function'
@@ -3,6 +3,8 @@ import { charge as evmCharge } from '../../evm/server/Charge.js';
3
3
  import * as tempoDefaults from '../../tempo/internal/defaults.js';
4
4
  import { charge as tempoCharge } from '../../tempo/server/Charge.js';
5
5
  import { session as tempoSession } from '../../tempo/session/server/Session.js';
6
+ import * as z from '../../zod.js';
7
+ import * as PaymentIntent from '../internal/payment-intent.js';
6
8
  import { charge as charge_ } from './Charge.js';
7
9
  import { findOrCreateDepositAddress as _findOrCreateDepositAddress } from './internal/deposit-address.js';
8
10
  import { recordCryptoPayment } from './internal/record-payment.js';
@@ -96,14 +98,14 @@ export function stripe(parameters) {
96
98
  const handler = callMetadata
97
99
  ? createPaymentSuccessHandler(client, 'tempo', connect, { ...metadata, ...callMetadata })
98
100
  : tempoPaymentHandler;
99
- return tempoCharge({
101
+ return withPaymentIntentInput(tempoCharge({
100
102
  currency: tempoCurrency,
101
103
  recipient,
102
104
  ...(!livemode && { testnet: true }),
103
105
  canOffer: cryptoCanOffer,
104
106
  onPaymentSuccess: handler,
105
107
  ...rest,
106
- });
108
+ }));
107
109
  }
108
110
  function makeTempoSession(params) {
109
111
  const { recipient, ...rest } = params;
@@ -119,14 +121,14 @@ export function stripe(parameters) {
119
121
  const handler = callMetadata
120
122
  ? createPaymentSuccessHandler(client, 'base', connect, { ...metadata, ...callMetadata })
121
123
  : basePaymentHandler;
122
- return evmCharge({
124
+ return withPaymentIntentInput(evmCharge({
123
125
  currency: livemode ? EvmAssets.base.USDC : EvmAssets.baseSepolia.USDC,
124
126
  recipient,
125
127
  x402,
126
128
  canOffer: cryptoCanOffer,
127
129
  onPaymentSuccess: handler,
128
130
  ...rest,
129
- });
131
+ }));
130
132
  }
131
133
  const defaultMethodBuilders = {
132
134
  tempo: (addresses) => {
@@ -180,7 +182,7 @@ export function stripe(parameters) {
180
182
  const canOffer = m.canOffer
181
183
  ? (params) => cryptoCanOffer(params) && m.canOffer(params)
182
184
  : cryptoCanOffer;
183
- result.push({ ...m, canOffer, onPaymentSuccess });
185
+ result.push(withPaymentIntentInput({ ...m, canOffer, onPaymentSuccess }));
184
186
  }
185
187
  }
186
188
  }
@@ -267,16 +269,69 @@ function customRails(additional) {
267
269
  }
268
270
  function createPaymentSuccessHandler(client, network, connect, metadata) {
269
271
  return (params) => {
270
- const { receipt, request } = params;
272
+ const { receipt, request, requestInput } = params;
271
273
  if (receipt?.reference && request?.amount) {
274
+ const resolvedMetadata = {
275
+ ...metadata,
276
+ ...requestInput?.paymentIntentOptions?.metadata,
277
+ };
272
278
  return recordCryptoPayment(client, {
273
279
  network,
274
280
  reference: receipt.reference,
275
281
  amount: String(request.amount),
276
282
  ...(connect && { connect }),
277
- ...(metadata && { metadata }),
283
+ ...(Object.keys(resolvedMetadata).length > 0 && { metadata: resolvedMetadata }),
278
284
  });
279
285
  }
280
286
  };
281
287
  }
288
+ /**
289
+ * Extends a rail's server-only input with Stripe PaymentIntent options while
290
+ * keeping its canonical request unchanged. The schema strips the options
291
+ * before challenge serialization, and delegated lifecycle hooks receive the
292
+ * underlying rail request without Stripe-only fields. The wrapper retains the
293
+ * options only in `requestInput` for payment-success recording.
294
+ */
295
+ function withPaymentIntentInput(method) {
296
+ const baseSchema = method.schema.request;
297
+ const baseRequest = method.request;
298
+ const baseRespond = method.respond;
299
+ const baseBroadcast = method.broadcast;
300
+ const baseValidate = method.validate;
301
+ const baseVerify = method.verify;
302
+ return {
303
+ ...method,
304
+ // Accept Stripe-only options without changing the canonical rail schema.
305
+ schema: {
306
+ ...method.schema,
307
+ request: z.pipe(z.custom(), z.transform((input) => {
308
+ const { paymentIntentOptions, ...request } = input;
309
+ z.optional(PaymentIntent.Schema).parse(paymentIntentOptions);
310
+ // Only the base schema's output is included in the challenge.
311
+ return baseSchema.parse(request);
312
+ })),
313
+ },
314
+ async request(context) {
315
+ const { paymentIntentOptions, ...request } = context.request;
316
+ const resolved = baseRequest ? await baseRequest({ ...context, request }) : request;
317
+ return { ...resolved, paymentIntentOptions };
318
+ },
319
+ ...(baseRespond && {
320
+ respond: (context) => baseRespond(withoutPaymentIntentOptions(context)),
321
+ }),
322
+ ...(baseBroadcast && {
323
+ broadcast: (context) => baseBroadcast(withoutPaymentIntentOptions(context)),
324
+ }),
325
+ ...(baseValidate && {
326
+ validate: (context) => baseValidate(withoutPaymentIntentOptions(context)),
327
+ }),
328
+ ...(baseVerify && {
329
+ verify: (context) => baseVerify(withoutPaymentIntentOptions(context)),
330
+ }),
331
+ };
332
+ }
333
+ function withoutPaymentIntentOptions(context) {
334
+ const { paymentIntentOptions: _, ...request } = context.request;
335
+ return { ...context, request };
336
+ }
282
337
  //# sourceMappingURL=Methods.js.map