@skillrecordings/sdk 0.2.0 → 0.2.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.
- package/dist/adapter.d.ts +43 -0
- package/dist/adapter.js +1 -0
- package/dist/adapter.js.map +1 -0
- package/dist/client.d.ts +70 -0
- package/dist/client.js +85 -0
- package/dist/client.js.map +1 -0
- package/dist/handler.d.ts +32 -0
- package/dist/handler.js +172 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.d.ts +145 -0
- package/dist/integration.js +1 -0
- package/dist/integration.js.map +1 -0
- package/dist/types.d.ts +77 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +29 -7
- package/CHANGELOG.md +0 -12
- package/src/__tests__/client.test.ts +0 -351
- package/src/__tests__/handler.test.ts +0 -442
- package/src/__tests__/types.test.ts +0 -121
- package/src/adapter.ts +0 -43
- package/src/client.ts +0 -146
- package/src/handler.ts +0 -271
- package/src/index.ts +0 -19
- package/src/integration.ts +0 -164
- package/src/types.ts +0 -82
- package/tsconfig.json +0 -8
- package/vitest.config.ts +0 -10
|
@@ -1,442 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
-
import { createSupportHandler } from '../handler'
|
|
3
|
-
import type { SupportIntegration } from '../integration'
|
|
4
|
-
|
|
5
|
-
describe('createSupportHandler', () => {
|
|
6
|
-
const mockIntegration: SupportIntegration = {
|
|
7
|
-
lookupUser: vi.fn(async (email: string) => ({
|
|
8
|
-
id: 'usr_123',
|
|
9
|
-
email,
|
|
10
|
-
name: 'Test User',
|
|
11
|
-
createdAt: new Date(),
|
|
12
|
-
})),
|
|
13
|
-
getPurchases: vi.fn(async (userId: string) => [
|
|
14
|
-
{
|
|
15
|
-
id: 'pur_123',
|
|
16
|
-
productId: 'prod_123',
|
|
17
|
-
productName: 'Test Product',
|
|
18
|
-
purchasedAt: new Date(),
|
|
19
|
-
amount: 10000,
|
|
20
|
-
currency: 'usd',
|
|
21
|
-
status: 'active' as const,
|
|
22
|
-
},
|
|
23
|
-
]),
|
|
24
|
-
revokeAccess: vi.fn(async (params) => ({ success: true })),
|
|
25
|
-
transferPurchase: vi.fn(async (params) => ({ success: true })),
|
|
26
|
-
generateMagicLink: vi.fn(async (params) => ({
|
|
27
|
-
url: 'https://example.com/magic?token=abc123',
|
|
28
|
-
})),
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const webhookSecret = 'whsec_test123'
|
|
32
|
-
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
vi.clearAllMocks()
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
function createSignature(
|
|
38
|
-
timestamp: number,
|
|
39
|
-
body: string,
|
|
40
|
-
secret: string
|
|
41
|
-
): string {
|
|
42
|
-
const crypto = require('crypto')
|
|
43
|
-
const payload = `${timestamp}.${body}`
|
|
44
|
-
const signature = crypto
|
|
45
|
-
.createHmac('sha256', secret)
|
|
46
|
-
.update(payload)
|
|
47
|
-
.digest('hex')
|
|
48
|
-
return `timestamp=${timestamp},v1=${signature}`
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function createRequest(
|
|
52
|
-
body: Record<string, unknown>,
|
|
53
|
-
options: {
|
|
54
|
-
timestamp?: number
|
|
55
|
-
secret?: string
|
|
56
|
-
skipSignature?: boolean
|
|
57
|
-
malformedSignature?: string
|
|
58
|
-
} = {}
|
|
59
|
-
): Request {
|
|
60
|
-
const timestamp = options.timestamp ?? Math.floor(Date.now() / 1000)
|
|
61
|
-
const bodyString = JSON.stringify(body)
|
|
62
|
-
const secret = options.secret ?? webhookSecret
|
|
63
|
-
const signature =
|
|
64
|
-
options.malformedSignature ??
|
|
65
|
-
(options.skipSignature
|
|
66
|
-
? ''
|
|
67
|
-
: createSignature(timestamp, bodyString, secret))
|
|
68
|
-
|
|
69
|
-
const headers = new Headers({
|
|
70
|
-
'content-type': 'application/json',
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
if (signature) {
|
|
74
|
-
headers.set('x-support-signature', signature)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return new Request('http://localhost:3000/api/support', {
|
|
78
|
-
method: 'POST',
|
|
79
|
-
headers,
|
|
80
|
-
body: bodyString,
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
describe('signature verification', () => {
|
|
85
|
-
it('accepts valid HMAC signature', async () => {
|
|
86
|
-
const handler = createSupportHandler({
|
|
87
|
-
integration: mockIntegration,
|
|
88
|
-
webhookSecret,
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
const request = createRequest({
|
|
92
|
-
action: 'lookupUser',
|
|
93
|
-
email: 'test@example.com',
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
const response = await handler(request)
|
|
97
|
-
expect(response.status).toBe(200)
|
|
98
|
-
|
|
99
|
-
const data = (await response.json()) as Record<string, unknown>
|
|
100
|
-
expect(data).toMatchObject({
|
|
101
|
-
id: 'usr_123',
|
|
102
|
-
email: 'test@example.com',
|
|
103
|
-
})
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('rejects missing signature header', async () => {
|
|
107
|
-
const handler = createSupportHandler({
|
|
108
|
-
integration: mockIntegration,
|
|
109
|
-
webhookSecret,
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
const request = createRequest(
|
|
113
|
-
{ action: 'lookupUser', email: 'test@example.com' },
|
|
114
|
-
{ skipSignature: true }
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
const response = await handler(request)
|
|
118
|
-
expect(response.status).toBe(401)
|
|
119
|
-
|
|
120
|
-
const data = (await response.json()) as Record<string, unknown>
|
|
121
|
-
expect(data.error).toBe('Missing signature header')
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it('rejects malformed signature header', async () => {
|
|
125
|
-
const handler = createSupportHandler({
|
|
126
|
-
integration: mockIntegration,
|
|
127
|
-
webhookSecret,
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
const request = createRequest(
|
|
131
|
-
{ action: 'lookupUser', email: 'test@example.com' },
|
|
132
|
-
{ malformedSignature: 'invalid_format' }
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
const response = await handler(request)
|
|
136
|
-
expect(response.status).toBe(401)
|
|
137
|
-
|
|
138
|
-
const data = (await response.json()) as Record<string, unknown>
|
|
139
|
-
expect(data.error).toBe('Invalid signature format')
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
it('rejects invalid HMAC signature', async () => {
|
|
143
|
-
const handler = createSupportHandler({
|
|
144
|
-
integration: mockIntegration,
|
|
145
|
-
webhookSecret,
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
const request = createRequest(
|
|
149
|
-
{ action: 'lookupUser', email: 'test@example.com' },
|
|
150
|
-
{ secret: 'wrong_secret' }
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
const response = await handler(request)
|
|
154
|
-
expect(response.status).toBe(401)
|
|
155
|
-
|
|
156
|
-
const data = (await response.json()) as Record<string, unknown>
|
|
157
|
-
expect(data.error).toBe('Invalid signature')
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
it('rejects replay attacks (timestamp > 5 minutes old)', async () => {
|
|
161
|
-
const handler = createSupportHandler({
|
|
162
|
-
integration: mockIntegration,
|
|
163
|
-
webhookSecret,
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
const fiveMinutesAgo = Math.floor(Date.now() / 1000) - 301 // 5 minutes + 1 second
|
|
167
|
-
const request = createRequest(
|
|
168
|
-
{ action: 'lookupUser', email: 'test@example.com' },
|
|
169
|
-
{ timestamp: fiveMinutesAgo }
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
const response = await handler(request)
|
|
173
|
-
expect(response.status).toBe(401)
|
|
174
|
-
|
|
175
|
-
const data = (await response.json()) as Record<string, unknown>
|
|
176
|
-
expect(data.error).toBe('Signature expired')
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
it('accepts timestamp within 5 minute window', async () => {
|
|
180
|
-
const handler = createSupportHandler({
|
|
181
|
-
integration: mockIntegration,
|
|
182
|
-
webhookSecret,
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
const fourMinutesAgo = Math.floor(Date.now() / 1000) - 240 // 4 minutes
|
|
186
|
-
const request = createRequest(
|
|
187
|
-
{ action: 'lookupUser', email: 'test@example.com' },
|
|
188
|
-
{ timestamp: fourMinutesAgo }
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
const response = await handler(request)
|
|
192
|
-
expect(response.status).toBe(200)
|
|
193
|
-
})
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
describe('action routing', () => {
|
|
197
|
-
it('routes lookupUser action', async () => {
|
|
198
|
-
const handler = createSupportHandler({
|
|
199
|
-
integration: mockIntegration,
|
|
200
|
-
webhookSecret,
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
const request = createRequest({
|
|
204
|
-
action: 'lookupUser',
|
|
205
|
-
email: 'test@example.com',
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
const response = await handler(request)
|
|
209
|
-
expect(response.status).toBe(200)
|
|
210
|
-
|
|
211
|
-
expect(mockIntegration.lookupUser).toHaveBeenCalledWith(
|
|
212
|
-
'test@example.com'
|
|
213
|
-
)
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
it('routes getPurchases action', async () => {
|
|
217
|
-
const handler = createSupportHandler({
|
|
218
|
-
integration: mockIntegration,
|
|
219
|
-
webhookSecret,
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
const request = createRequest({
|
|
223
|
-
action: 'getPurchases',
|
|
224
|
-
userId: 'usr_123',
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
const response = await handler(request)
|
|
228
|
-
expect(response.status).toBe(200)
|
|
229
|
-
|
|
230
|
-
expect(mockIntegration.getPurchases).toHaveBeenCalledWith('usr_123')
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
it('routes revokeAccess action', async () => {
|
|
234
|
-
const handler = createSupportHandler({
|
|
235
|
-
integration: mockIntegration,
|
|
236
|
-
webhookSecret,
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
const request = createRequest({
|
|
240
|
-
action: 'revokeAccess',
|
|
241
|
-
purchaseId: 'pur_123',
|
|
242
|
-
reason: 'Customer request',
|
|
243
|
-
refundId: 're_123',
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
const response = await handler(request)
|
|
247
|
-
expect(response.status).toBe(200)
|
|
248
|
-
|
|
249
|
-
expect(mockIntegration.revokeAccess).toHaveBeenCalledWith({
|
|
250
|
-
purchaseId: 'pur_123',
|
|
251
|
-
reason: 'Customer request',
|
|
252
|
-
refundId: 're_123',
|
|
253
|
-
})
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
it('routes transferPurchase action', async () => {
|
|
257
|
-
const handler = createSupportHandler({
|
|
258
|
-
integration: mockIntegration,
|
|
259
|
-
webhookSecret,
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
const request = createRequest({
|
|
263
|
-
action: 'transferPurchase',
|
|
264
|
-
purchaseId: 'pur_123',
|
|
265
|
-
fromUserId: 'usr_123',
|
|
266
|
-
toEmail: 'newuser@example.com',
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
const response = await handler(request)
|
|
270
|
-
expect(response.status).toBe(200)
|
|
271
|
-
|
|
272
|
-
expect(mockIntegration.transferPurchase).toHaveBeenCalledWith({
|
|
273
|
-
purchaseId: 'pur_123',
|
|
274
|
-
fromUserId: 'usr_123',
|
|
275
|
-
toEmail: 'newuser@example.com',
|
|
276
|
-
})
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
it('routes generateMagicLink action', async () => {
|
|
280
|
-
const handler = createSupportHandler({
|
|
281
|
-
integration: mockIntegration,
|
|
282
|
-
webhookSecret,
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
const request = createRequest({
|
|
286
|
-
action: 'generateMagicLink',
|
|
287
|
-
email: 'test@example.com',
|
|
288
|
-
expiresIn: 3600,
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
const response = await handler(request)
|
|
292
|
-
expect(response.status).toBe(200)
|
|
293
|
-
|
|
294
|
-
expect(mockIntegration.generateMagicLink).toHaveBeenCalledWith({
|
|
295
|
-
email: 'test@example.com',
|
|
296
|
-
expiresIn: 3600,
|
|
297
|
-
})
|
|
298
|
-
})
|
|
299
|
-
|
|
300
|
-
it('rejects unknown action', async () => {
|
|
301
|
-
const handler = createSupportHandler({
|
|
302
|
-
integration: mockIntegration,
|
|
303
|
-
webhookSecret,
|
|
304
|
-
})
|
|
305
|
-
|
|
306
|
-
const request = createRequest({
|
|
307
|
-
action: 'unknownAction',
|
|
308
|
-
foo: 'bar',
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
const response = await handler(request)
|
|
312
|
-
expect(response.status).toBe(400)
|
|
313
|
-
|
|
314
|
-
const data = (await response.json()) as Record<string, unknown>
|
|
315
|
-
expect(data.error).toBe('Unknown action: unknownAction')
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
it('rejects missing action field', async () => {
|
|
319
|
-
const handler = createSupportHandler({
|
|
320
|
-
integration: mockIntegration,
|
|
321
|
-
webhookSecret,
|
|
322
|
-
})
|
|
323
|
-
|
|
324
|
-
const request = createRequest({
|
|
325
|
-
email: 'test@example.com',
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
const response = await handler(request)
|
|
329
|
-
expect(response.status).toBe(400)
|
|
330
|
-
|
|
331
|
-
const data = (await response.json()) as Record<string, unknown>
|
|
332
|
-
expect(data.error).toBe('Missing action field')
|
|
333
|
-
})
|
|
334
|
-
})
|
|
335
|
-
|
|
336
|
-
describe('optional methods', () => {
|
|
337
|
-
it('routes getSubscriptions when implemented', async () => {
|
|
338
|
-
const integrationWithSubscriptions: SupportIntegration = {
|
|
339
|
-
...mockIntegration,
|
|
340
|
-
getSubscriptions: vi.fn(async (userId: string) => [
|
|
341
|
-
{
|
|
342
|
-
id: 'sub_123',
|
|
343
|
-
productId: 'prod_123',
|
|
344
|
-
productName: 'Monthly Subscription',
|
|
345
|
-
status: 'active' as const,
|
|
346
|
-
currentPeriodStart: new Date(),
|
|
347
|
-
currentPeriodEnd: new Date(),
|
|
348
|
-
cancelAtPeriodEnd: false,
|
|
349
|
-
},
|
|
350
|
-
]),
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const handler = createSupportHandler({
|
|
354
|
-
integration: integrationWithSubscriptions,
|
|
355
|
-
webhookSecret,
|
|
356
|
-
})
|
|
357
|
-
|
|
358
|
-
const request = createRequest({
|
|
359
|
-
action: 'getSubscriptions',
|
|
360
|
-
userId: 'usr_123',
|
|
361
|
-
})
|
|
362
|
-
|
|
363
|
-
const response = await handler(request)
|
|
364
|
-
expect(response.status).toBe(200)
|
|
365
|
-
|
|
366
|
-
expect(
|
|
367
|
-
integrationWithSubscriptions.getSubscriptions
|
|
368
|
-
).toHaveBeenCalledWith('usr_123')
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
it('returns 501 for optional method not implemented', async () => {
|
|
372
|
-
const handler = createSupportHandler({
|
|
373
|
-
integration: mockIntegration,
|
|
374
|
-
webhookSecret,
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
const request = createRequest({
|
|
378
|
-
action: 'getSubscriptions',
|
|
379
|
-
userId: 'usr_123',
|
|
380
|
-
})
|
|
381
|
-
|
|
382
|
-
const response = await handler(request)
|
|
383
|
-
expect(response.status).toBe(501)
|
|
384
|
-
|
|
385
|
-
const data = (await response.json()) as Record<string, unknown>
|
|
386
|
-
expect(data.error).toBe('Method not implemented: getSubscriptions')
|
|
387
|
-
})
|
|
388
|
-
})
|
|
389
|
-
|
|
390
|
-
describe('error handling', () => {
|
|
391
|
-
it('handles integration method errors', async () => {
|
|
392
|
-
const failingIntegration: SupportIntegration = {
|
|
393
|
-
...mockIntegration,
|
|
394
|
-
lookupUser: vi.fn(async () => {
|
|
395
|
-
throw new Error('Database connection failed')
|
|
396
|
-
}),
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const handler = createSupportHandler({
|
|
400
|
-
integration: failingIntegration,
|
|
401
|
-
webhookSecret,
|
|
402
|
-
})
|
|
403
|
-
|
|
404
|
-
const request = createRequest({
|
|
405
|
-
action: 'lookupUser',
|
|
406
|
-
email: 'test@example.com',
|
|
407
|
-
})
|
|
408
|
-
|
|
409
|
-
const response = await handler(request)
|
|
410
|
-
expect(response.status).toBe(500)
|
|
411
|
-
|
|
412
|
-
const data = (await response.json()) as Record<string, unknown>
|
|
413
|
-
expect(data.error).toContain('Database connection failed')
|
|
414
|
-
})
|
|
415
|
-
|
|
416
|
-
it('handles malformed JSON body', async () => {
|
|
417
|
-
const handler = createSupportHandler({
|
|
418
|
-
integration: mockIntegration,
|
|
419
|
-
webhookSecret,
|
|
420
|
-
})
|
|
421
|
-
|
|
422
|
-
const timestamp = Math.floor(Date.now() / 1000)
|
|
423
|
-
const malformedBody = 'not valid json'
|
|
424
|
-
const signature = createSignature(timestamp, malformedBody, webhookSecret)
|
|
425
|
-
|
|
426
|
-
const request = new Request('http://localhost:3000/api/support', {
|
|
427
|
-
method: 'POST',
|
|
428
|
-
headers: {
|
|
429
|
-
'content-type': 'application/json',
|
|
430
|
-
'x-support-signature': signature,
|
|
431
|
-
},
|
|
432
|
-
body: malformedBody,
|
|
433
|
-
})
|
|
434
|
-
|
|
435
|
-
const response = await handler(request)
|
|
436
|
-
expect(response.status).toBe(400)
|
|
437
|
-
|
|
438
|
-
const data = (await response.json()) as Record<string, unknown>
|
|
439
|
-
expect(data.error).toContain('Invalid JSON')
|
|
440
|
-
})
|
|
441
|
-
})
|
|
442
|
-
})
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { describe, it, expectTypeOf } from 'vitest';
|
|
2
|
-
import type {
|
|
3
|
-
SupportIntegration,
|
|
4
|
-
User,
|
|
5
|
-
Purchase,
|
|
6
|
-
Subscription,
|
|
7
|
-
ActionResult,
|
|
8
|
-
ClaimedSeat,
|
|
9
|
-
} from '../integration';
|
|
10
|
-
|
|
11
|
-
describe('SDK Types', () => {
|
|
12
|
-
it('SupportIntegration has required methods', () => {
|
|
13
|
-
const integration: SupportIntegration = {
|
|
14
|
-
lookupUser: async (email: string) => null as User | null,
|
|
15
|
-
getPurchases: async (userId: string) => [] as Purchase[],
|
|
16
|
-
revokeAccess: async (params) => ({ success: true }),
|
|
17
|
-
transferPurchase: async (params) => ({ success: true }),
|
|
18
|
-
generateMagicLink: async (params) => ({ url: '' }),
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
expectTypeOf(integration.lookupUser).toBeFunction();
|
|
22
|
-
expectTypeOf(integration.getPurchases).toBeFunction();
|
|
23
|
-
expectTypeOf(integration.revokeAccess).toBeFunction();
|
|
24
|
-
expectTypeOf(integration.transferPurchase).toBeFunction();
|
|
25
|
-
expectTypeOf(integration.generateMagicLink).toBeFunction();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('SupportIntegration has optional methods', () => {
|
|
29
|
-
const integration: SupportIntegration = {
|
|
30
|
-
lookupUser: async (email: string) => null as User | null,
|
|
31
|
-
getPurchases: async (userId: string) => [] as Purchase[],
|
|
32
|
-
revokeAccess: async (params) => ({ success: true }),
|
|
33
|
-
transferPurchase: async (params) => ({ success: true }),
|
|
34
|
-
generateMagicLink: async (params) => ({ url: '' }),
|
|
35
|
-
// Optional methods
|
|
36
|
-
getSubscriptions: async (userId: string) => [] as Subscription[],
|
|
37
|
-
updateEmail: async (params) => ({ success: true }),
|
|
38
|
-
updateName: async (params) => ({ success: true }),
|
|
39
|
-
getClaimedSeats: async (bulkCouponId: string) => [] as ClaimedSeat[],
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
if (integration.getSubscriptions) {
|
|
43
|
-
expectTypeOf(integration.getSubscriptions).toBeFunction();
|
|
44
|
-
}
|
|
45
|
-
if (integration.updateEmail) {
|
|
46
|
-
expectTypeOf(integration.updateEmail).toBeFunction();
|
|
47
|
-
}
|
|
48
|
-
if (integration.updateName) {
|
|
49
|
-
expectTypeOf(integration.updateName).toBeFunction();
|
|
50
|
-
}
|
|
51
|
-
if (integration.getClaimedSeats) {
|
|
52
|
-
expectTypeOf(integration.getClaimedSeats).toBeFunction();
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('User type has required fields', () => {
|
|
57
|
-
const user: User = {
|
|
58
|
-
id: 'usr_123',
|
|
59
|
-
email: 'test@example.com',
|
|
60
|
-
name: 'Test User',
|
|
61
|
-
createdAt: new Date(),
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
expectTypeOf(user).toMatchTypeOf<User>();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('Purchase type includes stripeChargeId', () => {
|
|
68
|
-
const purchase: Purchase = {
|
|
69
|
-
id: 'pur_123',
|
|
70
|
-
productId: 'prod_123',
|
|
71
|
-
productName: 'Test Product',
|
|
72
|
-
purchasedAt: new Date(),
|
|
73
|
-
amount: 10000,
|
|
74
|
-
currency: 'usd',
|
|
75
|
-
stripeChargeId: 'ch_123',
|
|
76
|
-
status: 'active',
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
expectTypeOf(purchase.stripeChargeId).toMatchTypeOf<string | undefined>();
|
|
80
|
-
expectTypeOf(purchase.status).toMatchTypeOf<
|
|
81
|
-
'active' | 'refunded' | 'transferred'
|
|
82
|
-
>();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('ActionResult has success and optional error', () => {
|
|
86
|
-
const success: ActionResult = { success: true };
|
|
87
|
-
const failure: ActionResult = {
|
|
88
|
-
success: false,
|
|
89
|
-
error: 'Something went wrong',
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
expectTypeOf(success).toMatchTypeOf<ActionResult>();
|
|
93
|
-
expectTypeOf(failure).toMatchTypeOf<ActionResult>();
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('Subscription type has all required fields', () => {
|
|
97
|
-
const subscription: Subscription = {
|
|
98
|
-
id: 'sub_123',
|
|
99
|
-
productId: 'prod_123',
|
|
100
|
-
productName: 'Test Subscription',
|
|
101
|
-
status: 'active',
|
|
102
|
-
currentPeriodStart: new Date(),
|
|
103
|
-
currentPeriodEnd: new Date(),
|
|
104
|
-
cancelAtPeriodEnd: false,
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
expectTypeOf(subscription.status).toMatchTypeOf<
|
|
108
|
-
'active' | 'cancelled' | 'expired' | 'paused'
|
|
109
|
-
>();
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('ClaimedSeat has user info and timestamp', () => {
|
|
113
|
-
const seat: ClaimedSeat = {
|
|
114
|
-
userId: 'usr_123',
|
|
115
|
-
email: 'test@example.com',
|
|
116
|
-
claimedAt: new Date(),
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
expectTypeOf(seat).toMatchTypeOf<ClaimedSeat>();
|
|
120
|
-
});
|
|
121
|
-
});
|
package/src/adapter.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { Customer, Purchase, RefundRequest, RefundResult } from './types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Base adapter interface that apps must implement to integrate with the support platform.
|
|
5
|
-
*
|
|
6
|
-
* @deprecated Use SupportIntegration interface from './integration' instead.
|
|
7
|
-
* This interface is kept for backwards compatibility during migration.
|
|
8
|
-
*
|
|
9
|
-
* Migration path:
|
|
10
|
-
* - Replace AppAdapter with SupportIntegration
|
|
11
|
-
* - Rename getCustomer to lookupUser
|
|
12
|
-
* - Replace processRefund with revokeAccess
|
|
13
|
-
* - Add required methods: transferPurchase, generateMagicLink
|
|
14
|
-
*
|
|
15
|
-
* @see {@link SupportIntegration} for the new interface
|
|
16
|
-
*
|
|
17
|
-
* Each app (egghead, Total TypeScript, etc.) provides:
|
|
18
|
-
* - Customer lookup by email
|
|
19
|
-
* - Purchase history retrieval
|
|
20
|
-
* - Refund processing capabilities
|
|
21
|
-
*/
|
|
22
|
-
export interface AppAdapter {
|
|
23
|
-
/**
|
|
24
|
-
* Fetch customer by email address
|
|
25
|
-
* @deprecated Use lookupUser from SupportIntegration
|
|
26
|
-
* @returns Customer if found, null otherwise
|
|
27
|
-
*/
|
|
28
|
-
getCustomer(email: string): Promise<Customer | null>;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Fetch all purchases for a given customer
|
|
32
|
-
* @deprecated Use getPurchases from SupportIntegration (same signature)
|
|
33
|
-
* @returns Array of purchases, empty if none found
|
|
34
|
-
*/
|
|
35
|
-
getPurchases(customerId: string): Promise<Purchase[]>;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Process a refund for a purchase
|
|
39
|
-
* @deprecated Use revokeAccess from SupportIntegration instead
|
|
40
|
-
* @returns RefundResult indicating success/failure
|
|
41
|
-
*/
|
|
42
|
-
processRefund(request: RefundRequest): Promise<RefundResult>;
|
|
43
|
-
}
|