mppx 0.1.0 → 0.2.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.
Files changed (231) hide show
  1. package/README.md +1 -1
  2. package/dist/Challenge.d.ts +18 -18
  3. package/dist/Challenge.d.ts.map +1 -1
  4. package/dist/Challenge.js +8 -8
  5. package/dist/Challenge.js.map +1 -1
  6. package/dist/Errors.d.ts +58 -8
  7. package/dist/Errors.d.ts.map +1 -1
  8. package/dist/Errors.js +51 -9
  9. package/dist/Errors.js.map +1 -1
  10. package/dist/Method.d.ts +154 -0
  11. package/dist/Method.d.ts.map +1 -0
  12. package/dist/Method.js +81 -0
  13. package/dist/Method.js.map +1 -0
  14. package/dist/PaymentRequest.d.ts +5 -5
  15. package/dist/PaymentRequest.d.ts.map +1 -1
  16. package/dist/PaymentRequest.js +5 -5
  17. package/dist/cli.js +67 -18
  18. package/dist/cli.js.map +1 -1
  19. package/dist/client/Methods.d.ts +2 -2
  20. package/dist/client/Methods.d.ts.map +1 -1
  21. package/dist/client/Methods.js +2 -2
  22. package/dist/client/Methods.js.map +1 -1
  23. package/dist/client/Mppx.d.ts +12 -7
  24. package/dist/client/Mppx.d.ts.map +1 -1
  25. package/dist/client/Mppx.js +10 -5
  26. package/dist/client/Mppx.js.map +1 -1
  27. package/dist/client/internal/Fetch.d.ts +13 -11
  28. package/dist/client/internal/Fetch.d.ts.map +1 -1
  29. package/dist/client/internal/Fetch.js +8 -4
  30. package/dist/client/internal/Fetch.js.map +1 -1
  31. package/dist/index.d.ts +1 -2
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/mcp-sdk/client/McpClient.d.ts +6 -6
  36. package/dist/mcp-sdk/client/McpClient.d.ts.map +1 -1
  37. package/dist/mcp-sdk/client/McpClient.js +4 -4
  38. package/dist/mcp-sdk/client/McpClient.js.map +1 -1
  39. package/dist/middlewares/elysia.d.ts +1 -1
  40. package/dist/middlewares/express.d.ts +1 -1
  41. package/dist/middlewares/hono.d.ts +1 -1
  42. package/dist/middlewares/internal/mppx.d.ts +7 -7
  43. package/dist/middlewares/internal/mppx.d.ts.map +1 -1
  44. package/dist/middlewares/internal/mppx.js +5 -5
  45. package/dist/middlewares/internal/mppx.js.map +1 -1
  46. package/dist/middlewares/nextjs.d.ts +1 -1
  47. package/dist/proxy/Service.js +2 -2
  48. package/dist/proxy/Service.js.map +1 -1
  49. package/dist/server/Methods.d.ts +2 -2
  50. package/dist/server/Methods.d.ts.map +1 -1
  51. package/dist/server/Methods.js +2 -2
  52. package/dist/server/Methods.js.map +1 -1
  53. package/dist/server/Mppx.d.ts +17 -17
  54. package/dist/server/Mppx.d.ts.map +1 -1
  55. package/dist/server/Mppx.js +9 -9
  56. package/dist/server/Mppx.js.map +1 -1
  57. package/dist/stripe/{Intents.d.ts → Methods.d.ts} +22 -22
  58. package/dist/stripe/Methods.d.ts.map +1 -0
  59. package/dist/stripe/Methods.js +42 -0
  60. package/dist/stripe/Methods.js.map +1 -0
  61. package/dist/stripe/client/Charge.d.ts +48 -44
  62. package/dist/stripe/client/Charge.d.ts.map +1 -1
  63. package/dist/stripe/client/Charge.js +22 -17
  64. package/dist/stripe/client/Charge.js.map +1 -1
  65. package/dist/stripe/client/{MethodIntents.d.ts → Methods.d.ts} +25 -24
  66. package/dist/stripe/client/Methods.d.ts.map +1 -0
  67. package/dist/stripe/client/{MethodIntents.js → Methods.js} +4 -4
  68. package/dist/stripe/client/Methods.js.map +1 -0
  69. package/dist/stripe/client/index.d.ts +1 -1
  70. package/dist/stripe/client/index.d.ts.map +1 -1
  71. package/dist/stripe/client/index.js +1 -1
  72. package/dist/stripe/client/index.js.map +1 -1
  73. package/dist/stripe/index.d.ts +1 -1
  74. package/dist/stripe/index.d.ts.map +1 -1
  75. package/dist/stripe/index.js +1 -1
  76. package/dist/stripe/index.js.map +1 -1
  77. package/dist/stripe/internal/types.d.ts +25 -0
  78. package/dist/stripe/internal/types.d.ts.map +1 -0
  79. package/dist/stripe/internal/types.js +2 -0
  80. package/dist/stripe/internal/types.js.map +1 -0
  81. package/dist/stripe/server/Charge.d.ts +47 -28
  82. package/dist/stripe/server/Charge.d.ts.map +1 -1
  83. package/dist/stripe/server/Charge.js +90 -32
  84. package/dist/stripe/server/Charge.js.map +1 -1
  85. package/dist/stripe/server/{MethodIntents.d.ts → Methods.d.ts} +24 -23
  86. package/dist/stripe/server/Methods.d.ts.map +1 -0
  87. package/dist/stripe/server/{MethodIntents.js → Methods.js} +3 -3
  88. package/dist/stripe/server/Methods.js.map +1 -0
  89. package/dist/stripe/server/index.d.ts +1 -1
  90. package/dist/stripe/server/index.d.ts.map +1 -1
  91. package/dist/stripe/server/index.js +1 -1
  92. package/dist/stripe/server/index.js.map +1 -1
  93. package/dist/tempo/{Intents.d.ts → Methods.d.ts} +72 -69
  94. package/dist/tempo/Methods.d.ts.map +1 -0
  95. package/dist/tempo/Methods.js +118 -0
  96. package/dist/tempo/Methods.js.map +1 -0
  97. package/dist/tempo/client/ChannelOps.d.ts +1 -1
  98. package/dist/tempo/client/ChannelOps.js +1 -1
  99. package/dist/tempo/client/Charge.d.ts +25 -25
  100. package/dist/tempo/client/Charge.d.ts.map +1 -1
  101. package/dist/tempo/client/Charge.js +3 -3
  102. package/dist/tempo/client/Charge.js.map +1 -1
  103. package/dist/tempo/client/{MethodIntents.d.ts → Methods.d.ts} +74 -70
  104. package/dist/tempo/client/Methods.d.ts.map +1 -0
  105. package/dist/tempo/client/{MethodIntents.js → Methods.js} +3 -3
  106. package/dist/tempo/client/Methods.js.map +1 -0
  107. package/dist/tempo/client/Session.d.ts +49 -45
  108. package/dist/tempo/client/Session.d.ts.map +1 -1
  109. package/dist/tempo/client/Session.js +4 -4
  110. package/dist/tempo/client/Session.js.map +1 -1
  111. package/dist/tempo/client/SessionManager.d.ts +1 -1
  112. package/dist/tempo/client/SessionManager.d.ts.map +1 -1
  113. package/dist/tempo/client/SessionManager.js +10 -5
  114. package/dist/tempo/client/SessionManager.js.map +1 -1
  115. package/dist/tempo/client/index.d.ts +1 -1
  116. package/dist/tempo/client/index.d.ts.map +1 -1
  117. package/dist/tempo/client/index.js +1 -1
  118. package/dist/tempo/client/index.js.map +1 -1
  119. package/dist/tempo/index.d.ts +1 -1
  120. package/dist/tempo/index.d.ts.map +1 -1
  121. package/dist/tempo/index.js +1 -1
  122. package/dist/tempo/index.js.map +1 -1
  123. package/dist/tempo/internal/defaults.d.ts +1 -1
  124. package/dist/tempo/internal/defaults.js +1 -1
  125. package/dist/tempo/server/Charge.d.ts +27 -27
  126. package/dist/tempo/server/Charge.d.ts.map +1 -1
  127. package/dist/tempo/server/Charge.js +3 -3
  128. package/dist/tempo/server/Charge.js.map +1 -1
  129. package/dist/tempo/server/{MethodIntents.d.ts → Methods.d.ts} +73 -69
  130. package/dist/tempo/server/Methods.d.ts.map +1 -0
  131. package/dist/tempo/server/{MethodIntents.js → Methods.js} +4 -4
  132. package/dist/tempo/server/Methods.js.map +1 -0
  133. package/dist/tempo/server/Session.d.ts +51 -47
  134. package/dist/tempo/server/Session.d.ts.map +1 -1
  135. package/dist/tempo/server/Session.js +4 -4
  136. package/dist/tempo/server/Session.js.map +1 -1
  137. package/dist/tempo/server/index.d.ts +6 -0
  138. package/dist/tempo/server/index.d.ts.map +1 -0
  139. package/dist/tempo/server/index.js +6 -0
  140. package/dist/tempo/server/index.js.map +1 -0
  141. package/dist/tempo/server/internal/transport.d.ts.map +1 -1
  142. package/dist/tempo/server/internal/transport.js +2 -1
  143. package/dist/tempo/server/internal/transport.js.map +1 -1
  144. package/package.json +1 -1
  145. package/src/Challenge.test-d.ts +3 -3
  146. package/src/Challenge.test.ts +6 -6
  147. package/src/Challenge.ts +34 -34
  148. package/src/Errors.test.ts +75 -21
  149. package/src/Errors.ts +74 -9
  150. package/src/Method.test.ts +76 -0
  151. package/src/Method.ts +228 -0
  152. package/src/PaymentRequest.test.ts +4 -4
  153. package/src/PaymentRequest.ts +9 -9
  154. package/src/cli.test.ts +12 -22
  155. package/src/cli.ts +74 -21
  156. package/src/client/Methods.ts +2 -2
  157. package/src/client/Mppx.test-d.ts +6 -6
  158. package/src/client/Mppx.test.ts +26 -22
  159. package/src/client/Mppx.ts +29 -13
  160. package/src/client/Transport.test.ts +2 -2
  161. package/src/client/internal/Fetch.test.ts +35 -1
  162. package/src/client/internal/Fetch.ts +36 -27
  163. package/src/index.ts +1 -2
  164. package/src/mcp-sdk/client/McpClient.ts +11 -13
  165. package/src/middlewares/elysia.ts +1 -1
  166. package/src/middlewares/express.ts +1 -1
  167. package/src/middlewares/hono.ts +1 -1
  168. package/src/middlewares/internal/mppx.ts +10 -10
  169. package/src/middlewares/nextjs.ts +1 -1
  170. package/src/proxy/Service.ts +2 -2
  171. package/src/server/Methods.ts +2 -2
  172. package/src/server/Mppx.test-d.ts +27 -29
  173. package/src/server/Mppx.test.ts +23 -19
  174. package/src/server/Mppx.ts +43 -43
  175. package/src/server/Transport.test.ts +3 -3
  176. package/src/stripe/Charge.integration.test.ts +4 -1
  177. package/src/stripe/{Intents.test.ts → Methods.test.ts} +12 -12
  178. package/src/stripe/Methods.ts +45 -0
  179. package/src/stripe/client/Charge.test.ts +189 -0
  180. package/src/stripe/client/Charge.ts +40 -31
  181. package/src/stripe/client/{MethodIntents.ts → Methods.ts} +3 -3
  182. package/src/stripe/client/index.ts +1 -1
  183. package/src/stripe/index.ts +1 -1
  184. package/src/stripe/internal/types.ts +22 -0
  185. package/src/stripe/server/Charge.test.ts +241 -0
  186. package/src/stripe/server/Charge.ts +124 -38
  187. package/src/stripe/server/{MethodIntents.ts → Methods.ts} +2 -2
  188. package/src/stripe/server/index.ts +1 -1
  189. package/src/tempo/{Intents.test.ts → Methods.test.ts} +15 -15
  190. package/src/tempo/{Intents.ts → Methods.ts} +77 -22
  191. package/src/tempo/client/ChannelOps.ts +1 -1
  192. package/src/tempo/client/Charge.ts +3 -3
  193. package/src/tempo/client/{MethodIntents.ts → Methods.ts} +2 -2
  194. package/src/tempo/client/Session.ts +4 -4
  195. package/src/tempo/client/SessionManager.ts +11 -5
  196. package/src/tempo/client/index.ts +1 -1
  197. package/src/tempo/index.ts +1 -1
  198. package/src/tempo/internal/defaults.ts +1 -1
  199. package/src/tempo/server/Charge.ts +4 -7
  200. package/src/tempo/server/{MethodIntents.ts → Methods.ts} +3 -3
  201. package/src/tempo/server/Session.test.ts +4 -7
  202. package/src/tempo/server/Session.ts +6 -6
  203. package/src/tempo/server/index.ts +1 -1
  204. package/src/tempo/server/internal/transport.ts +3 -2
  205. package/dist/Intent.d.ts +0 -101
  206. package/dist/Intent.d.ts.map +0 -1
  207. package/dist/Intent.js +0 -83
  208. package/dist/Intent.js.map +0 -1
  209. package/dist/MethodIntent.d.ts +0 -225
  210. package/dist/MethodIntent.d.ts.map +0 -1
  211. package/dist/MethodIntent.js +0 -156
  212. package/dist/MethodIntent.js.map +0 -1
  213. package/dist/stripe/Intents.d.ts.map +0 -1
  214. package/dist/stripe/Intents.js +0 -27
  215. package/dist/stripe/Intents.js.map +0 -1
  216. package/dist/stripe/client/MethodIntents.d.ts.map +0 -1
  217. package/dist/stripe/client/MethodIntents.js.map +0 -1
  218. package/dist/stripe/server/MethodIntents.d.ts.map +0 -1
  219. package/dist/stripe/server/MethodIntents.js.map +0 -1
  220. package/dist/tempo/Intents.d.ts.map +0 -1
  221. package/dist/tempo/Intents.js +0 -81
  222. package/dist/tempo/Intents.js.map +0 -1
  223. package/dist/tempo/client/MethodIntents.d.ts.map +0 -1
  224. package/dist/tempo/client/MethodIntents.js.map +0 -1
  225. package/dist/tempo/server/MethodIntents.d.ts.map +0 -1
  226. package/dist/tempo/server/MethodIntents.js.map +0 -1
  227. package/src/Intent.test.ts +0 -180
  228. package/src/Intent.ts +0 -109
  229. package/src/MethodIntent.test.ts +0 -303
  230. package/src/MethodIntent.ts +0 -388
  231. package/src/stripe/Intents.ts +0 -27
