@ranwhenparked/trustap-sdk 0.1.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.
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Trustap Transaction State Machine
3
+ *
4
+ * Defines valid states and transitions for Trustap transactions
5
+ * States align with official Trustap API documentation
6
+ */
7
+
8
+ export type TrustapTransactionState =
9
+ | "created"
10
+ | "joined"
11
+ | "rejected"
12
+ | "paid"
13
+ | "cancelled"
14
+ | "cancelled_with_payment"
15
+ | "payment_refunded"
16
+ | "tracked"
17
+ | "delivered"
18
+ | "complained"
19
+ | "complaint_period_ended"
20
+ | "funds_released";
21
+
22
+ export type TrustapWebhookEventCode = (typeof trustapWebhookEventCodes)[number];
23
+
24
+ const trustapWebhookEventCodes = [
25
+ "basic_tx.joined",
26
+ "basic_tx.rejected",
27
+ "basic_tx.cancelled",
28
+ "basic_tx.claimed",
29
+ "basic_tx.listing_transaction_accepted",
30
+ "basic_tx.listing_transaction_rejected",
31
+ "basic_tx.payment_failed",
32
+ "basic_tx.paid",
33
+ "basic_tx.payment_refunded",
34
+ "basic_tx.payment_review_flagged",
35
+ "basic_tx.payment_review_finished",
36
+ "basic_tx.tracking_details_submission_deadline_extended",
37
+ "basic_tx.tracked",
38
+ "basic_tx.delivered",
39
+ "basic_tx.complained",
40
+ "basic_tx.complaint_period_ended",
41
+ "basic_tx.funds_released",
42
+ "basic_tx.funds_refunded",
43
+ ] as const satisfies readonly string[];
44
+
45
+ /**
46
+ * Maps webhook event types to Trustap transaction states
47
+ */
48
+ export function mapWebhookToTrustapState(
49
+ eventType: string,
50
+ ): TrustapTransactionState | null {
51
+ if (
52
+ !trustapWebhookEventCodes.includes(eventType as TrustapWebhookEventCode)
53
+ ) {
54
+ return null;
55
+ }
56
+
57
+ const eventStateMap: Record<string, TrustapTransactionState> = {
58
+ "basic_tx.joined": "joined",
59
+ "basic_tx.rejected": "rejected",
60
+ "basic_tx.cancelled": "cancelled",
61
+ "basic_tx.claimed": "created",
62
+ "basic_tx.listing_transaction_accepted": "joined",
63
+ "basic_tx.listing_transaction_rejected": "rejected",
64
+ "basic_tx.payment_failed": "created",
65
+ "basic_tx.paid": "paid",
66
+ "basic_tx.payment_refunded": "payment_refunded",
67
+ "basic_tx.payment_review_flagged": "paid",
68
+ "basic_tx.payment_review_finished": "paid",
69
+ "basic_tx.tracking_details_submission_deadline_extended": "tracked",
70
+ "basic_tx.tracked": "tracked",
71
+ "basic_tx.delivered": "delivered",
72
+ "basic_tx.complained": "complained",
73
+ "basic_tx.complaint_period_ended": "complaint_period_ended",
74
+ "basic_tx.funds_released": "funds_released",
75
+ "basic_tx.funds_refunded": "payment_refunded",
76
+ };
77
+
78
+ return eventStateMap[eventType] ?? null;
79
+ }
@@ -0,0 +1,400 @@
1
+ /**
2
+ * Trustap Webhook Event Zod Schemas
3
+ *
4
+ * Strict discriminated union schemas for all basic_tx.* webhook events.
5
+ * No fallback parsing - unknown events will fail loudly.
6
+ */
7
+
8
+ import { z } from "zod/v4";
9
+ import type { TrustapWebhookEventCode } from "./state-machine.ts";
10
+
11
+ // ============================================================================
12
+ // Shared Sub-Schemas
13
+ // ============================================================================
14
+
15
+ /**
16
+ * Tracking information attached to transactions after seller submits shipping details
17
+ */
18
+ const trackingSchema = z.object({
19
+ carrier: z.string(),
20
+ tracking_code: z.string(),
21
+ });
22
+
23
+ /**
24
+ * Complaint information when buyer raises an issue
25
+ */
26
+ const complaintSchema = z.object({
27
+ description: z.string(),
28
+ });
29
+
30
+ // ============================================================================
31
+ // Base Target Preview Schema
32
+ // ============================================================================
33
+
34
+ /**
35
+ * Base fields present in all target_preview objects
36
+ */
37
+ const baseTargetPreviewSchema = z.object({
38
+ id: z.number(),
39
+ status: z.string(),
40
+ currency: z.string(),
41
+ quantity: z.number(),
42
+ price: z.number(),
43
+ charge: z.number(),
44
+ charge_seller: z.number(),
45
+ description: z.string(),
46
+ created: z.string(),
47
+ is_payment_in_progress: z.boolean(),
48
+ client_id: z.string(),
49
+ buyer_id: z.string(),
50
+ seller_id: z.string(),
51
+ });
52
+
53
+ // ============================================================================
54
+ // Event-Specific Target Preview Schemas
55
+ // ============================================================================
56
+
57
+ // --- Joined State ---
58
+ const joinedTargetPreviewSchema = baseTargetPreviewSchema.extend({
59
+ joined: z.string(),
60
+ });
61
+
62
+ // --- Cancelled State ---
63
+ const cancelledTargetPreviewSchema = baseTargetPreviewSchema.extend({
64
+ joined: z.string(),
65
+ cancelled: z.string(),
66
+ });
67
+
68
+ // --- Rejected State ---
69
+ const rejectedTargetPreviewSchema = baseTargetPreviewSchema.extend({
70
+ rejected: z.string().optional(),
71
+ rejection_reason: z.string().optional(),
72
+ });
73
+
74
+ // --- Claimed State (transaction created but not yet joined) ---
75
+ const claimedTargetPreviewSchema = baseTargetPreviewSchema.extend({
76
+ claimed: z.string().optional(),
77
+ });
78
+
79
+ // --- Paid State ---
80
+ const paidTargetPreviewSchema = baseTargetPreviewSchema.extend({
81
+ joined: z.string(),
82
+ paid: z.string(),
83
+ tracking_details_window_started: z.string(),
84
+ tracking_details_deadline: z.string(),
85
+ charge_international_payment: z.number().optional(),
86
+ });
87
+
88
+ // --- Tracked State ---
89
+ const trackedTargetPreviewSchema = baseTargetPreviewSchema.extend({
90
+ joined: z.string(),
91
+ paid: z.string(),
92
+ tracked: z.string(),
93
+ tracking: trackingSchema,
94
+ tracking_details_window_started: z.string(),
95
+ tracking_details_deadline: z.string(),
96
+ charge_international_payment: z.number().optional(),
97
+ });
98
+
99
+ // --- Delivered State ---
100
+ const deliveredTargetPreviewSchema = baseTargetPreviewSchema.extend({
101
+ joined: z.string(),
102
+ paid: z.string(),
103
+ tracked: z.string(),
104
+ tracking: trackingSchema,
105
+ tracking_details_window_started: z.string(),
106
+ tracking_details_deadline: z.string(),
107
+ delivered: z.string(),
108
+ complaint_period_deadline: z.string(),
109
+ charge_international_payment: z.number().optional(),
110
+ });
111
+
112
+ // --- Complained State ---
113
+ const complainedTargetPreviewSchema = baseTargetPreviewSchema.extend({
114
+ joined: z.string(),
115
+ paid: z.string(),
116
+ tracked: z.string(),
117
+ tracking: trackingSchema,
118
+ tracking_details_window_started: z.string(),
119
+ tracking_details_deadline: z.string(),
120
+ delivered: z.string(),
121
+ complained: z.string(),
122
+ complaint: complaintSchema,
123
+ complaint_period_deadline: z.string(),
124
+ charge_international_payment: z.number().optional(),
125
+ });
126
+
127
+ // --- Complaint Period Ended State ---
128
+ const complaintPeriodEndedTargetPreviewSchema = baseTargetPreviewSchema.extend({
129
+ joined: z.string(),
130
+ paid: z.string(),
131
+ tracked: z.string(),
132
+ tracking: trackingSchema,
133
+ tracking_details_window_started: z.string(),
134
+ tracking_details_deadline: z.string(),
135
+ delivered: z.string(),
136
+ complaint_period_ended: z.string(),
137
+ complaint_period_deadline: z.string(),
138
+ charge_international_payment: z.number().optional(),
139
+ });
140
+
141
+ // --- Funds Released State ---
142
+ const fundsReleasedTargetPreviewSchema = baseTargetPreviewSchema.extend({
143
+ joined: z.string(),
144
+ paid: z.string(),
145
+ tracked: z.string(),
146
+ tracking: trackingSchema,
147
+ tracking_details_window_started: z.string(),
148
+ tracking_details_deadline: z.string(),
149
+ delivered: z.string(),
150
+ complaint_period_ended: z.string(),
151
+ complaint_period_deadline: z.string(),
152
+ funds_released: z.string(),
153
+ released_to_seller: z.boolean(),
154
+ amount_released: z.number(),
155
+ charge_international_payment: z.number().optional(),
156
+ });
157
+
158
+ // --- Payment Refunded State ---
159
+ const paymentRefundedTargetPreviewSchema = baseTargetPreviewSchema.extend({
160
+ joined: z.string(),
161
+ paid: z.string().optional(),
162
+ refunded: z.string().optional(),
163
+ refund_amount: z.number().optional(),
164
+ charge_international_payment: z.number().optional(),
165
+ });
166
+
167
+ // ============================================================================
168
+ // Metadata Schemas
169
+ // ============================================================================
170
+
171
+ const emptyMetadataSchema = z.looseObject({});
172
+
173
+ const paymentFailedMetadataSchema = z.looseObject({
174
+ failure_code: z.string(),
175
+ });
176
+
177
+ // ============================================================================
178
+ // Event Envelope Schemas
179
+ // ============================================================================
180
+
181
+ // Helper to create event schema
182
+ function createEventSchema<
183
+ TCode extends string,
184
+ TPreview extends z.ZodType,
185
+ TMetadata extends z.ZodType = typeof emptyMetadataSchema,
186
+ >(code: TCode, targetPreviewSchema: TPreview, metadataSchema?: TMetadata) {
187
+ return z.object({
188
+ code: z.literal(code),
189
+ user_id: z.string().nullable(),
190
+ target_id: z.string(),
191
+ target_preview: targetPreviewSchema,
192
+ time: z.string(),
193
+ metadata: metadataSchema ?? emptyMetadataSchema,
194
+ });
195
+ }
196
+
197
+ // --- Individual Event Schemas ---
198
+
199
+ export const basicTxJoinedEventSchema = createEventSchema(
200
+ "basic_tx.joined",
201
+ joinedTargetPreviewSchema,
202
+ );
203
+
204
+ export const basicTxRejectedEventSchema = createEventSchema(
205
+ "basic_tx.rejected",
206
+ rejectedTargetPreviewSchema,
207
+ );
208
+
209
+ export const basicTxCancelledEventSchema = createEventSchema(
210
+ "basic_tx.cancelled",
211
+ cancelledTargetPreviewSchema,
212
+ );
213
+
214
+ export const basicTxClaimedEventSchema = createEventSchema(
215
+ "basic_tx.claimed",
216
+ claimedTargetPreviewSchema,
217
+ );
218
+
219
+ export const basicTxListingTransactionAcceptedEventSchema = createEventSchema(
220
+ "basic_tx.listing_transaction_accepted",
221
+ joinedTargetPreviewSchema,
222
+ );
223
+
224
+ export const basicTxListingTransactionRejectedEventSchema = createEventSchema(
225
+ "basic_tx.listing_transaction_rejected",
226
+ rejectedTargetPreviewSchema,
227
+ );
228
+
229
+ export const basicTxPaymentFailedEventSchema = createEventSchema(
230
+ "basic_tx.payment_failed",
231
+ joinedTargetPreviewSchema,
232
+ paymentFailedMetadataSchema,
233
+ );
234
+
235
+ export const basicTxPaidEventSchema = createEventSchema(
236
+ "basic_tx.paid",
237
+ paidTargetPreviewSchema,
238
+ );
239
+
240
+ export const basicTxPaymentRefundedEventSchema = createEventSchema(
241
+ "basic_tx.payment_refunded",
242
+ paymentRefundedTargetPreviewSchema,
243
+ );
244
+
245
+ export const basicTxPaymentReviewFlaggedEventSchema = createEventSchema(
246
+ "basic_tx.payment_review_flagged",
247
+ paidTargetPreviewSchema,
248
+ );
249
+
250
+ export const basicTxPaymentReviewFinishedEventSchema = createEventSchema(
251
+ "basic_tx.payment_review_finished",
252
+ paidTargetPreviewSchema,
253
+ );
254
+
255
+ export const basicTxTrackingDetailsSubmissionDeadlineExtendedEventSchema = createEventSchema(
256
+ "basic_tx.tracking_details_submission_deadline_extended",
257
+ paidTargetPreviewSchema,
258
+ );
259
+
260
+ export const basicTxTrackedEventSchema = createEventSchema(
261
+ "basic_tx.tracked",
262
+ trackedTargetPreviewSchema,
263
+ );
264
+
265
+ export const basicTxDeliveredEventSchema = createEventSchema(
266
+ "basic_tx.delivered",
267
+ deliveredTargetPreviewSchema,
268
+ );
269
+
270
+ export const basicTxComplainedEventSchema = createEventSchema(
271
+ "basic_tx.complained",
272
+ complainedTargetPreviewSchema,
273
+ );
274
+
275
+ export const basicTxComplaintPeriodEndedEventSchema = createEventSchema(
276
+ "basic_tx.complaint_period_ended",
277
+ complaintPeriodEndedTargetPreviewSchema,
278
+ );
279
+
280
+ export const basicTxFundsReleasedEventSchema = createEventSchema(
281
+ "basic_tx.funds_released",
282
+ fundsReleasedTargetPreviewSchema,
283
+ );
284
+
285
+ export const basicTxFundsRefundedEventSchema = createEventSchema(
286
+ "basic_tx.funds_refunded",
287
+ paymentRefundedTargetPreviewSchema,
288
+ );
289
+
290
+ // ============================================================================
291
+ // Discriminated Union
292
+ // ============================================================================
293
+
294
+ /**
295
+ * All possible Trustap webhook event schemas as a discriminated union.
296
+ * Unknown events will fail validation - no fallback parsing.
297
+ */
298
+ export const trustapWebhookEventSchema = z.discriminatedUnion("code", [
299
+ basicTxJoinedEventSchema,
300
+ basicTxRejectedEventSchema,
301
+ basicTxCancelledEventSchema,
302
+ basicTxClaimedEventSchema,
303
+ basicTxListingTransactionAcceptedEventSchema,
304
+ basicTxListingTransactionRejectedEventSchema,
305
+ basicTxPaymentFailedEventSchema,
306
+ basicTxPaidEventSchema,
307
+ basicTxPaymentRefundedEventSchema,
308
+ basicTxPaymentReviewFlaggedEventSchema,
309
+ basicTxPaymentReviewFinishedEventSchema,
310
+ basicTxTrackingDetailsSubmissionDeadlineExtendedEventSchema,
311
+ basicTxTrackedEventSchema,
312
+ basicTxDeliveredEventSchema,
313
+ basicTxComplainedEventSchema,
314
+ basicTxComplaintPeriodEndedEventSchema,
315
+ basicTxFundsReleasedEventSchema,
316
+ basicTxFundsRefundedEventSchema,
317
+ ]);
318
+
319
+ /**
320
+ * Type for any Trustap webhook event
321
+ */
322
+ export type TrustapWebhookEvent = z.infer<typeof trustapWebhookEventSchema>;
323
+
324
+ // ============================================================================
325
+ // Exhaustive Handler Types
326
+ // ============================================================================
327
+
328
+ /**
329
+ * Handler function type for a specific event
330
+ */
331
+ type WebhookEventHandler<T extends TrustapWebhookEvent> = (
332
+ event: T,
333
+ ) => Promise<void> | void;
334
+
335
+ /**
336
+ * Exhaustive handler map type - TypeScript will error if any event is missing.
337
+ * Use this to ensure all webhook events are handled.
338
+ */
339
+ export type TrustapWebhookHandlers = {
340
+ [K in TrustapWebhookEventCode]: WebhookEventHandler<
341
+ Extract<TrustapWebhookEvent, { code: K }>
342
+ >;
343
+ };
344
+
345
+ /**
346
+ * Helper to create handlers with compile-time exhaustiveness checking.
347
+ *
348
+ * @example
349
+ * const handlers = createWebhookHandlers({
350
+ * "basic_tx.joined": (event) => { console.log(event.target_preview.joined); },
351
+ * "basic_tx.paid": (event) => { console.log(event.target_preview.paid); },
352
+ * // ... TypeScript errors if any event code is missing
353
+ * });
354
+ */
355
+ export function createWebhookHandlers(
356
+ handlers: TrustapWebhookHandlers,
357
+ ): TrustapWebhookHandlers {
358
+ return handlers;
359
+ }
360
+
361
+ /**
362
+ * Exhaustive switch helper for use in processors.
363
+ * Call in default case to get compile-time error if any case is missing.
364
+ *
365
+ * @example
366
+ * function handleEvent(event: TrustapWebhookEvent) {
367
+ * switch (event.code) {
368
+ * case "basic_tx.joined": return handleJoined(event);
369
+ * case "basic_tx.paid": return handlePaid(event);
370
+ * // ... all cases
371
+ * default: assertNever(event);
372
+ * }
373
+ * }
374
+ */
375
+ export function assertNever(x: never): never {
376
+ throw new Error(`Unhandled webhook event: ${JSON.stringify(x)}`);
377
+ }
378
+
379
+ // ============================================================================
380
+ // Individual Event Type Exports
381
+ // ============================================================================
382
+
383
+ export type BasicTxJoinedEvent = z.infer<typeof basicTxJoinedEventSchema>;
384
+ export type BasicTxRejectedEvent = z.infer<typeof basicTxRejectedEventSchema>;
385
+ export type BasicTxCancelledEvent = z.infer<typeof basicTxCancelledEventSchema>;
386
+ export type BasicTxClaimedEvent = z.infer<typeof basicTxClaimedEventSchema>;
387
+ export type BasicTxListingTransactionAcceptedEvent = z.infer<typeof basicTxListingTransactionAcceptedEventSchema>;
388
+ export type BasicTxListingTransactionRejectedEvent = z.infer<typeof basicTxListingTransactionRejectedEventSchema>;
389
+ export type BasicTxPaymentFailedEvent = z.infer<typeof basicTxPaymentFailedEventSchema>;
390
+ export type BasicTxPaidEvent = z.infer<typeof basicTxPaidEventSchema>;
391
+ export type BasicTxPaymentRefundedEvent = z.infer<typeof basicTxPaymentRefundedEventSchema>;
392
+ export type BasicTxPaymentReviewFlaggedEvent = z.infer<typeof basicTxPaymentReviewFlaggedEventSchema>;
393
+ export type BasicTxPaymentReviewFinishedEvent = z.infer<typeof basicTxPaymentReviewFinishedEventSchema>;
394
+ export type BasicTxTrackingDetailsSubmissionDeadlineExtendedEvent = z.infer<typeof basicTxTrackingDetailsSubmissionDeadlineExtendedEventSchema>;
395
+ export type BasicTxTrackedEvent = z.infer<typeof basicTxTrackedEventSchema>;
396
+ export type BasicTxDeliveredEvent = z.infer<typeof basicTxDeliveredEventSchema>;
397
+ export type BasicTxComplainedEvent = z.infer<typeof basicTxComplainedEventSchema>;
398
+ export type BasicTxComplaintPeriodEndedEvent = z.infer<typeof basicTxComplaintPeriodEndedEventSchema>;
399
+ export type BasicTxFundsReleasedEvent = z.infer<typeof basicTxFundsReleasedEventSchema>;
400
+ export type BasicTxFundsRefundedEvent = z.infer<typeof basicTxFundsRefundedEventSchema>;
@@ -0,0 +1,27 @@
1
+ {
2
+ "extends": "@rwp/tsconfig/internal-package.json",
3
+ "compilerOptions": {
4
+ "lib": ["ES2022", "dom", "dom.iterable"],
5
+ "rootDir": "./src",
6
+ "outDir": "./dist",
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "emitDeclarationOnly": false,
10
+ "noEmit": false
11
+ },
12
+ "include": [
13
+ "src/index.ts",
14
+ "src/core.ts",
15
+ "src/client-factory.ts",
16
+ "src/state-machine.ts",
17
+ "src/operations-map.ts",
18
+ "src/security-map.ts",
19
+ "src/schema.d.ts"
20
+ ],
21
+ "exclude": [
22
+ "src/index.deno.ts",
23
+ "src/client-deno.ts",
24
+ "src/**/*.test.ts",
25
+ "src/__tests__"
26
+ ]
27
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "extends": "@rwp/tsconfig/internal-package.json",
3
+ "compilerOptions": {
4
+ "lib": [
5
+ "ES2022",
6
+ "dom",
7
+ "dom.iterable"
8
+ ],
9
+ "rootDir": ".",
10
+ "allowImportingTsExtensions": true,
11
+ "allowJs": true,
12
+ "checkJs": false
13
+ },
14
+ "include": [
15
+ "src",
16
+ "scripts"
17
+ ],
18
+ "exclude": [
19
+ "node_modules",
20
+ "dist"
21
+ ]
22
+ }
@@ -0,0 +1,31 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: "node",
7
+ setupFiles: ["./src/__tests__/setup.ts"],
8
+ testTimeout: 10000,
9
+ hookTimeout: 5000,
10
+ exclude: ["src/__tests__/deno/**", "**/node_modules/**"],
11
+ env: {
12
+ NODE_ENV: "test",
13
+ },
14
+ coverage: {
15
+ provider: "v8",
16
+ reporter: ["text", "json", "html"],
17
+ exclude: [
18
+ "src/__tests__/**",
19
+ "src/schema.d.ts",
20
+ "src/operations-map.ts",
21
+ "src/security-map.ts",
22
+ "scripts/**",
23
+ ],
24
+ },
25
+ },
26
+ resolve: {
27
+ alias: {
28
+ "@": "./src",
29
+ },
30
+ },
31
+ });