swiftshopr-payments 1.0.0 → 1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swiftshopr-payments",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Official Node.js SDK for SwiftShopr Payments - Accept USDC payments with zero chargebacks",
5
5
  "main": "src/index.js",
6
6
  "types": "src/types/index.d.ts",
package/src/client.js CHANGED
@@ -6,6 +6,7 @@
6
6
  const { createHttpClient, SwiftShoprError } = require('./utils/http');
7
7
  const { createPaymentsAPI } = require('./payments');
8
8
  const { createRefundsAPI } = require('./refunds');
9
+ const { createReceiptsAPI } = require('./receipts');
9
10
  const { createDashboardAPI } = require('./dashboard');
10
11
  const { createBrandingAPI } = require('./branding');
11
12
  const { createConfigAPI } = require('./config');
@@ -72,6 +73,7 @@ class SwiftShoprClient {
72
73
  // Initialize API modules
73
74
  this.payments = createPaymentsAPI(this._http);
74
75
  this.refunds = createRefundsAPI(this._http);
76
+ this.receipts = createReceiptsAPI(this._http);
75
77
  this.dashboard = createDashboardAPI(this._http);
76
78
  this.branding = createBrandingAPI(this._http);
77
79
  this.config = createConfigAPI(this._http);
@@ -91,7 +93,7 @@ class SwiftShoprClient {
91
93
  * Get SDK version
92
94
  */
93
95
  static get VERSION() {
94
- return '1.0.0';
96
+ return '1.1.0';
95
97
  }
96
98
 
97
99
  /**
@@ -0,0 +1,262 @@
1
+ /**
2
+ * E-Receipts API Module
3
+ *
4
+ * Generate and verify e-receipts for Scan & Go checkout
5
+ *
6
+ * E-Receipt Flow:
7
+ * 1. Customer pays via USDC
8
+ * 2. Receipt is generated with verification code
9
+ * 3. Customer shows e-receipt on phone (screenshot-protected)
10
+ * 4. Employee visually verifies: code + timestamp + items
11
+ * 5. Customer exits store
12
+ *
13
+ * No hardware/scanners needed - just visual verification
14
+ */
15
+
16
+ const { SwiftShoprError } = require('./utils/http');
17
+
18
+ /**
19
+ * UUID validation regex
20
+ */
21
+ const UUID_REGEX =
22
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
23
+
24
+ /**
25
+ * Receipt ID validation regex (RCP-XXXXX-XXXXX format)
26
+ */
27
+ const RECEIPT_ID_REGEX = /^RCP-[A-Z0-9]+-[A-Z0-9]+$/i;
28
+
29
+ /**
30
+ * Validate receipt creation parameters
31
+ */
32
+ function validateCreateParams(params) {
33
+ const errors = [];
34
+
35
+ if (!params.intentId) {
36
+ errors.push('intentId is required');
37
+ } else if (!UUID_REGEX.test(params.intentId)) {
38
+ errors.push('intentId must be a valid UUID');
39
+ }
40
+
41
+ if (!params.storeId) {
42
+ errors.push('storeId is required');
43
+ }
44
+
45
+ if (!params.items || !Array.isArray(params.items) || params.items.length === 0) {
46
+ errors.push('items array is required and must not be empty');
47
+ } else {
48
+ params.items.forEach((item, index) => {
49
+ if (!item.barcode) errors.push(`items[${index}].barcode is required`);
50
+ if (!item.name) errors.push(`items[${index}].name is required`);
51
+ if (typeof item.price !== 'number' || item.price < 0) {
52
+ errors.push(`items[${index}].price must be a positive number`);
53
+ }
54
+ });
55
+ }
56
+
57
+ if (typeof params.total !== 'number' || params.total <= 0) {
58
+ errors.push('total must be a positive number');
59
+ }
60
+
61
+ if (errors.length > 0) {
62
+ throw new SwiftShoprError('VALIDATION_ERROR', errors.join('; '), { errors });
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Create Receipts API instance
68
+ */
69
+ function createReceiptsAPI(http) {
70
+ return {
71
+ /**
72
+ * Generate an e-receipt after payment completion
73
+ *
74
+ * @param {Object} params
75
+ * @param {string} params.intentId - Payment intent ID (UUID)
76
+ * @param {string} params.storeId - Store identifier
77
+ * @param {Array} params.items - Array of purchased items
78
+ * @param {number} params.subtotal - Subtotal before tax
79
+ * @param {number} [params.tax=0] - Tax amount
80
+ * @param {number} params.total - Total amount paid
81
+ * @param {string} [params.orderId] - Optional order reference
82
+ * @returns {Promise<Object>} E-receipt with verification code
83
+ *
84
+ * @example
85
+ * const receipt = await client.receipts.create({
86
+ * intentId: 'abc-123-def',
87
+ * storeId: 'STORE001',
88
+ * items: [
89
+ * { barcode: '012345678901', name: 'Coca-Cola', quantity: 2, price: 2.49, total: 4.98 }
90
+ * ],
91
+ * subtotal: 4.98,
92
+ * tax: 0.40,
93
+ * total: 5.38
94
+ * });
95
+ *
96
+ * console.log(receipt.verificationCode); // "A7B3C9D2"
97
+ */
98
+ async create(params) {
99
+ validateCreateParams(params);
100
+
101
+ // Format items for API
102
+ const formattedItems = params.items.map(item => ({
103
+ barcode: item.barcode,
104
+ name: item.name,
105
+ quantity: item.quantity || 1,
106
+ price: item.price,
107
+ total: item.total || (item.price * (item.quantity || 1)),
108
+ }));
109
+
110
+ const body = {
111
+ intent_id: params.intentId,
112
+ store_id: params.storeId,
113
+ items: formattedItems,
114
+ subtotal: params.subtotal || params.total,
115
+ tax: params.tax || 0,
116
+ total: params.total,
117
+ ...(params.orderId && { order_id: params.orderId }),
118
+ };
119
+
120
+ const response = await http.post('/api/v1/sdk/receipts', body);
121
+
122
+ if (!response.success) {
123
+ throw new SwiftShoprError(
124
+ response.code || 'RECEIPT_ERROR',
125
+ response.error || 'Failed to create receipt',
126
+ response
127
+ );
128
+ }
129
+
130
+ return formatReceiptResponse(response.receipt);
131
+ },
132
+
133
+ /**
134
+ * Get an e-receipt by receipt ID
135
+ *
136
+ * @param {string} receiptId - Receipt ID (RCP-XXXXX-XXXXX format)
137
+ * @returns {Promise<Object>} E-receipt details
138
+ */
139
+ async get(receiptId) {
140
+ if (!receiptId) {
141
+ throw new SwiftShoprError('VALIDATION_ERROR', 'receiptId is required');
142
+ }
143
+
144
+ const response = await http.get(`/api/v1/sdk/receipts/${receiptId}`);
145
+
146
+ if (!response.success) {
147
+ throw new SwiftShoprError(
148
+ response.code || 'RECEIPT_ERROR',
149
+ response.error || 'Failed to get receipt',
150
+ response
151
+ );
152
+ }
153
+
154
+ return formatReceiptResponse(response.receipt);
155
+ },
156
+
157
+ /**
158
+ * Get receipt by payment intent ID
159
+ *
160
+ * @param {string} intentId - Payment intent ID (UUID)
161
+ * @returns {Promise<Object>} E-receipt details
162
+ */
163
+ async getByIntent(intentId) {
164
+ if (!intentId) {
165
+ throw new SwiftShoprError('VALIDATION_ERROR', 'intentId is required');
166
+ }
167
+
168
+ const response = await http.get(`/api/v1/sdk/receipts/intent/${intentId}`);
169
+
170
+ if (!response.success) {
171
+ throw new SwiftShoprError(
172
+ response.code || 'RECEIPT_ERROR',
173
+ response.error || 'Failed to get receipt',
174
+ response
175
+ );
176
+ }
177
+
178
+ return formatReceiptResponse(response.receipt);
179
+ },
180
+
181
+ /**
182
+ * Verify an e-receipt (for employee verification)
183
+ *
184
+ * This is typically called by a store employee's device to verify
185
+ * the receipt shown on the customer's phone.
186
+ *
187
+ * @param {string} receiptId - Receipt ID to verify
188
+ * @param {Object} [options]
189
+ * @param {string} [options.location='exit'] - Verification location
190
+ * @returns {Promise<Object>} Verification result
191
+ *
192
+ * @example
193
+ * const result = await client.receipts.verify('RCP-ABC123-XYZ789');
194
+ *
195
+ * if (result.valid) {
196
+ * console.log('Verification code:', result.verificationCode);
197
+ * console.log('Receipt age:', result.ageMinutes, 'minutes');
198
+ * console.log('Items:', result.items);
199
+ * console.log('Total:', result.total);
200
+ * }
201
+ */
202
+ async verify(receiptId, options = {}) {
203
+ if (!receiptId) {
204
+ throw new SwiftShoprError('VALIDATION_ERROR', 'receiptId is required');
205
+ }
206
+
207
+ const queryParams = options.location ? `?location=${options.location}` : '';
208
+ const response = await http.get(`/api/v1/sdk/receipts/${receiptId}/verify${queryParams}`);
209
+
210
+ return {
211
+ valid: response.valid,
212
+ receiptId: response.receipt_id,
213
+ verificationCode: response.verification_code,
214
+ storeId: response.store_id,
215
+ items: response.items || [],
216
+ itemCount: response.item_count,
217
+ total: response.total,
218
+ createdAt: response.created_at,
219
+ ageMinutes: response.age_minutes,
220
+ paymentConfirmed: response.payment_confirmed,
221
+ txHash: response.tx_hash,
222
+ firstVerification: response.first_verification,
223
+ verificationCount: response.verification_count,
224
+ // Error info if invalid
225
+ error: response.error,
226
+ errorCode: response.code,
227
+ };
228
+ },
229
+ };
230
+ }
231
+
232
+ /**
233
+ * Format receipt response for SDK consumers
234
+ */
235
+ function formatReceiptResponse(receipt) {
236
+ return {
237
+ receiptId: receipt.receipt_id,
238
+ verificationCode: receipt.verification_code,
239
+ intentId: receipt.intent_id,
240
+ orderId: receipt.order_id,
241
+ storeId: receipt.store_id,
242
+ items: receipt.items,
243
+ itemCount: receipt.item_count,
244
+ subtotal: receipt.subtotal,
245
+ tax: receipt.tax,
246
+ total: receipt.total,
247
+ payment: {
248
+ status: receipt.payment?.status,
249
+ confirmedAt: receipt.payment?.confirmed_at,
250
+ txHash: receipt.payment?.tx_hash,
251
+ explorerUrl: receipt.payment?.explorer_url,
252
+ },
253
+ createdAt: receipt.created_at,
254
+ expiresAt: receipt.expires_at,
255
+ isExpired: receipt.is_expired,
256
+ verifiedAt: receipt.verified_at,
257
+ verificationCount: receipt.verification_count,
258
+ verifyUrl: receipt.verify_url,
259
+ };
260
+ }
261
+
262
+ module.exports = { createReceiptsAPI };
@@ -176,6 +176,112 @@ export interface RefundStatus {
176
176
  completedAt: string | null;
177
177
  }
178
178
 
179
+ // E-Receipt Types
180
+ export interface CreateReceiptParams {
181
+ /** Payment intent ID */
182
+ intentId: string;
183
+ /** Store identifier */
184
+ storeId: string;
185
+ /** Items purchased */
186
+ items: ReceiptItem[];
187
+ /** Subtotal before tax */
188
+ subtotal?: number;
189
+ /** Tax amount */
190
+ tax?: number;
191
+ /** Total amount paid */
192
+ total: number;
193
+ /** Optional order reference */
194
+ orderId?: string;
195
+ }
196
+
197
+ export interface ReceiptItem {
198
+ /** Product barcode/UPC */
199
+ barcode: string;
200
+ /** Product name */
201
+ name: string;
202
+ /** Quantity purchased */
203
+ quantity?: number;
204
+ /** Unit price */
205
+ price: number;
206
+ /** Line total (price * quantity) */
207
+ total?: number;
208
+ }
209
+
210
+ export interface EReceipt {
211
+ /** Receipt ID (RCP-XXXXX-XXXXX format) */
212
+ receiptId: string;
213
+ /** 8-character verification code for employee verification */
214
+ verificationCode: string;
215
+ /** Payment intent ID */
216
+ intentId: string;
217
+ /** Order reference */
218
+ orderId: string | null;
219
+ /** Store ID */
220
+ storeId: string;
221
+ /** Items purchased */
222
+ items: ReceiptItem[];
223
+ /** Number of items */
224
+ itemCount: number;
225
+ /** Subtotal before tax */
226
+ subtotal: number;
227
+ /** Tax amount */
228
+ tax: number;
229
+ /** Total paid */
230
+ total: number;
231
+ /** Payment details */
232
+ payment: {
233
+ status: string;
234
+ confirmedAt: string | null;
235
+ txHash: string | null;
236
+ explorerUrl: string | null;
237
+ };
238
+ /** When receipt was created */
239
+ createdAt: string;
240
+ /** When receipt expires */
241
+ expiresAt: string;
242
+ /** Whether receipt has expired */
243
+ isExpired: boolean;
244
+ /** When receipt was verified by employee */
245
+ verifiedAt: string | null;
246
+ /** How many times receipt has been verified */
247
+ verificationCount: number;
248
+ /** API endpoint for verification */
249
+ verifyUrl: string;
250
+ }
251
+
252
+ export interface ReceiptVerification {
253
+ /** Whether receipt is valid */
254
+ valid: boolean;
255
+ /** Receipt ID */
256
+ receiptId: string;
257
+ /** 8-char verification code */
258
+ verificationCode: string;
259
+ /** Store ID */
260
+ storeId: string;
261
+ /** Items for employee to verify against bag */
262
+ items: ReceiptItem[];
263
+ /** Item count */
264
+ itemCount: number;
265
+ /** Total paid */
266
+ total: number;
267
+ /** When receipt was created */
268
+ createdAt: string;
269
+ /** How old the receipt is in minutes */
270
+ ageMinutes: number;
271
+ /** Whether payment was confirmed */
272
+ paymentConfirmed: boolean;
273
+ /** Transaction hash */
274
+ txHash: string | null;
275
+ /** Whether this is the first verification */
276
+ firstVerification: boolean;
277
+ /** Total verification count */
278
+ verificationCount: number;
279
+ /** Error message if invalid */
280
+ error?: string;
281
+ /** Error code if invalid */
282
+ errorCode?: string;
283
+ }
284
+
179
285
  // Dashboard Types
180
286
  export interface DashboardSummary {
181
287
  today: {
@@ -341,6 +447,17 @@ export interface RefundsAPI {
341
447
  waitForCompletion(refundId: string, options?: WaitOptions): Promise<RefundStatus>;
342
448
  }
343
449
 
450
+ export interface ReceiptsAPI {
451
+ /** Generate an e-receipt after payment completion */
452
+ create(params: CreateReceiptParams): Promise<EReceipt>;
453
+ /** Get e-receipt by receipt ID */
454
+ get(receiptId: string): Promise<EReceipt>;
455
+ /** Get e-receipt by payment intent ID */
456
+ getByIntent(intentId: string): Promise<EReceipt>;
457
+ /** Verify e-receipt (for employee verification) */
458
+ verify(receiptId: string, options?: { location?: string }): Promise<ReceiptVerification>;
459
+ }
460
+
344
461
  export interface DashboardAPI {
345
462
  getSummary(): Promise<DashboardSummary>;
346
463
  getTransactions(params?: TransactionListParams): Promise<TransactionList>;
@@ -437,6 +554,7 @@ export declare class SwiftShoprClient {
437
554
 
438
555
  payments: PaymentsAPI;
439
556
  refunds: RefundsAPI;
557
+ receipts: ReceiptsAPI;
440
558
  dashboard: DashboardAPI;
441
559
  branding: BrandingAPI;
442
560
  config: ConfigAPI;