@@ -1,22 +1,37 @@
1
+ import type * as Credential from '../../Credential.js'
1
2
  import {
2
3
  PaymentActionRequiredError,
3
4
  PaymentExpiredError,
4
5
  VerificationFailedError,
5
6
  } from '../../Errors.js'
6
- import type { LooseOmit } from '../../internal/types.js'
7
- import * as MethodIntent from '../../MethodIntent.js'
8
- import * as Intents from '../Intents.js'
7
+ import type { LooseOmit, OneOf } from '../../internal/types.js'
8
+ import * as Method from '../../Method.js'
9
+ import type { StripeClient } from '../internal/types.js'
10
+ import * as Methods from '../Methods.js'
9
11
 
10
12
  /**
11
13
  * Creates a Stripe charge method intent for usage on the server.
12
14
  *
13
15
  * Verifies payment by creating a Stripe PaymentIntent with the provided SPT.
14
16
  *
17
+ * Accepts either a `client` (a pre-configured Stripe SDK instance) or a raw
18
+ * `secretKey`. Using `client` is recommended—it lets you configure retries,
19
+ * API version, and other options on the Stripe instance you control.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import Stripe from 'stripe'
24
+ * import { stripe } from 'mppx/server'
25
+ *
26
+ * const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!)
27
+ * const charge = stripe.charge({ client: stripeClient, networkId: 'internal', paymentMethodTypes: ['card'] })
28
+ * ```
29
+ *
15
30
  * @example
16
31
  * ```ts
17
32
  * import { stripe } from 'mppx/server'
18
33
  *
19
- * const charge = stripe.charge({ secretKey: 'sk_...' })
34
+ * const charge = stripe.charge({ secretKey: 'sk_...', networkId: 'internal', paymentMethodTypes: ['card'] })
20
35
  * ```
21
36
  */
