swiftshopr-payments 1.0.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,460 @@
1
+ /**
2
+ * TypeScript definitions for swiftshopr-payments
3
+ */
4
+
5
+ export interface SwiftShoprConfig {
6
+ /** Your SwiftShopr API key */
7
+ apiKey: string;
8
+ /** Webhook secret for signature verification */
9
+ webhookSecret?: string;
10
+ /** API base URL (default: production) */
11
+ baseUrl?: string;
12
+ /** Request timeout in milliseconds (default: 30000) */
13
+ timeout?: number;
14
+ /** Number of retries for failed requests (default: 3) */
15
+ retries?: number;
16
+ /** Hook called before each request */
17
+ onRequest?: (info: RequestInfo) => void;
18
+ /** Hook called after each response */
19
+ onResponse?: (info: ResponseInfo) => void;
20
+ /** Hook called on errors */
21
+ onError?: (error: Error) => void;
22
+ }
23
+
24
+ export interface RequestInfo {
25
+ method: string;
26
+ url: string;
27
+ headers: Record<string, string>;
28
+ body?: unknown;
29
+ }
30
+
31
+ export interface ResponseInfo {
32
+ status: number;
33
+ data: unknown;
34
+ headers: Headers;
35
+ }
36
+
37
+ // Payment Types
38
+ export interface CreateSessionParams {
39
+ /** Amount in USD */
40
+ amount: number;
41
+ /** Your order reference */
42
+ orderId?: string;
43
+ /** Store ID (resolves wallet automatically) */
44
+ storeId?: string;
45
+ /** Retailer wallet address */
46
+ destinationAddress?: string;
47
+ /** Payment method (ACH_BANK_ACCOUNT, CARD, etc.) */
48
+ paymentMethod?: string;
49
+ /** Custom metadata */
50
+ metadata?: Record<string, unknown>;
51
+ /** Idempotency key for safe retries */
52
+ idempotencyKey?: string;
53
+ }
54
+
55
+ export interface CreateTransferParams {
56
+ /** Amount in USD */
57
+ amount: number;
58
+ /** User's wallet address */
59
+ userWalletAddress: string;
60
+ /** Your order reference */
61
+ orderId?: string;
62
+ /** Store ID */
63
+ storeId?: string;
64
+ /** Retailer wallet address */
65
+ destinationAddress?: string;
66
+ /** Custom metadata */
67
+ metadata?: Record<string, unknown>;
68
+ /** Idempotency key */
69
+ idempotencyKey?: string;
70
+ }
71
+
72
+ export interface PaymentSession {
73
+ intentId: string;
74
+ sessionId: string;
75
+ orderId: string | null;
76
+ quoteId: string | null;
77
+ onrampUrl: string;
78
+ expiresAt: string;
79
+ branding: Branding | null;
80
+ }
81
+
82
+ export interface TransferResult {
83
+ intentId: string;
84
+ orderId: string | null;
85
+ status: string;
86
+ transfer: {
87
+ to: string;
88
+ amount: string;
89
+ asset: string;
90
+ network: string;
91
+ chainId: number;
92
+ };
93
+ expiresAt: string;
94
+ branding: Branding | null;
95
+ }
96
+
97
+ export interface PaymentStatus {
98
+ intentId: string;
99
+ orderId: string | null;
100
+ status: 'pending' | 'processing' | 'completed' | 'failed' | 'canceled' | 'expired';
101
+ amount: number;
102
+ storeId: string | null;
103
+ destinationAddress: string;
104
+ txHash: string | null;
105
+ explorerUrl: string | null;
106
+ confirmedAt: string | null;
107
+ createdAt: string;
108
+ expiresAt: string;
109
+ isExpired: boolean;
110
+ refund: RefundInfo | null;
111
+ }
112
+
113
+ export interface RefundInfo {
114
+ status: string;
115
+ amount: number;
116
+ txHash: string | null;
117
+ refundedAt: string | null;
118
+ }
119
+
120
+ export interface WaitOptions {
121
+ /** Max wait time in ms (default: 300000 for payments, 600000 for refunds) */
122
+ timeout?: number;
123
+ /** Poll interval in ms (default: 2000 for payments, 5000 for refunds) */
124
+ interval?: number;
125
+ /** Callback on status change */
126
+ onStatusChange?: (status: PaymentStatus | RefundStatus) => void;
127
+ }
128
+
129
+ // Refund Types
130
+ export interface CreateRefundParams {
131
+ /** Original payment intent ID */
132
+ intentId: string;
133
+ /** Refund amount (optional, defaults to full refund) */
134
+ amount?: number;
135
+ /** Reason for refund */
136
+ reason?: string;
137
+ /** Idempotency key */
138
+ idempotencyKey?: string;
139
+ }
140
+
141
+ export interface RefundResult {
142
+ id: string;
143
+ intentId: string;
144
+ orderId: string | null;
145
+ status: 'requested' | 'pending' | 'completed' | 'failed';
146
+ amount: number;
147
+ originalAmount: number;
148
+ isPartial: boolean;
149
+ reason: string;
150
+ createdAt: string;
151
+ instructions: {
152
+ message: string;
153
+ transfer: {
154
+ to: string;
155
+ amount: string;
156
+ asset: string;
157
+ network: string;
158
+ chainId: number;
159
+ };
160
+ fromWallet: string;
161
+ };
162
+ }
163
+
164
+ export interface RefundStatus {
165
+ id: string;
166
+ intentId: string;
167
+ orderId: string | null;
168
+ amount: number;
169
+ reason: string;
170
+ status: string;
171
+ txHash: string | null;
172
+ explorerUrl: string | null;
173
+ fromAddress: string;
174
+ toAddress: string;
175
+ createdAt: string;
176
+ completedAt: string | null;
177
+ }
178
+
179
+ // Dashboard Types
180
+ export interface DashboardSummary {
181
+ today: {
182
+ revenue: number;
183
+ transactionCount: number;
184
+ completed: number;
185
+ pending: number;
186
+ failed: number;
187
+ revenueTrendPercent: number;
188
+ };
189
+ week: {
190
+ revenue: number;
191
+ transactionCount: number;
192
+ completed: number;
193
+ };
194
+ month: {
195
+ revenue: number;
196
+ transactionCount: number;
197
+ completed: number;
198
+ avgTransaction: number;
199
+ estimatedCardSavings: number;
200
+ };
201
+ }
202
+
203
+ export interface TransactionListParams {
204
+ status?: string;
205
+ storeId?: string;
206
+ startDate?: string;
207
+ endDate?: string;
208
+ limit?: number;
209
+ offset?: number;
210
+ sort?: string;
211
+ }
212
+
213
+ export interface Transaction {
214
+ id: string;
215
+ orderId: string | null;
216
+ status: string;
217
+ amount: number;
218
+ storeId: string | null;
219
+ txHash: string | null;
220
+ explorerUrl: string | null;
221
+ createdAt: string;
222
+ confirmedAt: string | null;
223
+ refund: RefundInfo | null;
224
+ }
225
+
226
+ export interface TransactionList {
227
+ transactions: Transaction[];
228
+ pagination: {
229
+ total: number;
230
+ limit: number;
231
+ offset: number;
232
+ hasMore: boolean;
233
+ };
234
+ }
235
+
236
+ export interface StoreMetrics {
237
+ storeId: string;
238
+ storeName: string | null;
239
+ revenue: number;
240
+ transactionCount: number;
241
+ completed: number;
242
+ pending: number;
243
+ failed: number;
244
+ successRate: number;
245
+ avgTransaction: number;
246
+ }
247
+
248
+ export interface DailyMetrics {
249
+ date: string;
250
+ revenue: number;
251
+ transactionCount: number;
252
+ completed: number;
253
+ failed: number;
254
+ avgTransaction: number;
255
+ }
256
+
257
+ // Branding Types
258
+ export interface Branding {
259
+ storeId: string;
260
+ branding: {
261
+ name: string | null;
262
+ logoUrl: string | null;
263
+ theme: {
264
+ primaryColor: string;
265
+ secondaryColor: string;
266
+ backgroundColor: string;
267
+ textColor: string;
268
+ accentColor: string;
269
+ mode: 'light' | 'dark';
270
+ fontFamily: string | null;
271
+ };
272
+ cdpTheme: Record<string, string> | null;
273
+ };
274
+ }
275
+
276
+ // Webhook Types
277
+ export interface WebhookEvent {
278
+ type: string;
279
+ data: {
280
+ intentId: string;
281
+ orderId: string | null;
282
+ txHash: string | null;
283
+ amount: string;
284
+ status: string;
285
+ currency: string;
286
+ network: string;
287
+ explorerUrl: string | null;
288
+ storeId: string | null;
289
+ timestamp: string;
290
+ refundId?: string;
291
+ refundAmount?: string;
292
+ refundReason?: string;
293
+ };
294
+ receivedAt: string;
295
+ }
296
+
297
+ export interface WebhooksHelper {
298
+ verify(payload: string | object, signature: string, timestamp: string): boolean;
299
+ constructEvent(payload: string | object, headers: Record<string, string>): WebhookEvent;
300
+ events: typeof WebhookEvents;
301
+ }
302
+
303
+ export declare const WebhookEvents: {
304
+ PAYMENT_COMPLETED: 'payment.completed';
305
+ PAYMENT_FAILED: 'payment.failed';
306
+ PAYMENT_EXPIRED: 'payment.expired';
307
+ REFUND_REQUESTED: 'refund.requested';
308
+ REFUND_COMPLETED: 'refund.completed';
309
+ REFUND_FAILED: 'refund.failed';
310
+ };
311
+
312
+ // Error Types
313
+ export declare class HttpError extends Error {
314
+ statusCode: number;
315
+ response: unknown;
316
+ requestId: string | null;
317
+ }
318
+
319
+ export declare class TimeoutError extends Error {
320
+ timeout: number;
321
+ }
322
+
323
+ export declare class SwiftShoprError extends Error {
324
+ code: string;
325
+ details: Record<string, unknown>;
326
+ }
327
+
328
+ // API Modules
329
+ export interface PaymentsAPI {
330
+ createSession(params: CreateSessionParams): Promise<PaymentSession>;
331
+ createTransfer(params: CreateTransferParams): Promise<TransferResult>;
332
+ getStatus(intentId: string): Promise<PaymentStatus>;
333
+ getStatusByOrderId(orderId: string): Promise<PaymentStatus>;
334
+ waitForCompletion(intentId: string, options?: WaitOptions): Promise<PaymentStatus>;
335
+ }
336
+
337
+ export interface RefundsAPI {
338
+ create(params: CreateRefundParams): Promise<RefundResult>;
339
+ get(refundId: string): Promise<RefundStatus>;
340
+ listForPayment(intentId: string): Promise<RefundStatus[]>;
341
+ waitForCompletion(refundId: string, options?: WaitOptions): Promise<RefundStatus>;
342
+ }
343
+
344
+ export interface DashboardAPI {
345
+ getSummary(): Promise<DashboardSummary>;
346
+ getTransactions(params?: TransactionListParams): Promise<TransactionList>;
347
+ getAllTransactions(params?: TransactionListParams & { maxResults?: number }): Promise<Transaction[]>;
348
+ getStores(): Promise<StoreMetrics[]>;
349
+ getDaily(params?: { days?: number; storeId?: string }): Promise<DailyMetrics[]>;
350
+ export(params?: { startDate?: string; endDate?: string; status?: string; storeId?: string }): Promise<string>;
351
+ getRevenueAnalytics(params?: { period?: 'day' | 'week' | 'month' | 'year'; periods?: number }): Promise<{
352
+ totalRevenue: number;
353
+ totalTransactions: number;
354
+ averageDailyRevenue: number;
355
+ averageTransactionValue: number;
356
+ dataPoints: DailyMetrics[];
357
+ }>;
358
+ }
359
+
360
+ export interface BrandingAPI {
361
+ get(storeId: string): Promise<Branding>;
362
+ toCssVariables(branding: Branding): string;
363
+ toStyleObject(branding: Branding): Record<string, string>;
364
+ toCdpTheme(branding: Branding): Record<string, string> | null;
365
+ }
366
+
367
+ // Config Types
368
+ export interface RetailerConfig {
369
+ chainId: string;
370
+ label: string | null;
371
+ webhook: {
372
+ url: string | null;
373
+ hasSecret: boolean;
374
+ };
375
+ permissions: Record<string, boolean>;
376
+ rateLimit: number;
377
+ allowedIps: string[] | null;
378
+ createdAt: string;
379
+ lastUsedAt: string | null;
380
+ stores: RegisteredStore[];
381
+ }
382
+
383
+ export interface RegisteredStore {
384
+ id: string;
385
+ storeId: string;
386
+ storeName: string | null;
387
+ walletAddress: string;
388
+ webhook: {
389
+ url: string | null;
390
+ hasSecret: boolean;
391
+ } | null;
392
+ branding: Record<string, unknown>;
393
+ isActive: boolean;
394
+ createdAt: string;
395
+ updatedAt?: string;
396
+ }
397
+
398
+ export interface WebhookConfig {
399
+ url: string | null;
400
+ hasSecret: boolean;
401
+ secret?: string;
402
+ message?: string;
403
+ }
404
+
405
+ export interface RegisterStoreParams {
406
+ storeId: string;
407
+ walletAddress: string;
408
+ storeName?: string;
409
+ webhookUrl?: string;
410
+ branding?: Record<string, unknown>;
411
+ }
412
+
413
+ export interface UpdateStoreParams {
414
+ storeName?: string;
415
+ walletAddress?: string;
416
+ webhookUrl?: string;
417
+ branding?: Record<string, unknown>;
418
+ isActive?: boolean;
419
+ }
420
+
421
+ export interface ConfigAPI {
422
+ get(): Promise<RetailerConfig>;
423
+ setWebhook(params?: { url?: string; generateSecret?: boolean }): Promise<WebhookConfig>;
424
+ rotateWebhookSecret(): Promise<WebhookConfig>;
425
+ getStores(): Promise<RegisteredStore[]>;
426
+ registerStore(params: RegisterStoreParams): Promise<RegisteredStore>;
427
+ updateStore(storeId: string, updates: UpdateStoreParams): Promise<RegisteredStore>;
428
+ deactivateStore(storeId: string): Promise<{ message: string }>;
429
+ rotateStoreWebhookSecret(storeId: string): Promise<WebhookConfig>;
430
+ }
431
+
432
+ // Main Client
433
+ export declare class SwiftShoprClient {
434
+ constructor(config: SwiftShoprConfig);
435
+
436
+ static readonly VERSION: string;
437
+
438
+ payments: PaymentsAPI;
439
+ refunds: RefundsAPI;
440
+ dashboard: DashboardAPI;
441
+ branding: BrandingAPI;
442
+ config: ConfigAPI;
443
+ webhooks: WebhooksHelper | { configure(secret: string): WebhooksHelper };
444
+
445
+ getConfig(): {
446
+ baseUrl: string;
447
+ timeout: number;
448
+ retries: number;
449
+ hasApiKey: boolean;
450
+ hasWebhookSecret: boolean;
451
+ };
452
+ }
453
+
454
+ // Utility exports
455
+ export declare function generateIdempotencyKey(): string;
456
+ export declare function createWebhooksHelper(secret: string): WebhooksHelper;
457
+ export declare function webhookMiddleware(secret: string): (req: any, res: any, next: any) => void;
458
+
459
+ // Default export
460
+ export default SwiftShoprClient;
@@ -0,0 +1,217 @@
1
+ /**
2
+ * HTTP Client for SwiftShopr API
3
+ * Zero dependencies - uses native fetch
4
+ */
5
+
6
+ const DEFAULT_BASE_URL = 'https://shopr-scanner-backend.onrender.com';
7
+ const DEFAULT_TIMEOUT = 30000;
8
+ const DEFAULT_RETRIES = 3;
9
+ const RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504];
10
+
11
+ class HttpError extends Error {
12
+ constructor(message, statusCode, response, requestId) {
13
+ super(message);
14
+ this.name = 'HttpError';
15
+ this.statusCode = statusCode;
16
+ this.response = response;
17
+ this.requestId = requestId;
18
+ }
19
+ }
20
+
21
+ class TimeoutError extends Error {
22
+ constructor(message, timeout) {
23
+ super(message);
24
+ this.name = 'TimeoutError';
25
+ this.timeout = timeout;
26
+ }
27
+ }
28
+
29
+ class SwiftShoprError extends Error {
30
+ constructor(code, message, details = {}) {
31
+ super(message);
32
+ this.name = 'SwiftShoprError';
33
+ this.code = code;
34
+ this.details = details;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Sleep for specified milliseconds
40
+ */
41
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
42
+
43
+ /**
44
+ * Generate idempotency key
45
+ */
46
+ const generateIdempotencyKey = () => {
47
+ const timestamp = Date.now().toString(36);
48
+ const random = Math.random().toString(36).substring(2, 10);
49
+ return `idem_${timestamp}_${random}`;
50
+ };
51
+
52
+ /**
53
+ * Create HTTP client with configuration
54
+ */
55
+ function createHttpClient(config = {}) {
56
+ const baseUrl = config.baseUrl || DEFAULT_BASE_URL;
57
+ const { apiKey } = config;
58
+ const timeout = config.timeout || DEFAULT_TIMEOUT;
59
+ const maxRetries = config.retries ?? DEFAULT_RETRIES;
60
+ const onRequest = config.onRequest || null;
61
+ const onResponse = config.onResponse || null;
62
+ const onError = config.onError || null;
63
+
64
+ if (!apiKey) {
65
+ throw new SwiftShoprError('CONFIG_ERROR', 'API key is required');
66
+ }
67
+
68
+ /**
69
+ * Make HTTP request with retries and timeout
70
+ */
71
+ async function request(method, path, options = {}) {
72
+ const url = `${baseUrl}${path}`;
73
+ const idempotencyKey =
74
+ options.idempotencyKey ||
75
+ (['POST', 'PUT', 'PATCH'].includes(method)
76
+ ? generateIdempotencyKey()
77
+ : null);
78
+
79
+ const headers = {
80
+ 'Content-Type': 'application/json',
81
+ 'x-swiftshopr-key': apiKey,
82
+ ...(idempotencyKey && { 'Idempotency-Key': idempotencyKey }),
83
+ ...options.headers,
84
+ };
85
+
86
+ const fetchOptions = {
87
+ method,
88
+ headers,
89
+ ...(options.body && { body: JSON.stringify(options.body) }),
90
+ };
91
+
92
+ // Call onRequest hook
93
+ if (onRequest) {
94
+ onRequest({ method, url, headers, body: options.body });
95
+ }
96
+
97
+ let lastError;
98
+ let attempt = 0;
99
+
100
+ while (attempt <= maxRetries) {
101
+ attempt++;
102
+
103
+ try {
104
+ // Create abort controller for timeout
105
+ const controller = new AbortController();
106
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
107
+
108
+ const response = await fetch(url, {
109
+ ...fetchOptions,
110
+ signal: controller.signal,
111
+ });
112
+
113
+ clearTimeout(timeoutId);
114
+
115
+ // Parse response
116
+ const contentType = response.headers.get('content-type');
117
+ let data;
118
+
119
+ if (contentType && contentType.includes('application/json')) {
120
+ data = await response.json();
121
+ } else {
122
+ data = await response.text();
123
+ }
124
+
125
+ // Call onResponse hook
126
+ if (onResponse) {
127
+ onResponse({
128
+ status: response.status,
129
+ data,
130
+ headers: response.headers,
131
+ });
132
+ }
133
+
134
+ // Handle non-2xx responses
135
+ if (!response.ok) {
136
+ const error = new HttpError(
137
+ data?.message || data?.error || `HTTP ${response.status}`,
138
+ response.status,
139
+ data,
140
+ data?.requestId,
141
+ );
142
+
143
+ // Retry on specific status codes
144
+ if (
145
+ RETRY_STATUS_CODES.includes(response.status) &&
146
+ attempt <= maxRetries
147
+ ) {
148
+ lastError = error;
149
+ const backoff = Math.min(1000 * 2 ** (attempt - 1), 10000);
150
+ await sleep(backoff);
151
+ continue;
152
+ }
153
+
154
+ if (onError) {
155
+ onError(error);
156
+ }
157
+ throw error;
158
+ }
159
+
160
+ return data;
161
+ } catch (error) {
162
+ // Handle abort (timeout)
163
+ if (error.name === 'AbortError') {
164
+ const timeoutError = new TimeoutError(
165
+ `Request timed out after ${timeout}ms`,
166
+ timeout,
167
+ );
168
+
169
+ if (attempt <= maxRetries) {
170
+ lastError = timeoutError;
171
+ const backoff = Math.min(1000 * 2 ** (attempt - 1), 10000);
172
+ await sleep(backoff);
173
+ continue;
174
+ }
175
+
176
+ if (onError) {
177
+ onError(timeoutError);
178
+ }
179
+ throw timeoutError;
180
+ }
181
+
182
+ // Network errors - retry
183
+ if (error.name === 'TypeError' && attempt <= maxRetries) {
184
+ lastError = error;
185
+ const backoff = Math.min(1000 * 2 ** (attempt - 1), 10000);
186
+ await sleep(backoff);
187
+ continue;
188
+ }
189
+
190
+ if (onError) {
191
+ onError(error);
192
+ }
193
+ throw error;
194
+ }
195
+ }
196
+
197
+ // All retries exhausted
198
+ throw lastError;
199
+ }
200
+
201
+ return {
202
+ get: (path, options) => request('GET', path, options),
203
+ post: (path, body, options) => request('POST', path, { ...options, body }),
204
+ put: (path, body, options) => request('PUT', path, { ...options, body }),
205
+ patch: (path, body, options) =>
206
+ request('PATCH', path, { ...options, body }),
207
+ delete: (path, options) => request('DELETE', path, options),
208
+ };
209
+ }
210
+
211
+ module.exports = {
212
+ createHttpClient,
213
+ HttpError,
214
+ TimeoutError,
215
+ SwiftShoprError,
216
+ generateIdempotencyKey,
217
+ };