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.
- package/README.md +1 -1
- package/dist/Challenge.d.ts +18 -18
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +8 -8
- package/dist/Challenge.js.map +1 -1
- package/dist/Errors.d.ts +58 -8
- package/dist/Errors.d.ts.map +1 -1
- package/dist/Errors.js +51 -9
- package/dist/Errors.js.map +1 -1
- package/dist/Method.d.ts +154 -0
- package/dist/Method.d.ts.map +1 -0
- package/dist/Method.js +81 -0
- package/dist/Method.js.map +1 -0
- package/dist/PaymentRequest.d.ts +5 -5
- package/dist/PaymentRequest.d.ts.map +1 -1
- package/dist/PaymentRequest.js +5 -5
- package/dist/cli.js +67 -18
- package/dist/cli.js.map +1 -1
- package/dist/client/Methods.d.ts +2 -2
- package/dist/client/Methods.d.ts.map +1 -1
- package/dist/client/Methods.js +2 -2
- package/dist/client/Methods.js.map +1 -1
- package/dist/client/Mppx.d.ts +12 -7
- package/dist/client/Mppx.d.ts.map +1 -1
- package/dist/client/Mppx.js +10 -5
- package/dist/client/Mppx.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts +13 -11
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +8 -4
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp-sdk/client/McpClient.d.ts +6 -6
- package/dist/mcp-sdk/client/McpClient.d.ts.map +1 -1
- package/dist/mcp-sdk/client/McpClient.js +4 -4
- package/dist/mcp-sdk/client/McpClient.js.map +1 -1
- package/dist/middlewares/elysia.d.ts +1 -1
- package/dist/middlewares/express.d.ts +1 -1
- package/dist/middlewares/hono.d.ts +1 -1
- package/dist/middlewares/internal/mppx.d.ts +7 -7
- package/dist/middlewares/internal/mppx.d.ts.map +1 -1
- package/dist/middlewares/internal/mppx.js +5 -5
- package/dist/middlewares/internal/mppx.js.map +1 -1
- package/dist/middlewares/nextjs.d.ts +1 -1
- package/dist/proxy/Service.js +2 -2
- package/dist/proxy/Service.js.map +1 -1
- package/dist/server/Methods.d.ts +2 -2
- package/dist/server/Methods.d.ts.map +1 -1
- package/dist/server/Methods.js +2 -2
- package/dist/server/Methods.js.map +1 -1
- package/dist/server/Mppx.d.ts +17 -17
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +9 -9
- package/dist/server/Mppx.js.map +1 -1
- package/dist/stripe/{Intents.d.ts → Methods.d.ts} +22 -22
- package/dist/stripe/Methods.d.ts.map +1 -0
- package/dist/stripe/Methods.js +42 -0
- package/dist/stripe/Methods.js.map +1 -0
- package/dist/stripe/client/Charge.d.ts +48 -44
- package/dist/stripe/client/Charge.d.ts.map +1 -1
- package/dist/stripe/client/Charge.js +22 -17
- package/dist/stripe/client/Charge.js.map +1 -1
- package/dist/stripe/client/{MethodIntents.d.ts → Methods.d.ts} +25 -24
- package/dist/stripe/client/Methods.d.ts.map +1 -0
- package/dist/stripe/client/{MethodIntents.js → Methods.js} +4 -4
- package/dist/stripe/client/Methods.js.map +1 -0
- package/dist/stripe/client/index.d.ts +1 -1
- package/dist/stripe/client/index.d.ts.map +1 -1
- package/dist/stripe/client/index.js +1 -1
- package/dist/stripe/client/index.js.map +1 -1
- package/dist/stripe/index.d.ts +1 -1
- package/dist/stripe/index.d.ts.map +1 -1
- package/dist/stripe/index.js +1 -1
- package/dist/stripe/index.js.map +1 -1
- package/dist/stripe/internal/types.d.ts +25 -0
- package/dist/stripe/internal/types.d.ts.map +1 -0
- package/dist/stripe/internal/types.js +2 -0
- package/dist/stripe/internal/types.js.map +1 -0
- package/dist/stripe/server/Charge.d.ts +47 -28
- package/dist/stripe/server/Charge.d.ts.map +1 -1
- package/dist/stripe/server/Charge.js +90 -32
- package/dist/stripe/server/Charge.js.map +1 -1
- package/dist/stripe/server/{MethodIntents.d.ts → Methods.d.ts} +24 -23
- package/dist/stripe/server/Methods.d.ts.map +1 -0
- package/dist/stripe/server/{MethodIntents.js → Methods.js} +3 -3
- package/dist/stripe/server/Methods.js.map +1 -0
- package/dist/stripe/server/index.d.ts +1 -1
- package/dist/stripe/server/index.d.ts.map +1 -1
- package/dist/stripe/server/index.js +1 -1
- package/dist/stripe/server/index.js.map +1 -1
- package/dist/tempo/{Intents.d.ts → Methods.d.ts} +72 -69
- package/dist/tempo/Methods.d.ts.map +1 -0
- package/dist/tempo/Methods.js +118 -0
- package/dist/tempo/Methods.js.map +1 -0
- package/dist/tempo/client/ChannelOps.d.ts +1 -1
- package/dist/tempo/client/ChannelOps.js +1 -1
- package/dist/tempo/client/Charge.d.ts +25 -25
- package/dist/tempo/client/Charge.d.ts.map +1 -1
- package/dist/tempo/client/Charge.js +3 -3
- package/dist/tempo/client/Charge.js.map +1 -1
- package/dist/tempo/client/{MethodIntents.d.ts → Methods.d.ts} +74 -70
- package/dist/tempo/client/Methods.d.ts.map +1 -0
- package/dist/tempo/client/{MethodIntents.js → Methods.js} +3 -3
- package/dist/tempo/client/Methods.js.map +1 -0
- package/dist/tempo/client/Session.d.ts +49 -45
- package/dist/tempo/client/Session.d.ts.map +1 -1
- package/dist/tempo/client/Session.js +4 -4
- package/dist/tempo/client/Session.js.map +1 -1
- package/dist/tempo/client/SessionManager.d.ts +1 -1
- package/dist/tempo/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/client/SessionManager.js +10 -5
- package/dist/tempo/client/SessionManager.js.map +1 -1
- package/dist/tempo/client/index.d.ts +1 -1
- package/dist/tempo/client/index.d.ts.map +1 -1
- package/dist/tempo/client/index.js +1 -1
- package/dist/tempo/client/index.js.map +1 -1
- package/dist/tempo/index.d.ts +1 -1
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +1 -1
- package/dist/tempo/index.js.map +1 -1
- package/dist/tempo/internal/defaults.d.ts +1 -1
- package/dist/tempo/internal/defaults.js +1 -1
- package/dist/tempo/server/Charge.d.ts +27 -27
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +3 -3
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/{MethodIntents.d.ts → Methods.d.ts} +73 -69
- package/dist/tempo/server/Methods.d.ts.map +1 -0
- package/dist/tempo/server/{MethodIntents.js → Methods.js} +4 -4
- package/dist/tempo/server/Methods.js.map +1 -0
- package/dist/tempo/server/Session.d.ts +51 -47
- package/dist/tempo/server/Session.d.ts.map +1 -1
- package/dist/tempo/server/Session.js +4 -4
- package/dist/tempo/server/Session.js.map +1 -1
- package/dist/tempo/server/index.d.ts +6 -0
- package/dist/tempo/server/index.d.ts.map +1 -0
- package/dist/tempo/server/index.js +6 -0
- package/dist/tempo/server/index.js.map +1 -0
- package/dist/tempo/server/internal/transport.d.ts.map +1 -1
- package/dist/tempo/server/internal/transport.js +2 -1
- package/dist/tempo/server/internal/transport.js.map +1 -1
- package/package.json +1 -1
- package/src/Challenge.test-d.ts +3 -3
- package/src/Challenge.test.ts +6 -6
- package/src/Challenge.ts +34 -34
- package/src/Errors.test.ts +75 -21
- package/src/Errors.ts +74 -9
- package/src/Method.test.ts +76 -0
- package/src/Method.ts +228 -0
- package/src/PaymentRequest.test.ts +4 -4
- package/src/PaymentRequest.ts +9 -9
- package/src/cli.test.ts +12 -22
- package/src/cli.ts +74 -21
- package/src/client/Methods.ts +2 -2
- package/src/client/Mppx.test-d.ts +6 -6
- package/src/client/Mppx.test.ts +26 -22
- package/src/client/Mppx.ts +29 -13
- package/src/client/Transport.test.ts +2 -2
- package/src/client/internal/Fetch.test.ts +35 -1
- package/src/client/internal/Fetch.ts +36 -27
- package/src/index.ts +1 -2
- package/src/mcp-sdk/client/McpClient.ts +11 -13
- package/src/middlewares/elysia.ts +1 -1
- package/src/middlewares/express.ts +1 -1
- package/src/middlewares/hono.ts +1 -1
- package/src/middlewares/internal/mppx.ts +10 -10
- package/src/middlewares/nextjs.ts +1 -1
- package/src/proxy/Service.ts +2 -2
- package/src/server/Methods.ts +2 -2
- package/src/server/Mppx.test-d.ts +27 -29
- package/src/server/Mppx.test.ts +23 -19
- package/src/server/Mppx.ts +43 -43
- package/src/server/Transport.test.ts +3 -3
- package/src/stripe/Charge.integration.test.ts +4 -1
- package/src/stripe/{Intents.test.ts → Methods.test.ts} +12 -12
- package/src/stripe/Methods.ts +45 -0
- package/src/stripe/client/Charge.test.ts +189 -0
- package/src/stripe/client/Charge.ts +40 -31
- package/src/stripe/client/{MethodIntents.ts → Methods.ts} +3 -3
- package/src/stripe/client/index.ts +1 -1
- package/src/stripe/index.ts +1 -1
- package/src/stripe/internal/types.ts +22 -0
- package/src/stripe/server/Charge.test.ts +241 -0
- package/src/stripe/server/Charge.ts +124 -38
- package/src/stripe/server/{MethodIntents.ts → Methods.ts} +2 -2
- package/src/stripe/server/index.ts +1 -1
- package/src/tempo/{Intents.test.ts → Methods.test.ts} +15 -15
- package/src/tempo/{Intents.ts → Methods.ts} +77 -22
- package/src/tempo/client/ChannelOps.ts +1 -1
- package/src/tempo/client/Charge.ts +3 -3
- package/src/tempo/client/{MethodIntents.ts → Methods.ts} +2 -2
- package/src/tempo/client/Session.ts +4 -4
- package/src/tempo/client/SessionManager.ts +11 -5
- package/src/tempo/client/index.ts +1 -1
- package/src/tempo/index.ts +1 -1
- package/src/tempo/internal/defaults.ts +1 -1
- package/src/tempo/server/Charge.ts +4 -7
- package/src/tempo/server/{MethodIntents.ts → Methods.ts} +3 -3
- package/src/tempo/server/Session.test.ts +4 -7
- package/src/tempo/server/Session.ts +6 -6
- package/src/tempo/server/index.ts +1 -1
- package/src/tempo/server/internal/transport.ts +3 -2
- package/dist/Intent.d.ts +0 -101
- package/dist/Intent.d.ts.map +0 -1
- package/dist/Intent.js +0 -83
- package/dist/Intent.js.map +0 -1
- package/dist/MethodIntent.d.ts +0 -225
- package/dist/MethodIntent.d.ts.map +0 -1
- package/dist/MethodIntent.js +0 -156
- package/dist/MethodIntent.js.map +0 -1
- package/dist/stripe/Intents.d.ts.map +0 -1
- package/dist/stripe/Intents.js +0 -27
- package/dist/stripe/Intents.js.map +0 -1
- package/dist/stripe/client/MethodIntents.d.ts.map +0 -1
- package/dist/stripe/client/MethodIntents.js.map +0 -1
- package/dist/stripe/server/MethodIntents.d.ts.map +0 -1
- package/dist/stripe/server/MethodIntents.js.map +0 -1
- package/dist/tempo/Intents.d.ts.map +0 -1
- package/dist/tempo/Intents.js +0 -81
- package/dist/tempo/Intents.js.map +0 -1
- package/dist/tempo/client/MethodIntents.d.ts.map +0 -1
- package/dist/tempo/client/MethodIntents.js.map +0 -1
- package/dist/tempo/server/MethodIntents.d.ts.map +0 -1
- package/dist/tempo/server/MethodIntents.js.map +0 -1
- package/src/Intent.test.ts +0 -180
- package/src/Intent.ts +0 -109
- package/src/MethodIntent.test.ts +0 -303
- package/src/MethodIntent.ts +0 -388
- package/src/stripe/Intents.ts +0 -27
|
@@ -2,29 +2,32 @@ import { tempo } from 'mppx/server'
|
|
|
2
2
|
import { createClient, http } from 'viem'
|
|
3
3
|
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
4
4
|
import { describe, expectTypeOf, test } from 'vitest'
|
|
5
|
-
import * as
|
|
6
|
-
import * as MethodIntent from '../MethodIntent.js'
|
|
5
|
+
import * as Method from '../Method.js'
|
|
7
6
|
import * as z from '../zod.js'
|
|
8
7
|
import * as Mppx from './Mppx.js'
|
|
9
8
|
|
|
10
9
|
const account = privateKeyToAccount(generatePrivateKey())
|
|
11
10
|
const getClient = () => createClient({ account, transport: http() })
|
|
12
11
|
|
|
13
|
-
const fooCharge =
|
|
14
|
-
|
|
12
|
+
const fooCharge = Method.from({
|
|
13
|
+
name: 'test',
|
|
14
|
+
intent: 'charge',
|
|
15
15
|
schema: {
|
|
16
16
|
credential: {
|
|
17
17
|
payload: z.object({ signature: z.string() }),
|
|
18
18
|
},
|
|
19
|
-
request: {
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
request: z.object({
|
|
20
|
+
amount: z.string(),
|
|
21
|
+
currency: z.string(),
|
|
22
|
+
decimals: z.number(),
|
|
23
|
+
recipient: z.string(),
|
|
24
|
+
}),
|
|
22
25
|
},
|
|
23
26
|
})
|
|
24
27
|
|
|
25
28
|
describe('Mppx', () => {
|
|
26
29
|
test('has methods and realm properties', () => {
|
|
27
|
-
const method =
|
|
30
|
+
const method = Method.toServer(fooCharge, {
|
|
28
31
|
async verify() {
|
|
29
32
|
return {
|
|
30
33
|
method: 'test',
|
|
@@ -45,8 +48,8 @@ describe('Mppx', () => {
|
|
|
45
48
|
expectTypeOf(handler.realm).toBeString()
|
|
46
49
|
})
|
|
47
50
|
|
|
48
|
-
test('has
|
|
49
|
-
const method =
|
|
51
|
+
test('has method functions matching methods', () => {
|
|
52
|
+
const method = Method.toServer(fooCharge, {
|
|
50
53
|
async verify() {
|
|
51
54
|
return {
|
|
52
55
|
method: 'test',
|
|
@@ -66,8 +69,8 @@ describe('Mppx', () => {
|
|
|
66
69
|
expectTypeOf(handler.charge).toBeFunction()
|
|
67
70
|
})
|
|
68
71
|
|
|
69
|
-
test('
|
|
70
|
-
const method =
|
|
72
|
+
test('method function options include request', () => {
|
|
73
|
+
const method = Method.toServer(fooCharge, {
|
|
71
74
|
async verify() {
|
|
72
75
|
return {
|
|
73
76
|
method: 'test',
|
|
@@ -93,8 +96,8 @@ describe('Mppx', () => {
|
|
|
93
96
|
})
|
|
94
97
|
})
|
|
95
98
|
|
|
96
|
-
test('
|
|
97
|
-
const method =
|
|
99
|
+
test('method function returns handler that accepts Request', async () => {
|
|
100
|
+
const method = Method.toServer(fooCharge, {
|
|
98
101
|
async verify() {
|
|
99
102
|
return {
|
|
100
103
|
method: 'test',
|
|
@@ -128,10 +131,14 @@ describe('Mppx', () => {
|
|
|
128
131
|
}
|
|
129
132
|
})
|
|
130
133
|
|
|
131
|
-
test('multiple
|
|
132
|
-
const
|
|
133
|
-
name: '
|
|
134
|
+
test('multiple methods', () => {
|
|
135
|
+
const fooAuthorize = Method.from({
|
|
136
|
+
name: 'test',
|
|
137
|
+
intent: 'authorize',
|
|
134
138
|
schema: {
|
|
139
|
+
credential: {
|
|
140
|
+
payload: z.object({ token: z.string() }),
|
|
141
|
+
},
|
|
135
142
|
request: z.object({
|
|
136
143
|
scope: z.string(),
|
|
137
144
|
duration: z.number(),
|
|
@@ -139,16 +146,7 @@ describe('Mppx', () => {
|
|
|
139
146
|
},
|
|
140
147
|
})
|
|
141
148
|
|
|
142
|
-
const
|
|
143
|
-
method: 'test',
|
|
144
|
-
schema: {
|
|
145
|
-
credential: {
|
|
146
|
-
payload: z.object({ token: z.string() }),
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
const chargeMethod = MethodIntent.toServer(fooCharge, {
|
|
149
|
+
const chargeMethod = Method.toServer(fooCharge, {
|
|
152
150
|
defaults: {
|
|
153
151
|
currency: '0x1234',
|
|
154
152
|
recipient: '0xabc',
|
|
@@ -163,7 +161,7 @@ describe('Mppx', () => {
|
|
|
163
161
|
},
|
|
164
162
|
})
|
|
165
163
|
|
|
166
|
-
const authorizeMethod =
|
|
164
|
+
const authorizeMethod = Method.toServer(fooAuthorize, {
|
|
167
165
|
async verify() {
|
|
168
166
|
return {
|
|
169
167
|
method: 'test',
|
|
@@ -194,7 +192,7 @@ describe('Mppx', () => {
|
|
|
194
192
|
})
|
|
195
193
|
|
|
196
194
|
describe('defaults', () => {
|
|
197
|
-
test('defaulted fields are optional in
|
|
195
|
+
test('defaulted fields are optional in method options', () => {
|
|
198
196
|
const handler = Mppx.create({
|
|
199
197
|
methods: [tempo({ currency: '0x1234', recipient: '0xabc', getClient })],
|
|
200
198
|
realm: 'api.example.com',
|
package/src/server/Mppx.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Challenge, Credential,
|
|
1
|
+
import { Challenge, Credential, Method, z } from 'mppx'
|
|
2
2
|
import { Mppx, Transport, tempo } from 'mppx/server'
|
|
3
3
|
import { describe, expect, test } from 'vitest'
|
|
4
4
|
import * as Http from '~test/Http.js'
|
|
@@ -56,8 +56,8 @@ describe('request handler', () => {
|
|
|
56
56
|
"detail": "Payment is required for "api.example.com".",
|
|
57
57
|
"instance": "[instance]",
|
|
58
58
|
"status": 402,
|
|
59
|
-
"title": "
|
|
60
|
-
"type": "https://
|
|
59
|
+
"title": "Payment Required",
|
|
60
|
+
"type": "https://paymentauth.org/problems/payment-required",
|
|
61
61
|
}
|
|
62
62
|
`)
|
|
63
63
|
})
|
|
@@ -88,8 +88,8 @@ describe('request handler', () => {
|
|
|
88
88
|
"detail": "Credential is malformed: Invalid base64url or JSON..",
|
|
89
89
|
"instance": "[instance]",
|
|
90
90
|
"status": 402,
|
|
91
|
-
"title": "
|
|
92
|
-
"type": "https://
|
|
91
|
+
"title": "Malformed Credential",
|
|
92
|
+
"type": "https://paymentauth.org/problems/malformed-credential",
|
|
93
93
|
}
|
|
94
94
|
`)
|
|
95
95
|
})
|
|
@@ -132,8 +132,8 @@ describe('request handler', () => {
|
|
|
132
132
|
"detail": "Challenge "wrong-id" is invalid: challenge was not issued by this server.",
|
|
133
133
|
"instance": "[instance]",
|
|
134
134
|
"status": 402,
|
|
135
|
-
"title": "
|
|
136
|
-
"type": "https://
|
|
135
|
+
"title": "Invalid Challenge",
|
|
136
|
+
"type": "https://paymentauth.org/problems/invalid-challenge",
|
|
137
137
|
}
|
|
138
138
|
`)
|
|
139
139
|
})
|
|
@@ -178,8 +178,8 @@ describe('request handler', () => {
|
|
|
178
178
|
"detail": "[detail]",
|
|
179
179
|
"instance": "[instance]",
|
|
180
180
|
"status": 402,
|
|
181
|
-
"title": "
|
|
182
|
-
"type": "https://
|
|
181
|
+
"title": "Invalid Payload",
|
|
182
|
+
"type": "https://paymentauth.org/problems/invalid-payload",
|
|
183
183
|
}
|
|
184
184
|
`)
|
|
185
185
|
expect(body.detail).toContain('Credential payload is invalid')
|
|
@@ -218,8 +218,8 @@ describe('request handler (node)', () => {
|
|
|
218
218
|
"detail": "Payment is required for "api.example.com".",
|
|
219
219
|
"instance": "[instance]",
|
|
220
220
|
"status": 402,
|
|
221
|
-
"title": "
|
|
222
|
-
"type": "https://
|
|
221
|
+
"title": "Payment Required",
|
|
222
|
+
"type": "https://paymentauth.org/problems/payment-required",
|
|
223
223
|
}
|
|
224
224
|
`)
|
|
225
225
|
|
|
@@ -271,8 +271,8 @@ describe('request handler (node)', () => {
|
|
|
271
271
|
"detail": "[detail]",
|
|
272
272
|
"instance": "[instance]",
|
|
273
273
|
"status": 402,
|
|
274
|
-
"title": "
|
|
275
|
-
"type": "https://
|
|
274
|
+
"title": "Verification Failed",
|
|
275
|
+
"type": "https://paymentauth.org/problems/verification-failed",
|
|
276
276
|
}
|
|
277
277
|
`)
|
|
278
278
|
expect(body.detail).toContain('Payment verification failed')
|
|
@@ -283,19 +283,23 @@ describe('request handler (node)', () => {
|
|
|
283
283
|
|
|
284
284
|
describe('receipt handling', () => {
|
|
285
285
|
test('returns 200 when verify returns a success receipt', async () => {
|
|
286
|
-
const mockCharge =
|
|
287
|
-
|
|
286
|
+
const mockCharge = Method.from({
|
|
287
|
+
name: 'mock',
|
|
288
|
+
intent: 'charge',
|
|
288
289
|
schema: {
|
|
289
290
|
credential: {
|
|
290
291
|
payload: z.object({ token: z.string() }),
|
|
291
292
|
},
|
|
292
|
-
request: {
|
|
293
|
-
|
|
294
|
-
|
|
293
|
+
request: z.object({
|
|
294
|
+
amount: z.string(),
|
|
295
|
+
currency: z.string(),
|
|
296
|
+
decimals: z.number(),
|
|
297
|
+
recipient: z.string(),
|
|
298
|
+
}),
|
|
295
299
|
},
|
|
296
300
|
})
|
|
297
301
|
|
|
298
|
-
const mockMethod =
|
|
302
|
+
const mockMethod = Method.toServer(mockCharge, {
|
|
299
303
|
async verify() {
|
|
300
304
|
return {
|
|
301
305
|
method: 'mock',
|
package/src/server/Mppx.ts
CHANGED
|
@@ -2,14 +2,14 @@ import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
|
2
2
|
import * as Challenge from '../Challenge.js'
|
|
3
3
|
import type * as Credential from '../Credential.js'
|
|
4
4
|
import * as Errors from '../Errors.js'
|
|
5
|
-
import type * as
|
|
5
|
+
import type * as Method from '../Method.js'
|
|
6
6
|
import type * as Receipt from '../Receipt.js'
|
|
7
7
|
import type * as z from '../zod.js'
|
|
8
8
|
import * as NodeListener from './NodeListener.js'
|
|
9
9
|
import * as Request from './Request.js'
|
|
10
10
|
import * as Transport from './Transport.js'
|
|
11
11
|
|
|
12
|
-
export type Methods = readonly (
|
|
12
|
+
export type Methods = readonly (Method.AnyServer | readonly Method.AnyServer[])[]
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Payment handler.
|
|
@@ -26,14 +26,14 @@ export type Mppx<
|
|
|
26
26
|
transport: transport
|
|
27
27
|
} & Handlers<FlattenMethods<methods>, transport>
|
|
28
28
|
|
|
29
|
-
/** Extracts the transport override from a method
|
|
29
|
+
/** Extracts the transport override from a method, if any. */
|
|
30
30
|
type TransportOverrideOf<mi> = mi extends { transport?: infer transport }
|
|
31
31
|
? Exclude<transport, undefined> extends Transport.AnyTransport
|
|
32
32
|
? Exclude<transport, undefined>
|
|
33
33
|
: never
|
|
34
34
|
: never
|
|
35
35
|
|
|
36
|
-
/** Resolves the effective transport for
|
|
36
|
+
/** Resolves the effective transport for a method: override if present, else global default. */
|
|
37
37
|
type EffectiveTransportOf<mi, defaultTransport extends Transport.AnyTransport> = [
|
|
38
38
|
TransportOverrideOf<mi>,
|
|
39
39
|
] extends [never]
|
|
@@ -41,18 +41,18 @@ type EffectiveTransportOf<mi, defaultTransport extends Transport.AnyTransport> =
|
|
|
41
41
|
: TransportOverrideOf<mi>
|
|
42
42
|
|
|
43
43
|
type Handlers<
|
|
44
|
-
methods extends readonly
|
|
44
|
+
methods extends readonly Method.AnyServer[],
|
|
45
45
|
transport extends Transport.AnyTransport,
|
|
46
46
|
> = {
|
|
47
|
-
[
|
|
48
|
-
Extract<methods[number], {
|
|
49
|
-
EffectiveTransportOf<Extract<methods[number], {
|
|
50
|
-
NonNullable<Extract<methods[number], {
|
|
47
|
+
[method_name in methods[number]['intent']]: MethodFn<
|
|
48
|
+
Extract<methods[number], { intent: method_name }>,
|
|
49
|
+
EffectiveTransportOf<Extract<methods[number], { intent: method_name }>, transport>,
|
|
50
|
+
NonNullable<Extract<methods[number], { intent: method_name }>['defaults']>
|
|
51
51
|
>
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* Creates a server-side payment handler from
|
|
55
|
+
* Creates a server-side payment handler from methods.
|
|
56
56
|
*
|
|
57
57
|
* It is highly recommended to set a `secretKey` to bind challenges to their contents,
|
|
58
58
|
* and allow the server to verify that incoming credentials match challenges it issued.
|
|
@@ -82,9 +82,9 @@ export function create<
|
|
|
82
82
|
const handlers: Record<string, unknown> = {}
|
|
83
83
|
|
|
84
84
|
for (const mi of methods) {
|
|
85
|
-
handlers[mi.
|
|
85
|
+
handlers[mi.intent] = createMethodFn({
|
|
86
86
|
defaults: mi.defaults,
|
|
87
|
-
|
|
87
|
+
method: mi,
|
|
88
88
|
realm,
|
|
89
89
|
request: mi.request as never,
|
|
90
90
|
respond: mi.respond as never,
|
|
@@ -113,25 +113,25 @@ export declare namespace create {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
function
|
|
117
|
-
|
|
116
|
+
function createMethodFn<
|
|
117
|
+
method extends Method.Method,
|
|
118
118
|
transport extends Transport.AnyTransport,
|
|
119
119
|
defaults extends Record<string, unknown>,
|
|
120
120
|
>(
|
|
121
|
-
parameters:
|
|
122
|
-
):
|
|
121
|
+
parameters: createMethodFn.Parameters<method, transport, defaults>,
|
|
122
|
+
): createMethodFn.ReturnType<method, transport, defaults>
|
|
123
123
|
// biome-ignore lint/correctness/noUnusedVariables: _
|
|
124
|
-
function
|
|
125
|
-
const { defaults,
|
|
124
|
+
function createMethodFn(parameters: createMethodFn.Parameters): createMethodFn.ReturnType {
|
|
125
|
+
const { defaults, method, realm, respond, secretKey, transport, verify } = parameters
|
|
126
126
|
|
|
127
127
|
return (options) => {
|
|
128
128
|
const meta = {
|
|
129
|
-
...
|
|
129
|
+
...method,
|
|
130
130
|
...defaults,
|
|
131
131
|
...options,
|
|
132
132
|
}
|
|
133
133
|
return Object.assign(
|
|
134
|
-
async (input: Transport.InputOf): Promise<
|
|
134
|
+
async (input: Transport.InputOf): Promise<MethodFn.Response> => {
|
|
135
135
|
const { description, ...rest } = options
|
|
136
136
|
const expires = 'expires' in options ? (options.expires as string | undefined) : undefined
|
|
137
137
|
|
|
@@ -160,7 +160,7 @@ function createIntentFn(parameters: createIntentFn.Parameters): createIntentFn.R
|
|
|
160
160
|
// Recompute challenge from options. The HMAC-bound ID means we don't need to
|
|
161
161
|
// store challenges server-side—if the client echoes back a credential with
|
|
162
162
|
// a matching ID, we know it was issued by us with these exact parameters.
|
|
163
|
-
const challenge = Challenge.fromIntent(
|
|
163
|
+
const challenge = Challenge.fromIntent(method, {
|
|
164
164
|
description,
|
|
165
165
|
expires,
|
|
166
166
|
realm,
|
|
@@ -202,9 +202,9 @@ function createIntentFn(parameters: createIntentFn.Parameters): createIntentFn.R
|
|
|
202
202
|
return { challenge: response, status: 402 }
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
// Validate payload structure against
|
|
205
|
+
// Validate payload structure against method schema
|
|
206
206
|
try {
|
|
207
|
-
|
|
207
|
+
method.schema.credential.payload.parse(credential.payload)
|
|
208
208
|
} catch (e) {
|
|
209
209
|
const response = await transport.respondChallenge({
|
|
210
210
|
challenge,
|
|
@@ -265,50 +265,50 @@ function createIntentFn(parameters: createIntentFn.Parameters): createIntentFn.R
|
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
declare namespace
|
|
268
|
+
declare namespace createMethodFn {
|
|
269
269
|
type Parameters<
|
|
270
|
-
|
|
270
|
+
method extends Method.Method = Method.Method,
|
|
271
271
|
transport extends Transport.AnyTransport = Transport.Http,
|
|
272
272
|
defaults extends Record<string, unknown> = Record<string, unknown>,
|
|
273
273
|
> = {
|
|
274
274
|
defaults?: defaults
|
|
275
|
-
|
|
275
|
+
method: method
|
|
276
276
|
realm: string
|
|
277
|
-
request?:
|
|
278
|
-
respond?:
|
|
277
|
+
request?: Method.RequestFn<method>
|
|
278
|
+
respond?: Method.RespondFn<method>
|
|
279
279
|
secretKey: string
|
|
280
280
|
transport: transport
|
|
281
|
-
verify:
|
|
281
|
+
verify: Method.VerifyFn<method>
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
type ReturnType<
|
|
285
|
-
|
|
285
|
+
method extends Method.Method = Method.Method,
|
|
286
286
|
transport extends Transport.AnyTransport = Transport.Http,
|
|
287
287
|
defaults extends Record<string, unknown> = Record<string, unknown>,
|
|
288
|
-
> =
|
|
288
|
+
> = MethodFn<method, transport, defaults>
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
export type
|
|
292
|
-
|
|
291
|
+
export type MethodFn<
|
|
292
|
+
method extends Method.Method,
|
|
293
293
|
transport extends Transport.AnyTransport,
|
|
294
294
|
defaults extends Record<string, unknown>,
|
|
295
295
|
> = (
|
|
296
|
-
options:
|
|
297
|
-
) => (input: Transport.InputOf<transport>) => Promise<
|
|
296
|
+
options: MethodFn.Options<method, defaults>,
|
|
297
|
+
) => (input: Transport.InputOf<transport>) => Promise<MethodFn.Response<transport>>
|
|
298
298
|
/** @internal */
|
|
299
|
-
export type
|
|
299
|
+
export type AnyMethodFn = (options: any) => (input: any) => Promise<any>
|
|
300
300
|
|
|
301
301
|
/** @internal */
|
|
302
|
-
declare namespace
|
|
302
|
+
declare namespace MethodFn {
|
|
303
303
|
export type Options<
|
|
304
|
-
|
|
304
|
+
method extends Method.Method,
|
|
305
305
|
defaults extends Record<string, unknown> = Record<string, unknown>,
|
|
306
306
|
> = {
|
|
307
307
|
/** Optional human-readable description of the payment. */
|
|
308
308
|
description?: string | undefined
|
|
309
309
|
/** Optional challenge expiration timestamp (ISO 8601). */
|
|
310
310
|
expires?: string | undefined
|
|
311
|
-
} &
|
|
311
|
+
} & Method.WithDefaults<z.input<method['schema']['request']>, defaults>
|
|
312
312
|
|
|
313
313
|
export type Response<transport extends Transport.AnyTransport = Transport.Http> =
|
|
314
314
|
| {
|
|
@@ -346,8 +346,8 @@ declare namespace IntentFn {
|
|
|
346
346
|
* ```
|
|
347
347
|
*/
|
|
348
348
|
export function toNodeListener(
|
|
349
|
-
handler: (input: globalThis.Request) => Promise<
|
|
350
|
-
): (req: IncomingMessage, res: ServerResponse) => Promise<
|
|
349
|
+
handler: (input: globalThis.Request) => Promise<MethodFn.Response<Transport.Http>>,
|
|
350
|
+
): (req: IncomingMessage, res: ServerResponse) => Promise<MethodFn.Response<Transport.Http>> {
|
|
351
351
|
return async (req, res) => {
|
|
352
352
|
const result = await handler(Request.fromNodeListener(req, res))
|
|
353
353
|
|
|
@@ -370,9 +370,9 @@ type FlattenMethods<methods extends Methods> = methods extends readonly [
|
|
|
370
370
|
infer head,
|
|
371
371
|
...infer tail extends Methods,
|
|
372
372
|
]
|
|
373
|
-
? head extends readonly
|
|
373
|
+
? head extends readonly Method.AnyServer[]
|
|
374
374
|
? readonly [...head, ...FlattenMethods<tail>]
|
|
375
|
-
: head extends
|
|
375
|
+
: head extends Method.AnyServer
|
|
376
376
|
? readonly [head, ...FlattenMethods<tail>]
|
|
377
377
|
: never
|
|
378
378
|
: readonly []
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Challenge, Credential, Mcp, Receipt } from 'mppx'
|
|
2
2
|
import { Transport } from 'mppx/server'
|
|
3
|
-
import {
|
|
3
|
+
import { Methods } from 'mppx/tempo'
|
|
4
4
|
import { describe, expect, test } from 'vitest'
|
|
5
5
|
import { BadRequestError, ChannelClosedError } from '../Errors.js'
|
|
6
6
|
|
|
7
7
|
const realm = 'api.example.com'
|
|
8
8
|
const secretKey = 'test-secret-key'
|
|
9
9
|
|
|
10
|
-
const challenge = Challenge.fromIntent(
|
|
10
|
+
const challenge = Challenge.fromIntent(Methods.charge, {
|
|
11
11
|
realm,
|
|
12
12
|
secretKey,
|
|
13
13
|
request: {
|
|
@@ -111,7 +111,7 @@ describe('http', () => {
|
|
|
111
111
|
|
|
112
112
|
expect(response.status).toBe(400)
|
|
113
113
|
const body = await response.json()
|
|
114
|
-
expect(body.type).toBe('https://
|
|
114
|
+
expect(body.type).toBe('https://paymentauth.org/problems/bad-request')
|
|
115
115
|
expect(body.status).toBe(400)
|
|
116
116
|
})
|
|
117
117
|
|
|
@@ -87,7 +87,10 @@ describe.skipIf(!stripeSecretKey)('stripe', () => {
|
|
|
87
87
|
})
|
|
88
88
|
|
|
89
89
|
const clientCharge = stripe_client.charge({
|
|
90
|
-
|
|
90
|
+
createToken: async (params) => {
|
|
91
|
+
if (!params.paymentMethod) throw new Error('paymentMethod is required')
|
|
92
|
+
return createTestSpt({ ...params, paymentMethod: params.paymentMethod })
|
|
93
|
+
},
|
|
91
94
|
paymentMethod: 'pm_card_visa',
|
|
92
95
|
externalId: 'client_order_789',
|
|
93
96
|
})
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Methods } from 'mppx/stripe'
|
|
2
2
|
import { describe, expect, expectTypeOf, test } from 'vitest'
|
|
3
3
|
|
|
4
4
|
describe('charge', () => {
|
|
5
|
-
test('has correct name and
|
|
6
|
-
expect(
|
|
7
|
-
expect(
|
|
5
|
+
test('has correct name and intent', () => {
|
|
6
|
+
expect(Methods.charge.intent).toBe('charge')
|
|
7
|
+
expect(Methods.charge.name).toBe('stripe')
|
|
8
8
|
})
|
|
9
9
|
|
|
10
|
-
test('types:
|
|
11
|
-
expectTypeOf(
|
|
10
|
+
test('types: intent is literal', () => {
|
|
11
|
+
expectTypeOf(Methods.charge.intent).toEqualTypeOf<'charge'>()
|
|
12
12
|
})
|
|
13
13
|
|
|
14
|
-
test('types:
|
|
15
|
-
expectTypeOf(
|
|
14
|
+
test('types: name is literal', () => {
|
|
15
|
+
expectTypeOf(Methods.charge.name).toEqualTypeOf<'stripe'>()
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
test('schema: validates valid request', () => {
|
|
19
|
-
const result =
|
|
19
|
+
const result = Methods.charge.schema.request.safeParse({
|
|
20
20
|
amount: '1',
|
|
21
21
|
currency: 'usd',
|
|
22
22
|
decimals: 2,
|
|
@@ -29,14 +29,14 @@ describe('charge', () => {
|
|
|
29
29
|
})
|
|
30
30
|
|
|
31
31
|
test('schema: rejects invalid request', () => {
|
|
32
|
-
const result =
|
|
32
|
+
const result = Methods.charge.schema.request.safeParse({
|
|
33
33
|
amount: '1',
|
|
34
34
|
})
|
|
35
35
|
expect(result.success).toBe(false)
|
|
36
36
|
})
|
|
37
37
|
|
|
38
38
|
test('schema: validates spt payload', () => {
|
|
39
|
-
const result =
|
|
39
|
+
const result = Methods.charge.schema.credential.payload.safeParse({
|
|
40
40
|
spt: 'spt_test_123',
|
|
41
41
|
externalId: 'client_order_789',
|
|
42
42
|
})
|
|
@@ -44,7 +44,7 @@ describe('charge', () => {
|
|
|
44
44
|
})
|
|
45
45
|
|
|
46
46
|
test('schema: rejects invalid payload', () => {
|
|
47
|
-
const result =
|
|
47
|
+
const result = Methods.charge.schema.credential.payload.safeParse({
|
|
48
48
|
signature: '0x...',
|
|
49
49
|
})
|
|
50
50
|
expect(result.success).toBe(false)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { parseUnits } from 'viem'
|
|
2
|
+
import * as Expires from '../Expires.js'
|
|
3
|
+
import * as Method from '../Method.js'
|
|
4
|
+
import * as z from '../zod.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Stripe charge intent for one-time payments via Shared Payment Tokens (SPTs).
|
|
8
|
+
*
|
|
9
|
+
* @see https://github.com/tempoxyz/payment-auth-spec/blob/main/specs/methods/stripe/draft-stripe-charge-00.md
|
|
10
|
+
*/
|
|
11
|
+
export const charge = Method.from({
|
|
12
|
+
name: 'stripe',
|
|
13
|
+
intent: 'charge',
|
|
14
|
+
schema: {
|
|
15
|
+
credential: {
|
|
16
|
+
payload: z.object({
|
|
17
|
+
externalId: z.optional(z.string()),
|
|
18
|
+
spt: z.string(),
|
|
19
|
+
}),
|
|
20
|
+
},
|
|
21
|
+
request: z.pipe(
|
|
22
|
+
z.object({
|
|
23
|
+
amount: z.amount(),
|
|
24
|
+
currency: z.string(),
|
|
25
|
+
decimals: z.number(),
|
|
26
|
+
description: z.optional(z.string()),
|
|
27
|
+
expires: z._default(z.datetime(), () => Expires.minutes(5)),
|
|
28
|
+
externalId: z.optional(z.string()),
|
|
29
|
+
metadata: z.optional(z.record(z.string(), z.string())),
|
|
30
|
+
networkId: z.string(),
|
|
31
|
+
paymentMethodTypes: z.array(z.string()).check(z.minLength(1)),
|
|
32
|
+
recipient: z.optional(z.string()),
|
|
33
|
+
}),
|
|
34
|
+
z.transform(({ amount, decimals, metadata, networkId, paymentMethodTypes, ...rest }) => ({
|
|
35
|
+
...rest,
|
|
36
|
+
amount: parseUnits(amount, decimals).toString(),
|
|
37
|
+
methodDetails: {
|
|
38
|
+
networkId,
|
|
39
|
+
paymentMethodTypes,
|
|
40
|
+
...(metadata !== undefined && { metadata }),
|
|
41
|
+
},
|
|
42
|
+
})),
|
|
43
|
+
),
|
|
44
|
+
},
|
|
45
|
+
})
|