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.
- package/LICENSE +21 -0
- package/README.md +339 -0
- package/package.json +54 -0
- package/src/branding.js +127 -0
- package/src/client.js +111 -0
- package/src/config.js +273 -0
- package/src/dashboard.js +265 -0
- package/src/index.js +79 -0
- package/src/payments.js +319 -0
- package/src/refunds.js +209 -0
- package/src/types/index.d.ts +460 -0
- package/src/utils/http.js +217 -0
- package/src/webhooks.js +163 -0
package/src/payments.js
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payments API Module
|
|
3
|
+
* Handles payment sessions, transfers, and status checks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { SwiftShoprError } = require('./utils/http');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* UUID validation regex
|
|
10
|
+
*/
|
|
11
|
+
const UUID_REGEX =
|
|
12
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Ethereum address validation regex
|
|
16
|
+
*/
|
|
17
|
+
const ETH_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validate payment session parameters
|
|
21
|
+
*/
|
|
22
|
+
function validateSessionParams(params) {
|
|
23
|
+
const errors = [];
|
|
24
|
+
|
|
25
|
+
if (
|
|
26
|
+
!params.amount ||
|
|
27
|
+
typeof params.amount !== 'number' ||
|
|
28
|
+
params.amount <= 0
|
|
29
|
+
) {
|
|
30
|
+
errors.push('amount must be a positive number');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (params.amount > 10000) {
|
|
34
|
+
errors.push('amount cannot exceed $10,000 per transaction');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (
|
|
38
|
+
params.destinationAddress &&
|
|
39
|
+
!ETH_ADDRESS_REGEX.test(params.destinationAddress)
|
|
40
|
+
) {
|
|
41
|
+
errors.push('destinationAddress must be a valid Ethereum address');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!params.storeId && !params.destinationAddress) {
|
|
45
|
+
errors.push('Either storeId or destinationAddress is required');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (errors.length > 0) {
|
|
49
|
+
throw new SwiftShoprError('VALIDATION_ERROR', errors.join('; '), {
|
|
50
|
+
errors,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validate transfer parameters
|
|
57
|
+
*/
|
|
58
|
+
function validateTransferParams(params) {
|
|
59
|
+
const errors = [];
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
!params.amount ||
|
|
63
|
+
typeof params.amount !== 'number' ||
|
|
64
|
+
params.amount <= 0
|
|
65
|
+
) {
|
|
66
|
+
errors.push('amount must be a positive number');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (params.amount > 10000) {
|
|
70
|
+
errors.push('amount cannot exceed $10,000 per transaction');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (
|
|
74
|
+
!params.userWalletAddress ||
|
|
75
|
+
!ETH_ADDRESS_REGEX.test(params.userWalletAddress)
|
|
76
|
+
) {
|
|
77
|
+
errors.push('userWalletAddress must be a valid Ethereum address');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (
|
|
81
|
+
params.destinationAddress &&
|
|
82
|
+
!ETH_ADDRESS_REGEX.test(params.destinationAddress)
|
|
83
|
+
) {
|
|
84
|
+
errors.push('destinationAddress must be a valid Ethereum address');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!params.storeId && !params.destinationAddress) {
|
|
88
|
+
errors.push('Either storeId or destinationAddress is required');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (errors.length > 0) {
|
|
92
|
+
throw new SwiftShoprError('VALIDATION_ERROR', errors.join('; '), {
|
|
93
|
+
errors,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Create Payments API instance
|
|
100
|
+
*/
|
|
101
|
+
function createPaymentsAPI(http) {
|
|
102
|
+
return {
|
|
103
|
+
/**
|
|
104
|
+
* Create an onramp session (Add Funds button)
|
|
105
|
+
* User will be directed to Coinbase to purchase USDC
|
|
106
|
+
*
|
|
107
|
+
* @param {Object} params
|
|
108
|
+
* @param {number} params.amount - Amount in USD
|
|
109
|
+
* @param {string} [params.orderId] - Your order reference
|
|
110
|
+
* @param {string} [params.storeId] - Store ID (resolves wallet automatically)
|
|
111
|
+
* @param {string} [params.destinationAddress] - Retailer wallet address
|
|
112
|
+
* @param {string} [params.paymentMethod] - ACH_BANK_ACCOUNT, CARD, etc.
|
|
113
|
+
* @returns {Promise<Object>} Session with onramp_url
|
|
114
|
+
*/
|
|
115
|
+
async createSession(params) {
|
|
116
|
+
validateSessionParams(params);
|
|
117
|
+
|
|
118
|
+
const body = {
|
|
119
|
+
paymentAmount: params.amount.toString(),
|
|
120
|
+
paymentCurrency: 'USD',
|
|
121
|
+
paymentMethod: params.paymentMethod || 'ACH_BANK_ACCOUNT',
|
|
122
|
+
...(params.orderId && { orderId: params.orderId }),
|
|
123
|
+
...(params.storeId && { storeId: params.storeId }),
|
|
124
|
+
...(params.destinationAddress && {
|
|
125
|
+
destinationAddress: params.destinationAddress,
|
|
126
|
+
}),
|
|
127
|
+
...(params.metadata && { metadata: params.metadata }),
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const response = await http.post('/api/v1/sdk/onramp/session', body, {
|
|
131
|
+
idempotencyKey: params.idempotencyKey,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
intentId: response.intent_id,
|
|
136
|
+
sessionId: response.session_id,
|
|
137
|
+
orderId: response.order_id,
|
|
138
|
+
quoteId: response.quote_id,
|
|
139
|
+
onrampUrl: response.onramp_url,
|
|
140
|
+
expiresAt: response.expires_at,
|
|
141
|
+
branding: response.branding || null,
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Create a direct transfer (Purchase button)
|
|
147
|
+
* For users who already have USDC balance
|
|
148
|
+
*
|
|
149
|
+
* @param {Object} params
|
|
150
|
+
* @param {number} params.amount - Amount in USD
|
|
151
|
+
* @param {string} params.userWalletAddress - User's wallet address
|
|
152
|
+
* @param {string} [params.orderId] - Your order reference
|
|
153
|
+
* @param {string} [params.storeId] - Store ID (resolves wallet automatically)
|
|
154
|
+
* @param {string} [params.destinationAddress] - Retailer wallet address
|
|
155
|
+
* @returns {Promise<Object>} Transfer instructions
|
|
156
|
+
*/
|
|
157
|
+
async createTransfer(params) {
|
|
158
|
+
validateTransferParams(params);
|
|
159
|
+
|
|
160
|
+
const body = {
|
|
161
|
+
amount: params.amount.toString(),
|
|
162
|
+
userWalletAddress: params.userWalletAddress,
|
|
163
|
+
...(params.orderId && { orderId: params.orderId }),
|
|
164
|
+
...(params.storeId && { storeId: params.storeId }),
|
|
165
|
+
...(params.destinationAddress && {
|
|
166
|
+
destinationAddress: params.destinationAddress,
|
|
167
|
+
}),
|
|
168
|
+
...(params.metadata && { metadata: params.metadata }),
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const response = await http.post('/api/v1/sdk/onramp/transfer', body, {
|
|
172
|
+
idempotencyKey: params.idempotencyKey,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
intentId: response.intent_id,
|
|
177
|
+
orderId: response.order_id,
|
|
178
|
+
status: response.status,
|
|
179
|
+
transfer: {
|
|
180
|
+
to: response.transfer.to,
|
|
181
|
+
amount: response.transfer.amount,
|
|
182
|
+
asset: response.transfer.asset,
|
|
183
|
+
network: response.transfer.network,
|
|
184
|
+
chainId: response.transfer.chain_id,
|
|
185
|
+
},
|
|
186
|
+
expiresAt: response.expires_at,
|
|
187
|
+
branding: response.branding || null,
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get payment status by intent ID
|
|
193
|
+
*
|
|
194
|
+
* @param {string} intentId - Payment intent ID (UUID)
|
|
195
|
+
* @returns {Promise<Object>} Payment status
|
|
196
|
+
*/
|
|
197
|
+
async getStatus(intentId) {
|
|
198
|
+
if (!intentId) {
|
|
199
|
+
throw new SwiftShoprError('VALIDATION_ERROR', 'intentId is required');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const response = await http.get(
|
|
203
|
+
`/api/v1/sdk/onramp/payments/${intentId}/status`,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
intentId: response.intent_id,
|
|
208
|
+
orderId: response.order_id,
|
|
209
|
+
status: response.status,
|
|
210
|
+
amount: parseFloat(response.amount_usd),
|
|
211
|
+
storeId: response.store_id,
|
|
212
|
+
destinationAddress: response.destination_address,
|
|
213
|
+
txHash: response.tx_hash || null,
|
|
214
|
+
explorerUrl: response.explorer_url || null,
|
|
215
|
+
confirmedAt: response.confirmed_at || null,
|
|
216
|
+
createdAt: response.created_at,
|
|
217
|
+
expiresAt: response.expires_at,
|
|
218
|
+
isExpired: response.is_expired,
|
|
219
|
+
refund: response.refund || null,
|
|
220
|
+
};
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get payment status by order ID
|
|
225
|
+
*
|
|
226
|
+
* @param {string} orderId - Your order reference
|
|
227
|
+
* @returns {Promise<Object>} Payment status
|
|
228
|
+
*/
|
|
229
|
+
async getStatusByOrderId(orderId) {
|
|
230
|
+
if (!orderId) {
|
|
231
|
+
throw new SwiftShoprError('VALIDATION_ERROR', 'orderId is required');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const response = await http.get(
|
|
235
|
+
`/api/v1/sdk/onramp/payments/${encodeURIComponent(orderId)}/status?by=order`,
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
intentId: response.intent_id,
|
|
240
|
+
orderId: response.order_id,
|
|
241
|
+
status: response.status,
|
|
242
|
+
amount: parseFloat(response.amount_usd),
|
|
243
|
+
storeId: response.store_id,
|
|
244
|
+
destinationAddress: response.destination_address,
|
|
245
|
+
txHash: response.tx_hash || null,
|
|
246
|
+
explorerUrl: response.explorer_url || null,
|
|
247
|
+
confirmedAt: response.confirmed_at || null,
|
|
248
|
+
createdAt: response.created_at,
|
|
249
|
+
expiresAt: response.expires_at,
|
|
250
|
+
isExpired: response.is_expired,
|
|
251
|
+
refund: response.refund || null,
|
|
252
|
+
};
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Poll for payment completion
|
|
257
|
+
* Useful for waiting for webhook confirmation
|
|
258
|
+
*
|
|
259
|
+
* @param {string} intentId - Payment intent ID
|
|
260
|
+
* @param {Object} [options]
|
|
261
|
+
* @param {number} [options.timeout=300000] - Max wait time (default 5 min)
|
|
262
|
+
* @param {number} [options.interval=2000] - Poll interval (default 2s)
|
|
263
|
+
* @param {Function} [options.onStatusChange] - Callback on status change
|
|
264
|
+
* @returns {Promise<Object>} Final payment status
|
|
265
|
+
*/
|
|
266
|
+
async waitForCompletion(intentId, options = {}) {
|
|
267
|
+
const timeout = options.timeout || 300000;
|
|
268
|
+
const interval = options.interval || 2000;
|
|
269
|
+
const { onStatusChange } = options;
|
|
270
|
+
|
|
271
|
+
const startTime = Date.now();
|
|
272
|
+
let lastStatus = null;
|
|
273
|
+
|
|
274
|
+
while (Date.now() - startTime < timeout) {
|
|
275
|
+
const status = await this.getStatus(intentId);
|
|
276
|
+
|
|
277
|
+
if (lastStatus !== status.status) {
|
|
278
|
+
lastStatus = status.status;
|
|
279
|
+
if (onStatusChange) {
|
|
280
|
+
onStatusChange(status);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (status.status === 'completed' || status.status === 'COMPLETED') {
|
|
285
|
+
return status;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (status.status === 'failed' || status.status === 'FAILED') {
|
|
289
|
+
throw new SwiftShoprError('PAYMENT_FAILED', 'Payment failed', status);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (status.status === 'canceled' || status.status === 'CANCELED') {
|
|
293
|
+
throw new SwiftShoprError(
|
|
294
|
+
'PAYMENT_CANCELED',
|
|
295
|
+
'Payment was canceled',
|
|
296
|
+
status,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (status.isExpired) {
|
|
301
|
+
throw new SwiftShoprError(
|
|
302
|
+
'PAYMENT_EXPIRED',
|
|
303
|
+
'Payment expired',
|
|
304
|
+
status,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
throw new SwiftShoprError(
|
|
312
|
+
'TIMEOUT',
|
|
313
|
+
`Payment did not complete within ${timeout}ms`,
|
|
314
|
+
);
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
module.exports = { createPaymentsAPI };
|
package/src/refunds.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refunds API Module
|
|
3
|
+
* Handles refund requests and status tracking
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { SwiftShoprError } = require('./utils/http');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* UUID validation regex
|
|
10
|
+
*/
|
|
11
|
+
const UUID_REGEX =
|
|
12
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Validate refund parameters
|
|
16
|
+
*/
|
|
17
|
+
function validateRefundParams(params) {
|
|
18
|
+
const errors = [];
|
|
19
|
+
|
|
20
|
+
if (!params.intentId) {
|
|
21
|
+
errors.push('intentId is required');
|
|
22
|
+
} else if (!UUID_REGEX.test(params.intentId)) {
|
|
23
|
+
errors.push('intentId must be a valid UUID');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (params.amount !== undefined) {
|
|
27
|
+
if (typeof params.amount !== 'number' || params.amount <= 0) {
|
|
28
|
+
errors.push('amount must be a positive number');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (errors.length > 0) {
|
|
33
|
+
throw new SwiftShoprError('VALIDATION_ERROR', errors.join('; '), {
|
|
34
|
+
errors,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create Refunds API instance
|
|
41
|
+
*/
|
|
42
|
+
function createRefundsAPI(http) {
|
|
43
|
+
return {
|
|
44
|
+
/**
|
|
45
|
+
* Request a refund for a completed payment
|
|
46
|
+
*
|
|
47
|
+
* @param {Object} params
|
|
48
|
+
* @param {string} params.intentId - Original payment intent ID
|
|
49
|
+
* @param {number} [params.amount] - Refund amount (optional, defaults to full refund)
|
|
50
|
+
* @param {string} [params.reason] - Reason for refund
|
|
51
|
+
* @returns {Promise<Object>} Refund details with transfer instructions
|
|
52
|
+
*/
|
|
53
|
+
async create(params) {
|
|
54
|
+
validateRefundParams(params);
|
|
55
|
+
|
|
56
|
+
const body = {
|
|
57
|
+
intent_id: params.intentId,
|
|
58
|
+
...(params.amount && { amount: params.amount }),
|
|
59
|
+
...(params.reason && { reason: params.reason }),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const response = await http.post('/api/v1/sdk/onramp/refund', body, {
|
|
63
|
+
idempotencyKey: params.idempotencyKey,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!response.success) {
|
|
67
|
+
throw new SwiftShoprError(
|
|
68
|
+
response.error || 'REFUND_ERROR',
|
|
69
|
+
response.message || 'Refund request failed',
|
|
70
|
+
response,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
id: response.refund.id,
|
|
76
|
+
intentId: response.refund.intent_id,
|
|
77
|
+
orderId: response.refund.order_id,
|
|
78
|
+
status: response.refund.status,
|
|
79
|
+
amount: response.refund.amount,
|
|
80
|
+
originalAmount: response.refund.original_amount,
|
|
81
|
+
isPartial: response.refund.is_partial,
|
|
82
|
+
reason: response.refund.reason,
|
|
83
|
+
createdAt: response.refund.created_at,
|
|
84
|
+
instructions: {
|
|
85
|
+
message: response.instructions.message,
|
|
86
|
+
transfer: {
|
|
87
|
+
to: response.instructions.transfer.to,
|
|
88
|
+
amount: response.instructions.transfer.amount,
|
|
89
|
+
asset: response.instructions.transfer.asset,
|
|
90
|
+
network: response.instructions.transfer.network,
|
|
91
|
+
chainId: response.instructions.transfer.chain_id,
|
|
92
|
+
},
|
|
93
|
+
fromWallet: response.instructions.from_wallet,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get refund status by refund ID
|
|
100
|
+
*
|
|
101
|
+
* @param {string} refundId - Refund ID (UUID)
|
|
102
|
+
* @returns {Promise<Object>} Refund status
|
|
103
|
+
*/
|
|
104
|
+
async get(refundId) {
|
|
105
|
+
if (!refundId) {
|
|
106
|
+
throw new SwiftShoprError('VALIDATION_ERROR', 'refundId is required');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const response = await http.get(`/api/v1/sdk/onramp/refund/${refundId}`);
|
|
110
|
+
|
|
111
|
+
if (!response.success) {
|
|
112
|
+
throw new SwiftShoprError(
|
|
113
|
+
response.error || 'REFUND_ERROR',
|
|
114
|
+
response.message || 'Failed to get refund status',
|
|
115
|
+
response,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
id: response.refund.id,
|
|
121
|
+
intentId: response.refund.intent_id,
|
|
122
|
+
orderId: response.refund.order_id,
|
|
123
|
+
amount: response.refund.amount,
|
|
124
|
+
reason: response.refund.reason,
|
|
125
|
+
status: response.refund.status,
|
|
126
|
+
txHash: response.refund.tx_hash,
|
|
127
|
+
explorerUrl: response.refund.explorer_url,
|
|
128
|
+
fromAddress: response.refund.from_address,
|
|
129
|
+
toAddress: response.refund.to_address,
|
|
130
|
+
createdAt: response.refund.created_at,
|
|
131
|
+
completedAt: response.refund.completed_at,
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* List all refunds for a payment intent
|
|
137
|
+
*
|
|
138
|
+
* @param {string} intentId - Payment intent ID (UUID)
|
|
139
|
+
* @returns {Promise<Array>} List of refunds
|
|
140
|
+
*/
|
|
141
|
+
async listForPayment(intentId) {
|
|
142
|
+
if (!intentId) {
|
|
143
|
+
throw new SwiftShoprError('VALIDATION_ERROR', 'intentId is required');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const response = await http.get(
|
|
147
|
+
`/api/v1/sdk/onramp/payments/${intentId}/refunds`,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return response.map((refund) => ({
|
|
151
|
+
id: refund.id,
|
|
152
|
+
amount: refund.amount,
|
|
153
|
+
reason: refund.reason,
|
|
154
|
+
status: refund.status,
|
|
155
|
+
txHash: refund.tx_hash,
|
|
156
|
+
explorerUrl: refund.explorer_url,
|
|
157
|
+
createdAt: refund.created_at,
|
|
158
|
+
completedAt: refund.completed_at,
|
|
159
|
+
}));
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Poll for refund completion
|
|
164
|
+
*
|
|
165
|
+
* @param {string} refundId - Refund ID
|
|
166
|
+
* @param {Object} [options]
|
|
167
|
+
* @param {number} [options.timeout=600000] - Max wait time (default 10 min)
|
|
168
|
+
* @param {number} [options.interval=5000] - Poll interval (default 5s)
|
|
169
|
+
* @param {Function} [options.onStatusChange] - Callback on status change
|
|
170
|
+
* @returns {Promise<Object>} Final refund status
|
|
171
|
+
*/
|
|
172
|
+
async waitForCompletion(refundId, options = {}) {
|
|
173
|
+
const timeout = options.timeout || 600000;
|
|
174
|
+
const interval = options.interval || 5000;
|
|
175
|
+
const { onStatusChange } = options;
|
|
176
|
+
|
|
177
|
+
const startTime = Date.now();
|
|
178
|
+
let lastStatus = null;
|
|
179
|
+
|
|
180
|
+
while (Date.now() - startTime < timeout) {
|
|
181
|
+
const refund = await this.get(refundId);
|
|
182
|
+
|
|
183
|
+
if (lastStatus !== refund.status) {
|
|
184
|
+
lastStatus = refund.status;
|
|
185
|
+
if (onStatusChange) {
|
|
186
|
+
onStatusChange(refund);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (refund.status === 'completed') {
|
|
191
|
+
return refund;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (refund.status === 'failed') {
|
|
195
|
+
throw new SwiftShoprError('REFUND_FAILED', 'Refund failed', refund);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
throw new SwiftShoprError(
|
|
202
|
+
'TIMEOUT',
|
|
203
|
+
`Refund did not complete within ${timeout}ms`,
|
|
204
|
+
);
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = { createRefundsAPI };
|