22
37
  export function charge<const parameters extends charge.Parameters>(parameters: parameters) {
@@ -29,11 +44,13 @@ export function charge<const parameters extends charge.Parameters>(parameters: p
29
44
  metadata,
30
45
  networkId,
31
46
  paymentMethodTypes,
32
- secretKey,
33
47
  } = parameters
34
48
 
49
+ const client = 'client' in parameters ? parameters.client : undefined
50
+ const secretKey = 'secretKey' in parameters ? parameters.secretKey : undefined
51
+
35
52
  type Defaults = charge.DeriveDefaults<parameters>
36
- return MethodIntent.toServer<typeof Intents.charge, Defaults>(Intents.charge, {
53
+ return Method.toServer<typeof Methods.charge, Defaults>(Methods.charge, {
37
54
  defaults: {
38
55
  amount,
39
56
  currency,
@@ -52,41 +69,25 @@ export function charge<const parameters extends charge.Parameters>(parameters: p
52
69
  if (request.expires && new Date(request.expires) < new Date())
53
70
  throw new PaymentExpiredError({ expires: request.expires })
54
71
 
55
- const parsed = Intents.charge.schema.credential.payload.safeParse(credential.payload)
72
+ const parsed = Methods.charge.schema.credential.payload.safeParse(credential.payload)
56
73
  if (!parsed.success) throw new Error('Invalid credential payload: missing or malformed spt')
57
74
  const { spt, externalId: credentialExternalId } = parsed.data as {
58
75
  spt: string
59
76
  externalId?: string
60
77
  }
61
78
 
62
- const body = new URLSearchParams({
63
- amount: request.amount as string,
64
- currency: request.currency as string,
65
- shared_payment_granted_token: spt,
66
- confirm: 'true',
67
- 'automatic_payment_methods[enabled]': 'true',
68
- 'automatic_payment_methods[allow_redirects]': 'never',
69
- })
70
- const resolvedMetadata = request.methodDetails?.metadata as Record<string, string> | undefined
71
- if (resolvedMetadata) {
72
- for (const [key, value] of Object.entries(resolvedMetadata)) {
73
- body.set(`metadata[${key}]`, value)
74
- }
75
- }
79
+ const userMetadata = request.methodDetails?.metadata as Record<string, string> | undefined
80
+ const resolvedMetadata = { ...buildAnalytics({ credential }), ...userMetadata }
76
81
 
77
- const response = await fetch('https://api.stripe.com/v1/payment_intents', {
78
- method: 'POST',
79
- headers: {
80
- Authorization: `Basic ${btoa(`${secretKey}:`)}`,
81
- 'Content-Type': 'application/x-www-form-urlencoded',
82
- 'Idempotency-Key': `mppx_${challenge.id}_${spt}`,
83
- },
84
- body,
85
- })
86
-
87
- if (!response.ok) throw new VerificationFailedError({ reason: 'Stripe PaymentIntent failed' })
88
-
89
- const pi = (await response.json()) as { id: string; status: string }
82
+ const pi = client
83
+ ? await createWithClient({ client, challenge, request, spt, metadata: resolvedMetadata })
84
+ : await createWithSecretKey({
85
+ secretKey: secretKey!,
86
+ challenge,
87
+ request,
88
+ spt,
89
+ metadata: resolvedMetadata,
90
+ })
90
91
 
91
92
  if (pi.status === 'requires_action') {
92
93
  throw new PaymentActionRequiredError({ reason: 'Stripe PaymentIntent requires action' })
@@ -105,17 +106,102 @@ export function charge<const parameters extends charge.Parameters>(parameters: p
105
106
  }
106
107
 
107
108
  export declare namespace charge {
108
- type Defaults = LooseOmit<MethodIntent.RequestDefaults<typeof Intents.charge>, 'recipient'>
109
+ type Defaults = LooseOmit<Method.RequestDefaults<typeof Methods.charge>, 'recipient'>
109
110
 
110
111
  type Parameters = {
111
- /** Stripe secret API key. */
112
- secretKey: string
113
112
  /** Optional metadata to include in SPT creation requests. */
114
113
  metadata?: Record<string, string> | undefined
115
- } & Defaults
114
+ } & Defaults &
115
+ OneOf<
116
+ | {
117
+ /** Pre-configured Stripe SDK instance. Any object matching the duck-typed `StripeClient` shape works. */
118
+ client: StripeClient
119
+ }
120
+ | {
121
+ /** Stripe secret API key. */
122
+ secretKey: string
123
+ }
124
+ >
116
125
 
117
126
  type DeriveDefaults<parameters extends Parameters> = Pick<
118
127
  parameters,
119
128
  Extract<keyof parameters, keyof Defaults>
120
129
  > & { decimals: number }
121
130
  }
131
+
132
+ /** Creates a PaymentIntent using the Stripe SDK client. */
133
+ async function createWithClient(parameters: {
134
+ client: StripeClient
135
+ challenge: { id: string }
136
+ metadata: Record<string, string>
137
+ request: { amount: unknown; currency: unknown }
138
+ spt: string
139
+ }): Promise<{ id: string; status: string }> {
140
+ const { client, challenge, metadata, request, spt } = parameters
141
+ try {
142
+ const result = await client.paymentIntents.create(
143
+ {
144
+ amount: Number(request.amount),
145
+ automatic_payment_methods: { allow_redirects: 'never', enabled: true },
146
+ confirm: true,
147
+ currency: request.currency as string,
148
+ metadata,
149
+ payment_method: spt,
150
+ },
151
+ { idempotencyKey: `mppx_${challenge.id}_${spt}` },
152
+ )
153
+ return { id: result.id, status: result.status }
154
+ } catch {
155
+ throw new VerificationFailedError({ reason: 'Stripe PaymentIntent failed' })
156
+ }
157
+ }
158
+
159
+ /** Creates a PaymentIntent using a raw secret key and fetch. */
160
+ async function createWithSecretKey(parameters: {
161
+ secretKey: string
162
+ challenge: { id: string }
163
+ metadata: Record<string, string>
164
+ request: { amount: unknown; currency: unknown }
165
+ spt: string
166
+ }): Promise<{ id: string; status: string }> {
167
+ const { secretKey, challenge, metadata, request, spt } = parameters
168
+
169
+ const body = new URLSearchParams({
170
+ amount: request.amount as string,
171
+ 'automatic_payment_methods[allow_redirects]': 'never',
172
+ 'automatic_payment_methods[enabled]': 'true',
173
+ confirm: 'true',
174
+ currency: request.currency as string,
175
+ shared_payment_granted_token: spt,
176
+ })
177
+ for (const [key, value] of Object.entries(metadata)) {
178
+ body.set(`metadata[${key}]`, value)
179
+ }
180
+
181
+ const response = await fetch('https://api.stripe.com/v1/payment_intents', {
182
+ method: 'POST',
183
+ headers: {
184
+ Authorization: `Basic ${btoa(`${secretKey}:`)}`,
185
+ 'Content-Type': 'application/x-www-form-urlencoded',
186
+ 'Idempotency-Key': `mppx_${challenge.id}_${spt}`,
187
+ },
188
+ body,
189
+ })
190
+
191
+ if (!response.ok) throw new VerificationFailedError({ reason: 'Stripe PaymentIntent failed' })
192
+ return (await response.json()) as { id: string; status: string }
193
+ }
194
+
195
+ /** @internal */
196
+ function buildAnalytics(parameters: { credential: Credential.Credential }): Record<string, string> {
197
+ const { credential } = parameters
198
+ const { challenge } = credential
199
+ return {
200
+ mpp_version: '1',
201
+ mpp_is_mpp: 'true',
202
+ mpp_intent: challenge.intent,
203
+ mpp_challenge_id: challenge.id,
204
+ mpp_server_id: challenge.realm,
205
+ ...(credential.source ? { mpp_client_id: credential.source } : {}),
206
+ }
207
+ }
@@ -1,7 +1,7 @@
1
1
  import { charge as charge_ } from './Charge.js'
2
2
 
3
3
  /**
4
- * Creates a Stripe `charge` method intent for usage on the server.
4
+ * Creates a Stripe `charge` method for usage on the server.
5
5
  *
6
6
  * @example
7
7
  * ```ts
@@ -19,6 +19,6 @@ export function stripe<const parameters extends stripe.Parameters>(parameters: p
19
19
  export namespace stripe {
20
20
  export type Parameters = charge_.Parameters
21
21
 
22
- /** Creates a Stripe `charge` method intent for SPT-based payments. */
22
+ /** Creates a Stripe `charge` method for SPT-based payments. */
23
23
  export const charge = charge_
24
24
  }
@@ -1,2 +1,2 @@
1
1
  export { charge } from './Charge.js'
2
- export { stripe } from './MethodIntents.js'
2
+ export { stripe } from './Methods.js'
@@ -1,22 +1,22 @@
1
- import { MethodIntents } from 'mppx/tempo'
1
+ import { Methods } from 'mppx/tempo'
2
2
  import { describe, expect, expectTypeOf, test } from 'vitest'
3
3
 
4
4
  describe('charge', () => {
5
- test('has correct name and method', () => {
6
- expect(MethodIntents.charge.name).toBe('charge')
7
- expect(MethodIntents.charge.method).toBe('tempo')
5
+ test('has correct name and intent', () => {
6
+ expect(Methods.charge.intent).toBe('charge')
7
+ expect(Methods.charge.name).toBe('tempo')
8
8
  })
9
9
 
10
- test('types: name is literal', () => {
11
- expectTypeOf(MethodIntents.charge.name).toEqualTypeOf<'charge'>()
10
+ test('types: intent is literal', () => {
11
+ expectTypeOf(Methods.charge.intent).toEqualTypeOf<'charge'>()
12
12
  })
13
13
 
14
- test('types: method is literal', () => {
15
- expectTypeOf(MethodIntents.charge.method).toEqualTypeOf<'tempo'>()
14
+ test('types: name is literal', () => {
15
+ expectTypeOf(Methods.charge.name).toEqualTypeOf<'tempo'>()
16
16
  })
17
17
 
18
18
  test('schema: validates valid request', () => {
19
- const result = MethodIntents.charge.schema.request.safeParse({
19
+ const result = Methods.charge.schema.request.safeParse({
20
20
  amount: '1',
21
21
  currency: '0x20c0000000000000000000000000000000000001',
22
22
  decimals: 6,
@@ -27,7 +27,7 @@ describe('charge', () => {
27
27
  })
28
28
 
29
29
  test('schema: validates request with methodDetails', () => {
30
- const result = MethodIntents.charge.schema.request.safeParse({
30
+ const result = Methods.charge.schema.request.safeParse({
31
31
  amount: '1',
32
32
  currency: '0x20c0000000000000000000000000000000000001',
33
33
  decimals: 6,
@@ -40,7 +40,7 @@ describe('charge', () => {
40
40
  })
41
41
 
42
42
  test('schema: validates request with memo', () => {
43
- const result = MethodIntents.charge.schema.request.safeParse({
43
+ const result = Methods.charge.schema.request.safeParse({
44
44
  amount: '1',
45
45
  currency: '0x20c0000000000000000000000000000000000001',
46
46
  decimals: 6,
@@ -52,14 +52,14 @@ describe('charge', () => {
52
52
  })
53
53
 
54
54
  test('schema: rejects invalid request', () => {
55
- const result = MethodIntents.charge.schema.request.safeParse({
55
+ const result = Methods.charge.schema.request.safeParse({
56
56
  amount: '1',
57
57
  })
58
58
  expect(result.success).toBe(false)
59
59
  })
60
60
 
61
61
  test('schema: validates transaction payload', () => {
62
- const result = MethodIntents.charge.schema.credential.payload.safeParse({
62
+ const result = Methods.charge.schema.credential.payload.safeParse({
63
63
  signature: '0x76f90100000000000000000000000000000000000000000000000000000000000000000000',
64
64
  type: 'transaction',
65
65
  })
@@ -67,7 +67,7 @@ describe('charge', () => {
67
67
  })
68
68
 
69
69
  test('schema: validates hash payload', () => {
70
- const result = MethodIntents.charge.schema.credential.payload.safeParse({
70
+ const result = Methods.charge.schema.credential.payload.safeParse({
71
71
  hash: '0x1a2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890',
72
72
  type: 'hash',
73
73
  })
@@ -75,7 +75,7 @@ describe('charge', () => {
75
75
  })
76
76
 
77
77
  test('schema: rejects invalid payload type', () => {
78
- const result = MethodIntents.charge.schema.credential.payload.safeParse({
78
+ const result = Methods.charge.schema.credential.payload.safeParse({
79
79
  signature: '0x...',
80
80
  type: 'keyAuthorization',
81
81
  })
@@ -1,6 +1,7 @@
1
1
  import type { Account } from 'viem'
2
- import * as Intent from '../Intent.js'
3
- import * as MethodIntent from '../MethodIntent.js'
2
+ import { parseUnits } from 'viem'
3
+ import * as Expires from '../Expires.js'
4
+ import * as Method from '../Method.js'
4
5
  import * as z from '../zod.js'
5
6
 
6
7
  /**
@@ -8,8 +9,9 @@ import * as z from '../zod.js'
8
9
  *
9
10
  * @see https://github.com/tempoxyz/payment-auth-spec/blob/main/specs/methods/tempo/draft-tempo-charge-00.md
10
11
  */
11
- export const charge = MethodIntent.fromIntent(Intent.charge, {
12
- method: 'tempo',
12
+ export const charge = Method.from({
13
+ name: 'tempo',
14
+ intent: 'charge',
13
15
  schema: {
14
16
  credential: {
15
17
  payload: z.discriminatedUnion('type', [
@@ -17,9 +19,15 @@ export const charge = MethodIntent.fromIntent(Intent.charge, {
17
19
  z.object({ signature: z.signature(), type: z.literal('transaction') }),
18
20
  ]),
19
21
  },
20
- request: {
21
- methodDetails: z.object({
22
+ request: z.pipe(
23
+ z.object({
24
+ amount: z.amount(),
22
25
  chainId: z.optional(z.number()),
26
+ currency: z.string(),
27
+ decimals: z.number(),
28
+ description: z.optional(z.string()),
29
+ expires: z._default(z.datetime(), () => Expires.minutes(5)),
30
+ externalId: z.optional(z.string()),
23
31
  feePayer: z.optional(
24
32
  z.pipe(
25
33
  z.union([z.boolean(), z.custom<Account>()]),
@@ -27,9 +35,22 @@ export const charge = MethodIntent.fromIntent(Intent.charge, {
27
35
  ),
28
36
  ),
29
37
  memo: z.optional(z.hash()),
38
+ recipient: z.optional(z.string()),
30
39
  }),
31
- requires: ['decimals', 'recipient'],
32
- },
40
+ z.transform(({ amount, chainId, decimals, feePayer, memo, ...rest }) => ({
41
+ ...rest,
42
+ amount: parseUnits(amount, decimals).toString(),
43
+ ...(chainId !== undefined || feePayer !== undefined || memo !== undefined
44
+ ? {
45
+ methodDetails: {
46
+ ...(chainId !== undefined && { chainId }),
47
+ ...(feePayer !== undefined && { feePayer }),
48
+ ...(memo !== undefined && { memo }),
49
+ },
50
+ }
51
+ : {}),
52
+ })),
53
+ ),
33
54
  },
34
55
  })
35
56
 
@@ -39,26 +60,27 @@ export const charge = MethodIntent.fromIntent(Intent.charge, {
39
60
  * Uses cumulative vouchers over a payment channel. Credential payloads
40
61
  * are a discriminated union on `action`: open, topUp, voucher, close.
41
62
  */
42
- export const session = MethodIntent.fromIntent(Intent.session, {
43
- method: 'tempo',
63
+ export const session = Method.from({
64
+ name: 'tempo',
65
+ intent: 'session',
44
66
  schema: {
45
67
  credential: {
46
68
  payload: z.discriminatedUnion('action', [
47
69
  z.object({
48
70
  action: z.literal('open'),
49
- type: z.literal('transaction'),
50
- channelId: z.hash(),
51
- transaction: z.signature(),
52
71
  authorizedSigner: z.optional(z.string()),
72
+ channelId: z.hash(),
53
73
  cumulativeAmount: z.amount(),
54
74
  signature: z.signature(),
75
+ transaction: z.signature(),
76
+ type: z.literal('transaction'),
55
77
  }),
56
78
  z.object({
57
79
  action: z.literal('topUp'),
58
- type: z.literal('transaction'),
80
+ additionalDeposit: z.amount(),
59
81
  channelId: z.hash(),
60
82
  transaction: z.signature(),
61
- additionalDeposit: z.amount(),
83
+ type: z.literal('transaction'),
62
84
  }),
63
85
  z.object({
64
86
  action: z.literal('voucher'),
@@ -74,20 +96,53 @@ export const session = MethodIntent.fromIntent(Intent.session, {
74
96
  }),
75
97
  ]),
76
98
  },
77
- request: {
78
- methodDetails: z.object({
79
- escrowContract: z.string(),
80
- channelId: z.optional(z.hash()),
81
- minVoucherDelta: z.optional(z.amount()),
99
+ request: z.pipe(
100
+ z.object({
101
+ amount: z.amount(),
82
102
  chainId: z.optional(z.number()),
103
+ channelId: z.optional(z.hash()),
104
+ currency: z.string(),
105
+ decimals: z.number(),
106
+ escrowContract: z.optional(z.string()),
83
107
  feePayer: z.optional(
84
108
  z.pipe(
85
109
  z.union([z.boolean(), z.custom<Account>()]),
86
110
  z.transform((v): boolean => (typeof v === 'object' ? true : v)),
87
111
  ),
88
112
  ),
113
+ minVoucherDelta: z.optional(z.amount()),
114
+ recipient: z.optional(z.string()),
115
+ suggestedDeposit: z.optional(z.amount()),
116
+ unitType: z.string(),
89
117
  }),
90
- requires: ['decimals', 'recipient', 'currency'],
91
- },
118
+ z.transform(
119
+ ({
120
+ amount,
121
+ chainId,
122
+ channelId,
123
+ decimals,
124
+ escrowContract,
125
+ feePayer,
126
+ minVoucherDelta,
127
+ suggestedDeposit,
128
+ ...rest
129
+ }) => ({
130
+ ...rest,
131
+ amount: parseUnits(amount, decimals).toString(),
132
+ ...(suggestedDeposit
133
+ ? {
134
+ suggestedDeposit: parseUnits(suggestedDeposit, decimals).toString(),
135
+ }
136
+ : {}),
137
+ methodDetails: {
138
+ escrowContract,
139
+ ...(channelId !== undefined && { channelId }),
140
+ ...(minVoucherDelta !== undefined && { minVoucherDelta }),
141
+ ...(chainId !== undefined && { chainId }),
142
+ ...(feePayer !== undefined && { feePayer }),
143
+ },
144
+ }),
145
+ ),
146
+ ),
92
147
  },
93
148
  })
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Shared client-side channel operations.
3
3
  *
4
- * Provides the low-level helpers that both `session()` (MethodIntent plugin)
4
+ * Provides the low-level helpers that both `session()`
5
5
  * and `sessionManager()` (orchestrator) rely on: escrow resolution, channel
6
6
  * ID computation, on-chain open/voucher/close payload construction, channel
7
7
  * recovery from on-chain state, and credential serialization.
@@ -3,13 +3,13 @@ import { prepareTransactionRequest, signTransaction } from 'viem/actions'
3
3
  import { tempo as tempo_chain } from 'viem/chains'
4
4
  import { Actions } from 'viem/tempo'
5
5
  import * as Credential from '../../Credential.js'
6
- import * as MethodIntent from '../../MethodIntent.js'
6
+ import * as Method from '../../Method.js'
7
7
  import * as Account from '../../viem/Account.js'
8
8
  import * as Client from '../../viem/Client.js'
9
9
  import * as z from '../../zod.js'
10
10
  import * as Attribution from '../Attribution.js'
11
- import * as Intents from '../Intents.js'
12
11
  import * as defaults from '../internal/defaults.js'
12
+ import * as Methods from '../Methods.js'
13
13
 
14
14
  /**
15
15
  * Creates a Tempo charge method intent for usage on the client.
@@ -33,7 +33,7 @@ export function charge(parameters: charge.Parameters = {}) {
33
33
  })
34
34
  const getAccount = Account.getResolver({ account: parameters.account })
35
35
 
36
- return MethodIntent.toClient(Intents.charge, {
36
+ return Method.toClient(Methods.charge, {
37
37
  context: z.object({
38
38
  account: z.optional(z.custom<Account.getResolver.Parameters['account']>()),
39
39
  }),
@@ -3,7 +3,7 @@ import { session as sessionIntent_ } from './Session.js'
3
3
  import { sessionManager as session_ } from './SessionManager.js'
4
4
 
5
5
  /**
6
- * Creates both Tempo `charge` and `session` client method intents from shared parameters.
6
+ * Creates both Tempo `charge` and `session` client methods from shared parameters.
7
7
  *
8
8
  * @example
9
9
  * ```ts
@@ -21,7 +21,7 @@ export function tempo(parameters: tempo.Parameters = {}) {
21
21
  export namespace tempo {
22
22
  export type Parameters = charge_.Parameters & sessionIntent_.Parameters
23
23
 
24
- /** Creates a Tempo `charge` client method intent for one-time TIP-20 token transfers. */
24
+ /** Creates a Tempo `charge` client method for one-time TIP-20 token transfers. */
25
25
  export const charge = charge_
26
26
  /** Creates a client-side streaming session for managing payment channels. */
27
27
  export const session = session_
@@ -2,12 +2,12 @@ import type { Hex } from 'ox'
2
2
  import { type Address, parseUnits, type Account as viem_Account } from 'viem'
3
3
  import { tempo as tempo_chain } from 'viem/chains'
4
4
  import type * as Challenge from '../../Challenge.js'
5
- import * as MethodIntent from '../../MethodIntent.js'
5
+ import * as Method from '../../Method.js'
6
6
  import * as Account from '../../viem/Account.js'
7
7
  import * as Client from '../../viem/Client.js'
8
8
  import * as z from '../../zod.js'
9
- import * as Intents from '../Intents.js'
10
9
  import * as defaults from '../internal/defaults.js'
10
+ import * as Methods from '../Methods.js'
11
11
  import type { StreamCredentialPayload } from '../stream/Types.js'
12
12
  import { signVoucher } from '../stream/Voucher.js'
13
13
  import {
@@ -35,7 +35,7 @@ export const streamContextSchema = z.object({
35
35
  export type StreamContext = z.infer<typeof streamContextSchema>
36
36
 
37
37
  /**
38
- * Creates a session payment MethodIntent plugin for use with `Mppx.create()`.
38
+ * Creates a session payment method for use with `Mppx.create()`.
39
39
  *
40
40
  * Supports both auto mode (set `deposit` to manage channels automatically)
41
41
  * and manual mode (pass `context.action` to control each step).
@@ -330,7 +330,7 @@ export function session(parameters: session.Parameters = {}) {
330
330
  return serializeCredential(challenge, payload, chainId, account)
331
331
  }
332
332
 
333
- return MethodIntent.toClient(Intents.session, {
333
+ return Method.toClient(Methods.session, {
334
334
  context: streamContextSchema,
335
335
 
336
336
  async createCredential({ challenge, context }) {
@@ -38,7 +38,7 @@ export type PaymentResponse = Response & {
38
38
  * Creates a session manager that handles the full client payment lifecycle:
39
39
  * channel open, incremental vouchers, SSE streaming, and channel close.
40
40
  *
41
- * Internally delegates to the `session()` MethodIntent plugin for all
41
+ * Internally delegates to the `session()` method for all
42
42
  * channel state management and credential creation, and to `Fetch.from`
43
43
  * for the 402 challenge/retry flow.
44
44
  *
@@ -75,8 +75,9 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
75
75
  const wrappedFetch = Fetch.from({
76
76
  fetch: fetchFn,
77
77
  methods: [method],
78
- onChallenge(c) {
79
- lastChallenge = c
78
+ onChallenge: async (challenge, _helpers) => {
79
+ lastChallenge = challenge
80
+ return undefined
80
81
  },
81
82
  })
82
83
 
@@ -150,10 +151,15 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
150
151
  ...fetchInit.headers,
151
152
  Accept: 'text/event-stream',
152
153
  },
154
+ ...(signal ? { signal } : {}),
153
155
  }
154
156
 
155
157
  const response = await doFetch(input, sseInit)
156
158
 
159
+ // Snapshot the challenge at SSE open time so concurrent
160
+ // calls don't overwrite it.
161
+ const sseChallenge = lastChallenge
162
+
157
163
  if (!response.body) throw new Error('Response has no body.')
158
164
 
159
165
  const reader = response.body.getReader()
@@ -186,13 +192,13 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
186
192
  break
187
193
 
188
194
  case 'payment-need-voucher': {
189
- if (!channel || !lastChallenge) break
195
+ if (!channel || !sseChallenge) break
190
196
  const required = BigInt(event.data.requiredCumulative)
191
197
  channel.cumulativeAmount =
192
198
  channel.cumulativeAmount > required ? channel.cumulativeAmount : required
193
199
 
194
200
  const credential = await method.createCredential({
195
- challenge: lastChallenge as never,
201
+ challenge: sseChallenge as never,
196
202
  context: {
197
203
  action: 'voucher',
198
204
  channelId: channel.channelId,
@@ -1,5 +1,5 @@
1
1
  export { charge } from './Charge.js'
2
- export { tempo } from './MethodIntents.js'
2
+ export { tempo } from './Methods.js'
3
3
  export { session } from './Session.js'
4
4
  export type { PaymentResponse, SessionManager } from './SessionManager.js'
5
5
  export { sessionManager } from './SessionManager.js'
@@ -1,2 +1,2 @@
1
- export * as MethodIntents from './Intents.js'
1
+ export * as Methods from './Methods.js'
2
2
  export * as Stream from './stream/index.js'
@@ -11,7 +11,7 @@ export const escrowContract = {
11
11
  export const testnetChainId = 42431
12
12
 
13
13
  /**
14
- * Default token decimals for TIP-20 stablecoins (e.g. alphaUSD).
14
+ * Default token decimals for TIP-20 stablecoins (e.g. pathUSD).
15
15
  *
16
16
  * All TIP-20 tokens on Tempo use 6 decimals, so there is no risk of
17
17
  * client/server mismatch within the Tempo ecosystem. Other chains and