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 +1 -1
- package/src/client.js +3 -1
- package/src/receipts.js +262 -0
- package/src/types/index.d.ts +118 -0
package/package.json
CHANGED
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.
|
|
96
|
+
return '1.1.0';
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
/**
|
package/src/receipts.js
ADDED
|
@@ -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 };
|
package/src/types/index.d.ts
CHANGED
|
@@ -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;
|